From 1a88a982b43f2f3a0735890b00a45e178727812f Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 4 Aug 2013 15:28:40 -0400 Subject: find some more inline imports and move them out --- lib/sqlalchemy/orm/strategies.py | 1 - 1 file changed, 1 deletion(-) (limited to 'lib/sqlalchemy/orm/strategies.py') diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index aa46d06a8..fac8a3c6f 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -350,7 +350,6 @@ class LazyLoader(AbstractRelationshipLoader): # determine if our "lazywhere" clause is the same as the mapper's # get() clause. then we can just use mapper.get() - #from sqlalchemy.orm import query self.use_get = not self.uselist and \ self.mapper._get_clause[0].compare( self._lazywhere, -- cgit v1.2.1 From f6198d9abf453182f4b111e0579a7a4ef1614e79 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 12 Aug 2013 17:50:37 -0400 Subject: - A large refactoring of the ``sqlalchemy.sql`` package has reorganized the import structure of many core modules. ``sqlalchemy.schema`` and ``sqlalchemy.types`` remain in the top-level package, but are now just lists of names that pull from within ``sqlalchemy.sql``. Their implementations are now broken out among ``sqlalchemy.sql.type_api``, ``sqlalchemy.sql.sqltypes``, ``sqlalchemy.sql.schema`` and ``sqlalchemy.sql.ddl``, the last of which was moved from ``sqlalchemy.engine``. ``sqlalchemy.sql.expression`` is also a namespace now which pulls implementations mostly from ``sqlalchemy.sql.elements``, ``sqlalchemy.sql.selectable``, and ``sqlalchemy.sql.dml``. Most of the "factory" functions used to create SQL expression objects have been moved to classmethods or constructors, which are exposed in ``sqlalchemy.sql.expression`` using a programmatic system. Care has been taken such that all the original import namespaces remain intact and there should be no impact on any existing applications. The rationale here was to break out these very large modules into smaller ones, provide more manageable lists of function names, to greatly reduce "import cycles" and clarify the up-front importing of names, and to remove the need for redundant functions and documentation throughout the expression package. --- lib/sqlalchemy/orm/strategies.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) (limited to 'lib/sqlalchemy/orm/strategies.py') diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index fac8a3c6f..39ddaa7b8 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -111,6 +111,7 @@ class UninstrumentedColumnLoader(LoaderStrategy): return None, None, None +@log.class_logger class ColumnLoader(LoaderStrategy): """Provide loading behavior for a :class:`.ColumnProperty`.""" @@ -156,9 +157,8 @@ class ColumnLoader(LoaderStrategy): return expire_for_non_present_col, None, None -log.class_logger(ColumnLoader) - +@log.class_logger class DeferredColumnLoader(LoaderStrategy): """Provide loading behavior for a deferred :class:`.ColumnProperty`.""" @@ -251,8 +251,6 @@ class DeferredColumnLoader(LoaderStrategy): return attributes.ATTR_WAS_SET -log.class_logger(DeferredColumnLoader) - class LoadDeferredColumns(object): """serializable loader object used by DeferredColumnLoader""" @@ -304,6 +302,7 @@ class AbstractRelationshipLoader(LoaderStrategy): +@log.class_logger class NoLoader(AbstractRelationshipLoader): """Provide loading behavior for a :class:`.RelationshipProperty` with "lazy=None". @@ -325,9 +324,8 @@ class NoLoader(AbstractRelationshipLoader): return invoke_no_load, None, None -log.class_logger(NoLoader) - +@log.class_logger class LazyLoader(AbstractRelationshipLoader): """Provide loading behavior for a :class:`.RelationshipProperty` with "lazy=True", that is loads when first accessed. @@ -629,8 +627,6 @@ class LazyLoader(AbstractRelationshipLoader): return reset_for_lazy_callable, None, None -log.class_logger(LazyLoader) - class LoadLazyAttribute(object): """serializable loader object used by LazyLoader""" @@ -666,6 +662,7 @@ class ImmediateLoader(AbstractRelationshipLoader): return None, None, load_immediate +@log.class_logger class SubqueryLoader(AbstractRelationshipLoader): def __init__(self, parent): super(SubqueryLoader, self).__init__(parent) @@ -983,9 +980,8 @@ class SubqueryLoader(AbstractRelationshipLoader): return load_scalar_from_subq, None, None -log.class_logger(SubqueryLoader) - +@log.class_logger class JoinedLoader(AbstractRelationshipLoader): """Provide loading behavior for a :class:`.RelationshipProperty` using joined eager loading. @@ -1201,7 +1197,7 @@ class JoinedLoader(AbstractRelationshipLoader): # by the Query propagates those columns outward. # This has the effect # of "undefering" those columns. - for col in sql_util.find_columns( + for col in sql_util._find_columns( self.parent_property.primaryjoin): if localparent.mapped_table.c.contains_column(col): if adapter: @@ -1335,9 +1331,6 @@ class JoinedLoader(AbstractRelationshipLoader): None, load_scalar_from_joined_exec -log.class_logger(JoinedLoader) - - class EagerLazyOption(StrategizedOption): def __init__(self, key, lazy=True, chained=False, propagate_to_loaders=True -- cgit v1.2.1 From 59141d360e70d1a762719206e3cb0220b4c53fef Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 14 Aug 2013 19:58:34 -0400 Subject: - apply an import refactoring to the ORM as well - rework the event system so that event modules load after their targets, dependencies are reversed - create an improved strategy lookup system for the ORM - rework the ORM to have very few import cycles - move out "importlater" to just util.dependency - other tricks to cross-populate modules in as clear a way as possible --- lib/sqlalchemy/orm/strategies.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'lib/sqlalchemy/orm/strategies.py') diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 39ddaa7b8..fb63de807 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -16,6 +16,7 @@ from . import ( ) from .state import InstanceState from .util import _none_set +from . import properties from .interfaces import ( LoaderStrategy, StrategizedOption, MapperOption, PropertyOption, StrategizedProperty @@ -23,7 +24,6 @@ from .interfaces import ( from .session import _state_session import itertools - def _register_attribute(strategy, mapper, useobject, compare_function=None, typecallable=None, @@ -88,7 +88,7 @@ def _register_attribute(strategy, mapper, useobject, for hook in listen_hooks: hook(desc, prop) - +@properties.ColumnProperty._strategy_for(dict(instrument=False, deferred=False)) class UninstrumentedColumnLoader(LoaderStrategy): """Represent the a non-instrumented MapperProperty. @@ -112,6 +112,7 @@ class UninstrumentedColumnLoader(LoaderStrategy): @log.class_logger +@properties.ColumnProperty._strategy_for(dict(instrument=True, deferred=False)) class ColumnLoader(LoaderStrategy): """Provide loading behavior for a :class:`.ColumnProperty`.""" @@ -159,6 +160,7 @@ class ColumnLoader(LoaderStrategy): @log.class_logger +@properties.ColumnProperty._strategy_for(dict(deferred=True, instrument=True)) class DeferredColumnLoader(LoaderStrategy): """Provide loading behavior for a deferred :class:`.ColumnProperty`.""" @@ -303,6 +305,7 @@ class AbstractRelationshipLoader(LoaderStrategy): @log.class_logger +@properties.RelationshipProperty._strategy_for(dict(lazy=None), dict(lazy="noload")) class NoLoader(AbstractRelationshipLoader): """Provide loading behavior for a :class:`.RelationshipProperty` with "lazy=None". @@ -326,6 +329,7 @@ class NoLoader(AbstractRelationshipLoader): @log.class_logger +@properties.RelationshipProperty._strategy_for(dict(lazy=True), dict(lazy="select")) class LazyLoader(AbstractRelationshipLoader): """Provide loading behavior for a :class:`.RelationshipProperty` with "lazy=True", that is loads when first accessed. @@ -643,6 +647,7 @@ class LoadLazyAttribute(object): return strategy._load_for_state(state, passive) +@properties.RelationshipProperty._strategy_for(dict(lazy="immediate")) class ImmediateLoader(AbstractRelationshipLoader): def init_class_attribute(self, mapper): self.parent_property.\ @@ -663,6 +668,7 @@ class ImmediateLoader(AbstractRelationshipLoader): @log.class_logger +@properties.RelationshipProperty._strategy_for(dict(lazy="subquery")) class SubqueryLoader(AbstractRelationshipLoader): def __init__(self, parent): super(SubqueryLoader, self).__init__(parent) @@ -982,6 +988,7 @@ class SubqueryLoader(AbstractRelationshipLoader): @log.class_logger +@properties.RelationshipProperty._strategy_for(dict(lazy=False), dict(lazy="joined")) class JoinedLoader(AbstractRelationshipLoader): """Provide loading behavior for a :class:`.RelationshipProperty` using joined eager loading. @@ -1346,6 +1353,7 @@ class EagerLazyOption(StrategizedOption): self.lazy = lazy self.chained = chained self.propagate_to_loaders = propagate_to_loaders + #self.strategy_cls = properties.RelationshipProperty._strategy_lookup(lazy=lazy) self.strategy_cls = factory(lazy) def get_strategy_class(self): -- cgit v1.2.1 From 2038837fa4c03c6da4b6d4c389fd062abc4e844c Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sat, 17 Aug 2013 12:14:58 -0400 Subject: - spot checking of imports, obsolete functions --- lib/sqlalchemy/orm/strategies.py | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) (limited to 'lib/sqlalchemy/orm/strategies.py') diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index fb63de807..63b7d7c0b 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -941,7 +941,7 @@ class SubqueryLoader(AbstractRelationshipLoader): collections = path.get(context.attributes, "collections") if collections is None: collections = dict( - (k, [v[0] for v in v]) + (k, [vv[0] for vv in v]) for k, v in itertools.groupby( subq, lambda x: x[1:] @@ -1353,27 +1353,11 @@ class EagerLazyOption(StrategizedOption): self.lazy = lazy self.chained = chained self.propagate_to_loaders = propagate_to_loaders - #self.strategy_cls = properties.RelationshipProperty._strategy_lookup(lazy=lazy) - self.strategy_cls = factory(lazy) + self.strategy_cls = properties.RelationshipProperty._strategy_lookup(lazy=lazy) def get_strategy_class(self): return self.strategy_cls -_factory = { - False: JoinedLoader, - "joined": JoinedLoader, - None: NoLoader, - "noload": NoLoader, - "select": LazyLoader, - True: LazyLoader, - "subquery": SubqueryLoader, - "immediate": ImmediateLoader -} - - -def factory(identifier): - return _factory.get(identifier, LazyLoader) - class EagerJoinOption(PropertyOption): -- cgit v1.2.1 From 5a6895471fb6bf9afe9bdf017f1fa2c6246ae303 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 6 Sep 2013 21:39:36 -0400 Subject: - modify what we did in [ticket:2793] so that we can also set the version id programmatically outside of the generator. using this system, we can also leave the version id alone. --- lib/sqlalchemy/orm/strategies.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/sqlalchemy/orm/strategies.py') diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 63b7d7c0b..761e6b999 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -133,7 +133,8 @@ class ColumnLoader(LoaderStrategy): coltype = self.columns[0].type # TODO: check all columns ? check for foreign key as well? active_history = self.parent_property.active_history or \ - self.columns[0].primary_key + self.columns[0].primary_key or \ + mapper.version_id_col in set(self.columns) _register_attribute(self, mapper, useobject=False, compare_function=coltype.compare_values, -- cgit v1.2.1 From 1b25ed907fb7311d28d2273c9b9858b50c1a7afc Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 6 Oct 2013 20:29:08 -0400 Subject: - merge ticket_1418 branch, [ticket:1418] - The system of loader options has been entirely rearchitected to build upon a much more comprehensive base, the :class:`.Load` object. This base allows any common loader option like :func:`.joinedload`, :func:`.defer`, etc. to be used in a "chained" style for the purpose of specifying options down a path, such as ``joinedload("foo").subqueryload("bar")``. The new system supersedes the usage of dot-separated path names, multiple attributes within options, and the usage of ``_all()`` options. - Added a new load option :func:`.orm.load_only`. This allows a series of column names to be specified as loading "only" those attributes, deferring the rest. --- lib/sqlalchemy/orm/strategies.py | 287 +++++++++++++++++---------------------- 1 file changed, 121 insertions(+), 166 deletions(-) (limited to 'lib/sqlalchemy/orm/strategies.py') diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 761e6b999..6ca737c64 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -18,8 +18,7 @@ from .state import InstanceState from .util import _none_set from . import properties from .interfaces import ( - LoaderStrategy, StrategizedOption, MapperOption, PropertyOption, - StrategizedProperty + LoaderStrategy, StrategizedProperty ) from .session import _state_session import itertools @@ -88,7 +87,7 @@ def _register_attribute(strategy, mapper, useobject, for hook in listen_hooks: hook(desc, prop) -@properties.ColumnProperty._strategy_for(dict(instrument=False, deferred=False)) +@properties.ColumnProperty.strategy_for(instrument=False, deferred=False) class UninstrumentedColumnLoader(LoaderStrategy): """Represent the a non-instrumented MapperProperty. @@ -100,19 +99,19 @@ 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, loadopt, 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, context, path, mapper, row, adapter): + def create_row_processor(self, context, path, loadopt, mapper, row, adapter): return None, None, None @log.class_logger -@properties.ColumnProperty._strategy_for(dict(instrument=True, deferred=False)) +@properties.ColumnProperty.strategy_for(instrument=True, deferred=False) class ColumnLoader(LoaderStrategy): """Provide loading behavior for a :class:`.ColumnProperty`.""" @@ -121,7 +120,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, loadopt, adapter, column_collection, **kwargs): for c in self.columns: if adapter: @@ -142,7 +141,7 @@ class ColumnLoader(LoaderStrategy): ) def create_row_processor(self, context, path, - mapper, row, adapter): + loadopt, mapper, row, adapter): key = self.key # look through list of columns represented here # to see which, if any, is present in the row. @@ -161,7 +160,7 @@ class ColumnLoader(LoaderStrategy): @log.class_logger -@properties.ColumnProperty._strategy_for(dict(deferred=True, instrument=True)) +@properties.ColumnProperty.strategy_for(deferred=True, instrument=True) class DeferredColumnLoader(LoaderStrategy): """Provide loading behavior for a deferred :class:`.ColumnProperty`.""" @@ -173,16 +172,16 @@ class DeferredColumnLoader(LoaderStrategy): self.columns = self.parent_property.columns self.group = self.parent_property.group - def create_row_processor(self, context, path, mapper, row, adapter): + def create_row_processor(self, context, path, loadopt, mapper, row, adapter): col = self.columns[0] if adapter: col = adapter.columns[col] key = self.key if col in row: - return self.parent_property._get_strategy(ColumnLoader).\ + return self.parent_property._get_strategy_by_cls(ColumnLoader).\ create_row_processor( - context, path, mapper, row, adapter) + context, path, loadopt, mapper, row, adapter) elif not self.is_class_level: set_deferred_for_local_state = InstanceState._row_processor( @@ -205,15 +204,15 @@ class DeferredColumnLoader(LoaderStrategy): expire_missing=False ) - def setup_query(self, context, entity, path, adapter, + def setup_query(self, context, entity, path, loadopt, adapter, only_load_props=None, **kwargs): if ( - self.group is not None and - context.attributes.get(('undefer', self.group), False) + loadopt and self.group and + loadopt.local_opts.get('undefer_group', False) == self.group ) or (only_load_props and self.key in only_load_props): - self.parent_property._get_strategy(ColumnLoader).\ + self.parent_property._get_strategy_by_cls(ColumnLoader).\ setup_query(context, entity, - path, adapter, **kwargs) + path, loadopt, adapter, **kwargs) def _load_for_state(self, state, passive): if not state.key: @@ -270,29 +269,6 @@ class LoadDeferredColumns(object): return strategy._load_for_state(state, passive) -class DeferredOption(StrategizedOption): - propagate_to_loaders = True - - def __init__(self, key, defer=False): - super(DeferredOption, self).__init__(key) - self.defer = defer - - def get_strategy_class(self): - if self.defer: - return DeferredColumnLoader - else: - return ColumnLoader - - -class UndeferGroupOption(MapperOption): - propagate_to_loaders = True - - def __init__(self, group): - self.group = group - - def process_query(self, query): - query._attributes[("undefer", self.group)] = True - class AbstractRelationshipLoader(LoaderStrategy): """LoaderStratgies which deal with related objects.""" @@ -306,7 +282,8 @@ class AbstractRelationshipLoader(LoaderStrategy): @log.class_logger -@properties.RelationshipProperty._strategy_for(dict(lazy=None), dict(lazy="noload")) +@properties.RelationshipProperty.strategy_for(lazy="noload") +@properties.RelationshipProperty.strategy_for(lazy=None) class NoLoader(AbstractRelationshipLoader): """Provide loading behavior for a :class:`.RelationshipProperty` with "lazy=None". @@ -322,7 +299,7 @@ class NoLoader(AbstractRelationshipLoader): typecallable=self.parent_property.collection_class, ) - def create_row_processor(self, context, path, mapper, row, adapter): + def create_row_processor(self, context, path, loadopt, mapper, row, adapter): def invoke_no_load(state, dict_, row): state._initialize(self.key) return invoke_no_load, None, None @@ -330,7 +307,8 @@ class NoLoader(AbstractRelationshipLoader): @log.class_logger -@properties.RelationshipProperty._strategy_for(dict(lazy=True), dict(lazy="select")) +@properties.RelationshipProperty.strategy_for(lazy=True) +@properties.RelationshipProperty.strategy_for(lazy="select") class LazyLoader(AbstractRelationshipLoader): """Provide loading behavior for a :class:`.RelationshipProperty` with "lazy=True", that is loads when first accessed. @@ -544,7 +522,8 @@ class LazyLoader(AbstractRelationshipLoader): for pk in self.mapper.primary_key ] - def _emit_lazyload(self, session, state, ident_key, passive): + @util.dependencies("sqlalchemy.orm.strategy_options") + def _emit_lazyload(self, strategy_options, session, state, ident_key, passive): q = session.query(self.mapper)._adapt_all_clauses() q = q._with_invoke_all_eagers(False) @@ -573,7 +552,7 @@ class LazyLoader(AbstractRelationshipLoader): if rev.direction is interfaces.MANYTOONE and \ rev._use_get and \ not isinstance(rev.strategy, LazyLoader): - q = q.options(EagerLazyOption((rev.key,), lazy='select')) + q = q.options(strategy_options.Load(rev.parent).lazyload(rev.key)) lazy_clause = self.lazy_clause(state, passive=passive) @@ -600,7 +579,7 @@ class LazyLoader(AbstractRelationshipLoader): else: return None - def create_row_processor(self, context, path, + def create_row_processor(self, context, path, loadopt, mapper, row, adapter): key = self.key if not self.is_class_level: @@ -648,19 +627,19 @@ class LoadLazyAttribute(object): return strategy._load_for_state(state, passive) -@properties.RelationshipProperty._strategy_for(dict(lazy="immediate")) +@properties.RelationshipProperty.strategy_for(lazy="immediate") class ImmediateLoader(AbstractRelationshipLoader): def init_class_attribute(self, mapper): self.parent_property.\ - _get_strategy(LazyLoader).\ + _get_strategy_by_cls(LazyLoader).\ init_class_attribute(mapper) def setup_query(self, context, entity, - path, adapter, column_collection=None, + path, loadopt, adapter, column_collection=None, parentmapper=None, **kwargs): pass - def create_row_processor(self, context, path, + def create_row_processor(self, context, path, loadopt, mapper, row, adapter): def load_immediate(state, dict_, row): state.get_impl(self.key).get(state, dict_) @@ -669,7 +648,7 @@ class ImmediateLoader(AbstractRelationshipLoader): @log.class_logger -@properties.RelationshipProperty._strategy_for(dict(lazy="subquery")) +@properties.RelationshipProperty.strategy_for(lazy="subquery") class SubqueryLoader(AbstractRelationshipLoader): def __init__(self, parent): super(SubqueryLoader, self).__init__(parent) @@ -677,11 +656,11 @@ class SubqueryLoader(AbstractRelationshipLoader): def init_class_attribute(self, mapper): self.parent_property.\ - _get_strategy(LazyLoader).\ + _get_strategy_by_cls(LazyLoader).\ init_class_attribute(mapper) def setup_query(self, context, entity, - path, adapter, + path, loadopt, adapter, column_collection=None, parentmapper=None, **kwargs): @@ -706,7 +685,7 @@ class SubqueryLoader(AbstractRelationshipLoader): # if not via query option, check for # a cycle - if not path.contains(context.attributes, "loaderstrategy"): + if not path.contains(context.attributes, "loader"): if self.join_depth: if path.length / 2 > self.join_depth: return @@ -919,7 +898,7 @@ class SubqueryLoader(AbstractRelationshipLoader): q = q.order_by(*eager_order_by) return q - def create_row_processor(self, context, path, + def create_row_processor(self, context, path, loadopt, mapper, row, adapter): if not self.parent.class_manager[self.key].impl.supports_population: raise sa_exc.InvalidRequestError( @@ -989,7 +968,8 @@ class SubqueryLoader(AbstractRelationshipLoader): @log.class_logger -@properties.RelationshipProperty._strategy_for(dict(lazy=False), dict(lazy="joined")) +@properties.RelationshipProperty.strategy_for(lazy="joined") +@properties.RelationshipProperty.strategy_for(lazy=False) class JoinedLoader(AbstractRelationshipLoader): """Provide loading behavior for a :class:`.RelationshipProperty` using joined eager loading. @@ -1001,9 +981,9 @@ class JoinedLoader(AbstractRelationshipLoader): def init_class_attribute(self, mapper): self.parent_property.\ - _get_strategy(LazyLoader).init_class_attribute(mapper) + _get_strategy_by_cls(LazyLoader).init_class_attribute(mapper) - def setup_query(self, context, entity, path, adapter, \ + def setup_query(self, context, entity, path, loadopt, adapter, \ column_collection=None, parentmapper=None, allow_innerjoin=True, **kwargs): @@ -1016,19 +996,19 @@ class JoinedLoader(AbstractRelationshipLoader): with_polymorphic = None - user_defined_adapter = path.get(context.attributes, - "user_defined_eager_row_processor", - False) + user_defined_adapter = self._init_user_defined_eager_proc( + loadopt, context) if loadopt else False + if user_defined_adapter is not False: clauses, adapter, add_to_collection = \ - self._get_user_defined_adapter( + self._setup_query_on_user_defined_adapter( context, entity, path, adapter, user_defined_adapter ) else: # if not via query option, check for # a cycle - if not path.contains(context.attributes, "loaderstrategy"): + if not path.contains(context.attributes, "loader"): if self.join_depth: if path.length / 2 > self.join_depth: return @@ -1037,7 +1017,7 @@ class JoinedLoader(AbstractRelationshipLoader): clauses, adapter, add_to_collection, \ allow_innerjoin = self._generate_row_adapter( - context, entity, path, adapter, + context, entity, path, loadopt, adapter, column_collection, parentmapper, allow_innerjoin ) @@ -1072,24 +1052,74 @@ class JoinedLoader(AbstractRelationshipLoader): "when using joined loading with with_polymorphic()." ) - def _get_user_defined_adapter(self, context, entity, + def _init_user_defined_eager_proc(self, loadopt, context): + + # check if the opt applies at all + if "eager_from_alias" not in loadopt.local_opts: + # nope + return False + + path = loadopt.path.parent + + # the option applies. check if the "user_defined_eager_row_processor" + # has been built up. + adapter = path.get(context.attributes, + "user_defined_eager_row_processor", False) + if adapter is not False: + # just return it + return adapter + + # otherwise figure it out. + alias = loadopt.local_opts["eager_from_alias"] + + root_mapper, prop = path[-2:] + + #from .mapper import Mapper + #from .interfaces import MapperProperty + #assert isinstance(root_mapper, Mapper) + #assert isinstance(prop, MapperProperty) + + if alias is not None: + if isinstance(alias, str): + alias = prop.target.alias(alias) + adapter = sql_util.ColumnAdapter(alias, + equivalents=prop.mapper._equivalent_columns) + else: + if path.contains(context.attributes, "path_with_polymorphic"): + with_poly_info = path.get(context.attributes, + "path_with_polymorphic") + adapter = orm_util.ORMAdapter( + with_poly_info.entity, + equivalents=prop.mapper._equivalent_columns) + else: + adapter = context.query._polymorphic_adapters.get(prop.mapper, None) + path.set(context.attributes, + "user_defined_eager_row_processor", + adapter) + + return adapter + + def _setup_query_on_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.attributes, "user_defined_eager_row_processor", - user_defined_adapter) - elif adapter: - user_defined_adapter = adapter - path.set(context.attributes, "user_defined_eager_row_processor", - user_defined_adapter) + # apply some more wrapping to the "user defined adapter" + # if we are setting up the query for SQL render. + 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.attributes, "user_defined_eager_row_processor", + user_defined_adapter) + elif adapter: + user_defined_adapter = adapter + path.set(context.attributes, "user_defined_eager_row_processor", + user_defined_adapter) - add_to_collection = context.primary_columns - return user_defined_adapter, adapter, add_to_collection + add_to_collection = context.primary_columns + return user_defined_adapter, adapter, add_to_collection def _generate_row_adapter(self, - context, entity, path, adapter, + context, entity, path, loadopt, adapter, column_collection, parentmapper, allow_innerjoin ): with_poly_info = path.get( @@ -1112,9 +1142,12 @@ class JoinedLoader(AbstractRelationshipLoader): if self.parent_property.direction != interfaces.MANYTOONE: context.multi_row_eager_loaders = True - innerjoin = allow_innerjoin and path.get(context.attributes, - "eager_join_type", - self.parent_property.innerjoin) + innerjoin = allow_innerjoin and ( + loadopt.local_opts.get( + 'innerjoin', self.parent_property.innerjoin) + if loadopt is not None + else self.parent_property.innerjoin + ) if not innerjoin: # if this is an outer join, all eager joins from # here must also be outer joins @@ -1221,10 +1254,10 @@ class JoinedLoader(AbstractRelationshipLoader): ) ) - def _create_eager_adapter(self, context, row, adapter, path): - user_defined_adapter = path.get(context.attributes, - "user_defined_eager_row_processor", - False) + def _create_eager_adapter(self, context, row, adapter, path, loadopt): + user_defined_adapter = self._init_user_defined_eager_proc( + loadopt, context) if loadopt else False + if user_defined_adapter is not False: decorator = user_defined_adapter # user defined eagerloads are part of the "primary" @@ -1247,7 +1280,7 @@ class JoinedLoader(AbstractRelationshipLoader): # processor, will cause a degrade to lazy return False - def create_row_processor(self, context, path, mapper, row, adapter): + def create_row_processor(self, context, path, loadopt, mapper, row, adapter): if not self.parent.class_manager[self.key].impl.supports_population: raise sa_exc.InvalidRequestError( "'%s' does not support object " @@ -1259,7 +1292,7 @@ class JoinedLoader(AbstractRelationshipLoader): eager_adapter = self._create_eager_adapter( context, row, - adapter, our_path) + adapter, our_path, loadopt) if eager_adapter is not False: key = self.key @@ -1276,9 +1309,9 @@ class JoinedLoader(AbstractRelationshipLoader): return self._create_collection_loader(context, key, _instance) else: return self.parent_property.\ - _get_strategy(LazyLoader).\ + _get_strategy_by_cls(LazyLoader).\ create_row_processor( - context, path, + context, path, loadopt, mapper, row, adapter) def _create_collection_loader(self, context, key, _instance): @@ -1339,84 +1372,6 @@ class JoinedLoader(AbstractRelationshipLoader): None, load_scalar_from_joined_exec -class EagerLazyOption(StrategizedOption): - def __init__(self, key, lazy=True, chained=False, - propagate_to_loaders=True - ): - if isinstance(key[0], str) and key[0] == '*': - if len(key) != 1: - raise sa_exc.ArgumentError( - "Wildcard identifier '*' must " - "be specified alone.") - key = ("relationship:*",) - propagate_to_loaders = False - super(EagerLazyOption, self).__init__(key) - self.lazy = lazy - self.chained = chained - self.propagate_to_loaders = propagate_to_loaders - self.strategy_cls = properties.RelationshipProperty._strategy_lookup(lazy=lazy) - - def get_strategy_class(self): - return self.strategy_cls - - -class EagerJoinOption(PropertyOption): - - def __init__(self, key, innerjoin, chained=False): - super(EagerJoinOption, self).__init__(key) - self.innerjoin = innerjoin - self.chained = chained - - def process_query_property(self, query, paths): - if self.chained: - for path in paths: - path.set(query._attributes, "eager_join_type", self.innerjoin) - else: - paths[-1].set(query._attributes, "eager_join_type", self.innerjoin) - - -class LoadEagerFromAliasOption(PropertyOption): - - def __init__(self, key, alias=None, chained=False): - super(LoadEagerFromAliasOption, self).__init__(key) - if alias is not None: - if not isinstance(alias, str): - info = inspect(alias) - alias = info.selectable - self.alias = alias - self.chained = chained - - def process_query_property(self, query, paths): - if self.chained: - for path in paths[0:-1]: - (root_mapper, prop) = path.path[-2:] - adapter = query._polymorphic_adapters.get(prop.mapper, None) - path.setdefault(query._attributes, - "user_defined_eager_row_processor", - adapter) - - root_mapper, prop = paths[-1].path[-2:] - if self.alias is not None: - if isinstance(self.alias, str): - self.alias = prop.target.alias(self.alias) - paths[-1].set(query._attributes, - "user_defined_eager_row_processor", - sql_util.ColumnAdapter(self.alias, - equivalents=prop.mapper._equivalent_columns) - ) - else: - if paths[-1].contains(query._attributes, "path_with_polymorphic"): - with_poly_info = paths[-1].get(query._attributes, - "path_with_polymorphic") - adapter = orm_util.ORMAdapter( - with_poly_info.entity, - equivalents=prop.mapper._equivalent_columns) - else: - adapter = query._polymorphic_adapters.get(prop.mapper, None) - paths[-1].set(query._attributes, - "user_defined_eager_row_processor", - adapter) - def single_parent_validator(desc, prop): def _do_check(state, value, oldvalue, initiator): -- cgit v1.2.1 From f00544a589d5002ddf0146706c4ba67509452ea7 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 13 Oct 2013 16:42:32 -0400 Subject: - Added new option to :func:`.relationship` ``distinct_target_key``. This enables the subquery eager loader strategy to apply a DISTINCT to the innermost SELECT subquery, to assist in the case where duplicate rows are generated by the innermost query which corresponds to this relationship (there's not yet a general solution to the issue of dupe rows within subquery eager loading, however, when joins outside of the innermost subquery produce dupes). When the flag is set to ``True``, the DISTINCT is rendered unconditionally, and when it is set to ``None``, DISTINCT is rendered if the innermost relationship targets columns that do not comprise a full primary key. The option defaults to False in 0.8 (e.g. off by default in all cases), None in 0.9 (e.g. automatic by default). Thanks to Alexander Koval for help with this. [ticket:2836] --- lib/sqlalchemy/orm/strategies.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) (limited to 'lib/sqlalchemy/orm/strategies.py') diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 6ca737c64..009bf74a4 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -692,7 +692,7 @@ class SubqueryLoader(AbstractRelationshipLoader): elif subq_path.contains_mapper(self.mapper): return - subq_mapper, leftmost_mapper, leftmost_attr = \ + subq_mapper, leftmost_mapper, leftmost_attr, leftmost_relationship = \ self._get_leftmost(subq_path) orig_query = context.attributes.get( @@ -703,7 +703,8 @@ class SubqueryLoader(AbstractRelationshipLoader): # produce a subquery from it. left_alias = self._generate_from_original_query( orig_query, leftmost_mapper, - leftmost_attr, entity.mapper + leftmost_attr, leftmost_relationship, + entity.mapper ) # generate another Query that will join the @@ -752,11 +753,12 @@ class SubqueryLoader(AbstractRelationshipLoader): leftmost_mapper._columntoproperty[c].class_attribute for c in leftmost_cols ] - return subq_mapper, leftmost_mapper, leftmost_attr + return subq_mapper, leftmost_mapper, leftmost_attr, leftmost_prop def _generate_from_original_query(self, orig_query, leftmost_mapper, - leftmost_attr, entity_mapper + leftmost_attr, leftmost_relationship, + entity_mapper ): # reformat the original query # to look only for significant columns @@ -767,8 +769,22 @@ class SubqueryLoader(AbstractRelationshipLoader): if not q._from_obj and entity_mapper.isa(leftmost_mapper): q._set_select_from([entity_mapper], False) + target_cols = q._adapt_col_list(leftmost_attr) + # select from the identity columns of the outer - q._set_entities(q._adapt_col_list(leftmost_attr)) + q._set_entities(target_cols) + + distinct_target_key = leftmost_relationship.distinct_target_key + + if distinct_target_key is True: + q._distinct = True + elif distinct_target_key is None: + # if target_cols refer to a non-primary key or only + # part of a composite primary key, set the q as distinct + for t in set(c.table for c in target_cols): + if not set(target_cols).issuperset(t.primary_key): + q._distinct = True + break if q._order_by is False: q._order_by = leftmost_mapper.order_by -- cgit v1.2.1 From 63508b82cd5710c660383bcac5fcfd3bb6af83c1 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Tue, 19 Nov 2013 19:16:26 -0500 Subject: - The ``viewonly`` flag on :func:`.relationship` will now prevent attribute history from being written on behalf of the target attribute. This has the effect of the object not being written to the Session.dirty list if it is mutated. Previously, the object would be present in Session.dirty, but no change would take place on behalf of the modified attribute during flush. The attribute still emits events such as backref events and user-defined events and will still receive mutations from backrefs. [ticket:2833] --- lib/sqlalchemy/orm/strategies.py | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/sqlalchemy/orm/strategies.py') diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 009bf74a4..b04338d9c 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -80,6 +80,7 @@ def _register_attribute(strategy, mapper, useobject, callable_=callable_, active_history=active_history, impl_class=impl_class, + send_modified_events=not useobject or not prop.viewonly, doc=prop.doc, **kw ) -- cgit v1.2.1 From 50e3847f09580d1e322fb11f54983e9a31846f19 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 2 Dec 2013 12:40:50 -0500 Subject: - Added new argument ``include_backrefs=True`` to the :func:`.validates` function; when set to False, a validation event will not be triggered if the event was initated as a backref to an attribute operation from the other side. [ticket:1535] - break out validation tests into an updated module test_validators --- lib/sqlalchemy/orm/strategies.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/sqlalchemy/orm/strategies.py') diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index b04338d9c..8226a0e0f 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -44,10 +44,10 @@ def _register_attribute(strategy, mapper, useobject, listen_hooks.append(single_parent_validator) if prop.key in prop.parent.validators: - fn, include_removes = prop.parent.validators[prop.key] + fn, opts = prop.parent.validators[prop.key] listen_hooks.append( lambda desc, prop: orm_util._validator_events(desc, - prop.key, fn, include_removes) + prop.key, fn, **opts) ) if useobject: -- cgit v1.2.1 From 84f1d3417978197c695850b3711ea4b7e2582be8 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 16 Dec 2013 19:17:41 -0500 Subject: - An adjustment to the :func:`.subqueryload` strategy which ensures that the query runs after the loading process has begun; this is so that the subqueryload takes precedence over other loaders that may be hitting the same attribute due to other eager/noload situations at the wrong time. [ticket:2887] --- lib/sqlalchemy/orm/strategies.py | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) (limited to 'lib/sqlalchemy/orm/strategies.py') diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 8226a0e0f..71bbf2bce 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -915,6 +915,35 @@ class SubqueryLoader(AbstractRelationshipLoader): q = q.order_by(*eager_order_by) return q + class _SubqCollections(object): + """Given a :class:`.Query` used to emit the "subquery load", + provide a load interface that executes the query at the + first moment a value is needed. + + """ + _data = None + + def __init__(self, subq): + self.subq = subq + + def get(self, key, default): + if self._data is None: + self._load() + return self._data.get(key, default) + + def _load(self): + self._data = dict( + (k, [vv[0] for vv in v]) + for k, v in itertools.groupby( + self.subq, + lambda x: x[1:] + ) + ) + + def loader(self, state, dict_, row): + if self._data is None: + self._load() + def create_row_processor(self, context, path, loadopt, mapper, row, adapter): if not self.parent.class_manager[self.key].impl.supports_population: @@ -937,12 +966,7 @@ class SubqueryLoader(AbstractRelationshipLoader): # call upon create_row_processor again collections = path.get(context.attributes, "collections") if collections is None: - collections = dict( - (k, [vv[0] for vv in v]) - for k, v in itertools.groupby( - subq, - lambda x: x[1:] - )) + collections = self._SubqCollections(subq) path.set(context.attributes, 'collections', collections) if adapter: @@ -962,7 +986,7 @@ class SubqueryLoader(AbstractRelationshipLoader): state.get_impl(self.key).\ set_committed_value(state, dict_, collection) - return load_collection_from_subq, None, None + return load_collection_from_subq, None, None, collections.loader def _create_scalar_loader(self, collections, local_cols): def load_scalar_from_subq(state, dict_, row): @@ -980,7 +1004,7 @@ class SubqueryLoader(AbstractRelationshipLoader): state.get_impl(self.key).\ set_committed_value(state, dict_, scalar) - return load_scalar_from_subq, None, None + return load_scalar_from_subq, None, None, collections.loader -- cgit v1.2.1 From f89d4d216bd7605c920b7b8a10ecde6bfea2238c Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 5 Jan 2014 16:57:05 -0500 Subject: - happy new year --- lib/sqlalchemy/orm/strategies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/sqlalchemy/orm/strategies.py') diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 71bbf2bce..033e3d064 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -1,5 +1,5 @@ # orm/strategies.py -# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors +# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors # # This module is part of SQLAlchemy and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php -- cgit v1.2.1