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.

  • Participants
  • Parent commits 2089a41

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

File 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

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

File 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')

File 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'