]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Added active_history flag to relationship()
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 18 Nov 2010 19:55:19 +0000 (14:55 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 18 Nov 2010 19:55:19 +0000 (14:55 -0500)
and column_property(), forces attribute events to
always load the "old" value, so that it's available to
attributes.get_history(). [ticket:1961]
- modernize test_relationship, give test classes meaningful names

CHANGES
doc/build/orm/mapper_config.rst
lib/sqlalchemy/orm/__init__.py
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/properties.py
lib/sqlalchemy/orm/strategies.py
test/orm/test_relationships.py

diff --git a/CHANGES b/CHANGES
index dbc588ac7a859321844940031d329d835705b326..d7f9dd27692e52e585c662e108b555607dda280d 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -32,6 +32,11 @@ CHANGES
     single column as well as a list or tuple.  [ticket:1971]
     The documentation examples that illustrated it as a 
     scalar value have been changed to lists.  
+
+  - Added active_history flag to relationship()
+    and column_property(), forces attribute events to
+    always load the "old" value, so that it's available to
+    attributes.get_history(). [ticket:1961]
     
 - sql
   - The 'info' attribute of Column is copied during 
index a8b86abcdc70f05c006345d1466353cad1fbfc86..40512f520e6a6fe4d8b52d6feff03a200b15fbad 100644 (file)
@@ -142,6 +142,33 @@ together using a list, as below where we map to a :func:`~.expression.join`::
 
 For further examples on this particular use case, see :ref:`maptojoin`.
 
+column_property API
+~~~~~~~~~~~~~~~~~~~
+
+The establishment of a :class:`.Column` on a :func:`.mapper` can be further
+customized using the :func:`.column_property` function, as specified
+to the ``properties`` dictionary.   This function is 
+usually invoked implicitly for each mapped :class:`.Column`.  Explicit usage
+looks like::
+
+    from sqlalchemy.orm import mapper, column_property
+    
+    mapper(User, users, properties={
+        'name':column_property(users.c.name, active_history=True)
+    })
+
+or with declarative::
+    
+    class User(Base):
+        __tablename__ = 'users'
+        
+        id = Column(Integer, primary_key=True)
+        name = column_property(Column(String(50)), active_history=True)
+
+Further examples of :func:`.column_property` are at :ref:`mapper_sql_expressions`.
+        
+.. autofunction:: column_property
+
 .. _deferred:
 
 Deferred Column Loading
@@ -267,8 +294,6 @@ Correlated subqueries may be used as well::
 
 The declarative form of the above is described in :ref:`declarative_sql_expressions`.
 
-.. autofunction:: column_property
-
 Note that :func:`.column_property` is used to provide the effect of a SQL
 expression that is actively rendered into the SELECT generated for a
 particular mapped class.  Alternatively, for the typical attribute that
index c2417d138e67cd193c42f9a26c9cf978e89a03c7..b51142909dc2cdf2be8676098eaa5496221a1f4c 100644 (file)
@@ -206,6 +206,16 @@ def relationship(argument, secondary=None, **kwargs):
       generally mutually exclusive with the use of the *secondary*
       keyword argument.
 
+    :param active_history=False:
+      When ``True``, indicates that the "previous" value for a
+      many-to-one reference should be loaded when replaced, if
+      not already loaded. Normally, history tracking logic for
+      simple many-to-ones only needs to be aware of the "new"
+      value in order to perform a flush. This flag is available
+      for applications that make use of
+      :func:`.attributes.get_history` which also need to know
+      the "previous" value of the attribute. (New in 0.6.6)
+      
     :param backref:
       indicates the string name of a property to be placed on the related
       mapper's class that will handle this relationship in the other
@@ -576,7 +586,7 @@ def column_property(*args, **kwargs):
     """Provide a column-level property for use with a Mapper.
 
     Column-based properties can normally be applied to the mapper's
-    ``properties`` dictionary using the ``schema.Column`` element directly.
+    ``properties`` dictionary using the :class:`.Column` element directly.
     Use this function when the given column is not directly present within the
     mapper's selectable; examples include SQL expressions, functions, and
     scalar SELECT queries.
@@ -587,6 +597,16 @@ def column_property(*args, **kwargs):
     :param \*cols:
           list of Column objects to be mapped.
 
+    :param active_history=False:
+      When ``True``, indicates that the "previous" value for a
+      scalar attribute should be loaded when replaced, if not
+      already loaded. Normally, history tracking logic for
+      simple non-primary-key scalar values only needs to be
+      aware of the "new" value in order to perform a flush. This
+      flag is available for applications that make use of
+      :func:`.attributes.get_history` which also need to know
+      the "previous" value of the attribute. (new in 0.6.6)
+
     :param comparator_factory: a class which extends
        :class:`.ColumnProperty.Comparator` which provides custom SQL clause
        generation for comparison operations.
index c3c9c754fb003cd62d78b0fda1b810061015a32c..a6fe153e5898a622956ae528dff0a35a49cb1f8c 100644 (file)
@@ -888,6 +888,10 @@ class AttributeExtension(object):
     active_history = True
     """indicates that the set() method would like to receive the 'old' value,
     even if it means firing lazy callables.
+    
+    Note that ``active_history`` can also be set directly via
+    :func:`.column_property` and :func:`.relationship`.
+    
     """
     
     def append(self, state, value, initiator):
index feee041cea4127ecc1bc8845676a2985abe95b07..edfb861f4a87eeb1a18eb9a07ed1e75f3638f27e 100644 (file)
@@ -60,6 +60,7 @@ class ColumnProperty(StrategizedProperty):
                                             self.__class__.Comparator)
         self.descriptor = kwargs.pop('descriptor', None)
         self.extension = kwargs.pop('extension', None)
+        self.active_history = kwargs.pop('active_history', False)
         
         if 'doc' in kwargs:
             self.doc = kwargs.pop('doc')
@@ -114,6 +115,7 @@ class ColumnProperty(StrategizedProperty):
         return ColumnProperty(
                         deferred=self.deferred, 
                         group=self.group, 
+                        active_history=self.active_history,
                         *self.columns)
 
     def _getattr(self, state, dict_, column, passive=False):
@@ -184,6 +186,7 @@ class CompositeProperty(ColumnProperty):
                         deferred=self.deferred, 
                         group=self.group,
                         composite_class=self.composite_class, 
+                        active_history=self.active_history,
                         *self.columns)
 
     def do_init(self):
@@ -444,6 +447,7 @@ class RelationshipProperty(StrategizedProperty):
         comparator_factory=None,
         single_parent=False, innerjoin=False,
         doc=None,
+        active_history=False,
         cascade_backrefs=True,
         load_on_pending=False,
         strategy_class=None, _local_remote_pairs=None, 
@@ -469,6 +473,7 @@ class RelationshipProperty(StrategizedProperty):
         self.query_class = query_class
         self.innerjoin = innerjoin
         self.doc = doc
+        self.active_history = active_history
         self.join_depth = join_depth
         self.local_remote_pairs = _local_remote_pairs
         self.extension = extension
index 398a63e7af31b1931c29d6ee724b5c3eca2f8632..04a23f000321988231efa5893c891c35b85a9eec 100644 (file)
@@ -108,7 +108,8 @@ class ColumnLoader(LoaderStrategy):
         self.is_class_level = True
         coltype = self.columns[0].type
         # TODO: check all columns ?  check for foreign key as well?
-        active_history = self.columns[0].primary_key  
+        active_history = self.parent_property.active_history or \
+                            self.columns[0].primary_key  
 
         _register_attribute(self, mapper, useobject=False,
             compare_function=coltype.compare_values,
@@ -163,8 +164,7 @@ class CompositeColumnLoader(ColumnLoader):
         _register_attribute(self, mapper, useobject=False,
             compare_function=compare,
             copy_function=copy,
-            mutable_scalars=True
-            #active_history ?
+            mutable_scalars=True,
         )
 
     def create_row_processor(self, selectcontext, path, mapper, 
@@ -398,6 +398,7 @@ class LazyLoader(AbstractRelationshipLoader):
                 uselist = self.parent_property.uselist,
                 typecallable = self.parent_property.collection_class,
                 active_history = \
+                    self.parent_property.active_history or \
                     self.parent_property.direction is not \
                         interfaces.MANYTOONE or \
                     not self.use_get,
index 187c9e5349535357bde3806f8b8723150d5f7c4a..03efa01832f1b6cb0d20dcafcc6709de5c9cb813 100644 (file)
@@ -5,25 +5,15 @@ from sqlalchemy.test import testing
 from sqlalchemy import Integer, String, ForeignKey, MetaData, and_
 from sqlalchemy.test.schema import Table, Column
 from sqlalchemy.orm import mapper, relationship, relation, \
-                    backref, create_session, compile_mappers, clear_mappers, sessionmaker
+                    backref, create_session, compile_mappers, \
+                    clear_mappers, sessionmaker, attributes,\
+                    Session, composite, column_property
 from sqlalchemy.test.testing import eq_, startswith_
 from test.orm import _base, _fixtures
 
 
-class RelationshipTest(_base.MappedTest):
-    """An extended topological sort test
-
-    This is essentially an extension of the "dependency.py" topological sort
-    test.  In this test, a table is dependent on two other tables that are
-    otherwise unrelated to each other.  The dependency sort must ensure that
-    this childmost table is below both parent tables in the outcome (a bug
-    existed where this was not always the case).
-
-    While the straight topological sort tests should expose this, since the
-    sorting can be different due to subtle differences in program execution,
-    this test case was exposing the bug whereas the simpler tests were not.
-
-    """
+class DependencyTwoParentTest(_base.MappedTest):
+    """Test flush() when a mapper is dependent on multiple relationships"""
 
     run_setup_mappers = 'once'
     run_inserts = 'once'
@@ -32,18 +22,24 @@ class RelationshipTest(_base.MappedTest):
     @classmethod
     def define_tables(cls, metadata):
         Table("tbl_a", metadata,
-            Column("id", Integer, primary_key=True, test_needs_autoincrement=True),
+            Column("id", Integer, primary_key=True, 
+                                test_needs_autoincrement=True),
             Column("name", String(128)))
         Table("tbl_b", metadata,
-            Column("id", Integer, primary_key=True, test_needs_autoincrement=True),
+            Column("id", Integer, primary_key=True, 
+                                test_needs_autoincrement=True),
             Column("name", String(128)))
         Table("tbl_c", metadata,
-            Column("id", Integer, primary_key=True, test_needs_autoincrement=True),
-            Column("tbl_a_id", Integer, ForeignKey("tbl_a.id"), nullable=False),
+            Column("id", Integer, primary_key=True, 
+                                test_needs_autoincrement=True),
+            Column("tbl_a_id", Integer, ForeignKey("tbl_a.id"), 
+                                nullable=False),
             Column("name", String(128)))
         Table("tbl_d", metadata,
-            Column("id", Integer, primary_key=True, test_needs_autoincrement=True),
-            Column("tbl_c_id", Integer, ForeignKey("tbl_c.id"), nullable=False),
+            Column("id", Integer, primary_key=True, 
+                                test_needs_autoincrement=True),
+            Column("tbl_c_id", Integer, ForeignKey("tbl_c.id"), 
+                                nullable=False),
             Column("tbl_b_id", Integer, ForeignKey("tbl_b.id")),
             Column("name", String(128)))
 
@@ -62,10 +58,12 @@ class RelationshipTest(_base.MappedTest):
     @testing.resolve_artifact_names
     def setup_mappers(cls):
         mapper(A, tbl_a, properties=dict(
-            c_rows=relationship(C, cascade="all, delete-orphan", backref="a_row")))
+            c_rows=relationship(C, cascade="all, delete-orphan", 
+                                    backref="a_row")))
         mapper(B, tbl_b)
         mapper(C, tbl_c, properties=dict(
-            d_rows=relationship(D, cascade="all, delete-orphan", backref="c_row")))
+            d_rows=relationship(D, cascade="all, delete-orphan", 
+                                    backref="c_row")))
         mapper(D, tbl_d, properties=dict(
             b_row=relationship(B)))
 
@@ -101,8 +99,12 @@ class RelationshipTest(_base.MappedTest):
         session.flush()
 
 
-class RelationshipTest2(_base.MappedTest):
-    """The ultimate relationship() test:
+class CompositeSelfRefFKTest(_base.MappedTest):
+    """Tests a composite FK where, in
+    the relationship(), one col points 
+    to itself in the same table.
+    
+    this is a very unusual case::
     
     company         employee
     ----------      ----------
@@ -117,22 +119,13 @@ class RelationshipTest2(_base.MappedTest):
     employee joins to its sub-employees
     both on reports_to_id, *and on company_id to itself*.
     
-    As of 0.5.5 we are making a slight behavioral change,
-    such that the custom foreign_keys setting
-    on the o2m side has to be explicitly 
-    unset on the backref m2o side - this to suit
-    the vast majority of use cases where the backref()
-    is to receive the same foreign_keys argument 
-    as the forwards reference.   But we also
-    have smartened the remote_side logic such that 
-    you don't even need the custom fks setting.
-    
     """
 
     @classmethod
     def define_tables(cls, metadata):
         Table('company_t', metadata,
-              Column('company_id', Integer, primary_key=True, test_needs_autoincrement=True),
+              Column('company_id', Integer, primary_key=True, 
+                                test_needs_autoincrement=True),
               Column('name', sa.Unicode(30)))
 
         Table('employee_t', metadata,
@@ -163,7 +156,10 @@ class RelationshipTest2(_base.MappedTest):
     def test_explicit(self):
         mapper(Company, company_t)
         mapper(Employee, employee_t, properties= {
-            'company':relationship(Company, primaryjoin=employee_t.c.company_id==company_t.c.company_id, backref='employees'),
+            'company':relationship(Company, 
+                            primaryjoin=employee_t.c.company_id==
+                                                company_t.c.company_id, 
+                                                backref='employees'),
             'reports_to':relationship(Employee, primaryjoin=
                 sa.and_(
                     employee_t.c.emp_id==employee_t.c.reports_to_id,
@@ -244,10 +240,12 @@ class RelationshipTest2(_base.MappedTest):
         test_e5 = sess.query(Employee).get([c2.company_id, e5.emp_id])
         assert test_e5.name == 'emp5', test_e5.name
         assert [x.name for x in test_e1.employees] == ['emp2', 'emp3']
-        assert sess.query(Employee).get([c1.company_id, 3]).reports_to.name == 'emp1'
-        assert sess.query(Employee).get([c2.company_id, 3]).reports_to.name == 'emp5'
+        assert sess.query(Employee).\
+                get([c1.company_id, 3]).reports_to.name == 'emp1'
+        assert sess.query(Employee).\
+                get([c2.company_id, 3]).reports_to.name == 'emp5'
 
-class RelationshipTest3(_base.MappedTest):
+class ComplexPostUpdateTest(_base.MappedTest):
     @classmethod
     def define_tables(cls, metadata):
         Table("jobs", metadata,
@@ -311,7 +309,8 @@ class RelationshipTest3(_base.MappedTest):
                 comment.content = u'some content'
                 return self.currentversion
             def add_comment(self):
-                nextnum = max([-1] + [c.comment_id for c in self.comments]) + 1
+                nextnum = max([-1] + 
+                            [c.comment_id for c in self.comments]) + 1
                 newcomment = PageComment()
                 newcomment.comment_id = nextnum
                 self.comments.append(newcomment)
@@ -340,7 +339,7 @@ class RelationshipTest3(_base.MappedTest):
                  PageVersion,
                  cascade="all, delete-orphan",
                  primaryjoin=sa.and_(pages.c.jobno==pageversions.c.jobno,
-                                     pages.c.pagename==pageversions.c.pagename),
+                                 pages.c.pagename==pageversions.c.pagename),
                  order_by=pageversions.c.version,
                  backref=backref('page',lazy='joined')
                 )})
@@ -348,7 +347,7 @@ class RelationshipTest3(_base.MappedTest):
             'page': relationship(
                   Page,
                   primaryjoin=sa.and_(pages.c.jobno==pagecomments.c.jobno,
-                                      pages.c.pagename==pagecomments.c.pagename),
+                                  pages.c.pagename==pagecomments.c.pagename),
                   backref=backref("comments",
                                   cascade="all, delete-orphan",
                                   order_by=pagecomments.c.comment_id))})
