On Aug 6, 2014, at 3:46 PM, Chad Dombrova <[email protected]> wrote:

> Hi all,
> I've recently discovered that the order in which events are fired for 
> individual instances is not deterministic.
> 
to be clear, events are always fired in a completely deterministic order.  In 
this case however, your events are linked to some particular activities 
performed by the unit of work which themselves obey an ordering that is not in 
line with your expectations.

There's a big green box in the docs for all the before/after 
insert/update/delete events that tries to make it known that there are caveats 
to using these events in order to coordinate between different types of 
instances:  "and in general should not affect any relationship() -mapped 
attributes, as session cascade rules will not function properly, nor is it 
always known if the related class has already been handled."   That's not to 
say it can't be done, that's just there so that people don't expect things to 
"just work" when they try coordinating between mappers at this level.  Usually 
I recommend things like this be done in before_flush().  But you're looking for 
auto generated primary keys, so OK read on...

> 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? 
> 
> 


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.

The bigger question is, if Author's primary key is needed directly for Book, 
why isn't there another relationship() between these two mappings?


-- 
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 [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to