diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-03-04 23:38:34 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-03-04 23:38:34 -0500 |
| commit | 2f7623b6b265cd5f25f2a6022e21bc3286d397a3 (patch) | |
| tree | 28e5479ddd8dea41d02f327633c59e611e142a51 /lib | |
| parent | 5c4c7b0c6793d4cec364fb5fa9c5063feb4827f7 (diff) | |
| download | sqlalchemy-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.py | 34 |
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. |
