From: Mike Bayer Date: Mon, 22 Mar 2010 18:03:09 +0000 (-0400) Subject: - ordering tests X-Git-Tag: rel_0_6beta3~12^2~36 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e62b7d408774941184b286e216806074a72ddfdb;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - ordering tests - bring all lines in strategies.py to 78 chars --- diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 00b00be135..828530d7ac 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -4,7 +4,8 @@ # This module is part of SQLAlchemy and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php -"""sqlalchemy.orm.interfaces.LoaderStrategy implementations, and related MapperOptions.""" +"""sqlalchemy.orm.interfaces.LoaderStrategy + implementations, and related MapperOptions.""" from sqlalchemy import exc as sa_exc from sqlalchemy import sql, util, log @@ -39,7 +40,9 @@ def _register_attribute(strategy, mapper, useobject, attribute_ext.insert(0, _SingleParentValidator(prop)) if prop.key in prop.parent._validators: - attribute_ext.insert(0, mapperutil.Validator(prop.key, prop.parent._validators[prop.key])) + attribute_ext.insert(0, + mapperutil.Validator(prop.key, prop.parent._validators[prop.key]) + ) if useobject: attribute_ext.append(sessionlib.UOWEventHandler(prop.key)) @@ -67,7 +70,7 @@ def _register_attribute(strategy, mapper, useobject, ) class UninstrumentedColumnLoader(LoaderStrategy): - """Represent the strategy for a MapperProperty that doesn't instrument the class. + """Represent the a non-instrumented MapperProperty. The polymorphic_on argument of mapper() often results in this, if the argument is against the with_polymorphic selectable. @@ -76,14 +79,15 @@ class UninstrumentedColumnLoader(LoaderStrategy): def init(self): self.columns = self.parent_property.columns - def setup_query(self, context, entity, path, adapter, column_collection=None, **kwargs): + def setup_query(self, context, entity, path, adapter, + column_collection=None, **kwargs): for c in self.columns: if adapter: c = adapter.columns[c] column_collection.append(c) def create_row_processor(self, selectcontext, path, mapper, row, adapter): - return (None, None) + return None, None class ColumnLoader(LoaderStrategy): """Strategize the loading of a plain column-based MapperProperty.""" @@ -92,7 +96,8 @@ class ColumnLoader(LoaderStrategy): self.columns = self.parent_property.columns self.is_composite = hasattr(self.parent_property, 'composite_class') - def setup_query(self, context, entity, path, adapter, column_collection=None, **kwargs): + def setup_query(self, context, entity, path, adapter, + column_collection=None, **kwargs): for c in self.columns: if adapter: c = adapter.columns[c] @@ -136,7 +141,8 @@ class CompositeColumnLoader(ColumnLoader): def copy(obj): if obj is None: return None - return self.parent_property.composite_class(*obj.__composite_values__()) + return self.parent_property.\ + composite_class(*obj.__composite_values__()) def compare(a, b): if a is None or b is None: @@ -157,7 +163,8 @@ class CompositeColumnLoader(ColumnLoader): #active_history ? ) - def create_row_processor(self, selectcontext, path, mapper, row, adapter): + def create_row_processor(self, selectcontext, path, mapper, + row, adapter): key = self.key columns = self.columns composite_class = self.parent_property.composite_class @@ -204,7 +211,8 @@ class DeferredColumnLoader(LoaderStrategy): def init(self): if hasattr(self.parent_property, 'composite_class'): - raise NotImplementedError("Deferred loading for composite types not implemented yet") + raise NotImplementedError("Deferred loading for composite " + "types not implemented yet") self.columns = self.parent_property.columns self.group = self.parent_property.group @@ -219,13 +227,15 @@ class DeferredColumnLoader(LoaderStrategy): expire_missing=False ) - def setup_query(self, context, entity, path, adapter, only_load_props=None, **kwargs): - if \ - (self.group is not None and context.attributes.get(('undefer', self.group), False)) or \ - (only_load_props and self.key in only_load_props): - + def setup_query(self, context, entity, path, adapter, + only_load_props=None, **kwargs): + if ( + self.group is not None and + context.attributes.get(('undefer', self.group), False) + ) or (only_load_props and self.key in only_load_props): self.parent_property._get_strategy(ColumnLoader).\ - setup_query(context, entity, path, adapter, **kwargs) + setup_query(context, entity, + path, adapter, **kwargs) def _class_level_loader(self, state): if not mapperutil._state_has_identity(state): @@ -277,14 +287,15 @@ class LoadDeferredColumns(object): session = sessionlib._state_session(state) if session is None: raise orm_exc.DetachedInstanceError( - "Parent instance %s is not bound to a Session; " - "deferred load operation of attribute '%s' cannot proceed" % - (mapperutil.state_str(state), self.key) - ) + "Parent instance %s is not bound to a Session; " + "deferred load operation of attribute '%s' cannot proceed" % + (mapperutil.state_str(state), self.key) + ) query = session.query(localparent) ident = state.key[1] - query._get(None, ident=ident, only_load_props=group, refresh_state=state) + query._get(None, ident=ident, + only_load_props=group, refresh_state=state) return attributes.ATTR_WAS_SET class DeferredOption(StrategizedOption): @@ -310,7 +321,7 @@ class UndeferGroupOption(MapperOption): query._attributes[('undefer', self.group)] = True class AbstractRelationshipLoader(LoaderStrategy): - """LoaderStratgies which deal with related objects as opposed to scalars.""" + """LoaderStratgies which deal with related objects.""" def init(self): self.mapper = self.parent_property.mapper @@ -364,31 +375,47 @@ class LazyLoader(AbstractRelationshipLoader): for c in self.mapper._equivalent_columns[col]: self._equated_columns[c] = self._equated_columns[col] - self.logger.info("%s will use query.get() to optimize instance loads" % self) + self.logger.info("%s will use query.get() to " + "optimize instance loads" % self) def init_class_attribute(self, mapper): self.is_class_level = True - # MANYTOONE currently only needs the "old" value for delete-orphan - # cascades. the required _SingleParentValidator will enable active_history - # in that case. otherwise we don't need the "old" value during backref operations. + # MANYTOONE currently only needs the + # "old" value for delete-orphan + # cascades. the required _SingleParentValidator + # will enable active_history + # in that case. otherwise we don't need the + # "old" value during backref operations. _register_attribute(self, mapper, useobject=True, callable_=self._class_level_loader, uselist = self.parent_property.uselist, typecallable = self.parent_property.collection_class, - active_history = self.parent_property.direction is not interfaces.MANYTOONE or not self.use_get, + active_history = \ + self.parent_property.direction is not \ + interfaces.MANYTOONE or \ + not self.use_get, ) - def lazy_clause(self, state, reverse_direction=False, alias_secondary=False, adapt_source=None): + def lazy_clause(self, state, reverse_direction=False, + alias_secondary=False, adapt_source=None): if state is None: - return self._lazy_none_clause(reverse_direction, adapt_source=adapt_source) + return self._lazy_none_clause( + reverse_direction, + adapt_source=adapt_source) if not reverse_direction: - (criterion, bind_to_col, rev) = (self.__lazywhere, self.__bind_to_col, self._equated_columns) + criterion, bind_to_col, rev = \ + self.__lazywhere, \ + self.__bind_to_col, \ + self._equated_columns else: - (criterion, bind_to_col, rev) = LazyLoader._create_lazy_clause(self.parent_property, reverse_direction=reverse_direction) + criterion, bind_to_col, rev = \ + LazyLoader._create_lazy_clause( + self.parent_property, + reverse_direction=reverse_direction) if reverse_direction: mapper = self.parent_property.mapper @@ -397,25 +424,38 @@ class LazyLoader(AbstractRelationshipLoader): def visit_bindparam(bindparam): if bindparam.key in bind_to_col: - # use the "committed" (database) version to get query column values - # also its a deferred value; so that when used by Query, the committed value is used + # use the "committed" (database) version to get + # query column values + # also its a deferred value; so that when used + # by Query, the committed value is used # after an autoflush occurs o = state.obj() # strong ref - bindparam.value = lambda: mapper._get_committed_attr_by_column(o, bind_to_col[bindparam.key]) + bindparam.value = \ + lambda: mapper._get_committed_attr_by_column( + o, bind_to_col[bindparam.key]) if self.parent_property.secondary is not None and alias_secondary: - criterion = sql_util.ClauseAdapter(self.parent_property.secondary.alias()).traverse(criterion) + criterion = sql_util.ClauseAdapter( + self.parent_property.secondary.alias()).\ + traverse(criterion) - criterion = visitors.cloned_traverse(criterion, {}, {'bindparam':visit_bindparam}) + criterion = visitors.cloned_traverse( + criterion, {}, {'bindparam':visit_bindparam}) if adapt_source: criterion = adapt_source(criterion) return criterion def _lazy_none_clause(self, reverse_direction=False, adapt_source=None): if not reverse_direction: - (criterion, bind_to_col, rev) = (self.__lazywhere, self.__bind_to_col, self._equated_columns) + criterion, bind_to_col, rev = \ + self.__lazywhere, \ + self.__bind_to_col,\ + self._equated_columns else: - (criterion, bind_to_col, rev) = LazyLoader._create_lazy_clause(self.parent_property, reverse_direction=reverse_direction) + criterion, bind_to_col, rev = \ + LazyLoader._create_lazy_clause( + self.parent_property, + reverse_direction=reverse_direction) criterion = sql_util.adapt_criterion_to_null(criterion, bind_to_col) @@ -433,22 +473,30 @@ class LazyLoader(AbstractRelationshipLoader): key = self.key if not self.is_class_level: def new_execute(state, dict_, row): - # we are not the primary manager for this attribute on this class - set up a - # per-instance lazyloader, which will override the class-level behavior. - # this currently only happens when using a "lazyload" option on a "no load" - # attribute - "eager" attributes always have a class-level lazyloader - # installed. + # we are not the primary manager for this attribute + # on this class - set up a + # per-instance lazyloader, which will override the + # class-level behavior. + # this currently only happens when using a + # "lazyload" option on a "no load" + # attribute - "eager" attributes always have a + # class-level lazyloader installed. state.set_callable(dict_, key, LoadLazyAttribute(state, key)) else: def new_execute(state, dict_, row): - # we are the primary manager for this attribute on this class - reset its - # per-instance attribute state, so that the class-level lazy loader is - # executed when next referenced on this instance. this is needed in - # populate_existing() types of scenarios to reset any existing state. + # we are the primary manager for this attribute on + # this class - reset its + # per-instance attribute state, so that the class-level + # lazy loader is + # executed when next referenced on this instance. + # this is needed in + # populate_existing() types of scenarios to reset + # any existing state. state.reset(dict_, key) return new_execute, None - + + @classmethod def _create_lazy_clause(cls, prop, reverse_direction=False): binds = util.column_dict() lookup = util.column_dict() @@ -478,18 +526,19 @@ class LazyLoader(AbstractRelationshipLoader): lazywhere = prop.primaryjoin if prop.secondaryjoin is None or not reverse_direction: - lazywhere = visitors.replacement_traverse(lazywhere, {}, col_to_bind) + lazywhere = visitors.replacement_traverse( + lazywhere, {}, col_to_bind) if prop.secondaryjoin is not None: secondaryjoin = prop.secondaryjoin if reverse_direction: - secondaryjoin = visitors.replacement_traverse(secondaryjoin, {}, col_to_bind) + secondaryjoin = visitors.replacement_traverse( + secondaryjoin, {}, col_to_bind) lazywhere = sql.and_(lazywhere, secondaryjoin) bind_to_col = dict((binds[col].key, col) for col in binds) - return (lazywhere, bind_to_col, equated_columns) - _create_lazy_clause = classmethod(_create_lazy_clause) + return lazywhere, bind_to_col, equated_columns log.class_logger(LazyLoader) @@ -511,12 +560,14 @@ class LoadLazyAttribute(object): prop = instance_mapper.get_property(self.key) strategy = prop._get_strategy(LazyLoader) - if kw.get('passive') is attributes.PASSIVE_NO_FETCH and not strategy.use_get: + if kw.get('passive') is attributes.PASSIVE_NO_FETCH and \ + not strategy.use_get: return attributes.PASSIVE_NO_RESULT if strategy._should_log_debug(): strategy.logger.debug("loading %s", - mapperutil.state_attribute_str(state, self.key)) + mapperutil.state_attribute_str( + state, self.key)) session = sessionlib._state_session(state) if session is None: @@ -537,8 +588,11 @@ class LoadLazyAttribute(object): ident = [] allnulls = True for primary_key in prop.mapper.primary_key: - val = instance_mapper._get_committed_state_attr_by_column( - state, strategy._equated_columns[primary_key], **kw) + val = instance_mapper.\ + _get_committed_state_attr_by_column( + state, + strategy._equated_columns[primary_key], + **kw) if val is attributes.PASSIVE_NO_RESULT: return val allnulls = allnulls and val is None @@ -570,7 +624,8 @@ class LoadLazyAttribute(object): if l > 1: util.warn( "Multiple rows returned with " - "uselist=False for lazily-loaded attribute '%s' " % prop) + "uselist=False for lazily-loaded attribute '%s' " + % prop) return result[0] else: @@ -578,10 +633,13 @@ class LoadLazyAttribute(object): class SubqueryLoader(AbstractRelationshipLoader): def init_class_attribute(self, mapper): - self.parent_property._get_strategy(LazyLoader).init_class_attribute(mapper) + self.parent_property.\ + _get_strategy(LazyLoader).\ + init_class_attribute(mapper) - def setup_query(self, context, entity, path, adapter, - column_collection=None, parentmapper=None, **kwargs): + def setup_query(self, context, entity, + path, adapter, column_collection=None, + parentmapper=None, **kwargs): if not context.query._enable_eagerloads: return @@ -597,16 +655,25 @@ class SubqueryLoader(AbstractRelationshipLoader): attr = self.parent_property.class_attribute - # modify the query to just look for parent columns in the join condition + # modify the query to just look for parent columns in the + # join condition - # TODO: what happens to options() in the parent query ? are they going + # TODO: what happens to options() in the parent query ? + # are they going # to get in the way here ? + # set the original query to only look + # for the significant columns, not order + # by anything. q = context.query._clone() q._set_entities(local_attr) + q._order_by = None + # now select from it as a subquery. q = q.from_self(self.mapper, *local_attr) + # and join to the related thing we want + # to load. q = q.join(attr) q = q.order_by(*local_attr) @@ -623,7 +690,10 @@ class SubqueryLoader(AbstractRelationshipLoader): else: return \ [p[0] for p in self.parent_property.synchronize_pairs],\ - [p[0] for p in self.parent_property.secondary_synchronize_pairs] + [ + p[0] for p in self.parent_property. + secondary_synchronize_pairs + ] def create_row_processor(self, context, path, mapper, row, adapter): path = path + (self.key,) @@ -631,38 +701,44 @@ class SubqueryLoader(AbstractRelationshipLoader): local_cols, remote_cols = self._local_remote_columns local_attr = [self.parent._get_col_to_prop(c).key for c in local_cols] - remote_attr = [self.mapper._get_col_to_prop(c).key for c in remote_cols] + remote_attr = [ + self.mapper._get_col_to_prop(c).key + for c in remote_cols] q = context.attributes[('subquery', path)] - collections = dict((k, [v[0] for v in v]) for k, v in itertools.groupby( + collections = dict( + (k, [v[0] for v in v]) + for k, v in itertools.groupby( q, lambda x:x[1:] )) - def execute(state, dict_, row): collection = collections.get( tuple([row[col] for col in local_cols]), () ) - state.get_impl(self.key).set_committed_value(state, dict_, collection) + state.get_impl(self.key).\ + set_committed_value(state, dict_, collection) - return (execute, None) - + return execute, None class EagerLoader(AbstractRelationshipLoader): - """Strategize a relationship() that loads within the process of the parent object being selected.""" + """Strategize a relationship() that loads within the process + of the parent object being selected.""" def init(self): super(EagerLoader, self).init() self.join_depth = self.parent_property.join_depth def init_class_attribute(self, mapper): - self.parent_property._get_strategy(LazyLoader).init_class_attribute(mapper) + self.parent_property.\ + _get_strategy(LazyLoader).init_class_attribute(mapper) def setup_query(self, context, entity, path, adapter, \ - column_collection=None, parentmapper=None, **kwargs): + column_collection=None, parentmapper=None, + **kwargs): """Add a left outer join to the statement thats being constructed.""" if not context.query._enable_eagerloads: @@ -673,16 +749,21 @@ class EagerLoader(AbstractRelationshipLoader): reduced_path = interfaces._reduce_path(path) # check for user-defined eager alias - if ("user_defined_eager_row_processor", reduced_path) in context.attributes: - clauses = context.attributes[("user_defined_eager_row_processor", reduced_path)] + if ("user_defined_eager_row_processor", reduced_path) in\ + context.attributes: + clauses = context.attributes[ + ("user_defined_eager_row_processor", + reduced_path)] adapter = entity._get_entity_clauses(context.query, context) if adapter and clauses: - context.attributes[("user_defined_eager_row_processor", reduced_path)] = \ - clauses = clauses.wrap(adapter) + context.attributes[ + ("user_defined_eager_row_processor", + reduced_path)] = clauses = clauses.wrap(adapter) elif adapter: - context.attributes[("user_defined_eager_row_processor", reduced_path)] = \ - clauses = adapter + context.attributes[ + ("user_defined_eager_row_processor", + reduced_path)] = clauses = adapter add_to_collection = context.primary_columns @@ -698,18 +779,24 @@ class EagerLoader(AbstractRelationshipLoader): if self.mapper.base_mapper in reduced_path: return - clauses = mapperutil.ORMAdapter(mapperutil.AliasedClass(self.mapper), - equivalents=self.mapper._equivalent_columns, adapt_required=True) + clauses = mapperutil.ORMAdapter( + mapperutil.AliasedClass(self.mapper), + equivalents=self.mapper._equivalent_columns, + adapt_required=True) if self.parent_property.direction != interfaces.MANYTOONE: context.multi_row_eager_loaders = True context.create_eager_joins.append( - (self._create_eager_join, context, entity, path, adapter, parentmapper, clauses) + (self._create_eager_join, context, + entity, path, adapter, + parentmapper, clauses) ) add_to_collection = context.secondary_columns - context.attributes[("eager_row_processor", reduced_path)] = clauses + context.attributes[ + ("eager_row_processor", reduced_path) + ] = clauses for value in self.mapper._iterate_polymorphic_properties(): value.setup( @@ -720,7 +807,8 @@ class EagerLoader(AbstractRelationshipLoader): parentmapper=self.mapper, column_collection=add_to_collection) - def _create_eager_join(self, context, entity, path, adapter, parentmapper, clauses): + def _create_eager_join(self, context, entity, + path, adapter, parentmapper, clauses): if parentmapper is None: localparent = entity.mapper @@ -738,12 +826,13 @@ class EagerLoader(AbstractRelationshipLoader): not should_nest_selectable and \ context.from_clause: index, clause = \ - sql_util.find_join_source(context.from_clause, entity.selectable) + sql_util.find_join_source( + context.from_clause, entity.selectable) if clause is not None: # join to an existing FROM clause on the query. # key it to its list index in the eager_joins dict. - # Query._compile_context will adapt as needed and append to the - # FROM clause of the select(). + # Query._compile_context will adapt as needed and + # append to the FROM clause of the select(). entity_key, default_towrap = index, clause if entity_key is None: @@ -754,28 +843,38 @@ class EagerLoader(AbstractRelationshipLoader): join_to_left = False if adapter: if getattr(adapter, 'aliased_class', None): - onclause = getattr(adapter.aliased_class, self.key, self.parent_property) + onclause = getattr( + adapter.aliased_class, self.key, + self.parent_property) else: - onclause = getattr(mapperutil.AliasedClass(self.parent, adapter.selectable), - self.key, self.parent_property) + onclause = getattr( + mapperutil.AliasedClass( + self.parent, + adapter.selectable + ), + self.key, self.parent_property + ) if onclause is self.parent_property: - # TODO: this is a temporary hack to account for polymorphic eager loads where + # TODO: this is a temporary hack to + # account for polymorphic eager loads where # the eagerload is referencing via of_type(). join_to_left = True else: onclause = self.parent_property - innerjoin = context.attributes.get(("eager_join_type", path), - self.parent_property.innerjoin) + innerjoin = context.attributes.get( + ("eager_join_type", path), + self.parent_property.innerjoin) - context.eager_joins[entity_key] = eagerjoin = mapperutil.join( - towrap, - clauses.aliased_class, - onclause, - join_to_left=join_to_left, - isouter=not innerjoin - ) + context.eager_joins[entity_key] = eagerjoin = \ + mapperutil.join( + towrap, + clauses.aliased_class, + onclause, + join_to_left=join_to_left, + isouter=not innerjoin + ) # send a hint to the Query as to where it may "splice" this join eagerjoin.stop_on = entity.selectable @@ -783,11 +882,14 @@ class EagerLoader(AbstractRelationshipLoader): if self.parent_property.secondary is None and \ not parentmapper: # for parentclause that is the non-eager end of the join, - # ensure all the parent cols in the primaryjoin are actually in the + # ensure all the parent cols in the primaryjoin are actually + # in the # columns clause (i.e. are not deferred), so that aliasing applied - # by the Query propagates those columns outward. This has the effect + # by the Query propagates those columns outward. + # This has the effect # of "undefering" those columns. - for col in sql_util.find_columns(self.parent_property.primaryjoin): + for col in sql_util.find_columns( + self.parent_property.primaryjoin): if localparent.mapped_table.c.contains_column(col): if adapter: col = adapter.columns[col] @@ -797,22 +899,29 @@ class EagerLoader(AbstractRelationshipLoader): context.eager_order_by += \ eagerjoin._target_adapter.\ copy_and_process( - util.to_list(self.parent_property.order_by) + util.to_list( + self.parent_property.order_by + ) ) def _create_eager_adapter(self, context, row, adapter, path): reduced_path = interfaces._reduce_path(path) - if ("user_defined_eager_row_processor", reduced_path) in context.attributes: - decorator = context.attributes[("user_defined_eager_row_processor", reduced_path)] - # user defined eagerloads are part of the "primary" portion of the load. + if ("user_defined_eager_row_processor", reduced_path) in \ + context.attributes: + decorator = context.attributes[ + ("user_defined_eager_row_processor", + reduced_path)] + # user defined eagerloads are part of the "primary" + # portion of the load. # the adapters applied to the Query should be honored. if context.adapter and decorator: decorator = decorator.wrap(context.adapter) elif context.adapter: decorator = context.adapter elif ("eager_row_processor", reduced_path) in context.attributes: - decorator = context.attributes[("eager_row_processor", reduced_path)] + decorator = context.attributes[ + ("eager_row_processor", reduced_path)] else: return False @@ -827,7 +936,10 @@ class EagerLoader(AbstractRelationshipLoader): def create_row_processor(self, context, path, mapper, row, adapter): path = path + (self.key,) - eager_adapter = self._create_eager_adapter(context, row, adapter, path) + eager_adapter = self._create_eager_adapter( + context, + row, + adapter, path) if eager_adapter is not False: key = self.key @@ -856,8 +968,8 @@ class EagerLoader(AbstractRelationshipLoader): return new_execute, existing_execute else: def new_execute(state, dict_, row): - collection = attributes.init_state_collection(state, dict_, - key) + collection = attributes.init_state_collection( + state, dict_, key) result_list = util.UniqueAppender(collection, 'append_without_event') context.attributes[(state, key)] = result_list @@ -873,14 +985,18 @@ class EagerLoader(AbstractRelationshipLoader): # distinct sets of result columns collection = attributes.init_state_collection(state, dict_, key) - result_list = util.UniqueAppender(collection, - 'append_without_event') + result_list = util.UniqueAppender( + collection, + 'append_without_event') context.attributes[(state, key)] = result_list _instance(row, result_list) return new_execute, existing_execute else: - return self.parent_property._get_strategy(LazyLoader).\ - create_row_processor(context, path, mapper, row, adapter) + return self.parent_property.\ + _get_strategy(LazyLoader).\ + create_row_processor( + context, path, + mapper, row, adapter) log.class_logger(EagerLoader) @@ -962,8 +1078,10 @@ class _SingleParentValidator(interfaces.AttributeExtension): if value is not None: hasparent = initiator.hasparent(attributes.instance_state(value)) if hasparent and oldvalue is not value: - raise sa_exc.InvalidRequestError("Instance %s is already associated with an instance " - "of %s via its %s attribute, and is only allowed a single parent." % + raise sa_exc.InvalidRequestError( + "Instance %s is already associated with an instance " + "of %s via its %s attribute, and is only allowed a " + "single parent." % (mapperutil.instance_str(value), state.class_, self.prop) ) return value diff --git a/test/orm/test_eager_relations.py b/test/orm/test_eager_relations.py index 0411a22c85..925a5b09fb 100644 --- a/test/orm/test_eager_relations.py +++ b/test/orm/test_eager_relations.py @@ -48,6 +48,7 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL): @testing.resolve_artifact_names def test_no_orphan(self): """An eagerly loaded child object is not marked as an orphan""" + mapper(User, users, properties={ 'addresses':relationship(Address, cascade="all,delete-orphan", lazy=False) }) @@ -55,13 +56,16 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL): sess = create_session() user = sess.query(User).get(7) - assert getattr(User, 'addresses').hasparent(sa.orm.attributes.instance_state(user.addresses[0]), optimistic=True) - assert not sa.orm.class_mapper(Address)._is_orphan(sa.orm.attributes.instance_state(user.addresses[0])) + assert getattr(User, 'addresses').\ + hasparent(sa.orm.attributes.instance_state(user.addresses[0]), optimistic=True) + assert not sa.orm.class_mapper(Address).\ + _is_orphan(sa.orm.attributes.instance_state(user.addresses[0])) @testing.resolve_artifact_names def test_orderby(self): mapper(User, users, properties = { - 'addresses':relationship(mapper(Address, addresses), lazy=False, order_by=addresses.c.email_address), + 'addresses':relationship(mapper(Address, addresses), + lazy=False, order_by=addresses.c.email_address), }) q = create_session().query(User) eq_([ @@ -82,7 +86,9 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL): @testing.resolve_artifact_names def test_orderby_multi(self): mapper(User, users, properties = { - 'addresses':relationship(mapper(Address, addresses), lazy=False, order_by=[addresses.c.email_address, addresses.c.id]), + 'addresses':relationship(mapper(Address, addresses), + lazy=False, + order_by=[addresses.c.email_address, addresses.c.id]), }) q = create_session().query(User) eq_([ @@ -102,7 +108,9 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL): @testing.resolve_artifact_names def test_orderby_related(self): - """A regular mapper select on a single table can order by a relationship to a second table""" + """A regular mapper select on a single table can + order by a relationship to a second table""" + mapper(Address, addresses) mapper(User, users, properties = dict( addresses = relationship(Address, lazy=False, order_by=addresses.c.id), @@ -150,11 +158,6 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL): @testing.resolve_artifact_names def test_deferred_fk_col(self): - User, Address, Dingaling = self.classes.get_all( - 'User', 'Address', 'Dingaling') - users, addresses, dingalings = self.tables.get_all( - 'users', 'addresses', 'dingalings') - mapper(Address, addresses, properties={ 'user_id':deferred(addresses.c.user_id), 'user':relationship(User, lazy=False) @@ -240,9 +243,6 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL): @testing.resolve_artifact_names def test_many_to_many(self): - Keyword, Item = self.Keyword, self.Item - keywords, item_keywords, items = self.tables.get_all( - 'keywords', 'item_keywords', 'items') mapper(Keyword, keywords) mapper(Item, items, properties = dict( @@ -267,10 +267,6 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL): @testing.resolve_artifact_names def test_eager_option(self): - Keyword, Item = self.Keyword, self.Item - keywords, item_keywords, items = self.tables.get_all( - 'keywords', 'item_keywords', 'items') - mapper(Keyword, keywords) mapper(Item, items, properties = dict( keywords = relationship(Keyword, secondary=item_keywords, lazy=True, @@ -288,8 +284,6 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL): @testing.resolve_artifact_names def test_cyclical(self): """A circular eager relationship breaks the cycle with a lazy loader""" - User, Address = self.User, self.Address - users, addresses = self.tables.get_all('users', 'addresses') mapper(Address, addresses) mapper(User, users, properties = dict( @@ -305,10 +299,6 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL): @testing.resolve_artifact_names def test_double(self): """Eager loading with two relationships simultaneously, from the same table, using aliases.""" - User, Address, Order = self.classes.get_all( - 'User', 'Address', 'Order') - users, addresses, orders = self.tables.get_all( - 'users', 'addresses', 'orders') openorders = sa.alias(orders, 'openorders') closedorders = sa.alias(orders, 'closedorders') @@ -362,10 +352,6 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL): @testing.resolve_artifact_names def test_double_same_mappers(self): """Eager loading with two relationships simulatneously, from the same table, using aliases.""" - User, Address, Order = self.classes.get_all( - 'User', 'Address', 'Order') - users, addresses, orders = self.tables.get_all( - 'users', 'addresses', 'orders') mapper(Address, addresses) mapper(Order, orders, properties={ @@ -432,10 +418,6 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL): @testing.resolve_artifact_names def test_no_false_hits(self): """Eager loaders don't interpret main table columns as part of their eager load.""" - User, Address, Order = self.classes.get_all( - 'User', 'Address', 'Order') - users, addresses, orders = self.tables.get_all( - 'users', 'addresses', 'orders') mapper(User, users, properties={ 'addresses':relationship(Address, lazy=False), diff --git a/test/orm/test_subquery_relations.py b/test/orm/test_subquery_relations.py index 303781ad2b..ec01a94a99 100644 --- a/test/orm/test_subquery_relations.py +++ b/test/orm/test_subquery_relations.py @@ -5,6 +5,7 @@ from sqlalchemy.orm import mapper, relationship, create_session, lazyload, alias from sqlalchemy.test.testing import eq_, assert_raises from sqlalchemy.test.assertsql import CompiledSQL from test.orm import _base, _fixtures +import sqlalchemy as sa class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL): run_inserts = 'once' @@ -57,6 +58,100 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL): filter(Keyword.name == 'red')).all()) self.assert_sql_count(testing.db, go, 2) + @testing.resolve_artifact_names + def test_orderby(self): + mapper(User, users, properties = { + 'addresses':relationship(mapper(Address, addresses), + lazy='subquery', order_by=addresses.c.email_address), + }) + q = create_session().query(User) + eq_([ + User(id=7, addresses=[ + Address(id=1) + ]), + User(id=8, addresses=[ + Address(id=3, email_address='ed@bettyboop.com'), + Address(id=4, email_address='ed@lala.com'), + Address(id=2, email_address='ed@wood.com') + ]), + User(id=9, addresses=[ + Address(id=5) + ]), + User(id=10, addresses=[]) + ], q.order_by(User.id).all()) + + @testing.resolve_artifact_names + def test_orderby_multi(self): + mapper(User, users, properties = { + 'addresses':relationship(mapper(Address, addresses), + lazy='subquery', + order_by=[addresses.c.email_address, addresses.c.id]), + }) + q = create_session().query(User) + eq_([ + User(id=7, addresses=[ + Address(id=1) + ]), + User(id=8, addresses=[ + Address(id=3, email_address='ed@bettyboop.com'), + Address(id=4, email_address='ed@lala.com'), + Address(id=2, email_address='ed@wood.com') + ]), + User(id=9, addresses=[ + Address(id=5) + ]), + User(id=10, addresses=[]) + ], q.order_by(User.id).all()) + + @testing.resolve_artifact_names + def test_orderby_related(self): + """A regular mapper select on a single table can + order by a relationship to a second table""" + + mapper(Address, addresses) + mapper(User, users, properties = dict( + addresses = relationship(Address, lazy='subquery', order_by=addresses.c.id), + )) + + q = create_session().query(User) + l = q.filter(User.id==Address.user_id).order_by(Address.email_address).all() + + eq_([ + User(id=8, addresses=[ + Address(id=2, email_address='ed@wood.com'), + Address(id=3, email_address='ed@bettyboop.com'), + Address(id=4, email_address='ed@lala.com'), + ]), + User(id=9, addresses=[ + Address(id=5) + ]), + User(id=7, addresses=[ + Address(id=1) + ]), + ], l) + + @testing.resolve_artifact_names + def test_orderby_desc(self): + mapper(Address, addresses) + mapper(User, users, properties = dict( + addresses = relationship(Address, lazy='subquery', + order_by=[sa.desc(addresses.c.email_address)]), + )) + sess = create_session() + eq_([ + User(id=7, addresses=[ + Address(id=1) + ]), + User(id=8, addresses=[ + Address(id=2, email_address='ed@wood.com'), + Address(id=4, email_address='ed@lala.com'), + Address(id=3, email_address='ed@bettyboop.com'), + ]), + User(id=9, addresses=[ + Address(id=5) + ]), + User(id=10, addresses=[]) + ], sess.query(User).order_by(User.id).all()) # TODO: all the tests in test_eager_relations