@@ -389,13 +388,14 @@ class RelationshipTest3(_base.MappedTest):
         s.delete(j)
         s.flush()
 
-class RelationshipTest4(_base.MappedTest):
+class FKsAsPksTest(_base.MappedTest):
     """Syncrules on foreign keys that are also primary"""
 
     @classmethod
     def define_tables(cls, metadata):
         Table("tableA", metadata,
-              Column("id",Integer,primary_key=True, test_needs_autoincrement=True),
+              Column("id",Integer,primary_key=True, 
+                            test_needs_autoincrement=True),
               Column("foo",Integer,),
               test_needs_fk=True)
               
@@ -413,7 +413,8 @@ class RelationshipTest4(_base.MappedTest):
 
     @testing.resolve_artifact_names
     def test_onetoone_switch(self):
-        """test that active history is enabled on a one-to-many/one that has use_get==True"""
+        """test that active history is enabled on a 
+        one-to-many/one that has use_get==True"""
         
         mapper(A, tableA, properties={
             'b':relationship(B, cascade="all,delete-orphan", uselist=False)})
@@ -502,7 +503,8 @@ class RelationshipTest4(_base.MappedTest):
 
     @testing.resolve_artifact_names
     def test_delete_cascade_BtoA(self):
-        """No 'blank the PK' error when the child is to be deleted as part of a cascade"""
+        """No 'blank the PK' error when the child is to 
+        be deleted as part of a cascade"""
 
         for cascade in ("save-update, delete",
                         #"save-update, delete-orphan",
@@ -527,7 +529,9 @@ class RelationshipTest4(_base.MappedTest):
 
     @testing.resolve_artifact_names
     def test_delete_cascade_AtoB(self):
-        """No 'blank the PK' error when the child is to be deleted as part of a cascade"""
+        """No 'blank the PK' error when the child is to 
+        be deleted as part of a cascade"""
+        
         for cascade in ("save-update, delete",
                         #"save-update, delete-orphan",
                         "save-update, delete, delete-orphan"):
@@ -590,19 +594,25 @@ class RelationshipTest4(_base.MappedTest):
         assert a1 not in sess
         assert b1 not in sess
 
-class RelationshipToUniqueTest(_base.MappedTest):
-    """test a relationship based on a primary join against a unique non-pk column"""
+class UniqueColReferenceSwitchTest(_base.MappedTest):
+    """test a relationship based on a primary 
+    join against a unique non-pk column"""
     
     @classmethod
     def define_tables(cls, metadata):
         Table("table_a", metadata,
-                        Column("id", Integer, primary_key=True, test_needs_autoincrement=True),
-                        Column("ident", String(10), nullable=False, unique=True),
+                        Column("id", Integer, primary_key=True, 
+                                        test_needs_autoincrement=True),
+                        Column("ident", String(10), nullable=False, 
+                                        unique=True),
                         )
 
         Table("table_b", metadata,
-                        Column("id", Integer, primary_key=True, test_needs_autoincrement=True),
-                        Column("a_ident", String(10), ForeignKey('table_a.ident'), nullable=False),
+                        Column("id", Integer, primary_key=True, 
+                                        test_needs_autoincrement=True),
+                        Column("a_ident", String(10), 
+                                        ForeignKey('table_a.ident'), 
+                                        nullable=False),
                         )
     
     @classmethod
@@ -632,7 +642,7 @@ class RelationshipToUniqueTest(_base.MappedTest):
         session.delete(a1)
         session.flush()
     
-class RelationshipTest5(_base.MappedTest):
+class RelationshipToSelectableTest(_base.MappedTest):
     """Test a map to a select that relates to a map to the table."""
 
     @classmethod
@@ -671,7 +681,8 @@ class RelationshipTest5(_base.MappedTest):
                        order_by=sa.asc(items.c.id),
                        primaryjoin=sa.and_(
                          container_select.c.policyNum==items.c.policyNum,
-                         container_select.c.policyEffDate==items.c.policyEffDate,
+                         container_select.c.policyEffDate==
+                                                    items.c.policyEffDate,
                          container_select.c.type==items.c.type),
                        foreign_keys=[
                          items.c.policyNum,
@@ -697,7 +708,7 @@ class RelationshipTest5(_base.MappedTest):
         for old, new in zip(con.lineItems, newcon.lineItems):
             eq_(old.id, new.id)
 
-class RelationshipTest6(_base.MappedTest):
+class FKEquatedToConstantTest(_base.MappedTest):
     """test a relationship with a non-column entity in the primary join, 
     is not viewonly, and also has the non-column's clause mentioned in the 
     foreign keys list.
@@ -706,12 +717,14 @@ class RelationshipTest6(_base.MappedTest):
     
     @classmethod
     def define_tables(cls, metadata):
-        Table('tags', metadata, Column("id", Integer, primary_key=True, test_needs_autoincrement=True),
+        Table('tags', metadata, Column("id", Integer, primary_key=True, 
+                                            test_needs_autoincrement=True),
             Column("data", String(50)),
         )
 
         Table('tag_foo', metadata, 
-            Column("id", Integer, primary_key=True, test_needs_autoincrement=True),
+            Column("id", Integer, primary_key=True, 
+                                        test_needs_autoincrement=True),
             Column('tagid', Integer),
             Column("data", String(50)),
         )
@@ -742,7 +755,10 @@ class RelationshipTest6(_base.MappedTest):
         sess.expunge_all()
         
         # relationship works
-        eq_(sess.query(Tag).all(), [Tag(data='some tag', foo=[TagInstance(data='iplc_case')])])
+        eq_(
+            sess.query(Tag).all(), 
+            [Tag(data='some tag', foo=[TagInstance(data='iplc_case')])]
+        )
         
         # both TagInstances were persisted
         eq_(
@@ -755,11 +771,13 @@ class BackrefPropagatesForwardsArgs(_base.MappedTest):
     @classmethod
     def define_tables(cls, metadata):
         Table('users', metadata, 
-            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+            Column('id', Integer, primary_key=True, 
+                                        test_needs_autoincrement=True),
             Column('name', String(50))
         )
         Table('addresses', metadata, 
-            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+            Column('id', Integer, primary_key=True, 
+                                        test_needs_autoincrement=True),
             Column('user_id', Integer),
             Column('email', String(50))
         )
@@ -791,24 +809,28 @@ class BackrefPropagatesForwardsArgs(_base.MappedTest):
         ])
     
 class AmbiguousJoinInterpretedAsSelfRef(_base.MappedTest):
-    """test ambiguous joins due to FKs on both sides treated as self-referential.
+    """test ambiguous joins due to FKs on both sides treated as
+    self-referential.
     
-    this mapping is very similar to that of test/orm/inheritance/query.py
-    SelfReferentialTestJoinedToBase , except that inheritance is not used
-    here.
+    this mapping is very similar to that of
+    test/orm/inheritance/query.py
+    SelfReferentialTestJoinedToBase , except that inheritance is
+    not used here.
     
     """
     
     @classmethod
     def define_tables(cls, metadata):
         subscriber_table = Table('subscriber', metadata,
-           Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+           Column('id', Integer, primary_key=True, 
+                            test_needs_autoincrement=True),
            Column('dummy', String(10)) # to appease older sqlite version
           )
 
         address_table = Table('address',
                  metadata,
-                 Column('subscriber_id', Integer, ForeignKey('subscriber.id'), primary_key=True),
+                 Column('subscriber_id', Integer, 
+                            ForeignKey('subscriber.id'), primary_key=True),
                  Column('type', String(1), primary_key=True),
                  )
 
@@ -816,7 +838,8 @@ class AmbiguousJoinInterpretedAsSelfRef(_base.MappedTest):
     @testing.resolve_artifact_names
     def setup_mappers(cls):
         subscriber_and_address = subscriber.join(address, 
-               and_(address.c.subscriber_id==subscriber.c.id, address.c.type.in_(['A', 'B', 'C'])))
+            and_(address.c.subscriber_id==subscriber.c.id, 
+                address.c.type.in_(['A', 'B', 'C'])))
 
         class Address(_base.ComparableEntity):
             pass
@@ -918,8 +941,10 @@ class ManualBackrefTest(_fixtures.FixtureTest):
         })
         
         assert_raises_message(sa.exc.ArgumentError, 
-            r"reverse_property 'dingaling' on relationship User.addresses references "
-            "relationship Address.dingaling, which does not reference mapper Mapper\|User\|users", 
+            r"reverse_property 'dingaling' on relationship "
+            "User.addresses references "
+            "relationship Address.dingaling, which does not "
+            "reference mapper Mapper\|User\|users", 
             compile_mappers)
         
 class JoinConditionErrorTest(testing.TestBase):
