From: Mike Bayer Date: Mon, 24 Sep 2007 14:51:23 +0000 (+0000) Subject: - added session.is_modified(obj) method; performs the same "history" comparison operation X-Git-Tag: rel_0_4beta6~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6b5543995b66b524097ad540a0a3f9dfd9eb9413;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - 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. --- diff --git a/CHANGES b/CHANGES index 55877928df..63959fdd62 100644 --- a/CHANGES +++ b/CHANGES @@ -29,6 +29,11 @@ CHANGES 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]. diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 3f08d8871d..511e4a3265 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -39,6 +39,9 @@ class InstrumentedAttribute(interfaces.PropComparator): 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() diff --git a/lib/sqlalchemy/orm/scoping.py b/lib/sqlalchemy/orm/scoping.py index 058c15cc96..f4cec04333 100644 --- a/lib/sqlalchemy/orm/scoping.py +++ b/lib/sqlalchemy/orm/scoping.py @@ -69,7 +69,7 @@ def instrument(name): 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): diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index fd75adff45..98b2a89022 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -1029,8 +1029,43 @@ class Session(object): 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``") diff --git a/test/orm/session.py b/test/orm/session.py index 33d4ba9c85..53afb7bbab 100644 --- a/test/orm/session.py +++ b/test/orm/session.py @@ -426,6 +426,41 @@ class SessionTest(AssertMixin): 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."""