Hi all,
I’ve recently discovered that the order in which events are fired for individual instances is not deterministic. For example, if I add several Author instances to an existing Book then commit the changes, the Book‘s post_update may run before or after the Author‘s post_insert, and the same code might produce different results on successive runs.
Here’s some example code (full gist with models is here: https://gist.github.com/chadrik/c3fff520bd41a48f0a57):
@event.listens_for(Book, 'after_update')
def book_after_update(mapper, connection, instance):
print "after_update", instance, instance.authors
@event.listens_for(Book, 'after_insert')
def book_after_insert(mapper, connection, instance):
print "after_insert", instance, instance.authors
@event.listens_for(Author, 'after_insert')
def author_after_insert(mapper, connection, instance):
print "after_insert", instance
def test():
Base.metadata.create_all(engine)
book = Book(title='sqlalchemy for dummies')
session = Session()
session.add(book)
session.commit()
john = Author(name='John Doe')
jane = Author(name='Jane Doe')
book.authors = [john, jane]
book2 = Book(title='sqlalchemy pro tips')
book2.authors = [john]
session.add(book2)
session.commit()
if __name__ == '__main__':
test()
running test() might print:
after_insert Book(1) []
after_insert Author(1)
after_insert Author(2)
after_insert Book(2) [Author(1)]
after_update Book(1) [Author(1), Author(2)]
or it might print:
after_insert Book(1) []
after_insert Book(2) [Author(None)]
after_update Book(1) [Author(None), Author(None)]
after_insert Author(1)
after_insert Author(2)
it can be tricky to consistently reproduce the behavior. sometimes successive runs change randomly with no intervention, sometimes it requires changing a single line of unrelated code, like a comment (I guess this has something to do with python’s byte-compiling and hashing). sometimes a test that will run consistently in the same order using python -m "sqlbooks" will run consistently in a different order using python -c "import sqlbooks;sqlbooks.test()"
setting __mapper_args__ = {'batch': False} did not help.
it seems that internally sqlalchemy is using unordered containers like dicts or sets when managing the instances passed to these events. my question is: is it possible to provide a meaningful and consistent order to these events?
ideally, for my case, I’m hoping to have the Author’s post_insert run before the Book’s post_update, such that the Author’s auto-incrementing id’s are available (you’ll notice in the two output blocks that I pasted, that in the second it prints Author(None)) where None should be the integer id. Also note that the relationship between Book and Author is many-to-many and handled by a secondary table, so no data is persisted to the db for the book table. I assume the post_update is purely to notify me that the Book.authors relationship changed, and as such, it would be highly convenient if the items which were added to the relationship were already inserted and had their primary keys.
thanks,
chad
try calling `session.flush()`that will "emit sql" and pull insert ids; syncing the SqlAlchemy Session to the database.
try calling `session.flush()`that will "emit sql" and pull insert ids; syncing the SqlAlchemy Session to the database.
--
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+...@googlegroups.com.
To post to this group, send email to sqlal...@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.
you can’t call flush() within before_insert/before_update/ etc. as you are already inside the flush process. I think at this point it should know to raise an exception for that action.
Hi all,
I’ve recently discovered that the order in which events are fired for individual instances is not deterministic.
The actual ordering here is based on a topological sort, and a description on how the unit of work makes use of this is in section 20.9 of http://aosabook.org/en/sqlalchemy.html.In this case, from SQLAlchemy’s point of view, it only needs to emit INSERT for Book and Author before it INSERTs into “link_books_authors”. It’s deterministic in this regard, but as far as Book and Author themselves, there is no natural ordering to that, so ultimately it is set/dict ordering that takes effect.If you’d like to affect the topological sort here, it’s doable by creating a dependency between Book and Author. But what is blowing me away is that there does not seem to be a public API for this as I really recall doing this for someone. I could show you something that does it but it would not be stable API.