summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2018-02-09 16:12:31 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2018-02-09 16:30:42 -0500
commit650b9eddae0eb198c8f8dc2d1e1e3c6ac53b18f3 (patch)
treeecb640dd5d1e0f000a1dd97c96f4fdb2f5841570 /lib/sqlalchemy/orm
parent9ef891e82c187b3fda0f778073f258ef8b55124f (diff)
downloadsqlalchemy-650b9eddae0eb198c8f8dc2d1e1e3c6ac53b18f3.tar.gz
Search through mapper superclass hierarchy for owner
Fixed regression caused by fix for issue :ticket:`4116` affecting versions 1.2.2 as well as 1.1.15, which had the effect of mis-calculation of the "owning class" of an :class:`.AssociationProxy` as the ``NoneType`` class in some declarative mixin/inheritance situations as well as if the association proxy were accessed off of an un-mapped class. The "figure out the owner" logic has been replaced by an in-depth routine that searches through the complete mapper hierarchy assigned to the class or subclass to determine the correct (we hope) match; will not assign the owner if no match is found. An exception is now raised if the proxy is used against an un-mapped instance. Change-Id: I611b590df2babe077ce6c19bea89e84251d1a7f4 Fixes: #4185
Diffstat (limited to 'lib/sqlalchemy/orm')
-rw-r--r--lib/sqlalchemy/orm/instrumentation.py35
1 files changed, 35 insertions, 0 deletions
diff --git a/lib/sqlalchemy/orm/instrumentation.py b/lib/sqlalchemy/orm/instrumentation.py
index 5a96c57cf..d5422b0d0 100644
--- a/lib/sqlalchemy/orm/instrumentation.py
+++ b/lib/sqlalchemy/orm/instrumentation.py
@@ -115,6 +115,41 @@ class ClassManager(dict):
# raises unless self.mapper has been assigned
raise exc.UnmappedClassError(self.class_)
+ def _locate_owning_manager(self, attribute):
+ """Scan through all instrumented classes in our hierarchy
+ searching for the given object as an attribute, and return
+ the bottommost owner.
+
+ E.g.::
+
+ foo = foobar()
+
+ class Parent:
+ attr = foo
+
+ class Child(Parent):
+ pass
+
+ Child.manager._locate_owning_manager(foo) would
+ give us Parent.
+
+ Needed by association proxy to correctly figure out the
+ owning class when the attribute is accessed.
+
+ """
+
+ stack = [None]
+ for supercls in self.class_.__mro__:
+ mgr = manager_of_class(supercls)
+ if not mgr:
+ continue
+ for key in set(supercls.__dict__):
+ val = supercls.__dict__[key]
+ if val is attribute:
+ stack.append(mgr)
+ continue
+ return stack[-1]
+
def _all_sqla_attributes(self, exclude=None):
"""return an iterator of all classbound attributes that are
implement :class:`.InspectionAttr`.