)
)
+ # a single MapperProperty is shared down a class inheritance
+ # hierarchy, so we set up attribute instrumentation and backref event
+ # for each mapper down the hierarchy.
+
+ # typically, "mapper" is the same as prop.parent, due to the way
+ # the configure_mappers() process runs, however this is not strongly
+ # enforced, and in the case of a second configure_mappers() run the
+ # mapper here might not be prop.parent; also, a subclass mapper may
+ # be called here before a superclass mapper. That is, can't depend
+ # on mappers not already being set up so we have to check each one.
+
for m in mapper.self_and_descendants:
- if prop is m._props.get(prop.key):
+ if prop is m._props.get(prop.key) and \
+ not m.class_manager._attr_has_impl(prop.key):
desc = attributes.register_attribute_impl(
m.class_,
useobject=useobject,
extension=attribute_ext,
trackparent=useobject and (
- prop.single_parent
- or prop.direction is interfaces.ONETOMANY),
+ prop.single_parent or
+ prop.direction is interfaces.ONETOMANY),
typecallable=typecallable,
callable_=callable_,
active_history=active_history,
})
assert getattr(Foo().__class__, 'name').impl is not None
+ def test_class_hier_only_instrument_once_multiple_configure(self):
+ users, addresses = (self.tables.users, self.tables.addresses)
+
+ class A(object):
+ pass
+
+ class ASub(A):
+ pass
+
+ class ASubSub(ASub):
+ pass
+
+ class B(object):
+ pass
+
+ from sqlalchemy.testing import mock
+ from sqlalchemy.orm.attributes import register_attribute_impl
+
+ with mock.patch(
+ "sqlalchemy.orm.attributes.register_attribute_impl",
+ side_effect=register_attribute_impl
+ ) as some_mock:
+
+ mapper(A, users, properties={
+ 'bs': relationship(B)
+ })
+ mapper(B, addresses)
+
+ configure_mappers()
+
+ mapper(ASub, inherits=A)
+ mapper(ASubSub, inherits=ASub)
+
+ configure_mappers()
+
+ b_calls = [
+ c for c in some_mock.mock_calls if c[1][1] == 'bs'
+ ]
+ eq_(len(b_calls), 3)
+
+
def test_check_descriptor_as_method(self):
User, users = self.classes.User, self.tables.users