--- /dev/null
+.. change::
+ :tags: bug, orm
+ :tickets: 4188
+
+ 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. Additionally
+ ensured that user-bound descriptors are not implicitly invoked at the class
+ level during the mapper configuration stage.
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.
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__)
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 \
from sqlalchemy.orm import attributes
from sqlalchemy.testing import eq_
from sqlalchemy.testing.schema import Table, Column
+from sqlalchemy.ext.hybrid import hybrid_property
+from sqlalchemy.testing import mock
class Employee(object):
assert set([repr(x) for x in session.query(Hacker).all()]) \
== set(["Hacker Kurt 'Badass' knows how to hack"])
+ def test_multi_level_no_base_w_hybrid(self):
+ pjoin = polymorphic_union(
+ {'manager': managers_table, 'engineer': engineers_table,
+ 'hacker': hackers_table},
+ 'type', 'pjoin')
+
+ test_calls = mock.Mock()
+
+ class ManagerWHybrid(Employee):
+
+ def __init__(self, name, manager_data):
+ self.name = name
+ self.manager_data = manager_data
+
+ @hybrid_property
+ def engineer_info(self):
+ test_calls.engineer_info_instance()
+ return self.manager_data
+
+ @engineer_info.expression
+ def engineer_info(cls):
+ test_calls.engineer_info_class()
+ return cls.manager_data
+
+ employee_mapper = mapper(Employee, pjoin,
+ polymorphic_on=pjoin.c.type)
+ mapper(
+ ManagerWHybrid, managers_table,
+ inherits=employee_mapper,
+ concrete=True,
+ polymorphic_identity='manager')
+ mapper(
+ Engineer,
+ engineers_table,
+ inherits=employee_mapper,
+ concrete=True,
+ polymorphic_identity='engineer')
+
+ session = create_session()
+ tom = ManagerWHybrid('Tom', 'mgrdata')
+
+ # mapping did not impact the engineer_info
+ # hybrid in any way
+ eq_(test_calls.mock_calls, [])
+
+ eq_(
+ tom.engineer_info, "mgrdata"
+ )
+ eq_(test_calls.mock_calls, [mock.call.engineer_info_instance()])
+
+ session.add(tom)
+ session.flush()
+
+ session.close()
+
+ tom = session.query(ManagerWHybrid).filter(
+ ManagerWHybrid.engineer_info == 'mgrdata').one()
+ eq_(
+ test_calls.mock_calls,
+ [
+ mock.call.engineer_info_instance(),
+ mock.call.engineer_info_class()]
+ )
+ eq_(
+ tom.engineer_info, "mgrdata"
+ )
+
def test_multi_level_with_base(self):
pjoin = polymorphic_union({
'employee': employees_table,