summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2021-03-04 23:38:34 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2021-03-04 23:38:34 -0500
commit2f7623b6b265cd5f25f2a6022e21bc3286d397a3 (patch)
tree28e5479ddd8dea41d02f327633c59e611e142a51 /lib
parent5c4c7b0c6793d4cec364fb5fa9c5063feb4827f7 (diff)
downloadsqlalchemy-2f7623b6b265cd5f25f2a6022e21bc3286d397a3.tar.gz
ensure composite refresh handler synced w/ mutable composite
Fixed issue where the :class:`_mutable.MutableComposite` construct could be placed into an invalid state when the parent object was already loaded, and then covered by a subsequent query, due to the composite properties' refresh handler replacing the object with a new one not handled by the mutable extension. Fixes: #6001 Change-Id: Ieebd8e6afe6b65f8902cc12dec1efb968f5438ef
Diffstat (limited to 'lib')
-rw-r--r--lib/sqlalchemy/orm/descriptor_props.py34
1 files changed, 27 insertions, 7 deletions
diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py
index 65fe58e61..c30672566 100644
--- a/lib/sqlalchemy/orm/descriptor_props.py
+++ b/lib/sqlalchemy/orm/descriptor_props.py
@@ -167,6 +167,8 @@ class CompositeProperty(DescriptorProperty):
"""
self._setup_arguments_on_columns()
+ _COMPOSITE_FGET = object()
+
def _create_descriptor(self):
"""Create the Python descriptor that will serve as
the access point on instances of the mapped class.
@@ -194,7 +196,9 @@ class CompositeProperty(DescriptorProperty):
state.key is not None or not _none_set.issuperset(values)
):
dict_[self.key] = self.composite_class(*values)
- state.manager.dispatch.refresh(state, None, [self.key])
+ state.manager.dispatch.refresh(
+ state, self._COMPOSITE_FGET, [self.key]
+ )
return dict_.get(self.key, None)
@@ -268,16 +272,32 @@ class CompositeProperty(DescriptorProperty):
def _setup_event_handlers(self):
"""Establish events that populate/expire the composite attribute."""
- def load_handler(state, *args):
- _load_refresh_handler(state, args, is_refresh=False)
+ def load_handler(state, context):
+ _load_refresh_handler(state, context, None, is_refresh=False)
+
+ def refresh_handler(state, context, to_load):
+ # note this corresponds to sqlalchemy.ext.mutable load_attrs()
- def refresh_handler(state, *args):
- _load_refresh_handler(state, args, is_refresh=True)
+ if not to_load or (
+ {self.key}.union(self._attribute_keys)
+ ).intersection(to_load):
+ _load_refresh_handler(state, context, to_load, is_refresh=True)
- def _load_refresh_handler(state, args, is_refresh):
+ def _load_refresh_handler(state, context, to_load, is_refresh):
dict_ = state.dict
- if not is_refresh and self.key in dict_:
+ # if context indicates we are coming from the
+ # fget() handler, this already set the value; skip the
+ # handler here. (other handlers like mutablecomposite will still
+ # want to catch it)
+ # there's an insufficiency here in that the fget() handler
+ # really should not be using the refresh event and there should
+ # be some other event that mutablecomposite can subscribe
+ # towards for this.
+
+ if (
+ not is_refresh or context is self._COMPOSITE_FGET
+ ) and self.key in dict_:
return
# if column elements aren't loaded, skip.