From 550141b14c8e165218cd32c27d91541eeee86d2a Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 26 Jul 2013 00:01:04 -0400 Subject: - The mechanism by which attribute events pass along an :class:`.AttributeImpl` as an "initiator" token has been changed; the object is now an event-specific object called :class:`.attributes.Event`. Additionally, the attribute system no longer halts events based on a matching "initiator" token; this logic has been moved to be specific to ORM backref event handlers, which are the typical source of the re-propagation of an attribute event onto subsequent append/set/remove operations. End user code which emulates the behavior of backrefs must now ensure that recursive event propagation schemes are halted, if the scheme does not use the backref handlers. Using this new system, backref handlers can now peform a "two-hop" operation when an object is appended to a collection, associated with a new many-to-one, de-associated with the previous many-to-one, and then removed from a previous collection. Before this change, the last step of removal from the previous collection would not occur. [ticket:2789] --- lib/sqlalchemy/orm/dynamic.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'lib/sqlalchemy/orm/dynamic.py') diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index 5814b47ca..fb46713d0 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -78,6 +78,9 @@ class DynamicAttributeImpl(attributes.AttributeImpl): history = self._get_collection_history(state, passive) return history.added_plus_unchanged + _append_token = attributes.Event._token_gen(attributes.OP_APPEND) + _remove_token = attributes.Event._token_gen(attributes.OP_REMOVE) + def fire_append_event(self, state, dict_, value, initiator, collection_history=None): if collection_history is None: @@ -86,7 +89,7 @@ class DynamicAttributeImpl(attributes.AttributeImpl): collection_history.add_added(value) for fn in self.dispatch.append: - value = fn(state, value, initiator or self) + value = fn(state, value, initiator or self._append_token) if self.trackparent and value is not None: self.sethasparent(attributes.instance_state(value), state, True) @@ -102,7 +105,7 @@ class DynamicAttributeImpl(attributes.AttributeImpl): self.sethasparent(attributes.instance_state(value), state, False) for fn in self.dispatch.remove: - fn(state, value, initiator or self) + fn(state, value, initiator or self._remove_token) def _modified_event(self, state, dict_): -- 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/dynamic.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'lib/sqlalchemy/orm/dynamic.py') diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index fb46713d0..4ad204357 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -19,7 +19,7 @@ from . import ( ) from .query import Query - +@log.class_logger class DynaLoader(strategies.AbstractRelationshipLoader): def init_class_attribute(self, mapper): self.is_class_level = True @@ -39,9 +39,6 @@ class DynaLoader(strategies.AbstractRelationshipLoader): backref=self.parent_property.back_populates, ) -log.class_logger(DynaLoader) - - class DynamicAttributeImpl(attributes.AttributeImpl): uses_objects = True accepts_scalar_loader = False -- 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/dynamic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/sqlalchemy/orm/dynamic.py') diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index 4ad204357..4631e806f 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -15,11 +15,12 @@ from .. import log, util, exc from ..sql import operators from . import ( attributes, object_session, util as orm_util, strategies, - object_mapper, exc as orm_exc + object_mapper, exc as orm_exc, properties ) from .query import Query @log.class_logger +@properties.RelationshipProperty._strategy_for(dict(lazy="dynamic")) class DynaLoader(strategies.AbstractRelationshipLoader): def init_class_attribute(self, mapper): self.is_class_level = True -- 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/dynamic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/sqlalchemy/orm/dynamic.py') diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index 4631e806f..b419d2a07 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -20,7 +20,7 @@ from . import ( from .query import Query @log.class_logger -@properties.RelationshipProperty._strategy_for(dict(lazy="dynamic")) +@properties.RelationshipProperty.strategy_for(lazy="dynamic") class DynaLoader(strategies.AbstractRelationshipLoader): def init_class_attribute(self, mapper): self.is_class_level = True -- cgit v1.2.1 From 30a50cc46aa836e24ebcbb889cbee583c511af82 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Tue, 26 Nov 2013 23:24:13 -0500 Subject: - the wrapped memoized_property here was not working, as the attribute name didn't match. use straight memoized_props here for now, add a perf test to check it --- lib/sqlalchemy/orm/dynamic.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'lib/sqlalchemy/orm/dynamic.py') diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index b419d2a07..829cda554 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -76,8 +76,13 @@ class DynamicAttributeImpl(attributes.AttributeImpl): history = self._get_collection_history(state, passive) return history.added_plus_unchanged - _append_token = attributes.Event._token_gen(attributes.OP_APPEND) - _remove_token = attributes.Event._token_gen(attributes.OP_REMOVE) + @util.memoized_property + def _append_token(self): + return attributes.Event(self, attributes.OP_APPEND) + + @util.memoized_property + def _remove_token(self): + return attributes.Event(self, attributes.OP_REMOVE) def fire_append_event(self, state, dict_, value, initiator, collection_history=None): -- 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/dynamic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/sqlalchemy/orm/dynamic.py') diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index 829cda554..bae09d32d 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -1,5 +1,5 @@ # orm/dynamic.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