@@ -966,7 +991,8 @@ class JoinConditionErrorTest(testing.TestBase):
         class C2(object):
             pass
 
-        mapper(C1, t1, properties={'c2':relationship(C2,  primaryjoin=t1.join(t2))})
+        mapper(C1, t1, properties={'c2':relationship(C2,  
+                                            primaryjoin=t1.join(t2))})
         mapper(C2, t2)
         assert_raises(sa.exc.ArgumentError, compile_mappers)
     
@@ -996,7 +1022,9 @@ class JoinConditionErrorTest(testing.TestBase):
             
             assert_raises_message(
                 sa.exc.ArgumentError, 
-                "Column-based expression object expected for argument '%s'; got: '%s', type %r" % (argname, arg[0], type(arg[0])),
+                "Column-based expression object expected "
+                "for argument '%s'; got: '%s', type %r" % 
+                (argname, arg[0], type(arg[0])),
                 compile_mappers)
         
     
@@ -1053,23 +1081,28 @@ class JoinConditionErrorTest(testing.TestBase):
         clear_mappers()    
         
 class TypeMatchTest(_base.MappedTest):
-    """test errors raised when trying to add items whose type is not handled by a relationship"""
+    """test errors raised when trying to add items 
+        whose type is not handled by a relationship"""
 
     @classmethod
     def define_tables(cls, metadata):
         Table("a", metadata,
-              Column('aid', Integer, primary_key=True, test_needs_autoincrement=True),
+              Column('aid', Integer, primary_key=True, 
+                                test_needs_autoincrement=True),
               Column('data', String(30)))
         Table("b", metadata,
-               Column('bid', Integer, primary_key=True, test_needs_autoincrement=True),
+               Column('bid', Integer, primary_key=True, 
+                                test_needs_autoincrement=True),
                Column("a_id", Integer, ForeignKey("a.aid")),
                Column('data', String(30)))
         Table("c", metadata,
-              Column('cid', Integer, primary_key=True, test_needs_autoincrement=True),
+              Column('cid', Integer, primary_key=True, 
+                                test_needs_autoincrement=True),
               Column("b_id", Integer, ForeignKey("b.bid")),
               Column('data', String(30)))
         Table("d", metadata,
-              Column('did', Integer, primary_key=True, test_needs_autoincrement=True),
+              Column('did', Integer, primary_key=True, 
+                                test_needs_autoincrement=True),
               Column("a_id", Integer, ForeignKey("a.aid")),
               Column('data', String(30)))
 
