diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2018-02-09 16:12:31 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2018-02-09 16:30:42 -0500 |
| commit | 650b9eddae0eb198c8f8dc2d1e1e3c6ac53b18f3 (patch) | |
| tree | ecb640dd5d1e0f000a1dd97c96f4fdb2f5841570 /test/ext | |
| parent | 9ef891e82c187b3fda0f778073f258ef8b55124f (diff) | |
| download | sqlalchemy-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.py | 158 |
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): |
