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.

  • Participants
  • Parent commits d5ab0d4

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