@@ -1115,7 +1148,8 @@ class TypeMatchTest(_base.MappedTest):
         sess.add(b1)
         sess.add(c1)
         assert_raises_message(sa.orm.exc.FlushError,
-                                 "Attempting to flush an item", sess.flush)
+                                 "Attempting to flush an item", 
+                                 sess.flush)
 
     @testing.resolve_artifact_names
     def test_o2m_nopoly_onflush(self):
@@ -1136,7 +1170,8 @@ class TypeMatchTest(_base.MappedTest):
         sess.add(b1)
         sess.add(c1)
         assert_raises_message(sa.orm.exc.FlushError,
-                                 "Attempting to flush an item", sess.flush)
+                                 "Attempting to flush an item", 
+                                 sess.flush)
 
     @testing.resolve_artifact_names
     def test_m2o_nopoly_onflush(self):
@@ -1153,7 +1188,8 @@ class TypeMatchTest(_base.MappedTest):
         sess.add(b1)
         sess.add(d1)
         assert_raises_message(sa.orm.exc.FlushError,
-                                 "Attempting to flush an item", sess.flush)
+                                 "Attempting to flush an item", 
+                                 sess.flush)
 
     @testing.resolve_artifact_names
     def test_m2o_oncascade(self):
@@ -1168,7 +1204,8 @@ class TypeMatchTest(_base.MappedTest):
         d1.a = b1
         sess = create_session()
         assert_raises_message(AssertionError,
-                                 "doesn't handle objects of type", sess.add, d1)
+                             "doesn't handle objects of type", 
+                             sess.add, d1)
 
 class TypedAssociationTable(_base.MappedTest):
 
