Thank you for the detailed explanation of what happens. Now I understand. 
Shame on me I hadn't noticed this in the docs...
Everything which deals with expiration and object state business is always 
a bit over my head...

You said it mostly the flush() business and is_modified() indeed looks like 
one of its private _function()s which behave bearing in mind what flush() 
wants and at what expense
BUT exposed as public. Maybe it worths rewriting it so it behaves without 
assuming some calling "context", even less productive, but with more 
expected behavior? It it would be pain maintaining diverging copies of 
logic... Anyway, thanks for the clarification, you're awesome as always

среда, 9 декабря 2015 г., 22:04:29 UTC+5 пользователь Michael Bayer написал:
>
>
>
> On 12/09/2015 02:07 AM, Юрий Пайков wrote: 
> > 
> > Ok, here is the test 
> > code https://gist.github.com/ojomio/941d03b728a88d93d010 
> > Apart from reproducing the (seeming) problem, it prints out 
> > "PASSIVE_NO_RESULT"(yes, you were right about the name) which is /in/ 
> > committed_state 
>
>
> great, thanks. The PASSIVE_NO_RESULT symbol in there is a normal thing, 
> it means that for this object, we didn't actually know what the "old" 
> value of the A.second attribute was - it was expired when you called 
> session.commit().  So the fact that we set it to this B() object we have 
> no choice but to record as "new history". 
>
> Now, if we really wanted it to know that no, this is the *same* object, 
> it would have to go out to the identity map and/or database and *load* 
> the old one.  You can have that, like this: 
>
> class A(Base): 
>     __tablename__ = 'a' 
>     first = Column(Integer, primary_key=True) 
>     second_id = Column(Integer, ForeignKey('b.id')) 
>     second = relationship('B', active_history=True) 
>
>
> this isn't the default setting, because when a many-to-one object is 
> assigned to a parent, we don't need to know what the old one was in 
> order to do an UPDATE, so if we don't have it, we don't bother most 
> likely hitting the database again and getting it.   You can see that the 
> test script emits an extra SELECT for it.   The majority of our users 
> were annoyed in the old days when every many-to-one assignment triggered 
> an unnecessary SELECT, so that's why it works this way. 
>
> Another level here is that, A.second points to a B which has an "id" 
> that is the same value we already know to have in A.second_id (we 
> emitted a SELECT for that also-expired value as well, but at least that 
> was just one SELECT), so in this (most common) case, we can actually see 
> that hey, no there really won't be an UPDATE, because if we look inside 
> this B, and its primary key,  and the primaryjoin condition inside our 
> relationship() (which could be anything) and run the rules for the 
> A.second relationship for synchronizing a many-to-one value into the 
> attributes of our A, there's no net change; that's essentially a really 
> involved and highly tweaked process that occurs within flush() which 
> unfortunately is not simplistic enough to just pull out and run in this 
> local, non-flush context.   I can see some potential tweaks where maybe 
> we look at the fact that this is a "simple" many-to-one (e.g. the 
> primaryjoin has no weird conditions in it) and then only consider the FK 
> columns, that could be doable.  I'd have it only on a flag. 
>
> The simple answer is that session.merge() is very likely to cause an 
> object to look "modified", and if very accurate detection of changes is 
> a use case, I wouldn't use merge(), or I'd make sure I'm not running it 
> on expired objects. 
>
> *or*, just look in the history yourself for the attributes you care about: 
>
>
>         def a_is_modified(some_a): 
>             attrs = inspect(some_a).attrs 
>             return bool(sum( 
>                 attrs[key].history.has_changes() 
>                 for key in ['first', 'second_id'] 
>             )) 
>
>
>         new_a = A(first=3, second=b) 
>         returned_a = session.merge(new_a) 
>         print a_is_modified(a) 
>
>
>          
>
> The docs for session.is_modified() point this out as well: 
>
>
> http://docs.sqlalchemy.org/en/rel_1_0/orm/session_api.html#sqlalchemy.orm.session.Session.is_modified
>  
>
> > Scalar attributes may not have recorded the previously set value when 
> a new value was applied, if the attribute was not loaded, or was 
> expired, at the time the new value was received - in these cases, the 
> attribute is assumed to have a change, even if there is ultimately no 
> net change against its database value. SQLAlchemy in most cases does not 
> need the “old” value when a set event occurs, so it skips the expense of 
> a SQL call if the old value isn’t present, based on the assumption that 
> an UPDATE of the scalar value is usually needed, and in those few cases 
> where it isn’t, is less expensive on average than issuing a defensive 
> SELECT. 
>
> > The “old” value is fetched unconditionally upon set only if the 
> attribute container has the active_history flag set to True. This flag 
> is set typically for primary key attributes and scalar object references 
> that are not a simple many-to-one. To set this flag for any arbitrary 
> mapped column, use the active_history argument with column_property(). 
>
>
>
>
>
>
>
> > вторник, 8 декабря 2015 г., 19:44:16 UTC+5 пользователь Michael Bayer 
> > написал: 
> > 
> > 
> >     We have a lot of "PASSIVE_*" symbols, but I can't find one, even 
> >     looking 
> >     way back, that is called "PASSIVE_NO_DELETE".  Also these particular 
> >     symbols don't get populated into objects, they are arguments we send 
> to 
> >     various attribute fetch/history methods.    If I had to guess, you 
> >     *might* be seeing PASSIVE_NO_RESULT, but that wouldn't be present in 
> >     committed_state. 
> > 
> >     Overall this description of behavior is not clear.  If you could 
> >     reproduce your exact results in a console session or script and 
> please 
> >     report on those exactly, that would help understand your case. 
> >     Following the guidelines at http://stackoverflow.com/help/mcve 
> >     <http://stackoverflow.com/help/mcve> would be 
> >     most helpful. 
> > 
> >     The is_modified() method up until 0.8 included a default setting for 
> >     its 
> >     own "passive" flag that would cause it to fetch relationships as a 
> side 
> >     effect.  This was fixed in 0.8.   Also it does not include 
> >     collection-bound relationships as part of its answer unless the 
> >     include_collections flag is set to True. 
> > 
> >     > 
> >     > Does anyone know why it happens like this? How can I correctly 
> >     check if 
> >     > the object's going to be updated on commit? 
> >     > 
> >     > -- 
> >     > 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 <javascript:> 
> >     > <mailto:sqlalchemy+unsubscr...@googlegroups.com <javascript:> 
> <javascript:>>. 
> >     > To post to this group, send email to sqlal...@googlegroups.com 
> >     <javascript:> 
> >     > <mailto:sqlal...@googlegroups.com <javascript:>>. 
> >     > Visit this group at http://groups.google.com/group/sqlalchemy 
> >     <http://groups.google.com/group/sqlalchemy>. 
> >     > For more options, visit https://groups.google.com/d/optout 
> >     <https://groups.google.com/d/optout>. 
> > 
> > -- 
> > 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 <javascript:> 
> > <mailto:sqlalchemy+unsubscr...@googlegroups.com <javascript:>>. 
> > To post to this group, send email to sqlal...@googlegroups.com 
> <javascript:> 
> > <mailto:sqlal...@googlegroups.com <javascript:>>. 
> > Visit this group at http://groups.google.com/group/sqlalchemy. 
> > For more options, visit https://groups.google.com/d/optout. 
>

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

Reply via email to