From: Mike Bayer Date: Fri, 20 Oct 2017 18:34:16 +0000 (-0400) Subject: Resolve AliasedClass when determining owning class of association proxy X-Git-Tag: rel_1_1_15~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e11194015680c0f0554f829d8f4cc56e8b285e67;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Resolve AliasedClass when determining owning class of association proxy Fixed bug where the association proxy would inadvertently link itself to an :class:`.AliasedClass` object if it were called first with the :class:`.AliasedClass` as a parent, causing errors upon subsequent usage. Change-Id: I9161bab67766bb75d73ca54d712ad1cad6de40dc Fixes: #4116 (cherry picked from commit f14a58dea4b825beb4baaef44389880927543cc4) --- diff --git a/doc/build/changelog/unreleased_11/4116.rst b/doc/build/changelog/unreleased_11/4116.rst new file mode 100644 index 0000000000..f4cf1f47cc --- /dev/null +++ b/doc/build/changelog/unreleased_11/4116.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, orm, ext + :tickets: 4116 + :versions: 1.2.0b4 + + Fixed bug where the association proxy would inadvertently link itself + to an :class:`.AliasedClass` object if it were called first with + the :class:`.AliasedClass` as a parent, causing errors upon subsequent + usage. \ No newline at end of file diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py index 6f570a1fa9..8007849557 100644 --- a/lib/sqlalchemy/ext/associationproxy.py +++ b/lib/sqlalchemy/ext/associationproxy.py @@ -19,6 +19,7 @@ import weakref from .. import exc, orm, util from ..orm import collections, interfaces from ..sql import not_, or_ +from .. import inspect def association_proxy(target_collection, attr, **kw): @@ -245,7 +246,17 @@ class AssociationProxy(interfaces.InspectionAttrInfo): def __get__(self, obj, class_): if self.owning_class is None: - self.owning_class = class_ and class_ or type(obj) + 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) + if obj is None: return self diff --git a/test/ext/test_associationproxy.py b/test/ext/test_associationproxy.py index 0f86a9a14e..3ef09599cb 100644 --- a/test/ext/test_associationproxy.py +++ b/test/ext/test_associationproxy.py @@ -1,4 +1,4 @@ -from sqlalchemy.testing import eq_, assert_raises +from sqlalchemy.testing import eq_, assert_raises, is_ import copy import pickle @@ -14,6 +14,7 @@ from sqlalchemy import testing from sqlalchemy.testing.schema import Table, Column from sqlalchemy.testing.mock import Mock, call from sqlalchemy.testing.assertions import expect_warnings +from sqlalchemy.ext.declarative import declarative_base class DictCollection(dict): @@ -1574,6 +1575,31 @@ class DictOfTupleUpdateTest(fixtures.TestBase): ) +class AttributeAccessTest(fixtures.TestBase): + def test_resolve_aliased_class(self): + Base = declarative_base() + + class A(Base): + __tablename__ = 'a' + id = Column(Integer, primary_key=True) + value = Column(String) + + class B(Base): + __tablename__ = 'b' + id = Column(Integer, primary_key=True) + a_id = Column(Integer, ForeignKey(A.id)) + a = relationship(A) + a_value = association_proxy('a', 'value') + + spec = aliased(B).a_value + + is_(spec.owning_class, B) + + spec = B.a_value + + is_(spec.owning_class, B) + + class InfoTest(fixtures.TestBase): def test_constructor(self): assoc = association_proxy('a', 'b', info={'some_assoc': 'some_value'})