@@ -1224,10 +1261,12 @@ class ViewOnlyM2MBackrefTest(_base.MappedTest):
     @classmethod
     def define_tables(cls, metadata):
         Table("t1", metadata,
-            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+            Column('id', Integer, primary_key=True, 
+                                test_needs_autoincrement=True),
             Column('data', String(40)))
         Table("t2", metadata,
-            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+            Column('id', Integer, primary_key=True, 
+                                test_needs_autoincrement=True),
             Column('data', String(40)),
         )
         Table("t1t2", metadata,
@@ -1241,7 +1280,8 @@ class ViewOnlyM2MBackrefTest(_base.MappedTest):
         class B(_base.ComparableEntity):pass
         
         mapper(A, t1, properties={
-            'bs':relationship(B, secondary=t1t2, backref=backref('as_', viewonly=True))
+            'bs':relationship(B, secondary=t1t2, 
+                                backref=backref('as_', viewonly=True))
         })
         mapper(B, t2)
         
@@ -1264,14 +1304,17 @@ class ViewOnlyOverlappingNames(_base.MappedTest):
     @classmethod
     def define_tables(cls, metadata):
         Table("t1", metadata,
-            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+            Column('id', Integer, primary_key=True, 
+                                    test_needs_autoincrement=True),
             Column('data', String(40)))
         Table("t2", metadata,
-            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+            Column('id', Integer, primary_key=True, 
+                                    test_needs_autoincrement=True),
             Column('data', String(40)),
             Column('t1id', Integer, ForeignKey('t1.id')))
         Table("t3", metadata,
-            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+            Column('id', Integer, primary_key=True, 
+                                    test_needs_autoincrement=True),
             Column('data', String(40)),
             Column('t2id', Integer, ForeignKey('t2.id')))
 
@@ -1324,14 +1367,17 @@ class ViewOnlyUniqueNames(_base.MappedTest):
     @classmethod
     def define_tables(cls, metadata):
         Table("t1", metadata,
-            Column('t1id', Integer, primary_key=True, test_needs_autoincrement=True),
+            Column('t1id', Integer, primary_key=True, 
+                                        test_needs_autoincrement=True),
             Column('data', String(40)))
         Table("t2", metadata,
-            Column('t2id', Integer, primary_key=True, test_needs_autoincrement=True),
+            Column('t2id', Integer, primary_key=True, 
+                                        test_needs_autoincrement=True),
             Column('data', String(40)),
             Column('t1id_ref', Integer, ForeignKey('t1.t1id')))
         Table("t3", metadata,
-            Column('t3id', Integer, primary_key=True, test_needs_autoincrement=True),
+            Column('t3id', Integer, primary_key=True, 
+                                        test_needs_autoincrement=True),
             Column('data', String(40)),
             Column('t2id_ref', Integer, ForeignKey('t2.t2id')))
 
@@ -1505,10 +1551,12 @@ class ViewOnlyRepeatedLocalColumn(_base.MappedTest):
     @classmethod
     def define_tables(cls, metadata):
         Table('foos', metadata,
-              Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+              Column('id', Integer, primary_key=True, 
+                                        test_needs_autoincrement=True),
               Column('data', String(50)))
 
-        Table('bars', metadata, Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+        Table('bars', metadata, Column('id', Integer, primary_key=True, 
+                                        test_needs_autoincrement=True),
               Column('fid1', Integer, ForeignKey('foos.id')),
               Column('fid2', Integer, ForeignKey('foos.id')),
               Column('data', String(50)))
@@ -1553,14 +1601,17 @@ class ViewOnlyComplexJoin(_base.MappedTest):
     @classmethod
     def define_tables(cls, metadata):
         Table('t1', metadata,
-            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+            Column('id', Integer, primary_key=True, 
+                                    test_needs_autoincrement=True),
             Column('data', String(50)))
         Table('t2', metadata,
-            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+            Column('id', Integer, primary_key=True, 
+                                    test_needs_autoincrement=True),
             Column('data', String(50)),
             Column('t1id', Integer, ForeignKey('t1.id')))
         Table('t3', metadata,
-            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+            Column('id', Integer, primary_key=True, 
+                                    test_needs_autoincrement=True),
             Column('data', String(50)))
         Table('t2tot3', metadata,
             Column('t2id', Integer, ForeignKey('t2.id')),
@@ -1624,10 +1675,12 @@ class ExplicitLocalRemoteTest(_base.MappedTest):
     @classmethod
     def define_tables(cls, metadata):
         Table('t1', metadata,
-            Column('id', String(50), primary_key=True, test_needs_autoincrement=True),
+            Column('id', String(50), primary_key=True, 
+                                        test_needs_autoincrement=True),
             Column('data', String(50)))
         Table('t2', metadata,
-            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+            Column('id', Integer, primary_key=True, 
+                                        test_needs_autoincrement=True),
             Column('data', String(50)),
             Column('t1id', String(50)))
 
@@ -1777,19 +1830,25 @@ class InvalidRemoteSideTest(_base.MappedTest):
             't1s':relationship(T1, backref='parent')
         })
 
-        assert_raises_message(sa.exc.ArgumentError, "T1.t1s and back-reference T1.parent are "
-                    "both of the same direction <symbol 'ONETOMANY>.  Did you "
-                    "mean to set remote_side on the many-to-one side ?", sa.orm.compile_mappers)
+        assert_raises_message(sa.exc.ArgumentError, 
+            "T1.t1s and back-reference T1.parent are "
+            "both of the same direction <symbol 'ONETOMANY>.  Did you "
+            "mean to set remote_side on the many-to-one side ?", 
+            sa.orm.compile_mappers)
 
     @testing.resolve_artifact_names
     def test_m2o_backref(self):
         mapper(T1, t1, properties={
-            't1s':relationship(T1, backref=backref('parent', remote_side=t1.c.id), remote_side=t1.c.id)
+            't1s':relationship(T1, 
+                        backref=backref('parent', remote_side=t1.c.id), 
+                        remote_side=t1.c.id)
         })
 
-        assert_raises_message(sa.exc.ArgumentError, "T1.t1s and back-reference T1.parent are "
-                    "both of the same direction <symbol 'MANYTOONE>.  Did you "
-                    "mean to set remote_side on the many-to-one side ?", sa.orm.compile_mappers)
+        assert_raises_message(sa.exc.ArgumentError, 
+            "T1.t1s and back-reference T1.parent are "
+            "both of the same direction <symbol 'MANYTOONE>.  Did you "
+            "mean to set remote_side on the many-to-one side ?", 
+            sa.orm.compile_mappers)
 
     @testing.resolve_artifact_names
     def test_o2m_explicit(self):
@@ -1800,20 +1859,24 @@ class InvalidRemoteSideTest(_base.MappedTest):
 
         # can't be sure of ordering here
         assert_raises_message(sa.exc.ArgumentError, 
-                    "both of the same direction <symbol 'ONETOMANY>.  Did you "
-                    "mean to set remote_side on the many-to-one side ?", sa.orm.compile_mappers)
+            "both of the same direction <symbol 'ONETOMANY>.  Did you "
+            "mean to set remote_side on the many-to-one side ?", 
+            sa.orm.compile_mappers)
 
     @testing.resolve_artifact_names
     def test_m2o_explicit(self):
         mapper(T1, t1, properties={
-            't1s':relationship(T1, back_populates='parent', remote_side=t1.c.id),
-            'parent':relationship(T1, back_populates='t1s', remote_side=t1.c.id)
+            't1s':relationship(T1, back_populates='parent', 
+                                remote_side=t1.c.id),
+            'parent':relationship(T1, back_populates='t1s', 
+                                remote_side=t1.c.id)
         })
 
         # can't be sure of ordering here
         assert_raises_message(sa.exc.ArgumentError, 
-                    "both of the same direction <symbol 'MANYTOONE>.  Did you "
-                    "mean to set remote_side on the many-to-one side ?", sa.orm.compile_mappers)
+            "both of the same direction <symbol 'MANYTOONE>.  Did you "
+            "mean to set remote_side on the many-to-one side ?", 
+            sa.orm.compile_mappers)
 
         
 class InvalidRelationshipEscalationTest(_base.MappedTest):
