diff options
-rw-r--r-- | lib/sqlalchemy/orm/__init__.py | 23 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 6 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/dependency.py | 20 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/dynamic.py | 52 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/properties.py | 18 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/query.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 49 | ||||
-rw-r--r-- | test/orm/dynamic.py | 142 | ||||
-rw-r--r-- | test/orm/fixtures.py | 4 | ||||
-rw-r--r-- | test/orm/lazy_relations.py | 5 | ||||
-rw-r--r-- | test/orm/query.py | 7 |
11 files changed, 187 insertions, 140 deletions
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 1982a94f7..380d29577 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -24,7 +24,7 @@ from sqlalchemy.orm.session import object_session, attribute_manager __all__ = ['relation', 'column_property', 'composite', 'backref', 'eagerload', 'eagerload_all', 'lazyload', 'noload', 'deferred', 'defer', 'undefer', 'undefer_group', 'extension', 'mapper', 'clear_mappers', - 'compile_mappers', 'class_mapper', 'object_mapper', + 'compile_mappers', 'class_mapper', 'object_mapper', 'dynamic_loader', 'MapperExtension', 'Query', 'polymorphic_union', 'create_session', 'synonym', 'contains_alias', 'contains_eager', 'EXT_PASS', 'object_session', 'PropComparator' @@ -170,7 +170,26 @@ def relation(argument, secondary=None, **kwargs): return PropertyLoader(argument, secondary=secondary, **kwargs) -# return _relation_loader(argument, secondary=secondary, **kwargs) +def dynamic_loader(argument, secondary=None, primaryjoin=None, secondaryjoin=None, entity_name=None, + foreign_keys=None, backref=None, post_update=False, cascade=None, remote_side=None, enable_typechecks=True): + """construct a dynamically-loading mapper property. + + This property is similar to relation(), except read operations + return an active Query object, which reads from the database in all + cases. Items may be appended to the attribute via append(), or + removed via remove(); changes will be persisted + to the database during a flush(). However, no other list mutation + operations are available. + + A subset of arguments available to relation() are available here. + """ + + from sqlalchemy.orm.strategies import DynaLoader + + return PropertyLoader(argument, secondary=secondary, primaryjoin=primaryjoin, + secondaryjoin=secondaryjoin, entity_name=entity_name, foreign_keys=foreign_keys, backref=backref, + post_update=post_update, cascade=cascade, remote_side=remote_side, enable_typechecks=enable_typechecks, + strategy_class=DynaLoader) #def _relation_loader(mapper, secondary=None, primaryjoin=None, secondaryjoin=None, lazy=True, **kwargs): diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index b903d5aa0..f783a381f 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -633,12 +633,6 @@ class AttributeHistory(object): def deleted_items(self): return self._deleted_items - def hasparent(self, obj): - """Deprecated. This should be called directly from the appropriate ``InstrumentedAttribute`` object. - """ - - return self.attr.hasparent(obj) - class AttributeManager(object): """Allow the instrumentation of object attributes.""" diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py index c06db6963..cfe501695 100644 --- a/lib/sqlalchemy/orm/dependency.py +++ b/lib/sqlalchemy/orm/dependency.py @@ -51,6 +51,12 @@ class DependencyProcessor(object): return getattr(self.parent.class_, self.key) + def hasparent(self, obj): + """return True if the given object instance has a parent, + according to the ``InstrumentedAttribute`` handled by this ``DependencyProcessor``.""" + + return self._get_instrumented_attribute().hasparent(obj) + def register_dependencies(self, uowcommit): """Tell a ``UOWTransaction`` what mappers are dependent on which, with regards to the two or three mappers handled by @@ -187,7 +193,7 @@ class OneToManyDP(DependencyProcessor): childlist = self.get_object_dependencies(obj, uowcommit, passive=self.passive_deletes) if childlist is not None: for child in childlist.deleted_items(): - if child is not None and childlist.hasparent(child) is False: + if child is not None and self.hasparent(child) is False: self._synchronize(obj, child, None, True, uowcommit) self._conditional_post_update(child, uowcommit, [obj]) for child in childlist.unchanged_items(): @@ -202,7 +208,7 @@ class OneToManyDP(DependencyProcessor): self._synchronize(obj, child, None, False, uowcommit) self._conditional_post_update(child, uowcommit, [obj]) for child in childlist.deleted_items(): - if not self.cascade.delete_orphan and not self._get_instrumented_attribute().hasparent(child): + if not self.cascade.delete_orphan and not self.hasparent(child): self._synchronize(obj, child, None, True, uowcommit) def preprocess_dependencies(self, task, deplist, uowcommit, delete = False): @@ -216,7 +222,7 @@ class OneToManyDP(DependencyProcessor): childlist = self.get_object_dependencies(obj, uowcommit, passive=self.passive_deletes) if childlist is not None: for child in childlist.deleted_items(): - if child is not None and childlist.hasparent(child) is False: + if child is not None and self.hasparent(child) is False: uowcommit.register_object(child) for child in childlist.unchanged_items(): if child is not None: @@ -231,7 +237,7 @@ class OneToManyDP(DependencyProcessor): for child in childlist.deleted_items(): if not self.cascade.delete_orphan: uowcommit.register_object(child, isdelete=False) - elif childlist.hasparent(child) is False: + elif self.hasparent(child) is False: uowcommit.register_object(child, isdelete=True) for c in self.mapper.cascade_iterator('delete', child): uowcommit.register_object(c, isdelete=True) @@ -285,7 +291,7 @@ class ManyToOneDP(DependencyProcessor): childlist = self.get_object_dependencies(obj, uowcommit, passive=self.passive_deletes) if childlist is not None: for child in childlist.deleted_items() + childlist.unchanged_items(): - if child is not None and childlist.hasparent(child) is False: + if child is not None and self.hasparent(child) is False: uowcommit.register_object(child, isdelete=True) for c in self.mapper.cascade_iterator('delete', child): uowcommit.register_object(c, isdelete=True) @@ -296,7 +302,7 @@ class ManyToOneDP(DependencyProcessor): childlist = self.get_object_dependencies(obj, uowcommit, passive=self.passive_deletes) if childlist is not None: for child in childlist.deleted_items(): - if childlist.hasparent(child) is False: + if self.hasparent(child) is False: uowcommit.register_object(child, isdelete=True) for c in self.mapper.cascade_iterator('delete', child): uowcommit.register_object(c, isdelete=True) @@ -382,7 +388,7 @@ class ManyToManyDP(DependencyProcessor): childlist = self.get_object_dependencies(obj, uowcommit, passive=True) if childlist is not None: for child in childlist.deleted_items(): - if self.cascade.delete_orphan and childlist.hasparent(child) is False: + if self.cascade.delete_orphan and self.hasparent(child) is False: uowcommit.register_object(child, isdelete=True) for c in self.mapper.cascade_iterator('delete', child): uowcommit.register_object(c, isdelete=True) diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index b12931482..d06b874c9 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -18,7 +18,8 @@ class DynamicCollectionAttribute(attributes.InstrumentedAttribute): def commit_to_state(self, state, obj, value=attributes.NO_VALUE): # we have our own AttributeHistory therefore dont need CommittedState - pass + # instead, we reset the history stored on the attribute + obj.__dict__[self.key] = CollectionHistory(self, obj) def set(self, obj, value, initiator): if initiator is self: @@ -58,38 +59,47 @@ class AppenderQuery(Query): self.instance = instance self.attr = attr - def __len__(self): + def __session(self): + sess = object_session(self.instance) + if sess is not None and self.instance in sess and sess.autoflush: + sess.flush() if not has_identity(self.instance): - # TODO: all these various calls to _added_items should be more - # intelligently calculated from the CollectionHistory object - # (i.e. account for deletes too) + return None + else: + return sess + + def __len__(self): + sess = self.__session() + if sess is None: return len(self.attr.get_history(self.instance)._added_items) else: - return self._clone().count() + return self._clone(sess).count() def __iter__(self): - if not has_identity(self.instance): + sess = self.__session() + if sess is None: return iter(self.attr.get_history(self.instance)._added_items) else: - return iter(self._clone()) + return iter(self._clone(sess)) def __getitem__(self, index): - if not has_identity(self.instance): - # TODO: hmm + sess = self.__session() + if sess is None: return self.attr.get_history(self.instance)._added_items.__getitem__(index) else: - return self._clone().__getitem__(index) - - def _clone(self): + return self._clone(sess).__getitem__(index) + + def _clone(self, sess=None): # note we're returning an entirely new Query class instance here # without any assignment capabilities; # the class of this query is determined by the session. - sess = object_session(self.instance) if sess is None: - try: - sess = mapper.object_mapper(instance).get_session() - except exceptions.InvalidRequestError: - raise exceptions.InvalidRequestError("Parent instance %s is not bound to a Session, and no contextual session is established; lazy load operation of attribute '%s' cannot proceed" % (instance.__class__, self.key)) + sess = object_session(self.instance) + if sess is None: + try: + sess = mapper.object_mapper(instance).get_session() + except exceptions.InvalidRequestError: + raise exceptions.InvalidRequestError("Parent instance %s is not bound to a Session, and no contextual session is established; lazy load operation of attribute '%s' cannot proceed" % (instance.__class__, self.key)) return sess.query(self.attr.target_mapper).with_parent(self.instance) @@ -102,11 +112,11 @@ class AppenderQuery(Query): return oldlist def append(self, item): - self.attr.append(self.instance, item, self.attr) + self.attr.append(self.instance, item, None) - # TODO:jek: I think this should probably be axed, time will tell. def remove(self, item): - self.attr.remove(self.instance, item, self.attr) + self.attr.remove(self.instance, item, None) + class CollectionHistory(attributes.AttributeHistory): """Overrides AttributeHistory to receive append/remove events directly.""" diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index ae73f9c7c..a6bb1a371 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -124,7 +124,7 @@ class PropertyLoader(StrategizedProperty): of items that correspond to a related database table. """ - def __init__(self, argument, secondary=None, primaryjoin=None, secondaryjoin=None, entity_name=None, foreign_keys=None, foreignkey=None, uselist=None, private=False, association=None, order_by=False, attributeext=None, backref=None, is_backref=False, post_update=False, cascade=None, viewonly=False, lazy=True, collection_class=None, passive_deletes=False, remote_side=None, enable_typechecks=True, join_depth=None): + def __init__(self, argument, secondary=None, primaryjoin=None, secondaryjoin=None, entity_name=None, foreign_keys=None, foreignkey=None, uselist=None, private=False, association=None, order_by=False, attributeext=None, backref=None, is_backref=False, post_update=False, cascade=None, viewonly=False, lazy=True, collection_class=None, passive_deletes=False, remote_side=None, enable_typechecks=True, join_depth=None, strategy_class=None): self.uselist = uselist self.argument = argument self.entity_name = entity_name @@ -144,6 +144,7 @@ class PropertyLoader(StrategizedProperty): self._parent_join_cache = {} self.comparator = PropertyLoader.Comparator(self) self.join_depth = join_depth + self.strategy_class = strategy_class if cascade is not None: self.cascade = mapperutil.CascadeOptions(cascade) @@ -247,22 +248,13 @@ class PropertyLoader(StrategizedProperty): return op(self.comparator, value) def _optimized_compare(self, value, value_is_parent=False): - # optimized operation for ==, uses a lazy clause. - (criterion, lazybinds, rev) = strategies.LazyLoader._create_lazy_clause(self, reverse_direction=not value_is_parent) - bind_to_col = dict([(lazybinds[col].key, col) for col in lazybinds]) - - class Visitor(sql.ClauseVisitor): - def visit_bindparam(s, bindparam): - mapper = value_is_parent and self.parent or self.mapper - bindparam.value = mapper.get_attr_by_column(value, bind_to_col[bindparam.key]) - Visitor().traverse(criterion) - return criterion + return self._get_strategy(strategies.LazyLoader).lazy_clause(value, reverse_direction=not value_is_parent) private = property(lambda s:s.cascade.delete_orphan) def create_strategy(self): - if self.lazy == 'dynamic': - return strategies.DynaLoader(self) + if self.strategy_class: + return self.strategy_class(self) elif self.lazy: return strategies.LazyLoader(self) elif self.lazy is False: diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 9040655b2..a504aa9a6 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -621,6 +621,7 @@ class Query(object): def __iter__(self): statement = self.compile() statement.use_labels = True + print "ITER !", self.session.autoflush if self.session.autoflush: self.session.flush() return self._execute_and_instances(statement) diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index beb8f2755..54a4c6524 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -282,6 +282,20 @@ class LazyLoader(AbstractRelationLoader): self.is_class_level = True self._register_attribute(self.parent.class_, callable_=lambda i: self.setup_loader(i)) + def lazy_clause(self, instance, reverse_direction=False): + if not reverse_direction: + (criterion, lazybinds, rev) = (self.lazywhere, self.lazybinds, self.lazyreverse) + else: + (criterion, lazybinds, rev) = LazyLoader._create_lazy_clause(self.parent_property, reverse_direction=reverse_direction) + bind_to_col = dict([(lazybinds[col].key, col) for col in lazybinds]) + + class Visitor(sql.ClauseVisitor): + def visit_bindparam(s, bindparam): + mapper = reverse_direction and self.parent_property.mapper or self.parent_property.parent + if bindparam.key in bind_to_col: + bindparam.value = mapper.get_attr_by_column(instance, bind_to_col[bindparam.key]) + return Visitor().traverse(criterion, clone=True) + def setup_loader(self, instance, options=None): if not mapper.has_mapper(instance): return None @@ -296,23 +310,8 @@ class LazyLoader(AbstractRelationLoader): def lazyload(): self.logger.debug("lazy load attribute %s on instance %s" % (self.key, mapperutil.instance_str(instance))) - params = {} - allparams = True - # if the instance wasnt loaded from the database, then it cannot lazy load - # child items. one reason for this is that a bi-directional relationship - # will not update properly, since bi-directional uses lazy loading functions - # in both directions, and this instance will not be present in the lazily-loaded - # results of the other objects since its not in the database - if not mapper.has_identity(instance): - return None - #print "setting up loader, lazywhere", str(self.lazywhere), "binds", self.lazybinds - for col, bind in self.lazybinds.iteritems(): - params[bind.key] = self.parent.get_attr_by_column(instance, col) - if params[bind.key] is None: - allparams = False - break - if not allparams: + if not mapper.has_identity(instance): return None session = sessionlib.object_session(instance) @@ -326,14 +325,15 @@ class LazyLoader(AbstractRelationLoader): # to possibly save a DB round trip q = session.query(self.mapper) if self.use_get: + params = {} + for col, bind in self.lazybinds.iteritems(): + params[bind.key] = self.parent.get_attr_by_column(instance, col) ident = [] - # TODO: when options are added to allow switching between union-based and non-union - # based polymorphic loads on a per-query basis, this code needs to switch between "mapper" and "select_mapper", - # probably via the query's own "mapper" property, and also use one of two "lazy" clauses, - # one against the "union" the other not for primary_key in self.select_mapper.primary_key: bind = self.lazyreverse[primary_key] ident.append(params[bind.key]) + if options: + q = q.options(*options) return q.get(ident) elif self.order_by is not False: q = q.order_by(self.order_by) @@ -342,7 +342,7 @@ class LazyLoader(AbstractRelationLoader): if options: q = q.options(*options) - q = q.filter(self.lazywhere).params(**params) + q = q.filter(self.lazy_clause(instance)) result = q.all() if self.uselist: @@ -352,11 +352,6 @@ class LazyLoader(AbstractRelationLoader): return result[0] else: return None - - if self.uselist: - return q.all() - else: - return q.first() return lazyload @@ -382,7 +377,7 @@ class LazyLoader(AbstractRelationLoader): sessionlib.attribute_manager.reset_instance_attribute(instance, self.key) return (execute, None) - def _create_lazy_clause(cls, prop, reverse_direction=False, op='=='): + def _create_lazy_clause(cls, prop, reverse_direction=False): (primaryjoin, secondaryjoin, remote_side) = (prop.polymorphic_primaryjoin, prop.polymorphic_secondaryjoin, prop.remote_side) binds = {} diff --git a/test/orm/dynamic.py b/test/orm/dynamic.py index 2cd616c12..958f5b159 100644 --- a/test/orm/dynamic.py +++ b/test/orm/dynamic.py @@ -16,7 +16,7 @@ class DynamicTest(QueryTest): def test_basic(self): mapper(User, users, properties={ - 'addresses':relation(mapper(Address, addresses), lazy='dynamic') + 'addresses':dynamic_loader(mapper(Address, addresses)) }) sess = create_session() q = sess.query(User) @@ -29,8 +29,11 @@ class DynamicTest(QueryTest): class FlushTest(FixtureTest): def test_basic(self): + class Fixture(Base): + pass + mapper(User, users, properties={ - 'addresses':relation(mapper(Address, addresses), lazy='dynamic') + 'addresses':dynamic_loader(mapper(Address, addresses)) }) sess = create_session() u1 = User(name='jack') @@ -42,88 +45,115 @@ class FlushTest(FixtureTest): sess.flush() sess.clear() + + # test the test fixture a little bit + assert User(name='jack', addresses=[Address(email_address='wrong')]) != sess.query(User).first() + assert User(name='jack', addresses=[Address(email_address='lala@hoho.com')]) == sess.query(User).first() - def go(): - assert [ - User(name='jack', addresses=[Address(email_address='lala@hoho.com')]), - User(name='ed', addresses=[Address(email_address='foo@bar.com')]) - ] == sess.query(User).all() - - # one query for the query(User).all(), one query for each address - # iter(), also one query for a count() on each address (the count() - # is an artifact of the fixtures.Base class, its not intrinsic to the - # property) - self.assert_sql_count(testbase.db, go, 5) - - def test_backref_unsaved_u(self): + assert [ + User(name='jack', addresses=[Address(email_address='lala@hoho.com')]), + User(name='ed', addresses=[Address(email_address='foo@bar.com')]) + ] == sess.query(User).all() + + def test_delete(self): mapper(User, users, properties={ - 'addresses':relation(mapper(Address, addresses), lazy='dynamic', - backref='user') + 'addresses':dynamic_loader(mapper(Address, addresses), backref='user') }) - sess = create_session() - - u = User(name='buffy') - - a = Address(email_address='foo@bar.com') - a.user = u - + sess = create_session(autoflush=True) + u = User(name='ed') + u.addresses.append(Address(email_address='a')) + u.addresses.append(Address(email_address='b')) + u.addresses.append(Address(email_address='c')) + u.addresses.append(Address(email_address='d')) + u.addresses.append(Address(email_address='e')) + u.addresses.append(Address(email_address='f')) sess.save(u) - sess.flush() + + assert Address(email_address='c') == u.addresses[2] + sess.delete(u.addresses[2]) + sess.delete(u.addresses[4]) + sess.delete(u.addresses[3]) + assert [Address(email_address='a'), Address(email_address='b'), Address(email_address='d')] == list(u.addresses) + + sess.close() - def test_backref_unsaved_a(self): + def test_remove_orphans(self): mapper(User, users, properties={ - 'addresses':relation(mapper(Address, addresses), lazy='dynamic', - backref='user') + 'addresses':dynamic_loader(mapper(Address, addresses), cascade="all, delete-orphan", backref='user') }) - sess = create_session() - - u = User(name='buffy') - - a = Address(email_address='foo@bar.com') - a.user = u + sess = create_session(autoflush=True) + u = User(name='ed') + u.addresses.append(Address(email_address='a')) + u.addresses.append(Address(email_address='b')) + u.addresses.append(Address(email_address='c')) + u.addresses.append(Address(email_address='d')) + u.addresses.append(Address(email_address='e')) + u.addresses.append(Address(email_address='f')) + sess.save(u) - self.assert_(list(u.addresses) == [a]) - self.assert_(u.addresses[0] == a) + assert [Address(email_address='a'), Address(email_address='b'), Address(email_address='c'), + Address(email_address='d'), Address(email_address='e'), Address(email_address='f')] == sess.query(Address).all() - sess.save(a) - sess.flush() + assert Address(email_address='c') == u.addresses[2] - self.assert_(list(u.addresses) == [a]) - - a.user = None - self.assert_(list(u.addresses) == [a]) + try: + del u.addresses[3] + assert False + except TypeError, e: + assert str(e) == "object doesn't support item deletion" + + for a in u.addresses.filter(Address.email_address.in_('c', 'e', 'f')): + u.addresses.remove(a) + + assert [Address(email_address='a'), Address(email_address='b'), Address(email_address='d')] == list(u.addresses) - sess.flush() - self.assert_(list(u.addresses) == []) + assert [Address(email_address='a'), Address(email_address='b'), Address(email_address='d')] == sess.query(Address).all() + sess.close() - def test_backref_unsaved_u(self): +def create_backref_test(autoflush, saveuser): + def test_backref(self): mapper(User, users, properties={ - 'addresses':relation(mapper(Address, addresses), lazy='dynamic', - backref='user') + 'addresses':dynamic_loader(mapper(Address, addresses), backref='user') }) - sess = create_session() + sess = create_session(autoflush=autoflush) u = User(name='buffy') a = Address(email_address='foo@bar.com') a.user = u - self.assert_(list(u.addresses) == [a]) - self.assert_(u.addresses[0] == a) + if saveuser: + sess.save(u) + else: + sess.save(a) - sess.save(u) - sess.flush() + if not autoflush: + sess.flush() + + assert u in sess + assert a in sess - assert list(u.addresses) == [a] + self.assert_(list(u.addresses) == [a]) a.user = None - self.assert_(list(u.addresses) == [a]) + if not autoflush: + self.assert_(list(u.addresses) == [a]) - sess.flush() + if not autoflush: + sess.flush() self.assert_(list(u.addresses) == []) - + test_backref.__name__ = "test_%s%s" % ( + (autoflush and "autoflush" or ""), + (saveuser and "_saveuser" or "_savead"), + ) + setattr(FlushTest, test_backref.__name__, test_backref) + +for autoflush in (False, True): + for saveuser in (False, True): + create_backref_test(autoflush, saveuser) + if __name__ == '__main__': testbase.main() diff --git a/test/orm/fixtures.py b/test/orm/fixtures.py index 8b7312251..73449eb96 100644 --- a/test/orm/fixtures.py +++ b/test/orm/fixtures.py @@ -28,8 +28,8 @@ class Base(object): continue value = getattr(self, attr) if hasattr(value, '__iter__') and not isinstance(value, basestring): - if len(value) == 0: - continue + if len(value) != len(getattr(other, attr)): + return False for (us, them) in zip(value, getattr(other, attr)): if us != them: return False diff --git a/test/orm/lazy_relations.py b/test/orm/lazy_relations.py index 6684c6288..6baba8394 100644 --- a/test/orm/lazy_relations.py +++ b/test/orm/lazy_relations.py @@ -214,6 +214,11 @@ class LazyTest(QueryTest): User(id=10) ] == q.all() + + sess = create_session() + user = sess.query(User).get(7) + assert [Order(id=1), Order(id=5)] == create_session().query(Order, entity_name='closed').with_parent(user, property='closed_orders').all() + assert [Order(id=3)] == create_session().query(Order, entity_name='open').with_parent(user, property='open_orders').all() def test_many_to_many(self): diff --git a/test/orm/query.py b/test/orm/query.py index 5d17d7d81..e02c5a643 100644 --- a/test/orm/query.py +++ b/test/orm/query.py @@ -6,7 +6,7 @@ from sqlalchemy.orm import * from testlib import * from fixtures import * -class QueryTest(ORMTest): +class QueryTest(FixtureTest): keep_mappers = True keep_data = True @@ -19,11 +19,6 @@ class QueryTest(ORMTest): clear_mappers() super(QueryTest, self).tearDownAll() - def define_tables(self, meta): - # a slight dirty trick here. - meta.tables = metadata.tables - metadata.connect(meta.bind) - def setup_mappers(self): mapper(User, users, properties={ 'addresses':relation(Address, backref='user'), |