From e2b5b9d0e40fe9ad6dcc5d3bfa1fddc89a1883bc Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 19 Jun 2006 17:47:21 +0000 Subject: [PATCH] added check for conflicting backrefs + unit test identified unit test where mapper properties must be set up before the surrogate mapper is created --- CHANGES | 1 + lib/sqlalchemy/orm/mapper.py | 4 +- lib/sqlalchemy/orm/properties.py | 10 +++ test/orm/alltests.py | 1 + test/orm/compile.py | 121 +++++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 test/orm/compile.py diff --git a/CHANGES b/CHANGES index c847502277..f08ae12c8f 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 2d118e9e56..8c5d5d0768 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 a6125c8943..4e096d99bd 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 4c038f1221..cfbfdb70b5 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 0000000000..c927ae53b3 --- /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() -- 2.47.3