diff options
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 72 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/base.py | 6 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/relationships.py | 3 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 40 |
4 files changed, 90 insertions, 31 deletions
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 105a9cfd2..9ba05395a 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -22,6 +22,7 @@ from . import interfaces from .base import ATTR_EMPTY from .base import ATTR_WAS_SET from .base import CALLABLES_OK +from .base import DEFERRED_HISTORY_LOAD from .base import INIT_OK from .base import instance_dict from .base import instance_state @@ -768,6 +769,9 @@ class AttributeImpl(object): else: self.accepts_scalar_loader = self.default_accepts_scalar_loader + _deferred_history = kwargs.pop("_deferred_history", False) + self._deferred_history = _deferred_history + if active_history: self.dispatch._active_history = True @@ -786,6 +790,7 @@ class AttributeImpl(object): "load_on_unexpire", "_modified_token", "accepts_scalar_loader", + "_deferred_history", ) def __str__(self): @@ -918,19 +923,7 @@ class AttributeImpl(object): if not passive & CALLABLES_OK: return PASSIVE_NO_RESULT - if ( - self.accepts_scalar_loader - and self.load_on_unexpire - and key in state.expired_attributes - ): - value = state._load_expired(state, passive) - elif key in state.callables: - callable_ = state.callables[key] - value = callable_(state, passive) - elif self.callable_: - value = self.callable_(state, passive) - else: - value = ATTR_EMPTY + value = self._fire_loader_callables(state, key, passive) if value is PASSIVE_NO_RESULT or value is NO_VALUE: return value @@ -955,6 +948,21 @@ class AttributeImpl(object): else: return self._default_value(state, dict_) + def _fire_loader_callables(self, state, key, passive): + if ( + self.accepts_scalar_loader + and self.load_on_unexpire + and key in state.expired_attributes + ): + return state._load_expired(state, passive) + elif key in state.callables: + callable_ = state.callables[key] + return callable_(state, passive) + elif self.callable_: + return self.callable_(state, passive) + else: + return ATTR_EMPTY + def append(self, state, dict_, value, initiator, passive=PASSIVE_OFF): self.set(state, dict_, value, initiator, passive=passive) @@ -1142,15 +1150,33 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): def get_history(self, state, dict_, passive=PASSIVE_OFF): if self.key in dict_: - return History.from_object_attribute(self, state, dict_[self.key]) + current = dict_[self.key] else: if passive & INIT_OK: passive ^= INIT_OK current = self.get(state, dict_, passive=passive) if current is PASSIVE_NO_RESULT: return HISTORY_BLANK - else: - return History.from_object_attribute(self, state, current) + + if not self._deferred_history: + return History.from_object_attribute(self, state, current) + else: + original = state.committed_state.get(self.key, _NO_HISTORY) + if original is PASSIVE_NO_RESULT: + + loader_passive = passive | ( + PASSIVE_ONLY_PERSISTENT + | NO_AUTOFLUSH + | LOAD_AGAINST_COMMITTED + | NO_RAISE + | DEFERRED_HISTORY_LOAD + ) + original = self._fire_loader_callables( + state, self.key, loader_passive + ) + return History.from_object_attribute( + self, state, current, original=original + ) def get_all_pending(self, state, dict_, passive=PASSIVE_NO_INITIALIZE): if self.key in dict_: @@ -1193,6 +1219,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): pop=False, ): """Set a value on the given InstanceState.""" + if self.dispatch._active_history: old = self.get( state, @@ -1227,7 +1254,11 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): dict_[self.key] = value def fire_remove_event(self, state, dict_, value, initiator): - if self.trackparent and value is not None: + if self.trackparent and value not in ( + None, + PASSIVE_NO_RESULT, + NO_VALUE, + ): self.sethasparent(instance_state(value), state, False) for fn in self.dispatch.remove: @@ -1930,8 +1961,11 @@ class History(util.namedtuple("History", ["added", "unchanged", "deleted"])): return cls([current], (), deleted) @classmethod - def from_object_attribute(cls, attribute, state, current): - original = state.committed_state.get(attribute.key, _NO_HISTORY) + def from_object_attribute( + cls, attribute, state, current, original=_NO_HISTORY + ): + if original is _NO_HISTORY: + original = state.committed_state.get(attribute.key, _NO_HISTORY) if original is _NO_HISTORY: if current is NO_VALUE: diff --git a/lib/sqlalchemy/orm/base.py b/lib/sqlalchemy/orm/base.py index 2932f1bb9..524407265 100644 --- a/lib/sqlalchemy/orm/base.py +++ b/lib/sqlalchemy/orm/base.py @@ -124,6 +124,12 @@ NO_RAISE = util.symbol( canonical=128, ) +DEFERRED_HISTORY_LOAD = util.symbol( + "DEFERRED_HISTORY_LOAD", + """indicates special load of the previous value of an attribute""", + canonical=256, +) + # pre-packaged sets of flags used as inputs PASSIVE_OFF = util.symbol( "PASSIVE_OFF", diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index 2224b4902..7b27d9fe7 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -151,6 +151,7 @@ class RelationshipProperty(StrategizedProperty): info=None, omit_join=None, sync_backref=None, + _legacy_inactive_history_style=False, ): """Provide a relationship between two mapped classes. @@ -1014,6 +1015,8 @@ class RelationshipProperty(StrategizedProperty): self.distinct_target_key = distinct_target_key self.doc = doc self.active_history = active_history + self._legacy_inactive_history_style = _legacy_inactive_history_style + self.join_depth = join_depth if omit_join: util.warn( diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 2a254f8de..7d7438452 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -695,18 +695,27 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots): def init_class_attribute(self, mapper): self.is_class_level = True - active_history = ( - self.parent_property.active_history - or self.parent_property.direction is not interfaces.MANYTOONE - or not self.use_get + _legacy_inactive_history_style = ( + self.parent_property._legacy_inactive_history_style ) - # MANYTOONE currently only needs the - # "old" value for delete-orphan - # cascades. the required _SingleParentValidator - # will enable active_history - # in that case. otherwise we don't need the - # "old" value during backref operations. + if self.parent_property.active_history: + active_history = True + _deferred_history = False + + elif ( + self.parent_property.direction is not interfaces.MANYTOONE + or not self.use_get + ): + if _legacy_inactive_history_style: + active_history = True + _deferred_history = False + else: + active_history = False + _deferred_history = True + else: + active_history = _deferred_history = False + _register_attribute( self.parent_property, mapper, @@ -714,6 +723,7 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots): callable_=self._load_for_state, typecallable=self.parent_property.collection_class, active_history=active_history, + _deferred_history=_deferred_history, ) def _memoized_attr__simple_lazy_clause(self): @@ -850,7 +860,10 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots): if _none_set.issuperset(primary_key_identity): return None - if self.key in state.dict: + if ( + self.key in state.dict + and not passive & attributes.DEFERRED_HISTORY_LOAD + ): return attributes.ATTR_WAS_SET # look for this identity in the identity map. Delegate to the @@ -1016,7 +1029,10 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots): "_sa_orm_load_options": load_options, } - if self.key in state.dict: + if ( + self.key in state.dict + and not passive & attributes.DEFERRED_HISTORY_LOAD + ): return attributes.ATTR_WAS_SET if pending: |