summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2006-06-19 17:47:21 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2006-06-19 17:47:21 +0000
commite2b5b9d0e40fe9ad6dcc5d3bfa1fddc89a1883bc (patch)
tree4820be33975af5d0272cf913121d23957edfcb44
parentf5c5c4e6a7041d10a83719a572b823d8cfbebb6c (diff)
downloadsqlalchemy-e2b5b9d0e40fe9ad6dcc5d3bfa1fddc89a1883bc.tar.gz
added check for conflicting backrefs + unit test
identified unit test where mapper properties must be set up before the surrogate mapper is created
-rw-r--r--CHANGES1
-rw-r--r--lib/sqlalchemy/orm/mapper.py4
-rw-r--r--lib/sqlalchemy/orm/properties.py10
-rw-r--r--test/orm/alltests.py1
-rw-r--r--test/orm/compile.py121
5 files changed, 135 insertions, 2 deletions
diff --git a/CHANGES b/CHANGES
index c84750227..f08ae12c8 100644
--- a/CHANGES
+++ b/CHANGES
@@ -8,6 +8,7 @@ would just return a blank list or None, it now raises an exception.
the given object was formerly attached to was garbage collected;
otherwise still requires you explicitly remove the instance from
the previous Session.
+- fixes to mapper compilation, checking for more error conditions
0.2.3
- overhaul to mapper compilation to be deferred. this allows mappers
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index 2d118e9e5..8c5d5d076 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -161,13 +161,13 @@ class Mapper(object):
constructed in an arbitrary order, completing their relationships when they have all been established."""
if self.__is_compiled:
return self
- #print "COMPILING!", self.class_key
+ #print "COMPILING!", self.class_key, "non primary: ", self.non_primary
self.__is_compiled = True
self._compile_extensions()
self._compile_inheritance()
self._compile_tables()
- self._compile_selectable()
self._compile_properties()
+ self._compile_selectable()
self._initialize_properties()
# compile some other mappers which have backrefs to this mapper
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py
index a6125c894..4e096d99b 100644
--- a/lib/sqlalchemy/orm/properties.py
+++ b/lib/sqlalchemy/orm/properties.py
@@ -186,6 +186,13 @@ class PropertyLoader(mapper.MapperProperty):
"""template method for subclasses of PropertyLoader"""
pass
+ def _get_target_class(self):
+ """return the target class of the relation, even if the property has not been initialized yet."""
+ if isinstance(self.argument, type):
+ return self.argument
+ else:
+ return self.argument.class_
+
def do_init(self, key, parent):
import sqlalchemy.orm
if isinstance(self.argument, type):
@@ -687,6 +694,9 @@ class BackRef(object):
mapper._compile_property(self.key, relation);
else:
# else set one of us as the "backreference"
+ parent = prop.parent.primary_mapper()
+ if parent.class_ is not mapper.props[self.key]._get_target_class():
+ raise exceptions.ArgumentError("Backrefs do not match: backref '%s' expects to connect to %s, but found a backref already connected to %s" % (self.key, str(parent.class_), str(mapper.props[self.key].mapper.class_)))
if not mapper.props[self.key].is_backref:
prop.is_backref=True
prop._dependency_processor.is_backref=True
diff --git a/test/orm/alltests.py b/test/orm/alltests.py
index 4c038f122..cfbfdb70b 100644
--- a/test/orm/alltests.py
+++ b/test/orm/alltests.py
@@ -19,6 +19,7 @@ def suite():
'orm.poly_linked_list',
'orm.entity',
+ 'orm.compile',
'orm.manytomany',
'orm.onetoone',
'orm.inheritance',
diff --git a/test/orm/compile.py b/test/orm/compile.py
new file mode 100644
index 000000000..c927ae53b
--- /dev/null
+++ b/test/orm/compile.py
@@ -0,0 +1,121 @@
+from sqlalchemy import *
+import testbase
+
+class CompileTest(testbase.AssertMixin):
+ """test various mapper compilation scenarios"""
+ def tearDownAll(self):
+ clear_mappers()
+
+ def testone(self):
+ global metadata, order, employee, product, tax, orderproduct
+ metadata = BoundMetaData(engine)
+
+ order = Table('orders', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('employee_id', Integer, ForeignKey('employees.id'), nullable=False),
+ Column('type', Unicode(16)))
+
+ employee = Table('employees', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('name', Unicode(16), unique=True, nullable=False))
+
+ product = Table('products', metadata,
+ Column('id', Integer, primary_key=True),
+ )
+
+ orderproduct = Table('orderproducts', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('order_id', Integer, ForeignKey("orders.id"), nullable=False),
+ Column('product_id', Integer, ForeignKey("products.id"), nullable=False),
+ )
+
+ class Order(object):
+ pass
+
+ class Employee(object):
+ pass
+
+ class Product(object):
+ pass
+
+ class OrderProduct(object):
+ pass
+
+ order_join = order.select().alias('pjoin')
+
+ order_mapper = mapper(Order, order,
+ select_table=order_join,
+ polymorphic_on=order_join.c.type,
+ polymorphic_identity='order',
+ properties={
+ 'orderproducts': relation(OrderProduct, lazy=True, backref='order')}
+ )
+
+ mapper(Product, product,
+ properties={
+ 'orderproducts': relation(OrderProduct, lazy=True, backref='product')}
+ )
+
+ mapper(Employee, employee,
+ properties={
+ 'orders': relation(Order, lazy=True, backref='employee')})
+
+ mapper(OrderProduct, orderproduct)
+
+ # this requires that the compilation of order_mapper's "surrogate mapper" occur after
+ # the initial setup of MapperProperty objects on the mapper.
+ class_mapper(Product).compile()
+
+ def testtwo(self):
+ """test that conflicting backrefs raises an exception"""
+ global metadata, order, employee, product, tax, orderproduct
+ metadata = BoundMetaData(engine)
+
+ order = Table('orders', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('type', Unicode(16)))
+
+ product = Table('products', metadata,
+ Column('id', Integer, primary_key=True),
+ )
+
+ orderproduct = Table('orderproducts', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('order_id', Integer, ForeignKey("orders.id"), nullable=False),
+ Column('product_id', Integer, ForeignKey("products.id"), nullable=False),
+ )
+
+ class Order(object):
+ pass
+
+ class Product(object):
+ pass
+
+ class OrderProduct(object):
+ pass
+
+ order_join = order.select().alias('pjoin')
+
+ order_mapper = mapper(Order, order,
+ select_table=order_join,
+ polymorphic_on=order_join.c.type,
+ polymorphic_identity='order',
+ properties={
+ 'orderproducts': relation(OrderProduct, lazy=True, backref='product')}
+ )
+
+ mapper(Product, product,
+ properties={
+ 'orderproducts': relation(OrderProduct, lazy=True, backref='product')}
+ )
+
+ mapper(OrderProduct, orderproduct)
+
+ try:
+ class_mapper(Product).compile()
+ assert False
+ except exceptions.ArgumentError, e:
+ assert str(e).index("Backrefs do not match") > -1
+
+if __name__ == '__main__':
+ testbase.main()