]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Default to using current mapped class as owner if none found
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 20 Feb 2018 17:15:57 +0000 (12:15 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 20 Feb 2018 21:42:24 +0000 (16:42 -0500)
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)

doc/build/changelog/unreleased_11/4185.rst [new file with mode: 0644]
lib/sqlalchemy/ext/associationproxy.py
test/ext/test_associationproxy.py

diff --git a/doc/build/changelog/unreleased_11/4185.rst b/doc/build/changelog/unreleased_11/4185.rst
new file mode 100644 (file)
index 0000000..e4dfbd2
--- /dev/null
@@ -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.
index e701f5908596e1eb2e675b35b9661d2151254955..4fdac827af7d50e99269d8384d7c13be06d90068 100644 (file)
@@ -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:
index d215bbc8d98288bcf8bf4834ffb6b9a8ef0f84c2..ab11df5bc030733a8a3e2383c7470bee6fda5bb2 100644 (file)
@@ -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)