]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- the mechanics of "backref" have been fully merged into the
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 5 Oct 2009 22:11:06 +0000 (22:11 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 5 Oct 2009 22:11:06 +0000 (22:11 +0000)
finer grained "back_populates" system, and take place entirely
within the _generate_backref() method of RelationProperty.  This
makes the initialization procedure of RelationProperty
simpler and allows easier propagation of settings (such as from
subclasses of RelationProperty) into the reverse reference.
The internal BackRef() is gone and backref() returns a plain
tuple that is understood by RelationProperty.

CHANGES
lib/sqlalchemy/ext/declarative.py
lib/sqlalchemy/orm/__init__.py
lib/sqlalchemy/orm/properties.py
lib/sqlalchemy/orm/strategies.py

diff --git a/CHANGES b/CHANGES
index 12f643e87e4a765268bd2d4b38f67c654e1865a0..1c2b913c4df24d7202f479972d3cb3fd0799679b 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -65,6 +65,15 @@ CHANGES
         i.e. ForeignKey(MyRelatedClass.id) doesn't break the
         "use_get" condition from taking place [ticket:1492]
 
+   - the mechanics of "backref" have been fully merged into the
+     finer grained "back_populates" system, and take place entirely
+     within the _generate_backref() method of RelationProperty.  This
+     makes the initialization procedure of RelationProperty
+     simpler and allows easier propagation of settings (such as from
+     subclasses of RelationProperty) into the reverse reference. 
+     The internal BackRef() is gone and backref() returns a plain 
+     tuple that is understood by RelationProperty.
+     
 - sql
     - the autoincrement flag on column now indicates the column
       which should be linked to cursor.lastrowid, if that method
index 3d8596e0510d9980c6d668022055a55fbb5621c3..5d712df0f8db7100c9ba1cda9e9dd1cd0c1fd76c 100644 (file)
@@ -630,10 +630,11 @@ def _deferred_relation(cls, prop):
             if isinstance(v, basestring):
                 setattr(prop, attr, resolve_arg(v))
 
-        if prop.backref:
+        if prop.backref and isinstance(prop.backref, tuple):
+            key, kwargs = prop.backref
             for attr in ('primaryjoin', 'secondaryjoin', 'secondary', 'foreign_keys', 'remote_side', 'order_by'):
-               if attr in prop.backref.kwargs and isinstance(prop.backref.kwargs[attr], basestring):
-                   prop.backref.kwargs[attr] = resolve_arg(prop.backref.kwargs[attr])
+               if attr in kwargs and isinstance(kwargs[attr], basestring):
+                   kwargs[attr] = resolve_arg(kwargs[attr])
 
 
     return prop
index 713ade3e5a0e4d6a860c73dbf6078d3635bb03de..a343dffb8163d33f8d39a77c1cee11f656533a0d 100644 (file)
@@ -38,7 +38,6 @@ from sqlalchemy.orm.util import (
      with_parent,
      )
 from sqlalchemy.orm.properties import (
-     BackRef,
      ColumnProperty,
      ComparableProperty,
      CompositeProperty,
@@ -582,14 +581,14 @@ def composite(class_, *cols, **kwargs):
 
 
 def backref(name, **kwargs):
-    """Create a BackRef object with explicit arguments, which are the same
+    """Create a back reference with explicit arguments, which are the same
     arguments one can send to ``relation()``.
 
     Used with the `backref` keyword argument to ``relation()`` in
     place of a string argument.
 
     """
-    return BackRef(name, **kwargs)
+    return (name, kwargs)
 
 def deferred(*columns, **kwargs):
     """Return a ``DeferredColumnProperty``, which indicates this
index 445496e34aa390f6c7489e36c0ee30da922b9ba6..f49f5fe88a464ee7ffb9849633c6a62d34fe2a2a 100644 (file)
@@ -417,16 +417,6 @@ class RelationProperty(StrategizedProperty):
             if backref:
                 raise sa_exc.ArgumentError("backref and back_populates keyword arguments are mutually exclusive")
             self.backref = None
-        elif isinstance(backref, basestring):
-            # propagate explicitly sent primary/secondary join conditions to the BackRef object if
-            # just a string was sent
-            if secondary is not None:
-                # reverse primary/secondary in case of a many-to-many
-                self.backref = BackRef(backref, primaryjoin=secondaryjoin, 
-                                    secondaryjoin=primaryjoin, passive_updates=self.passive_updates)
-            else:
-                self.backref = BackRef(backref, primaryjoin=primaryjoin, 
-                                    secondaryjoin=secondaryjoin, passive_updates=self.passive_updates)
         else:
             self.backref = backref
 
@@ -705,16 +695,18 @@ class RelationProperty(StrategizedProperty):
         
         if self.direction in (ONETOMANY, MANYTOONE) and self.direction == other.direction:
             raise sa_exc.ArgumentError("%s and back-reference %s are both of the same direction %r."
-                "  Did you mean to set remote_side on the many-to-one side ?" % (self, other, self.direction))
+                "  Did you mean to set remote_side on the many-to-one side ?" % (other, self, self.direction))
         
     def do_init(self):
         self._get_target()
+        self._assert_is_primary()
         self._process_dependent_arguments()
         self._determine_joins()
         self._determine_synchronize_pairs()
         self._determine_direction()
         self._determine_local_remote_pairs()
         self._post_init()
+        self._generate_backref()
         super(RelationProperty, self).do_init()
 
     def _get_target(self):
@@ -886,6 +878,7 @@ class RelationProperty(StrategizedProperty):
     def _determine_direction(self):
         if self.secondaryjoin is not None:
             self.direction = MANYTOMANY
+            
         elif self._refers_to_parent_table():
             # self referential defaults to ONETOMANY unless the "remote" side is present
             # and does not reference any foreign key columns
@@ -997,7 +990,66 @@ class RelationProperty(StrategizedProperty):
 
         self.local_side, self.remote_side = [util.ordered_column_set(x) for x in zip(*list(self.local_remote_pairs))]
 
+    def _assert_is_primary(self):
+        if not self.is_primary() and \
+            not mapper.class_mapper(self.parent.class_, compile=False)._get_property(self.key, raiseerr=False):
+
+            raise sa_exc.ArgumentError("Attempting to assign a new relation '%s' to "
+                "a non-primary mapper on class '%s'.  New relations can only be "
+                "added to the primary mapper, i.e. the very first "
+                "mapper created for class '%s' " % (self.key, self.parent.class_.__name__, self.parent.class_.__name__))
+            
+    def _generate_backref(self):
+        if not self.is_primary():
+            return
 
+        if self.backref is not None and not self.back_populates:
+            if isinstance(self.backref, basestring):
+                backref_key, kwargs = self.backref, {}
+            else:
+                backref_key, kwargs = self.backref
+            
+            mapper = self.mapper.primary_mapper()
+            if mapper._get_property(backref_key, raiseerr=False) is not None:
+                raise sa_exc.ArgumentError("Error creating backref '%s' on relation '%s': "
+                    "property of that name exists on mapper '%s'" % (backref_key, self, mapper))
+            
+            if self.secondary is not None:
+                pj = kwargs.pop('primaryjoin', self.secondaryjoin)
+                sj = kwargs.pop('secondaryjoin', self.primaryjoin)
+            else:
+                pj = kwargs.pop('primaryjoin', self.primaryjoin)
+                sj = kwargs.pop('secondaryjoin', None)
+                if sj:
+                    raise sa_exc.InvalidRequestError(
+                        "Can't assign 'secondaryjoin' on a backref against "
+                        "a non-secondary relation.")
+
+            foreign_keys = kwargs.pop('foreign_keys', self._foreign_keys)
+
+            parent = self.parent.primary_mapper()
+            kwargs.setdefault('viewonly', self.viewonly)
+            kwargs.setdefault('post_update', self.post_update)
+            
+            self.back_populates = backref_key
+            relation = RelationProperty(
+                                        parent, 
+                                        self.secondary, 
+                                        pj, 
+                                        sj, 
+                                        foreign_keys=foreign_keys,
+                                        back_populates=self.key,
+                                       **kwargs)
+
+            mapper._configure_property(backref_key, relation)
+
+
+        if self.back_populates:
+            self.extension = util.to_list(self.extension) or []
+            self.extension.append(attributes.GenericBackrefExtension(self.back_populates))
+            self._add_reverse_property(self.back_populates)
+        
+        
     def _post_init(self):
         if self._should_log_info:
             self.logger.info("%s setup primary join %s", self, self.primaryjoin)
@@ -1006,32 +1058,13 @@ class RelationProperty(StrategizedProperty):
             self.logger.info("%s secondary synchronize pairs [%s]", self, ",".join(("(%s => %s)" % (l, r) for l, r in self.secondary_synchronize_pairs or [])))
             self.logger.info("%s local/remote pairs [%s]", self, ",".join("(%s / %s)" % (l, r) for l, r in self.local_remote_pairs))
             self.logger.info("%s relation direction %s", self, self.direction)
-
-        if self.uselist is None and self.direction is MANYTOONE:
-            self.uselist = False
-
+        
         if self.uselist is None:
-            self.uselist = True
-
+            self.uselist = self.direction is not MANYTOONE
+            
         if not self.viewonly:
             self._dependency_processor = dependency.create_dependency_processor(self)
 
-        # primary property handler, set up class attributes
-        if self.is_primary():
-            if self.back_populates:
-                self.extension = util.to_list(self.extension) or []
-                self.extension.append(attributes.GenericBackrefExtension(self.back_populates))
-                self._add_reverse_property(self.back_populates)
-            
-            if self.backref is not None:
-                self.backref.compile(self)
-        elif not mapper.class_mapper(self.parent.class_, compile=False)._get_property(self.key, raiseerr=False):
-            raise sa_exc.ArgumentError("Attempting to assign a new relation '%s' to "
-                "a non-primary mapper on class '%s'.  New relations can only be "
-                "added to the primary mapper, i.e. the very first "
-                "mapper created for class '%s' " % (self.key, self.parent.class_.__name__, self.parent.class_.__name__))
-        
-
     def _refers_to_parent_table(self):
         for c, f in self.synchronize_pairs:
             if c.table is f.table:
@@ -1144,59 +1177,6 @@ class RelationProperty(StrategizedProperty):
 PropertyLoader = RelationProperty
 log.class_logger(RelationProperty)
 
-class BackRef(object):
-    """Attached to a RelationProperty to indicate a complementary reverse relationship.
-
-    Handles the job of creating the opposite RelationProperty according to configuration.
-    
-    Alternatively, two explicit RelationProperty objects can be associated bidirectionally
-    using the back_populates keyword argument on each.
-    
-    """
-
-    def __init__(self, key, _prop=None, **kwargs):
-        self.key = key
-        self.kwargs = kwargs
-        self.prop = _prop
-        self.extension = attributes.GenericBackrefExtension(self.key)
-
-    def compile(self, prop):
-        if self.prop:
-            return
-
-        self.prop = prop
-
-        mapper = prop.mapper.primary_mapper()
-        if mapper._get_property(self.key, raiseerr=False) is None:
-            if prop.secondary is not None:
-                pj = self.kwargs.pop('primaryjoin', prop.secondaryjoin)
-                sj = self.kwargs.pop('secondaryjoin', prop.primaryjoin)
-            else:
-                pj = self.kwargs.pop('primaryjoin', prop.primaryjoin)
-                sj = self.kwargs.pop('secondaryjoin', None)
-                if sj:
-                    raise sa_exc.InvalidRequestError(
-                        "Can't assign 'secondaryjoin' on a backref against "
-                        "a non-secondary relation.")
-            
-            foreign_keys = self.kwargs.pop('foreign_keys', prop._foreign_keys)
-            
-            parent = prop.parent.primary_mapper()
-            self.kwargs.setdefault('viewonly', prop.viewonly)
-            self.kwargs.setdefault('post_update', prop.post_update)
-
-            relation = RelationProperty(parent, prop.secondary, pj, sj, foreign_keys=foreign_keys,
-                                      backref=BackRef(prop.key, _prop=prop),
-                                      **self.kwargs)
-
-            mapper._configure_property(self.key, relation);
-
-            prop._add_reverse_property(self.key)
-
-        else:
-            raise sa_exc.ArgumentError("Error creating backref '%s' on relation '%s': "
-                "property of that name exists on mapper '%s'" % (self.key, prop, mapper))
-
 mapper.ColumnProperty = ColumnProperty
 mapper.SynonymProperty = SynonymProperty
 mapper.ComparableProperty = ComparableProperty
index 5192417439a013e854984a16ccb36bbfad78ca9b..a1369fa6b3d647d68b86366f2a11774a3d3eeff7 100644 (file)
@@ -35,13 +35,10 @@ def _register_attribute(strategy, mapper, useobject,
     attribute_ext = util.to_list(prop.extension) or []
         
     if useobject and prop.single_parent:
-        attribute_ext.append(_SingleParentValidator(prop))
+        attribute_ext.insert(0, _SingleParentValidator(prop))
 
-    if getattr(prop, 'backref', None):
-        attribute_ext.append(prop.backref.extension)
-    
     if prop.key in prop.parent._validators:
-        attribute_ext.append(mapperutil.Validator(prop.key, prop.parent._validators[prop.key]))
+        attribute_ext.insert(0, mapperutil.Validator(prop.key, prop.parent._validators[prop.key]))
     
     if useobject:
         attribute_ext.append(sessionlib.UOWEventHandler(prop.key))