Commits

Mike Bayer committed 194bcab

- a forward and complementing backwards reference which are both
of the same direction, i.e. ONETOMANY or MANYTOONE,
is now detected, and an error message is raised.
Saves crazy CircularDependencyErrors later on.

Comments (0)

Files changed (4)

     - Added "post_configure_attribute" method to InstrumentationManager,
       so that the "listen_for_events.py" example works again.
       [ticket:1314]
-      
+    
+    - a forward and complementing backwards reference which are both
+      of the same direction, i.e. ONETOMANY or MANYTOONE, 
+      is now detected, and an error message is raised.   
+      Saves crazy CircularDependencyErrors later on.
+        
     - Fixed bugs in Query regarding simultaneous selection of 
       multiple joined-table inheritance entities with common base 
       classes:

lib/sqlalchemy/orm/properties.py

             raise sa_exc.ArgumentError("reverse_property %r on relation %s references "
                     "relation %s, which does not reference mapper %s" % (key, self, other, self.parent))
         
+        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))
+        
     def do_init(self):
         self._get_target()
         self._process_dependent_arguments()

test/orm/mapper.py

                      include_properties=('id', 'type', 'name'))
         e_m = mapper(Employee, inherits=p_m, polymorphic_identity='employee',
           properties={
-            'boss': relation(Manager, backref='peon')
+            'boss': relation(Manager, backref=backref('peon', ), remote_side=t.c.id)
           },
           exclude_properties=('vendor_id',))
 

test/orm/relationships.py

         mapper(T2, t2)
         self.assertRaises(sa.exc.ArgumentError, sa.orm.compile_mappers)
 
+class InvalidRemoteSideTest(_base.MappedTest):
+    def define_tables(self, metadata):
+        Table('t1', metadata,
+            Column('id', Integer, primary_key=True),
+            Column('data', String(50)),
+            Column('t_id', Integer, ForeignKey('t1.id'))
+            )
 
+    @testing.resolve_artifact_names
+    def setup_classes(self):
+        class T1(_base.ComparableEntity):
+            pass
+
+    @testing.resolve_artifact_names
+    def test_o2m_backref(self):
+        mapper(T1, t1, properties={
+            't1s':relation(T1, backref='parent')
+        })
+
+        self.assertRaisesMessage(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':relation(T1, backref=backref('parent', remote_side=t1.c.id), remote_side=t1.c.id)
+        })
+
+        self.assertRaisesMessage(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):
+        mapper(T1, t1, properties={
+            't1s':relation(T1, back_populates='parent'),
+            'parent':relation(T1, back_populates='t1s'),
+        })
+
+        # can't be sure of ordering here
+        self.assertRaisesMessage(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)
+
+    @testing.resolve_artifact_names
+    def test_m2o_explicit(self):
+        mapper(T1, t1, properties={
+            't1s':relation(T1, back_populates='parent', remote_side=t1.c.id),
+            'parent':relation(T1, back_populates='t1s', remote_side=t1.c.id)
+        })
+
+        # can't be sure of ordering here
+        self.assertRaisesMessage(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)
+
+        
 class InvalidRelationEscalationTest(_base.MappedTest):
 
     def define_tables(self, metadata):