Commits

Mike Bayer committed 6a3c2d0

- 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.

Comments (0)

Files changed (5)

     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).

lib/sqlalchemy/orm/__init__.py

     :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

lib/sqlalchemy/orm/mapper.py

 
             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)

lib/sqlalchemy/orm/properties.py

 
         :param descriptor:
 
+        :param expire_on_flush:
+
         :param extension:
 
         """
         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')

test/orm/test_unitofwork.py

         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,
         sess.flush()
         eq_(sd1.aplusb, "hello there")
 
-    def _test(self):
+    def _test(self, expect_expiry):
         Data = self.classes.Data
 
         sess = create_session()
 
         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'