summaryrefslogtreecommitdiff
path: root/test/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 /test/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 'test/ext')
-rw-r--r--test/ext/test_associationproxy.py158
1 files changed, 158 insertions, 0 deletions
diff --git a/test/ext/test_associationproxy.py b/test/ext/test_associationproxy.py
index 0052bb188..111ba91a8 100644
--- a/test/ext/test_associationproxy.py
+++ b/test/ext/test_associationproxy.py
@@ -15,6 +15,7 @@ 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
+from sqlalchemy.ext.declarative import declared_attr
class DictCollection(dict):
@@ -1891,6 +1892,9 @@ class DictOfTupleUpdateTest(fixtures.TestBase):
class AttributeAccessTest(fixtures.TestBase):
+ def teardown(self):
+ clear_mappers()
+
def test_resolve_aliased_class(self):
Base = declarative_base()
@@ -1914,6 +1918,160 @@ class AttributeAccessTest(fixtures.TestBase):
is_(spec.owning_class, B)
+ def test_resolved_w_subclass(self):
+ # test for issue #4185, as well as several below
+
+ Base = declarative_base()
+
+ class Mixin(object):
+ @declared_attr
+ def children(cls):
+ return association_proxy('_children', 'value')
+
+ # 1. build parent, Mixin.children gets invoked, we add
+ # Parent.children
+ 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)
+
+ # 2. declarative builds up SubParent, scans through all attributes
+ # over all classes. Hits Mixin, hits "children", accesses "children"
+ # in terms of the class, e.g. SubParent.children. SubParent isn't
+ # mapped yet. association proxy then sets up "owning_class"
+ # as NoneType.
+ class SubParent(Parent):
+ __tablename__ = 'subparent'
+ id = Column(Integer, ForeignKey(Parent.id), primary_key=True)
+
+ configure_mappers()
+
+ # 3. which would break here.
+ p1 = Parent()
+ eq_(p1.children, [])
+
+ def test_resolved_to_correct_class_one(self):
+ Base = declarative_base()
+
+ class Parent(Base):
+ __tablename__ = 'parent'
+ id = Column(Integer, primary_key=True)
+ _children = relationship("Child")
+ children = association_proxy('_children', 'value')
+
+ class Child(Base):
+ __tablename__ = 'child'
+ parent_id = Column(
+ Integer, ForeignKey(Parent.id), primary_key=True)
+
+ class SubParent(Parent):
+ __tablename__ = 'subparent'
+ id = Column(Integer, ForeignKey(Parent.id), primary_key=True)
+
+ is_(SubParent.children.owning_class, Parent)
+
+ def test_resolved_to_correct_class_two(self):
+ Base = declarative_base()
+
+ class Parent(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)
+
+ class SubParent(Parent):
+ __tablename__ = 'subparent'
+ id = Column(Integer, ForeignKey(Parent.id), primary_key=True)
+ children = association_proxy('_children', 'value')
+
+ is_(SubParent.children.owning_class, SubParent)
+
+ def test_resolved_to_correct_class_three(self):
+ Base = declarative_base()
+
+ class Parent(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)
+
+ class SubParent(Parent):
+ __tablename__ = 'subparent'
+ id = Column(Integer, ForeignKey(Parent.id), primary_key=True)
+ children = association_proxy('_children', 'value')
+
+ class SubSubParent(SubParent):
+ __tablename__ = 'subsubparent'
+ id = Column(Integer, ForeignKey(SubParent.id), primary_key=True)
+
+ is_(SubSubParent.children.owning_class, SubParent)
+
+ def test_resolved_to_correct_class_four(self):
+ Base = declarative_base()
+
+ class Parent(Base):
+ __tablename__ = 'parent'
+ id = Column(Integer, primary_key=True)
+ _children = relationship("Child")
+ children = association_proxy(
+ '_children', 'value', creator=lambda value: Child(value=value))
+
+ class Child(Base):
+ __tablename__ = 'child'
+ parent_id = Column(
+ Integer, ForeignKey(Parent.id), primary_key=True)
+ value = Column(String)
+
+ class SubParent(Parent):
+ __tablename__ = 'subparent'
+ id = Column(Integer, ForeignKey(Parent.id), primary_key=True)
+
+ sp = SubParent()
+ sp.children = 'c'
+ is_(SubParent.children.owning_class, Parent)
+
+ def test_never_assign_nonetype(self):
+ foo = association_proxy('x', 'y')
+ foo._calc_owner(None, None)
+ is_(foo.owning_class, None)
+
+ class Bat(object):
+ foo = association_proxy('x', 'y')
+
+ Bat.foo
+ is_(Bat.foo.owning_class, None)
+
+ b1 = Bat()
+ assert_raises_message(
+ exc.InvalidRequestError,
+ "This association proxy has no mapped owning class; "
+ "can't locate a mapped property",
+ getattr, b1, "foo"
+ )
+ is_(Bat.foo.owning_class, None)
+
+ # after all that, we can map it
+ mapper(
+ Bat,
+ Table('bat', MetaData(), Column('x', Integer, primary_key=True)))
+
+ # answer is correct
+ is_(Bat.foo.owning_class, Bat)
+
class InfoTest(fixtures.TestBase):
def test_constructor(self):