@@ -1872,7 +1935,8 @@ class InvalidRelationshipEscalationTest(_base.MappedTest):
 
         assert_raises_message(
             sa.exc.ArgumentError,
-            "Could not determine relationship direction for primaryjoin condition",
+            "Could not determine relationship direction "
+            "for primaryjoin condition",
             sa.orm.compile_mappers)
 
     @testing.resolve_artifact_names
@@ -1953,7 +2017,8 @@ class InvalidRelationshipEscalationTest(_base.MappedTest):
 
         assert_raises_message(
             sa.exc.ArgumentError,
-            "Could not determine relationship direction for primaryjoin condition",
+            "Could not determine relationship direction for primaryjoin "
+            "condition",
             sa.orm.compile_mappers)
 
     @testing.resolve_artifact_names
@@ -2036,13 +2101,14 @@ class InvalidRelationshipEscalationTest(_base.MappedTest):
 
         assert_raises_message(
             sa.exc.ArgumentError,
-            "Could not determine relationship direction for primaryjoin condition",
+            "Could not determine relationship direction for primaryjoin "
+            "condition",
             sa.orm.compile_mappers)
 
         sa.orm.clear_mappers()
         mapper(Foo, foos_with_fks, properties={
             'bars':relationship(Bar,
-                            primaryjoin=foos_with_fks.c.id==bars_with_fks.c.fid)})
+                        primaryjoin=foos_with_fks.c.id==bars_with_fks.c.fid)})
         mapper(Bar, bars_with_fks)
         sa.orm.compile_mappers()
 
