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/state.py | 84 +++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 52 deletions(-) (limited to 'lib/sqlalchemy/orm/state.py') diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index c479d880d..7805a47e4 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -13,16 +13,11 @@ defines a large part of the ORM's interactivity. import weakref from .. import util -from . import exc as orm_exc, attributes, util as orm_util, interfaces -from .attributes import ( - PASSIVE_NO_RESULT, - SQL_OK, NEVER_SET, ATTR_WAS_SET, NO_VALUE,\ - PASSIVE_NO_INITIALIZE - ) -sessionlib = util.importlater("sqlalchemy.orm", "session") -instrumentation = util.importlater("sqlalchemy.orm", "instrumentation") -mapperlib = util.importlater("sqlalchemy.orm", "mapperlib") - +from . import exc as orm_exc, interfaces +from .path_registry import PathRegistry +from .base import PASSIVE_NO_RESULT, SQL_OK, NEVER_SET, ATTR_WAS_SET, \ + NO_VALUE, PASSIVE_NO_INITIALIZE +from . import base class InstanceState(interfaces._InspectionAttr): """tracks state information at the instance level.""" @@ -89,15 +84,16 @@ class InstanceState(interfaces._InspectionAttr): not self._attached @property - def _attached(self): + @util.dependencies("sqlalchemy.orm.session") + def _attached(self, sessionlib): return self.session_id is not None and \ self.session_id in sessionlib._sessions @property - def session(self): + @util.dependencies("sqlalchemy.orm.session") + def session(self, sessionlib): """Return the owning :class:`.Session` for this instance, or ``None`` if none available.""" - return sessionlib._state_session(self) @property @@ -186,7 +182,7 @@ class InstanceState(interfaces._InspectionAttr): def dict(self): o = self.obj() if o is not None: - return attributes.instance_dict(o) + return base.instance_dict(o) else: return {} @@ -214,8 +210,8 @@ class InstanceState(interfaces._InspectionAttr): return self._pending_mutations[key] def __getstate__(self): - d = {'instance': self.obj()} - d.update( + state_dict = {'instance': self.obj()} + state_dict.update( (k, self.__dict__[k]) for k in ( 'committed_state', '_pending_mutations', 'modified', 'expired', 'callables', 'key', 'parents', 'load_options', @@ -223,14 +219,14 @@ class InstanceState(interfaces._InspectionAttr): ) if k in self.__dict__ ) if self.load_path: - d['load_path'] = self.load_path.serialize() + state_dict['load_path'] = self.load_path.serialize() - self.manager.dispatch.pickle(self, d) + state_dict['manager'] = self.manager._serialize(self, state_dict) - return d + return state_dict - def __setstate__(self, state): - inst = state['instance'] + def __setstate__(self, state_dict): + inst = state_dict['instance'] if inst is not None: self.obj = weakref.ref(inst, self._cleanup) self.class_ = inst.__class__ @@ -239,42 +235,26 @@ class InstanceState(interfaces._InspectionAttr): # due to storage of state in "parents". "class_" # also new. self.obj = None - self.class_ = state['class_'] - self.manager = manager = instrumentation.manager_of_class(self.class_) - if manager is None: - raise orm_exc.UnmappedInstanceError( - inst, - "Cannot deserialize object of type %r - " - "no mapper() has " - "been configured for this class within the current " - "Python process!" % - self.class_) - elif manager.is_mapped and not manager.mapper.configured: - mapperlib.configure_mappers() - - self.committed_state = state.get('committed_state', {}) - self._pending_mutations = state.get('_pending_mutations', {}) - self.parents = state.get('parents', {}) - self.modified = state.get('modified', False) - self.expired = state.get('expired', False) - self.callables = state.get('callables', {}) + self.class_ = state_dict['class_'] + + self.committed_state = state_dict.get('committed_state', {}) + self._pending_mutations = state_dict.get('_pending_mutations', {}) + self.parents = state_dict.get('parents', {}) + self.modified = state_dict.get('modified', False) + self.expired = state_dict.get('expired', False) + self.callables = state_dict.get('callables', {}) self.__dict__.update([ - (k, state[k]) for k in ( + (k, state_dict[k]) for k in ( 'key', 'load_options', - ) if k in state + ) if k in state_dict ]) - if 'load_path' in state: - self.load_path = orm_util.PathRegistry.\ - deserialize(state['load_path']) + if 'load_path' in state_dict: + self.load_path = PathRegistry.\ + deserialize(state_dict['load_path']) - # setup _sa_instance_state ahead of time so that - # unpickle events can access the object normally. - # see [ticket:2362] - if inst is not None: - manager.setup_instance(inst, self) - manager.dispatch.unpickle(self, state) + state_dict['manager'](self, inst, state_dict) def _initialize(self, key): """Set this attribute to an empty value or collection, @@ -461,7 +441,7 @@ class InstanceState(interfaces._InspectionAttr): "collected." % ( self.manager[attr.key], - orm_util.state_class_str(self) + base.state_class_str(self) )) self.modified = True -- cgit v1.2.1 From b9bd6ffd43a138f0b8964cae0657e478f9b4a13a Mon Sep 17 00:00:00 2001 From: Hyunjun Kim Date: Sun, 25 Aug 2013 01:59:47 +0900 Subject: fix typos --- lib/sqlalchemy/orm/state.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/sqlalchemy/orm/state.py') diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index 7805a47e4..35305dc79 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -507,13 +507,13 @@ class AttributeState(object): to a particular attribute on a particular mapped object. The :class:`.AttributeState` object is accessed - via the :attr:`.InstanceState.attr` collection + via the :attr:`.InstanceState.attrs` collection of a particular :class:`.InstanceState`:: from sqlalchemy import inspect insp = inspect(some_mapped_object) - attr_state = insp.attr.some_attribute + attr_state = insp.attrs.some_attribute """ -- cgit v1.2.1 From 75be18004fac80de6aabea409c059eaaa0dd5244 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 11 Oct 2013 16:41:52 -0400 Subject: - fix bug due to regression from #2793, make sure we only go to load scalar attributes here and not relationships, else we get an error if there's no actual scalars to load --- lib/sqlalchemy/orm/state.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'lib/sqlalchemy/orm/state.py') diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index 35305dc79..2c947a7ac 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -392,6 +392,15 @@ class InstanceState(interfaces._InspectionAttr): difference(self.committed_state).\ difference(self.dict) + @property + def _unloaded_non_object(self): + return self.unloaded.difference(self._uses_objects) + + @property + def _uses_objects(self): + return (attr for attr in self.manager + if self.manager[attr].impl.uses_objects) + @property def expired_attributes(self): """Return the set of keys which are 'expired' to be loaded by -- cgit v1.2.1 From eabf41b392ac042228765c9b95138522e44365e7 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 11 Oct 2013 16:49:36 -0400 Subject: use accepts scalar loader here so we deal with _ProxyImpl correctly --- lib/sqlalchemy/orm/state.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'lib/sqlalchemy/orm/state.py') diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index 2c947a7ac..8d73c9426 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -394,12 +394,10 @@ class InstanceState(interfaces._InspectionAttr): @property def _unloaded_non_object(self): - return self.unloaded.difference(self._uses_objects) - - @property - def _uses_objects(self): - return (attr for attr in self.manager - if self.manager[attr].impl.uses_objects) + return self.unloaded.intersection( + attr for attr in self.manager + if self.manager[attr].impl.accepts_scalar_loader + ) @property def expired_attributes(self): -- cgit v1.2.1 From 2b1c8eabb10c932f6e83d08147c75bb05f96a161 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 25 Oct 2013 13:13:24 -0400 Subject: - :func:`.attributes.get_history()` when used with a scalar column-mapped attribute will now honor the "passive" flag passed to it; as this defaults to ``PASSIVE_OFF``, the function will by default query the database if the value is not present. This is a behavioral change vs. 0.8. [ticket:2787] - Added new method :meth:`.AttributeState.load_history`, works like :attr:`.AttributeState.history` but also fires loader callables. --- lib/sqlalchemy/orm/state.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) (limited to 'lib/sqlalchemy/orm/state.py') diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index 8d73c9426..957e29700 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -16,7 +16,7 @@ from .. import util from . import exc as orm_exc, interfaces from .path_registry import PathRegistry from .base import PASSIVE_NO_RESULT, SQL_OK, NEVER_SET, ATTR_WAS_SET, \ - NO_VALUE, PASSIVE_NO_INITIALIZE + NO_VALUE, PASSIVE_NO_INITIALIZE, INIT_OK, PASSIVE_OFF from . import base class InstanceState(interfaces._InspectionAttr): @@ -555,10 +555,40 @@ class AttributeState(object): """Return the current pre-flush change history for this attribute, via the :class:`.History` interface. + This method will **not** emit loader callables if the value of the + attribute is unloaded. + + .. seealso:: + + :meth:`.AttributeState.load_history` - retrieve history + using loader callables if the value is not locally present. + + :func:`.attributes.get_history` - underlying function + """ return self.state.get_history(self.key, PASSIVE_NO_INITIALIZE) + def load_history(self): + """Return the current pre-flush change history for + this attribute, via the :class:`.History` interface. + + This method **will** emit loader callables if the value of the + attribute is unloaded. + + .. seealso:: + + :attr:`.AttributeState.history` + + :func:`.attributes.get_history` - underlying function + + .. versionadded:: 0.9.0 + + """ + return self.state.get_history(self.key, + PASSIVE_OFF ^ INIT_OK) + + class PendingCollection(object): """A writable placeholder for an unloaded collection. -- 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/state.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib/sqlalchemy/orm/state.py') diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index 957e29700..ddd991acc 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -415,6 +415,8 @@ class InstanceState(interfaces._InspectionAttr): return None def _modified_event(self, dict_, attr, previous, collection=False): + if not attr.send_modified_events: + return if attr.key not in self.committed_state: if collection: if previous is NEVER_SET: -- 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/state.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/sqlalchemy/orm/state.py') diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index ddd991acc..9712dd055 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -1,5 +1,5 @@ # orm/state.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