diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2018-02-15 17:42:48 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2018-02-15 17:59:15 -0500 |
| commit | 283a969d68688018abee04fde479a6fd8495b6f6 (patch) | |
| tree | 96faee92575ba64df3c2d641f4c77bae6c8a231d /lib/sqlalchemy/orm | |
| parent | 6c6a7720f221d255a9c8abaec24fc5272403019b (diff) | |
| download | sqlalchemy-283a969d68688018abee04fde479a6fd8495b6f6.tar.gz | |
Test attributes for being non-mapped column properties more closely
Fixed bug in concrete inheritance mapping where user-defined
attributes such as hybrid properties that mirror the names
of mapped attributes from sibling classes would be overwritten by
the mapper as non-accessible at the instance level. Also
ensured that user-bound descriptors are not implicitly invoked at the class
level during the mapper configuration stage.
Change-Id: I52b84a15c296b14efeaffb72941fc941d1d52c0d
Fixes: #4188
Diffstat (limited to 'lib/sqlalchemy/orm')
| -rw-r--r-- | lib/sqlalchemy/orm/instrumentation.py | 9 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 26 |
2 files changed, 28 insertions, 7 deletions
diff --git a/lib/sqlalchemy/orm/instrumentation.py b/lib/sqlalchemy/orm/instrumentation.py index d5422b0d0..1b839cf5c 100644 --- a/lib/sqlalchemy/orm/instrumentation.py +++ b/lib/sqlalchemy/orm/instrumentation.py @@ -168,6 +168,15 @@ class ClassManager(dict): if isinstance(val, interfaces.InspectionAttr): yield key, val + def _get_class_attr_mro(self, key, default=None): + """return an attribute on the class without tripping it.""" + + for supercls in self.class_.__mro__: + if key in supercls.__dict__: + return supercls.__dict__[key] + else: + return default + def _attr_has_impl(self, key): """Return True if the given attribute is fully initialized. diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index cd9e00b8b..a30a8c243 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -1613,10 +1613,23 @@ class Mapper(InspectionAttr): if not self.concrete: self._configure_property(key, prop, init=False, setparent=False) elif key not in self._props: - self._configure_property( - key, - properties.ConcreteInheritedProperty(), - init=init, setparent=True) + # determine if the class implements this attribute; if not, + # or if it is implemented by the attribute that is handling the + # given superclass-mapped property, then we need to report that we + # can't use this at the instance level since we are a concrete + # mapper and we don't map this. don't trip user-defined + # descriptors that might have side effects when invoked. + implementing_attribute = self.class_manager._get_class_attr_mro( + key, prop) + if implementing_attribute is prop or (isinstance( + implementing_attribute, + attributes.InstrumentedAttribute) and + implementing_attribute._parententity is prop.parent + ): + self._configure_property( + key, + properties.ConcreteInheritedProperty(), + init=init, setparent=True) def _configure_property(self, key, prop, init=True, setparent=True): self._log("_configure_property(%s, %s)", key, prop.__class__.__name__) @@ -2413,9 +2426,8 @@ class Mapper(InspectionAttr): self.class_.__dict__[assigned_name]): return True else: - if getattr(self.class_, assigned_name, None) is not None \ - and self._is_userland_descriptor( - getattr(self.class_, assigned_name)): + attr = self.class_manager._get_class_attr_mro(assigned_name, None) + if attr is not None and self._is_userland_descriptor(attr): return True if self.include_properties is not None and \ |
