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:>>. > > 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+unsubscr...@googlegroups.com > <mailto:sqlalchemy+unsubscr...@googlegroups.com>. > To post to this group, send email to sqlalchemy@googlegroups.com > <mailto:sqlalchemy@googlegroups.com>. > 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.