summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/ext
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/ext
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/ext')
-rw-r--r--lib/sqlalchemy/ext/associationproxy.py48
1 files changed, 35 insertions, 13 deletions
diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py
index 8b90f0925..72200c5e2 100644
--- a/lib/sqlalchemy/ext/associationproxy.py
+++ b/lib/sqlalchemy/ext/associationproxy.py
@@ -212,7 +212,12 @@ class AssociationProxy(interfaces.InspectionAttrInfo):
return (self.local_attr, self.remote_attr)
def _get_property(self):
- return (orm.class_mapper(self.owning_class).
+ owning_class = self.owning_class
+ if owning_class is None:
+ raise exc.InvalidRequestError(
+ "This association proxy has no mapped owning class; "
+ "can't locate a mapped property")
+ return (orm.class_mapper(owning_class).
get_property(self.target_collection))
@util.memoized_property
@@ -244,18 +249,33 @@ class AssociationProxy(interfaces.InspectionAttrInfo):
def _target_is_object(self):
return getattr(self.target_class, self.value_attr).impl.uses_objects
+ def _calc_owner(self, obj, class_):
+ if obj is not None and class_ is None:
+ target_cls = type(obj)
+ elif class_ is not None:
+ target_cls = class_
+ else:
+ return
+
+ # we might be getting invoked for a subclass
+ # that is not mapped yet, in some declarative situations.
+ # save until we are mapped
+ try:
+ insp = inspect(target_cls)
+ except exc.NoInspectionAvailable:
+ # can't find a mapper, don't set owner. if we are a not-yet-mapped
+ # subclass, we can also scan through __mro__ to find a mapped
+ # class, but instead just wait for us to be called again against a
+ # mapped class normally.
+ return
+
+ # note we can get our real .key here too
+ owner = insp.mapper.class_manager._locate_owning_manager(self)
+ self.owning_class = owner.class_
+
def __get__(self, obj, class_):
if self.owning_class is None:
- try:
- insp = inspect(class_)
- except exc.NoInspectionAvailable:
- pass
- else:
- if hasattr(insp, 'mapper'):
- self.owning_class = insp.mapper.class_
-
- if self.owning_class is None:
- self.owning_class = type(obj)
+ self._calc_owner(obj, class_)
if obj is None:
return self
@@ -278,7 +298,7 @@ class AssociationProxy(interfaces.InspectionAttrInfo):
def __set__(self, obj, values):
if self.owning_class is None:
- self.owning_class = type(obj)
+ self._calc_owner(obj, None)
if self.scalar:
creator = self.creator and self.creator or self.target_class
@@ -295,7 +315,8 @@ class AssociationProxy(interfaces.InspectionAttrInfo):
def __delete__(self, obj):
if self.owning_class is None:
- self.owning_class = type(obj)
+ self._calc_owner(obj, None)
+
delattr(obj, self.key)
def _initialize_scalar_accessors(self):
@@ -497,6 +518,7 @@ class AssociationProxy(interfaces.InspectionAttrInfo):
def __repr__(self):
return "AssociationProxy(%r, %r)" % (self.target_collection, self.value_attr)
+
class _lazy_collection(object):
def __init__(self, obj, target):
self.ref = weakref.ref(obj)