From: Mike Bayer Date: Tue, 20 Feb 2018 17:15:57 +0000 (-0500) Subject: Default to using current mapped class as owner if none found X-Git-Tag: rel_1_1_17~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b37f00773290882940f7dcd1a785ecfd81b284af;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Default to using current mapped class as owner if none found Repaired regression caused in 1.2.3 and 1.1.16 regarding association proxy objects, revising the approach to :ticket:`4185` when calculating the "owning class" of an association proxy to default to choosing the current class if the proxy object is not directly associated with a mapped class, such as a mixin. Change-Id: I87d0ac09f695dc285bd4bbe0a547f1d5ce23e068 Fixes: #4185 (cherry picked from commit 93881f7873048403b62cc3e179354712ba8e9282) --- diff --git a/doc/build/changelog/unreleased_11/4185.rst b/doc/build/changelog/unreleased_11/4185.rst new file mode 100644 index 0000000000..e4dfbd2451 --- /dev/null +++ b/doc/build/changelog/unreleased_11/4185.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, ext + :tickets: 4185 + + Repaired regression caused in 1.2.3 and 1.1.16 regarding association proxy + objects, revising the approach to :ticket:`4185` when calculating the + "owning class" of an association proxy to default to choosing the current + class if the proxy object is not directly associated with a mapped class, + such as a mixin. diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py index e701f59085..4fdac827af 100644 --- a/lib/sqlalchemy/ext/associationproxy.py +++ b/lib/sqlalchemy/ext/associationproxy.py @@ -271,7 +271,12 @@ class AssociationProxy(interfaces.InspectionAttrInfo): # note we can get our real .key here too owner = insp.mapper.class_manager._locate_owning_manager(self) - self.owning_class = owner.class_ + if owner is not None: + self.owning_class = owner.class_ + else: + # the proxy is attached to a class that is not mapped + # (like a mixin), we are mapped, so, it's us. + self.owning_class = target_cls def __get__(self, obj, class_): if self.owning_class is None: diff --git a/test/ext/test_associationproxy.py b/test/ext/test_associationproxy.py index d215bbc8d9..ab11df5bc0 100644 --- a/test/ext/test_associationproxy.py +++ b/test/ext/test_associationproxy.py @@ -1729,6 +1729,33 @@ class AttributeAccessTest(fixtures.TestBase): sp.children = 'c' is_(SubParent.children.owning_class, Parent) + def test_resolved_to_correct_class_five(self): + Base = declarative_base() + + class Mixin(object): + children = association_proxy('_children', 'value') + + class Parent(Mixin, Base): + __tablename__ = 'parent' + id = Column(Integer, primary_key=True) + _children = relationship("Child") + + class Child(Base): + __tablename__ = 'child' + parent_id = Column( + Integer, ForeignKey(Parent.id), primary_key=True) + value = Column(String) + + # this triggers the owning routine, doesn't fail + Mixin.children + + p1 = Parent() + + c1 = Child(value='c1') + p1._children.append(c1) + is_(Parent.children.owning_class, Parent) + eq_(p1.children, ["c1"]) + def test_never_assign_nonetype(self): foo = association_proxy('x', 'y') foo._calc_owner(None, None)