From e45a4150fdd7e7f58dbe343706782a59a10d703b Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 16 Jul 2012 20:09:38 -0400 Subject: [PATCH] - totally remove _entity_info and _extended_entity_info, replacing all usage with inspect() --- lib/sqlalchemy/orm/interfaces.py | 6 +- lib/sqlalchemy/orm/mapper.py | 25 ++- lib/sqlalchemy/orm/properties.py | 8 +- lib/sqlalchemy/orm/query.py | 48 ++++-- lib/sqlalchemy/orm/strategies.py | 274 +++++++++++++++---------------- lib/sqlalchemy/orm/util.py | 57 ++----- test/orm/test_inspect.py | 14 ++ 7 files changed, 226 insertions(+), 206 deletions(-) diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 1e47e66166..5b18bc1373 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -18,7 +18,7 @@ classes within should be considered mostly private. from __future__ import absolute_import from itertools import chain -from .. import exc as sa_exc, util +from .. import exc as sa_exc, util, inspect from ..sql import operators from collections import deque #from . import _instrumentation_ext @@ -575,13 +575,13 @@ class PropertyOption(MapperOption): if getattr(token, '_of_type', None): ac = token._of_type - ext_info = orm_util._extended_entity_info(ac) + ext_info = inspect(ac) path_element = mapper = ext_info.mapper if not ext_info.is_aliased_class: ac = orm_util.with_polymorphic( ext_info.mapper.base_mapper, ext_info.mapper, aliased=True) - ext_info = orm_util._extended_entity_info(ac) + ext_info = inspect(ac) path.set(query, "path_with_polymorphic", ext_info) else: path_element = mapper = getattr(prop, 'mapper', None) diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 01b4d3a0f4..39591ef990 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -213,6 +213,15 @@ class Mapper(_InspectionAttr): """ return self + @property + def entity(self): + """Part of the inspection API. + + Returns self.class_. + + """ + return self.class_ + local_table = None """The :class:`.Selectable` which this :class:`.Mapper` manages. @@ -1390,15 +1399,17 @@ class Mapper(_InspectionAttr): """ - selectable = _with_polymorphic_selectable - """The :func:`.select` construct this :class:`.Mapper` selects from - by default. + @property + def selectable(self): + """The :func:`.select` construct this :class:`.Mapper` selects from + by default. - Normally, this is equivalent to :attr:`.mapped_table`, unless - the ``with_polymorphic`` feature is in use, in which case the - full "polymoprhic" selectable is returned. + Normally, this is equivalent to :attr:`.mapped_table`, unless + the ``with_polymorphic`` feature is in use, in which case the + full "polymoprhic" selectable is returned. - """ + """ + return self._with_polymorphic_selectable def _with_polymorphic_args(self, spec=None, selectable=False, innerjoin=False): diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 6b1dd64629..044ba8e2cf 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -11,7 +11,7 @@ mapped attributes. """ -from .. import sql, util, log, exc as sa_exc +from .. import sql, util, log, exc as sa_exc, inspect from ..sql import operators, expression from . import ( attributes, mapper, @@ -19,7 +19,7 @@ from . import ( dependency ) from .util import CascadeOptions, \ - _orm_annotate, _orm_deannotate, _orm_full_deannotate, _entity_info + _orm_annotate, _orm_deannotate, _orm_full_deannotate from .interfaces import MANYTOMANY, MANYTOONE, ONETOMANY,\ PropComparator, StrategizedProperty @@ -409,7 +409,9 @@ class RelationshipProperty(StrategizedProperty): def _criterion_exists(self, criterion=None, **kwargs): if getattr(self, '_of_type', None): - target_mapper, to_selectable, is_aliased_class = _entity_info(self._of_type) + info = inspect(self._of_type) + target_mapper, to_selectable, is_aliased_class = \ + info.mapper, info.selectable, info.is_aliased_class if self.property._is_self_referential and not is_aliased_class: to_selectable = to_selectable.alias() diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index f981399db0..44f9ba7e5d 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -25,12 +25,11 @@ from . import ( exc as orm_exc, loading ) from .util import ( - AliasedClass, ORMAdapter, _entity_descriptor, _entity_info, - _extended_entity_info, PathRegistry, + AliasedClass, ORMAdapter, _entity_descriptor, PathRegistry, _is_aliased_class, _is_mapped_class, _orm_columns, _orm_selectable, join as orm_join,with_parent, aliased ) -from .. import sql, util, log, exc as sa_exc +from .. import sql, util, log, exc as sa_exc, inspect from ..sql import ( util as sql_util, expression, visitors @@ -127,7 +126,7 @@ class Query(object): for ent in entities: for entity in ent.entities: if entity not in d: - ext_info = _extended_entity_info(entity) + ext_info = inspect(entity) if not ext_info.is_aliased_class and \ ext_info.mapper.with_polymorphic: if ext_info.mapper.mapped_table not in \ @@ -1077,7 +1076,7 @@ class Query(object): :class:`.Table`, :class:`.Alias`, or ORM entity / mapped class /etc. """ - mapper, selectable, is_aliased_class = _entity_info(selectable) + selectable = inspect(selectable).selectable self._with_hints += ((selectable, text, dialect_name),) @@ -1719,8 +1718,12 @@ class Query(object): isinstance(onclause, interfaces.PropComparator): left_entity = onclause.parententity + info = inspect(self._joinpoint_zero()) left_mapper, left_selectable, left_is_aliased = \ - _entity_info(self._joinpoint_zero()) + getattr(info, 'mapper', None), \ + info.selectable, \ + getattr(info, 'is_aliased_class', None) + if left_mapper is left_entity: left_entity = self._joinpoint_zero() descriptor = _entity_descriptor(left_entity, @@ -1810,10 +1813,15 @@ class Query(object): def _prepare_right_side(self, right, onclause, outerjoin, create_aliases, prop): - right_mapper, right_selectable, right_is_aliased = _entity_info(right) + info = inspect(right) + + right_mapper, right_selectable, right_is_aliased = \ + getattr(info, 'mapper', None), \ + info.selectable, \ + getattr(info, 'is_aliased_class', False) if right_mapper: - self._join_entities += (right, ) + self._join_entities += (info, ) if right_mapper and prop and \ not right_mapper.common_parent(prop.mapper): @@ -1883,8 +1891,13 @@ class Query(object): return right, right_is_aliased, onclause - def _join_to_left(self, left, right, right_is_aliased, onclause, outerjoin): - left_mapper, left_selectable, left_is_aliased = _entity_info(left) + def _join_to_left(self, left, right, right_is_aliased, + onclause, outerjoin): + info = inspect(left) + left_mapper, left_selectable, left_is_aliased = \ + getattr(info, 'mapper', None),\ + info.selectable,\ + getattr(info, 'is_aliased_class', False) # this is an overly broad assumption here, but there's a # very wide variety of situations where we rely upon orm.join's @@ -1990,11 +2003,12 @@ class Query(object): """ obj = [] for fo in from_obj: - if _is_mapped_class(fo): - mapper, selectable, is_aliased_class = _entity_info(fo) + info = inspect(fo) + if hasattr(info, 'mapper') and \ + (info.is_mapper or info.is_aliased_class): self._select_from_entity = fo - obj.append(selectable) - elif not isinstance(fo, expression.FromClause): + obj.append(info.selectable) + elif not info.is_selectable: raise sa_exc.ArgumentError( "select_from() accepts FromClause objects only.") else: @@ -2669,7 +2683,7 @@ class Query(object): """ for (ext_info, adapter) in self._mapper_adapter_map.values(): - if ext_info.entity in self._join_entities: + if ext_info in self._join_entities: continue single_crit = ext_info.mapper._single_table_criterion if single_crit is not None: @@ -2718,7 +2732,7 @@ class _MapperEntity(_QueryEntity): self.is_aliased_class = ext_info.is_aliased_class self._with_polymorphic = ext_info.with_polymorphic_mappers self._polymorphic_discriminator = \ - ext_info.with_polymorphic_discriminator + ext_info.polymorphic_on if ext_info.is_aliased_class: self.entity_zero = ext_info.entity self._label_name = self.entity_zero._sa_label_name @@ -2760,7 +2774,7 @@ class _MapperEntity(_QueryEntity): return self.entity_zero def corresponds_to(self, entity): - entity_info = _extended_entity_info(entity) + entity_info = inspect(entity) if entity_info.is_aliased_class or self.is_aliased_class: return entity is self.entity_zero \ or \ diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 2bd60e76d4..21214af9a1 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -4,14 +4,14 @@ # 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 +"""sqlalchemy.orm.interfaces.LoaderStrategy implementations, and related MapperOptions.""" -from .. import exc as sa_exc +from .. import exc as sa_exc, inspect from .. import util, log, event from ..sql import util as sql_util, visitors from . import ( - attributes, interfaces, exc as orm_exc, loading, + attributes, interfaces, exc as orm_exc, loading, unitofwork, util as orm_util ) from .util import _none_set @@ -23,13 +23,13 @@ from .session import _state_session import itertools def _register_attribute(strategy, mapper, useobject, - compare_function=None, + compare_function=None, typecallable=None, uselist=False, - callable_=None, - proxy_property=None, + callable_=None, + proxy_property=None, active_history=False, - impl_class=None, + impl_class=None, **kw ): @@ -45,7 +45,7 @@ def _register_attribute(strategy, mapper, useobject, if prop.key in prop.parent.validators: fn, include_removes = prop.parent.validators[prop.key] listen_hooks.append( - lambda desc, prop: orm_util._validator_events(desc, + lambda desc, prop: orm_util._validator_events(desc, prop.key, fn, include_removes) ) @@ -57,8 +57,8 @@ def _register_attribute(strategy, mapper, useobject, backref = kw.pop('backref', None) if backref: listen_hooks.append( - lambda desc, prop: attributes.backref_listeners(desc, - backref, + lambda desc, prop: attributes.backref_listeners(desc, + backref, uselist) ) @@ -66,17 +66,17 @@ def _register_attribute(strategy, mapper, useobject, if prop is m._props.get(prop.key): desc = attributes.register_attribute_impl( - m.class_, - prop.key, + m.class_, + prop.key, parent_token=prop, - uselist=uselist, - compare_function=compare_function, + uselist=uselist, + compare_function=compare_function, useobject=useobject, - extension=attribute_ext, - trackparent=useobject and (prop.single_parent - or prop.direction is interfaces.ONETOMANY), + extension=attribute_ext, + trackparent=useobject and (prop.single_parent + or prop.direction is interfaces.ONETOMANY), typecallable=typecallable, - callable_=callable_, + callable_=callable_, active_history=active_history, impl_class=impl_class, doc=prop.doc, @@ -97,7 +97,7 @@ class UninstrumentedColumnLoader(LoaderStrategy): super(UninstrumentedColumnLoader, self).__init__(parent) self.columns = self.parent_property.columns - def setup_query(self, context, entity, path, adapter, + def setup_query(self, context, entity, path, adapter, column_collection=None, **kwargs): for c in self.columns: if adapter: @@ -115,7 +115,7 @@ 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, + def setup_query(self, context, entity, path, adapter, column_collection, **kwargs): for c in self.columns: if adapter: @@ -134,7 +134,7 @@ class ColumnLoader(LoaderStrategy): active_history = active_history ) - def create_row_processor(self, context, path, + def create_row_processor(self, context, path, mapper, row, adapter): key = self.key # look through list of columns represented here @@ -195,10 +195,10 @@ class DeferredColumnLoader(LoaderStrategy): expire_missing=False ) - def setup_query(self, context, entity, path, adapter, + def setup_query(self, context, entity, path, adapter, only_load_props=None, **kwargs): if ( - self.group is not None and + 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).\ @@ -216,10 +216,10 @@ class DeferredColumnLoader(LoaderStrategy): if self.group: toload = [ - p.key for p in - localparent.iterate_properties - if isinstance(p, StrategizedProperty) and - isinstance(p.strategy, DeferredColumnLoader) and + p.key for p in + localparent.iterate_properties + if isinstance(p, StrategizedProperty) and + isinstance(p.strategy, DeferredColumnLoader) and p.group==self.group ] else: @@ -232,12 +232,12 @@ class DeferredColumnLoader(LoaderStrategy): 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" % + "deferred load operation of attribute '%s' cannot proceed" % (orm_util.state_str(state), self.key) ) query = session.query(localparent) - if loading.load_on_ident(query, state.key, + if loading.load_on_ident(query, state.key, only_load_props=group, refresh_state=state) is None: raise orm_exc.ObjectDeletedError(state) @@ -294,14 +294,14 @@ class AbstractRelationshipLoader(LoaderStrategy): class NoLoader(AbstractRelationshipLoader): """Provide loading behavior for a :class:`.RelationshipProperty` with "lazy=None". - + """ def init_class_attribute(self, mapper): self.is_class_level = True _register_attribute(self, mapper, - useobject=True, + useobject=True, uselist=self.parent_property.uselist, typecallable = self.parent_property.collection_class, ) @@ -316,7 +316,7 @@ log.class_logger(NoLoader) class LazyLoader(AbstractRelationshipLoader): """Provide loading behavior for a :class:`.RelationshipProperty` with "lazy=True", that is loads when first accessed. - + """ def __init__(self, parent): @@ -338,8 +338,8 @@ class LazyLoader(AbstractRelationshipLoader): #from sqlalchemy.orm import query self.use_get = not self.uselist and \ self.mapper._get_clause[0].compare( - self._lazywhere, - use_proxies=True, + self._lazywhere, + use_proxies=True, equivalents=self.mapper._equivalent_columns ) @@ -355,13 +355,13 @@ class LazyLoader(AbstractRelationshipLoader): def init_class_attribute(self, mapper): self.is_class_level = True - # MANYTOONE currently only needs the + # MANYTOONE currently only needs the # "old" value for delete-orphan - # cascades. the required _SingleParentValidator + # cascades. the required _SingleParentValidator # will enable active_history - # in that case. otherwise we don't need the + # in that case. otherwise we don't need the # "old" value during backref operations. - _register_attribute(self, + _register_attribute(self, mapper, useobject=True, callable_=self._load_for_state, @@ -375,12 +375,12 @@ class LazyLoader(AbstractRelationshipLoader): not self.use_get, ) - def lazy_clause(self, state, reverse_direction=False, - alias_secondary=False, + 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, + reverse_direction, adapt_source=adapt_source) if not reverse_direction: @@ -411,14 +411,14 @@ class LazyLoader(AbstractRelationshipLoader): if bindparam._identifying_key in bind_to_col: bindparam.callable = \ lambda: mapper._get_committed_state_attr_by_column( - state, dict_, + state, dict_, bind_to_col[bindparam._identifying_key]) else: def visit_bindparam(bindparam): if bindparam._identifying_key in bind_to_col: bindparam.callable = \ lambda: mapper._get_state_attr_by_column( - state, dict_, + state, dict_, bind_to_col[bindparam._identifying_key]) @@ -477,11 +477,11 @@ class LazyLoader(AbstractRelationshipLoader): if not session: raise orm_exc.DetachedInstanceError( "Parent instance %s is not bound to a Session; " - "lazy load operation of attribute '%s' cannot proceed" % + "lazy load operation of attribute '%s' cannot proceed" % (orm_util.state_str(state), self.key) ) - # if we have a simple primary key load, check the + # if we have a simple primary key load, check the # identity map without generating a Query at all if self.use_get: ident = self._get_ident_for_use_get( @@ -550,7 +550,7 @@ class LazyLoader(AbstractRelationshipLoader): q = q.order_by(*util.to_list(self.parent_property.order_by)) for rev in self.parent_property._reverse_property: - # reverse props that are MANYTOONE are loading *this* + # reverse props that are MANYTOONE are loading *this* # object from get(), so don't need to eager out to those. if rev.direction is interfaces.MANYTOONE and \ rev._use_get and \ @@ -575,7 +575,7 @@ class LazyLoader(AbstractRelationshipLoader): if l > 1: util.warn( "Multiple rows returned with " - "uselist=False for lazily-loaded attribute '%s' " + "uselist=False for lazily-loaded attribute '%s' " % self.parent_property) return result[0] @@ -583,30 +583,30 @@ class LazyLoader(AbstractRelationshipLoader): return None - def create_row_processor(self, context, path, + def create_row_processor(self, context, path, mapper, row, adapter): key = self.key if not self.is_class_level: def set_lazy_callable(state, dict_, row): - # we are not the primary manager for this attribute + # we are not the primary manager for this attribute # on this class - set up a - # per-instance lazyloader, which will override the + # per-instance lazyloader, which will override the # class-level behavior. - # this currently only happens when using a + # this currently only happens when using a # "lazyload" option on a "no load" - # attribute - "eager" attributes always have a + # attribute - "eager" attributes always have a # class-level lazyloader installed. state.set_callable(dict_, key, LoadLazyAttribute(state, key)) return set_lazy_callable, None, None else: def reset_for_lazy_callable(state, dict_, row): - # we are the primary manager for this attribute on + # we are the primary manager for this attribute on # this class - reset its - # per-instance attribute state, so that the class-level + # 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 + # populate_existing() types of scenarios to reset # any existing state. state.reset(dict_, key) @@ -637,12 +637,12 @@ class ImmediateLoader(AbstractRelationshipLoader): _get_strategy(LazyLoader).\ init_class_attribute(mapper) - def setup_query(self, context, entity, + def setup_query(self, context, entity, path, adapter, column_collection=None, parentmapper=None, **kwargs): pass - def create_row_processor(self, context, path, + def create_row_processor(self, context, path, mapper, row, adapter): def load_immediate(state, dict_, row): state.get_impl(self.key).get(state, dict_) @@ -659,8 +659,8 @@ class SubqueryLoader(AbstractRelationshipLoader): _get_strategy(LazyLoader).\ init_class_attribute(mapper) - def setup_query(self, context, entity, - path, adapter, + def setup_query(self, context, entity, + path, adapter, column_collection=None, parentmapper=None, **kwargs): @@ -677,12 +677,12 @@ class SubqueryLoader(AbstractRelationshipLoader): else: effective_entity = self.mapper - subq_path = context.attributes.get(('subquery_path', None), + subq_path = context.attributes.get(('subquery_path', None), orm_util.PathRegistry.root) subq_path = subq_path + path - # if not via query option, check for + # if not via query option, check for # a cycle if not path.contains(context, "loaderstrategy"): if self.join_depth: @@ -695,7 +695,7 @@ class SubqueryLoader(AbstractRelationshipLoader): self._get_leftmost(subq_path) orig_query = context.attributes.get( - ("orig_query", SubqueryLoader), + ("orig_query", SubqueryLoader), context.query) # generate a new Query from the original, then @@ -705,7 +705,7 @@ class SubqueryLoader(AbstractRelationshipLoader): leftmost_attr ) - # generate another Query that will join the + # generate another Query that will join the # left alias to the target relationships. # basically doing a longhand # "from_self()". (from_self() itself not quite industrial @@ -722,13 +722,13 @@ class SubqueryLoader(AbstractRelationshipLoader): q = q.order_by(*local_attr) q = q.add_columns(*local_attr) - q = self._apply_joins(q, to_join, left_alias, + q = self._apply_joins(q, to_join, left_alias, parent_alias, effective_entity) q = self._setup_options(q, subq_path, orig_query, effective_entity) q = self._setup_outermost_orderby(q) - # add new query to attributes to be picked up + # add new query to attributes to be picked up # by create_row_processor path.set(context, "subquery", q) @@ -761,7 +761,7 @@ class SubqueryLoader(AbstractRelationshipLoader): # to look only for significant columns q = orig_query._clone().correlate(None) - # TODO: why does polymporphic etc. require hardcoding + # TODO: why does polymporphic etc. require hardcoding # into _adapt_col_list ? Does query.add_columns(...) work # with polymorphic loading ? q._set_entities(q._adapt_col_list(leftmost_attr)) @@ -785,7 +785,7 @@ class SubqueryLoader(AbstractRelationshipLoader): # figure out what's being joined. a.k.a. the fun part to_join = [ - (subq_path[i], subq_path[i+1]) + (subq_path[i], subq_path[i+1]) for i in xrange(0, len(subq_path), 2) ] @@ -793,21 +793,21 @@ class SubqueryLoader(AbstractRelationshipLoader): # which needs to be aliased. if len(to_join) > 1: - ext = orm_util._extended_entity_info(subq_path[-2]) + info = inspect(subq_path[-2]) if len(to_join) < 2: # in the case of a one level eager load, this is the # leftmost "left_alias". parent_alias = left_alias - elif ext.mapper.isa(self.parent): + elif info.mapper.isa(self.parent): # In the case of multiple levels, retrieve - # it from subq_path[-2]. This is the same as self.parent - # in the vast majority of cases, and [ticket:2014] + # it from subq_path[-2]. This is the same as self.parent + # in the vast majority of cases, and [ticket:2014] # illustrates a case where sub_path[-2] is a subclass # of self.parent parent_alias = orm_util.AliasedClass(subq_path[-2]) else: - # if of_type() were used leading to this relationship, + # if of_type() were used leading to this relationship, # self.parent is more specific than subq_path[-2] parent_alias = orm_util.AliasedClass(self.parent) @@ -819,15 +819,15 @@ class SubqueryLoader(AbstractRelationshipLoader): ] return to_join, local_attr, parent_alias - def _apply_joins(self, q, to_join, left_alias, parent_alias, + def _apply_joins(self, q, to_join, left_alias, parent_alias, effective_entity): for i, (mapper, key) in enumerate(to_join): # we need to use query.join() as opposed to - # orm.join() here because of the - # rich behavior it brings when dealing with + # orm.join() here because of the + # rich behavior it brings when dealing with # "with_polymorphic" mappers. "aliased" - # and "from_joinpoint" take care of most of + # and "from_joinpoint" take care of most of # the chaining and aliasing for us. first = i == 0 @@ -857,7 +857,7 @@ class SubqueryLoader(AbstractRelationshipLoader): # these will fire relative to subq_path. q = q._with_current_path(subq_path) q = q._conditional_options(*orig_query._with_options) - if orig_query._populate_existing: + if orig_query._populate_existing: q._populate_existing = orig_query._populate_existing return q @@ -865,7 +865,7 @@ class SubqueryLoader(AbstractRelationshipLoader): def _setup_outermost_orderby(self, q): if self.parent_property.order_by: # if there's an ORDER BY, alias it the same - # way joinedloader does, but we have to pull out + # way joinedloader does, but we have to pull out # the "eagerjoin" from the query. # this really only picks up the "secondary" table # right now. @@ -885,7 +885,7 @@ class SubqueryLoader(AbstractRelationshipLoader): if not self.parent.class_manager[self.key].impl.supports_population: raise sa_exc.InvalidRequestError( "'%s' does not support object " - "population - eager loading cannot be applied." % + "population - eager loading cannot be applied." % self) path = path[self.key] @@ -902,9 +902,9 @@ class SubqueryLoader(AbstractRelationshipLoader): collections = path.get(context, "collections") if collections is None: collections = dict( - (k, [v[0] for v in v]) + (k, [v[0] for v in v]) for k, v in itertools.groupby( - subq, + subq, lambda x:x[1:] )) path.set(context, 'collections', collections) @@ -920,7 +920,7 @@ class SubqueryLoader(AbstractRelationshipLoader): def _create_collection_loader(self, collections, local_cols): def load_collection_from_subq(state, dict_, row): collection = collections.get( - tuple([row[col] for col in local_cols]), + tuple([row[col] for col in local_cols]), () ) state.get_impl(self.key).\ @@ -931,7 +931,7 @@ class SubqueryLoader(AbstractRelationshipLoader): def _create_scalar_loader(self, collections, local_cols): def load_scalar_from_subq(state, dict_, row): collection = collections.get( - tuple([row[col] for col in local_cols]), + tuple([row[col] for col in local_cols]), (None,) ) if len(collection) > 1: @@ -951,7 +951,7 @@ log.class_logger(SubqueryLoader) class JoinedLoader(AbstractRelationshipLoader): """Provide loading behavior for a :class:`.RelationshipProperty` using joined eager loading. - + """ def __init__(self, parent): super(JoinedLoader, self).__init__(parent) @@ -974,8 +974,8 @@ class JoinedLoader(AbstractRelationshipLoader): with_polymorphic = None - user_defined_adapter = path.get(context, - "user_defined_eager_row_processor", + user_defined_adapter = path.get(context, + "user_defined_eager_row_processor", False) if user_defined_adapter is not False: clauses, adapter, add_to_collection = \ @@ -984,7 +984,7 @@ class JoinedLoader(AbstractRelationshipLoader): user_defined_adapter ) else: - # if not via query option, check for + # if not via query option, check for # a cycle if not path.contains(context, "loaderstrategy"): if self.join_depth: @@ -1000,7 +1000,7 @@ class JoinedLoader(AbstractRelationshipLoader): ) with_poly_info = path.get( - context, + context, "path_with_polymorphic", None ) @@ -1013,37 +1013,37 @@ class JoinedLoader(AbstractRelationshipLoader): for value in self.mapper._iterate_polymorphic_properties( mappers=with_polymorphic): value.setup( - context, - entity, - path, - clauses, - parentmapper=self.mapper, + context, + entity, + path, + clauses, + parentmapper=self.mapper, column_collection=add_to_collection, allow_innerjoin=allow_innerjoin) - def _get_user_defined_adapter(self, context, entity, + def _get_user_defined_adapter(self, context, entity, path, adapter, user_defined_adapter): adapter = entity._get_entity_clauses(context.query, context) if adapter and user_defined_adapter: user_defined_adapter = user_defined_adapter.wrap(adapter) - path.set(context, "user_defined_eager_row_processor", + path.set(context, "user_defined_eager_row_processor", user_defined_adapter) elif adapter: user_defined_adapter = adapter - path.set(context, "user_defined_eager_row_processor", + path.set(context, "user_defined_eager_row_processor", user_defined_adapter) add_to_collection = context.primary_columns return user_defined_adapter, adapter, add_to_collection - def _generate_row_adapter(self, + def _generate_row_adapter(self, context, entity, path, adapter, column_collection, parentmapper, allow_innerjoin ): with_poly_info = path.get( - context, - "path_with_polymorphic", + context, + "path_with_polymorphic", None ) if with_poly_info: @@ -1051,7 +1051,7 @@ class JoinedLoader(AbstractRelationshipLoader): else: to_adapt = orm_util.AliasedClass(self.mapper) clauses = orm_util.ORMAdapter( - to_adapt, + to_adapt, equivalents=self.mapper._equivalent_columns, adapt_required=True) assert clauses.aliased_class is not None @@ -1060,7 +1060,7 @@ class JoinedLoader(AbstractRelationshipLoader): context.multi_row_eager_loaders = True innerjoin = allow_innerjoin and path.get(context, - "eager_join_type", + "eager_join_type", self.parent_property.innerjoin) if not innerjoin: # if this is an outer join, all eager joins from @@ -1068,8 +1068,8 @@ class JoinedLoader(AbstractRelationshipLoader): allow_innerjoin = False context.create_eager_joins.append( - (self._create_eager_join, context, - entity, path, adapter, + (self._create_eager_join, context, + entity, path, adapter, parentmapper, clauses, innerjoin) ) @@ -1077,8 +1077,8 @@ class JoinedLoader(AbstractRelationshipLoader): path.set(context, "eager_row_processor", clauses) return clauses, adapter, add_to_collection, allow_innerjoin - def _create_eager_join(self, context, entity, - path, adapter, parentmapper, + def _create_eager_join(self, context, entity, + path, adapter, parentmapper, clauses, innerjoin): if parentmapper is None: @@ -1087,7 +1087,7 @@ class JoinedLoader(AbstractRelationshipLoader): localparent = parentmapper # whether or not the Query will wrap the selectable in a subquery, - # and then attach eager load joins to that (i.e., in the case of + # and then attach eager load joins to that (i.e., in the case of # LIMIT/OFFSET etc.) should_nest_selectable = context.multi_row_eager_loaders and \ context.query._should_nest_selectable @@ -1103,7 +1103,7 @@ class JoinedLoader(AbstractRelationshipLoader): 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 + # Query._compile_context will adapt as needed and # append to the FROM clause of the select(). entity_key, default_towrap = index, clause @@ -1121,14 +1121,14 @@ class JoinedLoader(AbstractRelationshipLoader): else: onclause = getattr( orm_util.AliasedClass( - self.parent, + self.parent, adapter.selectable - ), + ), self.key, self.parent_property ) if onclause is self.parent_property: - # TODO: this is a temporary hack to + # TODO: this is a temporary hack to # account for polymorphic eager loads where # the eagerload is referencing via of_type(). join_to_left = True @@ -1138,10 +1138,10 @@ class JoinedLoader(AbstractRelationshipLoader): assert clauses.aliased_class is not None context.eager_joins[entity_key] = eagerjoin = \ orm_util.join( - towrap, - clauses.aliased_class, - onclause, - join_to_left=join_to_left, + towrap, + clauses.aliased_class, + onclause, + join_to_left=join_to_left, isouter=not innerjoin ) @@ -1151,11 +1151,11 @@ class JoinedLoader(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 + # ensure all the parent cols in the primaryjoin are actually # in the - # columns clause (i.e. are not deferred), so that aliasing applied + # columns clause (i.e. are not deferred), so that aliasing applied # by the Query propagates those columns outward. - # This has the effect + # This has the effect # of "undefering" those columns. for col in sql_util.find_columns( self.parent_property.primaryjoin): @@ -1175,12 +1175,12 @@ class JoinedLoader(AbstractRelationshipLoader): def _create_eager_adapter(self, context, row, adapter, path): - user_defined_adapter = path.get(context, - "user_defined_eager_row_processor", + user_defined_adapter = path.get(context, + "user_defined_eager_row_processor", False) if user_defined_adapter is not False: decorator = user_defined_adapter - # user defined eagerloads are part of the "primary" + # 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: @@ -1196,7 +1196,7 @@ class JoinedLoader(AbstractRelationshipLoader): self.mapper.identity_key_from_row(row, decorator) return decorator except KeyError: - # no identity key - dont return a row + # no identity key - dont return a row # processor, will cause a degrade to lazy return False @@ -1204,14 +1204,14 @@ class JoinedLoader(AbstractRelationshipLoader): if not self.parent.class_manager[self.key].impl.supports_population: raise sa_exc.InvalidRequestError( "'%s' does not support object " - "population - eager loading cannot be applied." % + "population - eager loading cannot be applied." % self) our_path = path[self.key] eager_adapter = self._create_eager_adapter( - context, - row, + context, + row, adapter, our_path) if eager_adapter is not False: @@ -1219,7 +1219,7 @@ class JoinedLoader(AbstractRelationshipLoader): _instance = loading.instance_processor( self.mapper, - context, + context, our_path[self.mapper], eager_adapter) @@ -1346,8 +1346,8 @@ class LoadEagerFromAliasOption(PropertyOption): super(LoadEagerFromAliasOption, self).__init__(key) if alias is not None: if not isinstance(alias, basestring): - mapper, alias, is_aliased_class = \ - orm_util._entity_info(alias) + info = inspect(alias) + alias = info.selectable self.alias = alias self.chained = chained @@ -1357,8 +1357,8 @@ class LoadEagerFromAliasOption(PropertyOption): (root_mapper, propname) = path.path[-2:] prop = root_mapper._props[propname] adapter = query._polymorphic_adapters.get(prop.mapper, None) - path.setdefault(query, - "user_defined_eager_row_processor", + path.setdefault(query, + "user_defined_eager_row_processor", adapter) root_mapper, propname = paths[-1].path[-2:] @@ -1366,31 +1366,31 @@ class LoadEagerFromAliasOption(PropertyOption): if self.alias is not None: if isinstance(self.alias, basestring): self.alias = prop.target.alias(self.alias) - paths[-1].set(query, "user_defined_eager_row_processor", - sql_util.ColumnAdapter(self.alias, + paths[-1].set(query, "user_defined_eager_row_processor", + sql_util.ColumnAdapter(self.alias, equivalents=prop.mapper._equivalent_columns) ) else: if paths[-1].contains(query, "path_with_polymorphic"): with_poly_info = paths[-1].get(query, "path_with_polymorphic") adapter = orm_util.ORMAdapter( - with_poly_info.entity, + with_poly_info.entity, equivalents=prop.mapper._equivalent_columns, adapt_required=True) else: adapter = query._polymorphic_adapters.get(prop.mapper, None) - paths[-1].set(query, "user_defined_eager_row_processor", + paths[-1].set(query, "user_defined_eager_row_processor", adapter) def single_parent_validator(desc, prop): def _do_check(state, value, oldvalue, initiator): if value is not None and initiator.key == prop.key: hasparent = initiator.hasparent(attributes.instance_state(value)) - if hasparent and oldvalue is not 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." % + "single parent." % (orm_util.instance_str(value), state.class_, prop) ) return value @@ -1401,8 +1401,8 @@ def single_parent_validator(desc, prop): def set_(state, value, oldvalue, initiator): return _do_check(state, value, oldvalue, initiator) - event.listen(desc, 'append', append, raw=True, retval=True, + event.listen(desc, 'append', append, raw=True, retval=True, active_history=True) - event.listen(desc, 'set', set_, raw=True, retval=True, + event.listen(desc, 'set', set_, raw=True, retval=True, active_history=True) diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index e9e11ee875..517f1acb48 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -220,7 +220,11 @@ class ORMAdapter(sql_util.ColumnAdapter): """ def __init__(self, entity, equivalents=None, chain_to=None, adapt_required=False): - self.mapper, selectable, is_aliased_class = _entity_info(entity) + info = inspection.inspect(entity) + + self.mapper = info.mapper + selectable = info.selectable + is_aliased_class = info.is_aliased_class if is_aliased_class: self.aliased_class = entity else: @@ -446,6 +450,7 @@ class AliasedClass(object): if alias is None: alias = mapper._with_polymorphic_selectable.alias(name=name) self._aliased_insp = AliasedInsp( + self, mapper, alias, name, @@ -490,6 +495,7 @@ class AliasedClass(object): def __setstate__(self, state): self._aliased_insp = AliasedInsp( + self, state['mapper'], state['alias'], state['name'], @@ -548,6 +554,7 @@ class AliasedClass(object): id(self), self.__target.__name__) AliasedInsp = util.namedtuple("AliasedInsp", [ + "entity", "mapper", "selectable", "name", @@ -674,11 +681,19 @@ class _ORMJoin(expression.Join): if join_to_left: adapt_from = left.right else: - left_mapper, left, left_is_aliased = _entity_info(left) + info = inspection.inspect(left) + left_mapper = getattr(info, 'mapper', None) + left = info.selectable + left_is_aliased = getattr(info, 'is_aliased_class', False) + if join_to_left and (left_is_aliased or not left_mapper): adapt_from = left - right_mapper, right, right_is_aliased = _entity_info(right) + info = inspection.inspect(right) + right_mapper = getattr(info, 'mapper', None) + right = info.selectable + right_is_aliased = getattr(info, 'is_aliased_class', False) + if right_is_aliased: adapt_to = right else: @@ -939,42 +954,6 @@ def _is_aliased_class(entity): return insp is not None and \ getattr(insp, "is_aliased_class", False) -extended_entity_info = util.namedtuple("extended_entity_info", [ - "entity", - "mapper", - "selectable", - "is_aliased_class", - "with_polymorphic_mappers", - "with_polymorphic_discriminator" -]) -def _extended_entity_info(entity): - insp = inspection.inspect(entity) - return extended_entity_info( - entity, - insp.mapper if not insp.is_selectable else None, - insp.selectable, - insp.is_aliased_class if not insp.is_selectable else False, - insp.with_polymorphic_mappers if not insp.is_selectable else None, - insp.polymorphic_on if not insp.is_selectable else None - ) - -def _entity_info(entity, compile=True): - """Return mapping information given a class, mapper, or AliasedClass. - - Returns 3-tuple of: mapper, mapped selectable, boolean indicating if this - is an aliased() construct. - - If the given entity is not a mapper, mapped class, or aliased construct, - returns None, the entity, False. This is typically used to allow - unmapped selectables through. - - """ - insp = inspection.inspect(entity) - return \ - insp.mapper if not insp.is_selectable else None,\ - insp.selectable,\ - insp.is_aliased_class if not insp.is_selectable else False, - def _entity_descriptor(entity, key): """Return a class attribute given an entity and string name. diff --git a/test/orm/test_inspect.py b/test/orm/test_inspect.py index 4ee01ac62f..487182fac2 100644 --- a/test/orm/test_inspect.py +++ b/test/orm/test_inspect.py @@ -62,6 +62,20 @@ class TestORMInspection(_fixtures.FixtureTest): assert not insp.is_selectable assert not insp.is_aliased_class + def test_mapper_selectable_fixed(self): + from sqlalchemy.orm import mapper + class Foo(object): + pass + class Bar(Foo): + pass + user_table = self.tables.users + addresses_table = self.tables.addresses + mapper(Foo, user_table, with_polymorphic=(Bar,)) + mapper(Bar, addresses_table, inherits=Foo) + i1 = inspect(Foo) + i2 = inspect(Foo) + assert i1.selectable is i2.selectable + def test_aliased_class(self): Address = self.classes.Address ualias = aliased(Address) -- 2.47.3