@@ -2054,7 +2120,8 @@ class InvalidRelationshipEscalationTest(_base.MappedTest):
 
         assert_raises_message(
             sa.exc.ArgumentError,
-            "Could not determine relationship direction for primaryjoin condition",
+            "Could not determine relationship direction for primaryjoin "
+            "condition",
             sa.orm.compile_mappers)
         
 
@@ -2067,7 +2134,8 @@ class InvalidRelationshipEscalationTest(_base.MappedTest):
 
         assert_raises_message(
             sa.exc.ArgumentError,
-            "Could not determine relationship direction for primaryjoin condition",
+            "Could not determine relationship direction for primaryjoin "
+            "condition",
             sa.orm.compile_mappers)
 
 
@@ -2148,9 +2216,12 @@ class InvalidRelationshipEscalationTestM2M(_base.MappedTest):
         
         sa.orm.clear_mappers()
         mapper(Foo, foos, properties={
-                        'bars': relationship(Bar, secondary=foobars_with_many_columns, 
-                              primaryjoin=foos.c.id==foobars_with_many_columns.c.fid,
-                              secondaryjoin=foobars_with_many_columns.c.bid==bars.c.id)})
+                        'bars': relationship(Bar, 
+                            secondary=foobars_with_many_columns, 
+                          primaryjoin=foos.c.id==
+                                            foobars_with_many_columns.c.fid,
+                          secondaryjoin=foobars_with_many_columns.c.bid==
+                                                    bars.c.id)})
         mapper(Bar, bars)
 
         assert_raises_message(sa.exc.SAWarning,
@@ -2188,9 +2259,12 @@ class InvalidRelationshipEscalationTestM2M(_base.MappedTest):
 
         sa.orm.clear_mappers()
         mapper(Foo, foos, properties={
-                        'bars': relationship(Bar, secondary=foobars_with_many_columns, 
-                              primaryjoin=foos.c.id==foobars_with_many_columns.c.fid,
-                              secondaryjoin=foobars_with_many_columns.c.bid==bars.c.id)})
+                        'bars': relationship(Bar, 
+                                secondary=foobars_with_many_columns, 
+                              primaryjoin=foos.c.id==
+                                        foobars_with_many_columns.c.fid,
+                              secondaryjoin=foobars_with_many_columns.c.bid==
+                                        bars.c.id)})
         mapper(Bar, bars)
         sa.orm.compile_mappers()
         eq_(
@@ -2214,7 +2288,8 @@ class InvalidRelationshipEscalationTestM2M(_base.MappedTest):
 
         assert_raises_message(
             sa.exc.ArgumentError,
-            "Could not determine relationship direction for primaryjoin condition",
+            "Could not determine relationship direction for "
+            "primaryjoin condition",
             sa.orm.compile_mappers)
     
         sa.orm.clear_mappers()
@@ -2226,7 +2301,8 @@ class InvalidRelationshipEscalationTestM2M(_base.MappedTest):
         mapper(Bar, bars)
         assert_raises_message(
             sa.exc.ArgumentError,
-            "Could not locate any equated, locally mapped column pairs for primaryjoin condition ",
+            "Could not locate any equated, locally mapped column pairs for "
+            "primaryjoin condition ",
             sa.orm.compile_mappers)
 
         sa.orm.clear_mappers()
@@ -2279,8 +2355,71 @@ class InvalidRelationshipEscalationTestM2M(_base.MappedTest):
             "Could not locate any equated, locally mapped column pairs for "
             "secondaryjoin condition", sa.orm.compile_mappers)
 
