From: Mike Bayer Date: Tue, 11 Oct 2011 18:15:09 +0000 (-0400) Subject: - Added new flag expire_on_flush=False to column_property(), X-Git-Tag: rel_0_7_3~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f07ffb4739734599e0112bbf7c379e80687f8899;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - Added new flag expire_on_flush=False to column_property(), marks those properties that would otherwise be considered to be "readonly", i.e. derived from SQL expressions, to retain their value after a flush has occurred, including if the parent object itself was involved in an update. --- diff --git a/CHANGES b/CHANGES index f34bd810dd..7d3dd04ec1 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,12 @@ CHANGES attribute, provided the name is the same as that of the entity mapped column. + - Added new flag expire_on_flush=False to column_property(), + marks those properties that would otherwise be considered + to be "readonly", i.e. derived from SQL expressions, + to retain their value after a flush has occurred, including + if the parent object itself was involved in an update. + - Enhanced the instrumentation in the ORM to support Py3K's new argument style of "required kw arguments", i.e. fn(a, b, *, c, d), fn(a, b, *args, c, d). diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index b7f7884f24..8c44682f2d 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -686,6 +686,22 @@ def column_property(*args, **kwargs): :param doc: optional string that will be applied as the doc on the class-bound descriptor. + + :param expire_on_flush=True: + Disable expiry on flush. A column_property() which refers + to a SQL expression (and not a single table-bound column) + is considered to be a "read only" property; populating it + has no effect on the state of data, and it can only return + database state. For this reason a column_property()'s value + is expired whenever the parent object is involved in a + flush, that is, has any kind of "dirty" state within a flush. + Setting this parameter to ``False`` will have the effect of + leaving any existing value present after the flush proceeds. + Note however that the :class:`.Session` with default expiration + settings still expires + all attributes after a :meth:`.Session.commit` call, however. + New in 0.7.3. + :param extension: an diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index b1a6b1a33f..e6ec422b06 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -2250,7 +2250,8 @@ class Mapper(object): if mapper._readonly_props: readonly = state.unmodified_intersection( - [p.key for p in mapper._readonly_props] + [p.key for p in mapper._readonly_props + if p.expire_on_flush or p.key not in state.dict] ) if readonly: state.expire_attributes(state.dict, readonly) diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index b9ec20c868..aa01757ac0 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -55,6 +55,8 @@ class ColumnProperty(StrategizedProperty): :param descriptor: + :param expire_on_flush: + :param extension: """ @@ -67,6 +69,7 @@ class ColumnProperty(StrategizedProperty): self.descriptor = kwargs.pop('descriptor', None) self.extension = kwargs.pop('extension', None) self.active_history = kwargs.pop('active_history', False) + self.expire_on_flush = kwargs.pop('expire_on_flush', True) if 'doc' in kwargs: self.doc = kwargs.pop('doc') diff --git a/test/orm/test_unitofwork.py b/test/orm/test_unitofwork.py index c81f15c983..be781109d2 100644 --- a/test/orm/test_unitofwork.py +++ b/test/orm/test_unitofwork.py @@ -940,14 +940,23 @@ class ColumnPropertyTest(fixtures.MappedTest): mapper(Data, data, properties={ 'aplusb':column_property(data.c.a + literal_column("' '") + data.c.b) }) - self._test() + self._test(True) + + def test_no_refresh(self): + Data, data = self.classes.Data, self.tables.data + + mapper(Data, data, properties={ + 'aplusb':column_property(data.c.a + literal_column("' '") + data.c.b, + expire_on_flush=False) + }) + self._test(False) def test_refreshes_post_init(self): Data, data = self.classes.Data, self.tables.data m = mapper(Data, data) m.add_property('aplusb', column_property(data.c.a + literal_column("' '") + data.c.b)) - self._test() + self._test(True) def test_with_inheritance(self): subdata, data, Data = (self.tables.subdata, @@ -967,7 +976,7 @@ class ColumnPropertyTest(fixtures.MappedTest): sess.flush() eq_(sd1.aplusb, "hello there") - def _test(self): + def _test(self, expect_expiry): Data = self.classes.Data sess = create_session() @@ -980,7 +989,11 @@ class ColumnPropertyTest(fixtures.MappedTest): d1.b = "bye" sess.flush() - eq_(d1.aplusb, "hello bye") + if expect_expiry: + eq_(d1.aplusb, "hello bye") + else: + eq_(d1.aplusb, "hello there") + d1.b = 'foobar' d1.aplusb = 'im setting this explicitly'