From 6a30dd13909a10e84ebc09a50a3c0561f5ae803c Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 5 Jul 2007 00:09:06 +0000 Subject: [PATCH] - replaced calls for mapper.props in Query with mapper.get_property(), which resolves synonyms. fixes [ticket:598] for join/join_to/join_via/with_parent --- CHANGES | 2 ++ lib/sqlalchemy/orm/mapper.py | 23 ++++++++++++++-- lib/sqlalchemy/orm/query.py | 29 +++++++------------- test/orm/mapper.py | 52 ++++++++++++++++++++++++++++-------- 4 files changed, 74 insertions(+), 32 deletions(-) diff --git a/CHANGES b/CHANGES index 37f24deb2f..a20bc4cc8b 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,8 @@ - added synchronization to the mapper() construction step, to avoid thread collections when pre-existing mappers are compiling in a different thread [ticket:613] + - synonym() properties are fully supported by all Query joining/ + with_parent operations [ticket:598] - fixed very stupid bug when deleting items with many-to-many uselist=False relations - remember all that stuff about polymorphic_union ? for diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index b037d0d184..37efdeb1fc 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -8,7 +8,7 @@ from sqlalchemy import sql, schema, util, exceptions, logging from sqlalchemy import sql_util as sqlutil from sqlalchemy.orm import util as mapperutil from sqlalchemy.orm import sync -from sqlalchemy.orm.interfaces import MapperProperty, MapperOption, OperationContext +from sqlalchemy.orm.interfaces import MapperProperty, MapperOption, OperationContext, SynonymProperty import weakref __all__ = ['Mapper', 'MapperExtension', 'class_mapper', 'object_mapper', 'EXT_PASS', 'mapper_registry', 'ExtensionOption'] @@ -302,7 +302,26 @@ class Mapper(object): return self.__props props = property(_get_props, doc="compiles this mapper if needed, and returns the " - "dictionary of MapperProperty objects associated with this mapper.") + "dictionary of MapperProperty objects associated with this mapper." + "(Deprecated; use get_property() and iterate_properties)") + + def get_property(self, key, resolve_synonyms=False, raiseerr=True): + """return MapperProperty with the given key. + + forwards compatible with 0.4. + """ + + self.compile() + prop = self.__props.get(key, None) + if resolve_synonyms: + while isinstance(prop, SynonymProperty): + prop = self.__props.get(prop.name, None) + if prop is None and raiseerr: + raise exceptions.InvalidRequestError("Mapper '%s' has no property '%s'" % (str(self), key)) + return prop + + iterate_properties = property(lambda self: self._get_props().itervalues(), doc="returns an iterator of all MapperProperty objects." + " Forwards compatible with 0.4") def compile(self): """Compile this mapper into its final internal format. diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 2ad7047027..9c3feacf05 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -208,7 +208,7 @@ class Query(object): mapper = self.mapper clause = None for key in keys: - prop = mapper.props[key] + prop = mapper.get_property(key, resolve_synonyms=True) if clause is None: clause = prop.get_join(mapper) else: @@ -403,7 +403,7 @@ class Query(object): """ mapper = object_mapper(instance) - prop = mapper.props[property] + prop = mapper.get_property(property, resolve_synonyms=True) target = prop.mapper criterion = cls._with_lazy_criterion(instance, prop) return Query(target, **kwargs).filter(criterion) @@ -427,13 +427,13 @@ class Query(object): from sqlalchemy.orm import properties mapper = object_mapper(instance) if property is None: - for prop in mapper.props.values(): + for prop in mapper.iterate_properties: if isinstance(prop, properties.PropertyLoader) and prop.mapper is self.mapper: break else: raise exceptions.InvalidRequestError("Could not locate a property which relates instances of class '%s' to instances of class '%s'" % (self.mapper.class_.__name__, instance.__class__.__name__)) else: - prop = mapper.props[property] + prop = mapper.get_property(property, resolve_synonyms=True) return self.filter(Query._with_lazy_criterion(instance, prop)) def add_entity(self, entity): @@ -524,20 +524,13 @@ class Query(object): def _join_to(self, prop, outerjoin=False): if isinstance(prop, list): - mapper = self._joinpoint - keys = [] - for key in prop: - p = mapper.props[key] - if p._is_self_referential(): - raise exceptions.InvalidRequestError("Self-referential query on '%s' property must be constructed manually using an Alias object for the related table." % (str(p))) - keys.append(key) - mapper = p.mapper + keys = prop else: [keys,p] = self._locate_prop(prop, start=self._joinpoint) clause = self._from_obj[-1] mapper = self._joinpoint for key in keys: - prop = mapper.props[key] + prop = mapper.get_property(key, resolve_synonyms=True) if prop._is_self_referential(): raise exceptions.InvalidRequestError("Self-referential query on '%s' property must be constructed manually using an Alias object for the related table." % str(prop)) if outerjoin: @@ -593,14 +586,12 @@ class Query(object): return None seen.add(mapper_) if mapper_.props.has_key(key): - prop = mapper_.props[key] - if isinstance(prop, SynonymProperty): - prop = mapper_.props[prop.name] + prop = mapper_.get_property(key, resolve_synonyms=True) if isinstance(prop, properties.PropertyLoader): keys.insert(0, prop.key) return prop else: - for prop in mapper_.props.values(): + for prop in mapper_.iterate_properties: if not isinstance(prop, properties.PropertyLoader): continue x = search_for_prop(prop.mapper) @@ -1103,7 +1094,7 @@ class Query(object): # give all the attached properties a chance to modify the query # TODO: doing this off the select_mapper. if its the polymorphic mapper, then # it has no relations() on it. should we compile those too into the query ? (i.e. eagerloads) - for value in self.select_mapper.props.values(): + for value in self.select_mapper.iterate_properties: value.setup(context) # additional entities/columns, add those to selection criterion @@ -1111,7 +1102,7 @@ class Query(object): if isinstance(m, type): m = mapper.class_mapper(m) if isinstance(m, mapper.Mapper): - for value in m.props.values(): + for value in m.iterate_properties: value.setup(context) elif isinstance(m, sql.ColumnElement): statement.append_column(m) diff --git a/test/orm/mapper.py b/test/orm/mapper.py index f3d94c10f5..88b474e242 100644 --- a/test/orm/mapper.py +++ b/test/orm/mapper.py @@ -304,9 +304,12 @@ class MapperTest(MapperSuperTest): """test the with_parent()) method and one-to-many relationships""" m = mapper(User, users, properties={ + 'user_name_syn':synonym('user_name'), 'orders':relation(mapper(Order, orders, properties={ - 'items':relation(mapper(Item, orderitems)) - })) + 'items':relation(mapper(Item, orderitems)), + 'items_syn':synonym('items') + })), + 'orders_syn':synonym('orders') }) sess = create_session() @@ -334,6 +337,21 @@ class MapperTest(MapperSuperTest): assert False except exceptions.InvalidRequestError, e: assert str(e) == "Could not locate a property which relates instances of class 'Item' to instances of class 'User'" + + + for nameprop, orderprop in ( + ('user_name', 'orders'), + ('user_name_syn', 'orders'), + ('user_name', 'orders_syn'), + ('user_name_syn', 'orders_syn'), + ): + sess = create_session() + q = sess.query(User) + + u1 = q.filter_by(**{nameprop:'jack'}).one() + + o = sess.query(Order).with_parent(u1, property=orderprop).list() + self.assert_result(o, Order, *user_all_result[0]['orders'][1]) def testwithparentm2m(self): """test the with_parent() method and many-to-many relationships""" @@ -347,21 +365,36 @@ class MapperTest(MapperSuperTest): self.assert_result(k, Keyword, *item_keyword_result[1]['keywords'][1]) - def testautojoin(self): + def test_join(self): """test functions derived from Query's _join_to function.""" m = mapper(User, users, properties={ 'orders':relation(mapper(Order, orders, properties={ - 'items':relation(mapper(Item, orderitems)) - })) + 'items':relation(mapper(Item, orderitems)), + 'items_syn':synonym('items') + })), + + 'orders_syn':synonym('orders'), }) sess = create_session() q = sess.query(m) - l = q.filter(orderitems.c.item_name=='item 4').join(['orders', 'items']).list() - self.assert_result(l, User, user_result[0]) - + for j in ( + ['orders', 'items'], + ['orders', 'items_syn'], + ['orders_syn', 'items'], + ['orders_syn', 'items_syn'], + ): + for q in ( + q.filter(orderitems.c.item_name=='item 4').join(j), + q.filter(orderitems.c.item_name=='item 4').join(j[-1]), + q.filter(orderitems.c.item_name=='item 4').filter(q.join_via(j)), + q.filter(orderitems.c.item_name=='item 4').filter(q.join_to(j[-1])), + ): + l = q.all() + self.assert_result(l, User, user_result[0]) + l = q.select_by(item_name='item 4') self.assert_result(l, User, user_result[0]) @@ -1269,9 +1302,6 @@ class EagerTest(MapperSuperTest): l = q.select(q.join_to('orders'), order_by=desc(orders.c.user_id), limit=2, offset=1) self.assert_result(l, User, *(user_all_result[2], user_all_result[0])) - l = q.select(q.join_to('addresses'), order_by=desc(addresses.c.email_address), limit=1, offset=0) - self.assert_result(l, User, *(user_all_result[0],)) - def testonetoone(self): m = mapper(User, users, properties = dict( address = relation(mapper(Address, addresses), lazy = False, uselist = False) -- 2.47.2