+class ActiveHistoryFlagTest(_fixtures.FixtureTest):
+    run_inserts = None
+    run_deletes = None
+    
+    def _test_attribute(self, obj, attrname, newvalue):
+        sess = Session()
+        sess.add(obj)
+        oldvalue = getattr(obj, attrname)
+        sess.commit()
+        
+        # expired
+        assert attrname not in obj.__dict__
+        
+        setattr(obj, attrname, newvalue)
+        eq_(
+            attributes.get_history(obj, attrname),
+            ([newvalue,], (), [oldvalue,])
+        )
+    
+    @testing.resolve_artifact_names
+    def test_column_property_flag(self):
+        mapper(User, users, properties={
+            'name':column_property(users.c.name, active_history=True)
+        })
+        u1 = User(name='jack')
+        self._test_attribute(u1, 'name', 'ed')
+
+    @testing.resolve_artifact_names
+    def test_relationship_property_flag(self):
+        mapper(Address, addresses, properties={
+            'user':relationship(User, active_history=True)
+        })
+        mapper(User, users)
+        u1 = User(name='jack')
+        u2 = User(name='ed')
+        a1 = Address(email_address='a1', user=u1)
+        self._test_attribute(a1, 'user', u2)
+    
+    @testing.resolve_artifact_names
+    def test_composite_property_flag(self):
+        # active_history is implicit for composites
+        # right now, no flag needed
+        class MyComposite(object):
+            def __init__(self, description, isopen):
+                self.description = description
+                self.isopen = isopen
+            def __composite_values__(self):
+                return [self.description, self.isopen]
+            def __eq__(self, other):
+                return isinstance(other, MyComposite) and \
+                    other.description == self.description
+        mapper(Order, orders, properties={
+            'composite':composite(
+                                MyComposite, 
+                                orders.c.description, 
+                                orders.c.isopen)
+        })
+        o1 = Order(composite=MyComposite('foo', 1))
+        self._test_attribute(o1, "composite", MyComposite('bar', 1))
+        
+    
 
 class RelationDeprecationTest(_base.MappedTest):
+    """test usage of the old 'relation' function."""
+    
     run_inserts = 'once'
     run_deletes = None