diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-03-09 10:48:59 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-03-09 11:13:16 -0500 |
| commit | 1031fc6f781a3a6088467791e837f23a4b338aef (patch) | |
| tree | 1ed3cf02629ac9f7e38016d467ed2f95257f3e06 /lib | |
| parent | 85c843bcc3ea1ca16be06fe85b03d99c4e519f3d (diff) | |
| download | sqlalchemy-1031fc6f781a3a6088467791e837f23a4b338aef.tar.gz | |
Link to state, not object, for mutable extension
The ``sqlalchemy.ext.mutable`` extension now tracks the "parents"
collection using the :class:`.InstanceState` associated with objects,
rather than the object itself. The latter approach required that the object
be hashable so that it can be inside of a ``WeakKeyDictionary``, which goes
against the behavioral contract of the ORM overall which is that ORM mapped
objects do not need to provide any particular kind of ``__hash__()`` method
and that unhashable objects are supported.
Fixes: #6020
Change-Id: I414c8861bc5691f5f320dac3eb696f4b8c37d347
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/sqlalchemy/ext/mutable.py | 23 |
1 files changed, 14 insertions, 9 deletions
diff --git a/lib/sqlalchemy/ext/mutable.py b/lib/sqlalchemy/ext/mutable.py index 4bb9e795b..fef886501 100644 --- a/lib/sqlalchemy/ext/mutable.py +++ b/lib/sqlalchemy/ext/mutable.py @@ -357,10 +357,10 @@ pickling process of the parent's object-relational state so that the import weakref from .. import event +from .. import inspect from .. import types from ..orm import Mapper from ..orm import mapper -from ..orm import object_mapper from ..orm.attributes import flag_modified from ..sql.base import SchemaEventTarget from ..util import memoized_property @@ -374,12 +374,17 @@ class MutableBase(object): @memoized_property def _parents(self): - """Dictionary of parent object->attribute name on the parent. + """Dictionary of parent object's :class:`.InstanceState`->attribute + name on the parent. This attribute is a so-called "memoized" property. It initializes itself with a new ``weakref.WeakKeyDictionary`` the first time it is accessed, returning the same object upon subsequent access. + .. versionchanged:: 1.4 the :class:`.InstanceState` is now used + as the key in the weak dictionary rather than the instance + itself. + """ return weakref.WeakKeyDictionary() @@ -461,7 +466,7 @@ class MutableBase(object): if coerce: val = cls.coerce(key, val) state.dict[key] = val - val._parents[state.obj()] = key + val._parents[state] = key def load_attrs(state, ctx, attrs): if not attrs or listen_keys.intersection(attrs): @@ -482,9 +487,9 @@ class MutableBase(object): if not isinstance(value, cls): value = cls.coerce(key, value) if value is not None: - value._parents[target.obj()] = key + value._parents[target] = key if isinstance(oldvalue, cls): - oldvalue._parents.pop(target.obj(), None) + oldvalue._parents.pop(inspect(target), None) return value def pickle(state, state_dict): @@ -497,7 +502,7 @@ class MutableBase(object): def unpickle(state, state_dict): if "ext.mutable.values" in state_dict: for val in state_dict["ext.mutable.values"]: - val._parents[state.obj()] = key + val._parents[state] = key event.listen(parent_cls, "load", load, raw=True, propagate=True) event.listen( @@ -527,7 +532,7 @@ class Mutable(MutableBase): """Subclasses should call this method whenever change events occur.""" for parent, key in self._parents.items(): - flag_modified(parent, key) + flag_modified(parent.obj(), key) @classmethod def associate_with_attribute(cls, attribute): @@ -647,11 +652,11 @@ class MutableComposite(MutableBase): for parent, key in self._parents.items(): - prop = object_mapper(parent).get_property(key) + prop = parent.mapper.get_property(key) for value, attr_name in zip( self.__composite_values__(), prop._attribute_keys ): - setattr(parent, attr_name, value) + setattr(parent.obj(), attr_name, value) def _setup_composite_listener(): |
