as occurs within a flush operation; setting include_collections=False gives the same
result as is used when the flush determines whether or not to issue an UPDATE for the
instance's row.
adequate __eq__() implementation can be set up with "PickleType(comparator=operator.eq)"
[ticket:560]
+- added session.is_modified(obj) method; performs the same "history" comparison operation
+ as occurs within a flush operation; setting include_collections=False gives the same
+ result as is used when the flush determines whether or not to issue an UPDATE for the
+ instance's row.
+
- added "schema" argument to Sequence; use this with Postgres /Oracle when the sequence is
located in an alternate schema. Implements part of [ticket:584], should fix [ticket:761].
return self
return self.impl.get(obj._state)
+ def get_history(self, obj, **kwargs):
+ return self.impl.get_history(obj._state, **kwargs)
+
def clause_element(self):
return self.comparator.clause_element()
def do(self, *args, **kwargs):
return getattr(self.registry(), name)(*args, **kwargs)
return do
-for meth in ('get', 'load', 'close', 'save', 'commit', 'update', 'flush', 'query', 'delete', 'merge', 'clear', 'refresh', 'expire', 'expunge', 'rollback', 'begin', 'begin_nested', 'connection', 'execute', 'scalar', 'get_bind'):
+for meth in ('get', 'load', 'close', 'save', 'commit', 'update', 'flush', 'query', 'delete', 'merge', 'clear', 'refresh', 'expire', 'expunge', 'rollback', 'begin', 'begin_nested', 'connection', 'execute', 'scalar', 'get_bind', 'is_modified'):
setattr(ScopedSession, meth, instrument(meth))
def makeprop(name):
return iter(list(self.uow.new) + self.uow.identity_map.values())
+ def is_modified(self, obj, include_collections=True, passive=False):
+ """return True if the given object has modified attributes.
+
+ This method retrieves a history instance for each instrumented attribute
+ on the instance and performs a comparison of the current value to its
+ previously committed value. Note that instances present in the 'dirty'
+ collection may result in a value of ``False`` when tested with this method.
+
+ 'include_collections' indicates if multivalued collections should be included
+ in the operation. Setting this to False is a way to detect only local-column
+ based properties (i.e. scalar columns or many-to-one foreign keys) that would
+ result in an UPDATE for this instance upon flush.
+
+ the 'passive' flag indicates if unloaded attributes and collections should
+ not be loaded in the course of performing this test.
+ """
+
+ for attr in attribute_manager.managed_attributes(obj.__class__):
+ if not include_collections and hasattr(attr.impl, 'get_collection'):
+ continue
+ if attr.get_history(obj).is_modified():
+ return True
+ return False
+
dirty = property(lambda s:s.uow.locate_dirty(),
- doc="A ``Set`` of all objects marked as 'dirty' within this ``Session``")
+ doc="""A ``Set`` of all objects marked as 'dirty' within this ``Session``.
+
+ Note that the 'dirty' state here is 'optimistic'; most attribute-setting or collection
+ modification operations will mark an instance as 'dirty' and place it in this set,
+ even if there is no net change to the attribute's value. At flush time, the value
+ of each attribute is compared to its previously saved value,
+ and if there's no net change, no SQL operation will occur (this is a more expensive
+ operation so it's only done at flush time).
+
+ To check if an instance has actionable net changes to its attributes, use the
+ is_modified() method.
+ """)
deleted = property(lambda s:s.uow.deleted,
doc="A ``Set`` of all objects marked as 'deleted' within this ``Session``")
assert user in s
assert user not in s.dirty
+ def test_is_modified(self):
+ s = create_session()
+ class User(object):pass
+ class Address(object):pass
+
+ mapper(User, users, properties={'addresses':relation(Address)})
+ mapper(Address, addresses)
+
+ # save user
+ u = User()
+ u.user_name = 'fred'
+ s.save(u)
+ s.flush()
+ s.clear()
+
+ user = s.query(User).one()
+ assert user not in s.dirty
+ assert not s.is_modified(user)
+ user.user_name = 'fred'
+ assert user in s.dirty
+ assert not s.is_modified(user)
+ user.user_name = 'ed'
+ assert user in s.dirty
+ assert s.is_modified(user)
+ s.flush()
+ assert user not in s.dirty
+ assert not s.is_modified(user)
+
+ a = Address()
+ user.addresses.append(a)
+ assert user in s.dirty
+ assert s.is_modified(user)
+ assert not s.is_modified(user, include_collections=False)
+
+
def test_weak_ref(self):
"""test the weak-referencing identity map, which strongly-references modified items."""