summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/orm/attributes.py72
-rw-r--r--lib/sqlalchemy/orm/base.py6
-rw-r--r--lib/sqlalchemy/orm/relationships.py3
-rw-r--r--lib/sqlalchemy/orm/strategies.py40
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: