Files
sqlalchemy/test/orm/test_utils.py
T
Mike Bayer aeeff72e80 pep-484: ORM public API, constructors
for the moment, abandoning using @overload with
relationship() and mapped_column().  The overloads
are very difficult to get working at all, and
the overloads that were there all wouldn't pass on
mypy.  various techniques of getting them to
"work", meaning having right hand side dictate
what's legal on the left, have mixed success
and wont give consistent results; additionally,
it's legal to have Optional / non-optional
independent of nullable in any case for columns.
relationship cases are less ambiguous but mypy
was not going along with things.

we have a comprehensive system of allowing
left side annotations to drive the right side,
in the absense of explicit settings on the right.
so type-centric SQLAlchemy will be left-side
driven just like dataclasses, and the various flags
and switches on the right side will just not be
needed very much.

in other matters, one surprise, forgot to remove string support
from orm.join(A, B, "somename") or do deprecations
for it in 1.4.   This is a really not-directly-used
structure barely
mentioned in the docs for many years, the example
shows a relationship being used, not a string, so
we will just change it to raise the usual error here.

Change-Id: Iefbbb8d34548b538023890ab8b7c9a5d9496ec6e
2022-04-20 15:14:09 -04:00

1213 lines
37 KiB
Python

from sqlalchemy import Column
from sqlalchemy import inspect
from sqlalchemy import Integer
from sqlalchemy import MetaData
from sqlalchemy import select
from sqlalchemy import Table
from sqlalchemy import testing
from sqlalchemy.engine import result
from sqlalchemy.ext.hybrid import hybrid_method
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import aliased
from sqlalchemy.orm import clear_mappers
from sqlalchemy.orm import Session
from sqlalchemy.orm import synonym
from sqlalchemy.orm import util as orm_util
from sqlalchemy.orm import with_polymorphic
from sqlalchemy.orm.path_registry import PathRegistry
from sqlalchemy.orm.path_registry import PathToken
from sqlalchemy.orm.path_registry import RootRegistry
from sqlalchemy.testing import assert_raises
from sqlalchemy.testing import AssertsCompiledSQL
from sqlalchemy.testing import eq_
from sqlalchemy.testing import expect_raises
from sqlalchemy.testing import expect_warnings
from sqlalchemy.testing import fixtures
from sqlalchemy.testing import is_
from sqlalchemy.testing.assertions import is_true
from sqlalchemy.testing.fixtures import fixture_session
from test.orm import _fixtures
from .inheritance import _poly_fixtures
class AliasedClassTest(fixtures.MappedTest, AssertsCompiledSQL):
__dialect__ = "default"
def _fixture(self, cls, properties={}):
table = Table(
"point",
MetaData(),
Column("id", Integer(), primary_key=True),
Column("x", Integer),
Column("y", Integer),
)
clear_mappers()
self.mapper_registry.map_imperatively(
cls, table, properties=properties
)
return table
def test_simple(self):
class Point:
pass
table = self._fixture(Point)
alias = aliased(Point)
assert alias.id
assert alias.x
assert alias.y
assert Point.id.__clause_element__().table is table
assert alias.id.__clause_element__().table is not table
def test_named_entity(self):
class Point:
pass
self._fixture(Point)
alias = aliased(Point, name="pp")
self.assert_compile(
select(alias), "SELECT pp.id, pp.x, pp.y FROM point AS pp"
)
def test_named_selectable(self):
class Point:
pass
table = self._fixture(Point)
alias = aliased(table, name="pp")
self.assert_compile(
select(alias), "SELECT pp.id, pp.x, pp.y FROM point AS pp"
)
def test_not_instantiatable(self):
class Point:
pass
self._fixture(Point)
alias = aliased(Point)
assert_raises(TypeError, alias)
def test_instancemethod(self):
class Point:
def zero(self):
self.x, self.y = 0, 0
self._fixture(Point)
alias = aliased(Point)
assert Point.zero
assert getattr(alias, "zero")
def test_classmethod(self):
class Point:
@classmethod
def max_x(cls):
return 100
self._fixture(Point)
alias = aliased(Point)
assert Point.max_x
assert alias.max_x
assert Point.max_x() == alias.max_x() == 100
def test_simple_property(self):
class Point:
@property
def max_x(self):
return 100
self._fixture(Point)
alias = aliased(Point)
assert Point.max_x
assert Point.max_x != 100
assert alias.max_x
assert Point.max_x is alias.max_x
def test_descriptors(self):
class descriptor:
def __init__(self, fn):
self.fn = fn
def __get__(self, obj, owner):
if obj is not None:
return self.fn(obj, obj)
else:
return self
def method(self):
return "method"
class Point:
center = (0, 0)
@descriptor
def thing(self, arg):
return arg.center
self._fixture(Point)
alias = aliased(Point)
assert Point.thing != (0, 0)
assert Point().thing == (0, 0)
assert Point.thing.method() == "method"
assert alias.thing != (0, 0)
assert alias.thing.method() == "method"
def _assert_has_table(self, expr, table):
from sqlalchemy import Column # override testlib's override
for child in expr.get_children():
if isinstance(child, Column):
assert child.table is table
def test_hybrid_descriptor_one(self):
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
@hybrid_method
def left_of(self, other):
return self.x < other.x
self._fixture(Point)
alias = aliased(Point)
sess = fixture_session()
self.assert_compile(
sess.query(alias).filter(alias.left_of(Point)),
"SELECT point_1.id AS point_1_id, point_1.x AS point_1_x, "
"point_1.y AS point_1_y FROM point AS point_1, point "
"WHERE point_1.x < point.x",
)
def test_hybrid_descriptor_two(self):
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
@hybrid_property
def double_x(self):
return self.x * 2
self._fixture(Point)
alias = aliased(Point)
eq_(str(Point.double_x), "Point.double_x")
eq_(str(alias.double_x), "aliased(Point).double_x")
eq_(str(Point.double_x.__clause_element__()), "point.x * :x_1")
eq_(str(alias.double_x.__clause_element__()), "point_1.x * :x_1")
sess = fixture_session()
self.assert_compile(
sess.query(alias).filter(alias.double_x > Point.x),
"SELECT point_1.id AS point_1_id, point_1.x AS point_1_x, "
"point_1.y AS point_1_y FROM point AS point_1, point "
"WHERE point_1.x * :x_1 > point.x",
)
def test_hybrid_descriptor_three(self):
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
@hybrid_property
def x_alone(self):
return self.x
self._fixture(Point)
alias = aliased(Point)
eq_(str(Point.x_alone), "Point.x_alone")
eq_(str(alias.x_alone), "aliased(Point).x_alone")
# from __clause_element__() perspective, Point.x_alone
# and Point.x return the same thing, so that's good
eq_(str(Point.x.__clause_element__()), "point.x")
eq_(str(Point.x_alone.__clause_element__()), "point.x")
# same for the alias
eq_(str(alias.x + 1), "point_1.x + :x_1")
eq_(str(alias.x_alone + 1), "point_1.x + :x_1")
point_mapper = inspect(Point)
eq_(
Point.x_alone._annotations,
{
"entity_namespace": point_mapper,
"parententity": point_mapper,
"parentmapper": point_mapper,
"proxy_key": "x_alone",
"proxy_owner": point_mapper,
},
)
eq_(
Point.x._annotations,
{
"entity_namespace": point_mapper,
"parententity": point_mapper,
"parentmapper": point_mapper,
"proxy_key": "x",
"proxy_owner": point_mapper,
},
)
eq_(str(alias.x_alone == alias.x), "point_1.x = point_1.x")
a2 = aliased(Point)
eq_(str(a2.x_alone == alias.x), "point_1.x = point_2.x")
eq_(
a2.x._annotations,
{
"entity_namespace": inspect(a2),
"parententity": inspect(a2),
"parentmapper": point_mapper,
"proxy_key": "x",
"proxy_owner": inspect(a2),
},
)
sess = fixture_session()
self.assert_compile(
sess.query(alias).filter(alias.x_alone > Point.x),
"SELECT point_1.id AS point_1_id, point_1.x AS point_1_x, "
"point_1.y AS point_1_y FROM point AS point_1, point "
"WHERE point_1.x > point.x",
)
def test_proxy_descriptor_one(self):
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
self._fixture(Point, properties={"x_syn": synonym("x")})
alias = aliased(Point)
eq_(str(Point.x_syn), "Point.x_syn")
eq_(str(alias.x_syn), "aliased(Point).x_syn")
sess = fixture_session()
self.assert_compile(
sess.query(alias.x_syn).filter(alias.x_syn > Point.x_syn),
"SELECT point_1.x AS point_1_x FROM point AS point_1, point "
"WHERE point_1.x > point.x",
)
def test_meta_getattr_one(self):
class MetaPoint(type):
def __getattr__(cls, key):
if key == "x_syn":
return cls.x
raise AttributeError(key)
class Point(metaclass=MetaPoint):
pass
self._fixture(Point)
alias = aliased(Point)
eq_(str(Point.x_syn), "Point.x")
eq_(str(alias.x_syn), "aliased(Point).x")
# from __clause_element__() perspective, Point.x_syn
# and Point.x return the same thing, so that's good
eq_(str(Point.x.__clause_element__()), "point.x")
eq_(str(Point.x_syn.__clause_element__()), "point.x")
# same for the alias
eq_(str(alias.x + 1), "point_1.x + :x_1")
eq_(str(alias.x_syn + 1), "point_1.x + :x_1")
is_(Point.x_syn.__clause_element__(), Point.x.__clause_element__())
eq_(str(alias.x_syn == alias.x), "point_1.x = point_1.x")
a2 = aliased(Point)
eq_(str(a2.x_syn == alias.x), "point_1.x = point_2.x")
sess = fixture_session()
self.assert_compile(
sess.query(alias).filter(alias.x_syn > Point.x),
"SELECT point_1.id AS point_1_id, point_1.x AS point_1_x, "
"point_1.y AS point_1_y FROM point AS point_1, point "
"WHERE point_1.x > point.x",
)
def test_meta_getattr_two(self):
class MetaPoint(type):
def __getattr__(cls, key):
if key == "double_x":
return cls._impl_double_x
raise AttributeError(key)
class Point(metaclass=MetaPoint):
@hybrid_property
def _impl_double_x(self):
return self.x * 2
self._fixture(Point)
alias = aliased(Point)
eq_(str(Point.double_x), "Point._impl_double_x")
eq_(str(alias.double_x), "aliased(Point)._impl_double_x")
eq_(str(Point.double_x.__clause_element__()), "point.x * :x_1")
eq_(str(alias.double_x.__clause_element__()), "point_1.x * :x_1")
sess = fixture_session()
self.assert_compile(
sess.query(alias).filter(alias.double_x > Point.x),
"SELECT point_1.id AS point_1_id, point_1.x AS point_1_x, "
"point_1.y AS point_1_y FROM point AS point_1, point "
"WHERE point_1.x * :x_1 > point.x",
)
def test_meta_getattr_three(self):
class MetaPoint(type):
def __getattr__(cls, key):
@hybrid_property
def double_x(me):
return me.x * 2
if key == "double_x":
return double_x.__get__(None, cls)
raise AttributeError(key)
class Point(metaclass=MetaPoint):
pass
self._fixture(Point)
alias = aliased(Point)
eq_(str(Point.double_x.__clause_element__()), "point.x * :x_1")
eq_(str(alias.double_x.__clause_element__()), "point_1.x * :x_1")
sess = fixture_session()
self.assert_compile(
sess.query(alias).filter(alias.double_x > Point.x),
"SELECT point_1.id AS point_1_id, point_1.x AS point_1_x, "
"point_1.y AS point_1_y FROM point AS point_1, point "
"WHERE point_1.x * :x_1 > point.x",
)
def test_parententity_vs_parentmapper(self):
class Point:
pass
self._fixture(Point, properties={"x_syn": synonym("x")})
pa = aliased(Point)
is_(Point.x_syn._parententity, inspect(Point))
is_(Point.x._parententity, inspect(Point))
is_(Point.x_syn._parentmapper, inspect(Point))
is_(Point.x._parentmapper, inspect(Point))
is_(
Point.x_syn.__clause_element__()._annotations["parententity"],
inspect(Point),
)
is_(
Point.x.__clause_element__()._annotations["parententity"],
inspect(Point),
)
is_(
Point.x_syn.__clause_element__()._annotations["parentmapper"],
inspect(Point),
)
is_(
Point.x.__clause_element__()._annotations["parentmapper"],
inspect(Point),
)
pa = aliased(Point)
is_(pa.x_syn._parententity, inspect(pa))
is_(pa.x._parententity, inspect(pa))
is_(pa.x_syn._parentmapper, inspect(Point))
is_(pa.x._parentmapper, inspect(Point))
is_(
pa.x_syn.__clause_element__()._annotations["parententity"],
inspect(pa),
)
is_(
pa.x.__clause_element__()._annotations["parententity"], inspect(pa)
)
is_(
pa.x_syn.__clause_element__()._annotations["parentmapper"],
inspect(Point),
)
is_(
pa.x.__clause_element__()._annotations["parentmapper"],
inspect(Point),
)
class IdentityKeyTest(_fixtures.FixtureTest):
run_inserts = None
def _cases():
return testing.combinations(
(orm_util,), (Session,), argnames="ormutil"
)
@_cases()
def test_identity_key_1(self, ormutil):
User, users = self.classes.User, self.tables.users
self.mapper_registry.map_imperatively(User, users)
key = ormutil.identity_key(User, [1])
eq_(key, (User, (1,), None))
key = ormutil.identity_key(User, ident=[1])
eq_(key, (User, (1,), None))
@_cases()
def test_identity_key_scalar(self, ormutil):
User, users = self.classes.User, self.tables.users
self.mapper_registry.map_imperatively(User, users)
key = ormutil.identity_key(User, 1)
eq_(key, (User, (1,), None))
key = ormutil.identity_key(User, ident=1)
eq_(key, (User, (1,), None))
@_cases()
def test_identity_key_2(self, ormutil):
users, User = self.tables.users, self.classes.User
self.mapper_registry.map_imperatively(User, users)
s = fixture_session()
u = User(name="u1")
s.add(u)
s.flush()
key = ormutil.identity_key(instance=u)
eq_(key, (User, (u.id,), None))
@_cases()
@testing.combinations("dict", "row", "mapping", argnames="rowtype")
def test_identity_key_3(self, ormutil, rowtype):
"""test a real Row works with identity_key.
this was broken w/ 1.4 future mode as we are assuming a mapping
here. to prevent regressions, identity_key now accepts any of
dict, RowMapping, Row for the "row".
found_during_type_annotation
"""
User, users = self.classes.User, self.tables.users
self.mapper_registry.map_imperatively(User, users)
if rowtype == "dict":
row = {users.c.id: 1, users.c.name: "Frank"}
elif rowtype in ("mapping", "row"):
row = result.result_tuple([users.c.id, users.c.name])((1, "Frank"))
if rowtype == "mapping":
row = row._mapping
key = ormutil.identity_key(User, row=row)
eq_(key, (User, (1,), None))
def test_identity_key_token(self):
User, users = self.classes.User, self.tables.users
self.mapper_registry.map_imperatively(User, users)
key = orm_util.identity_key(User, [1], identity_token="token")
eq_(key, (User, (1,), "token"))
key = orm_util.identity_key(User, ident=[1], identity_token="token")
eq_(key, (User, (1,), "token"))
class PathRegistryTest(_fixtures.FixtureTest):
run_setup_mappers = "once"
run_inserts = None
run_deletes = None
@classmethod
def setup_mappers(cls):
cls._setup_stock_mapping()
def test_root_registry(self):
umapper = inspect(self.classes.User)
is_(RootRegistry()[umapper], umapper._path_registry)
eq_(RootRegistry()[umapper], PathRegistry.coerce((umapper,)))
def test_expand(self):
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
path = PathRegistry.coerce((umapper,))
eq_(
path[umapper.attrs.addresses][amapper][
amapper.attrs.email_address
],
PathRegistry.coerce(
(
umapper,
umapper.attrs.addresses,
amapper,
amapper.attrs.email_address,
)
),
)
def test_entity_boolean(self):
umapper = inspect(self.classes.User)
path = PathRegistry.coerce((umapper,))
is_(bool(path), True)
def test_key_boolean(self):
umapper = inspect(self.classes.User)
path = PathRegistry.coerce((umapper, umapper.attrs.addresses))
is_(bool(path), True)
def test_aliased_class(self):
User = self.classes.User
ua = aliased(User)
ua_insp = inspect(ua)
path = PathRegistry.coerce((ua_insp, ua_insp.mapper.attrs.addresses))
assert path.parent.is_aliased_class
def test_indexed_entity(self):
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
path = PathRegistry.coerce(
(
umapper,
umapper.attrs.addresses,
amapper,
amapper.attrs.email_address,
)
)
is_(path[0], umapper)
is_(path[2], amapper)
def test_indexed_key_token(self):
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
path = PathRegistry.coerce(
(
umapper,
umapper.attrs.addresses,
amapper,
PathToken.intern(":*"),
)
)
is_true(path.is_token)
eq_(path[1], umapper.attrs.addresses)
eq_(path[3], ":*")
with expect_raises(IndexError):
path[amapper]
def test_slice_token(self):
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
path = PathRegistry.coerce(
(
umapper,
umapper.attrs.addresses,
amapper,
PathToken.intern(":*"),
)
)
is_true(path.is_token)
eq_(path[1:3], (umapper.attrs.addresses, amapper))
def test_indexed_key(self):
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
path = PathRegistry.coerce(
(
umapper,
umapper.attrs.addresses,
amapper,
amapper.attrs.email_address,
)
)
eq_(path[1], umapper.attrs.addresses)
eq_(path[3], amapper.attrs.email_address)
def test_slice(self):
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
path = PathRegistry.coerce(
(
umapper,
umapper.attrs.addresses,
amapper,
amapper.attrs.email_address,
)
)
eq_(path[1:3], (umapper.attrs.addresses, amapper))
def test_addition(self):
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
p2 = PathRegistry.coerce((amapper, amapper.attrs.email_address))
eq_(
p1 + p2,
PathRegistry.coerce(
(
umapper,
umapper.attrs.addresses,
amapper,
amapper.attrs.email_address,
)
),
)
def test_length(self):
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
pneg1 = PathRegistry.coerce(())
p0 = PathRegistry.coerce((umapper,))
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
p3 = PathRegistry.coerce(
(
umapper,
umapper.attrs.addresses,
amapper,
amapper.attrs.email_address,
)
)
eq_(len(pneg1), 0)
eq_(len(p0), 1)
eq_(len(p1), 2)
eq_(len(p2), 3)
eq_(len(p3), 4)
eq_(pneg1.length, 0)
eq_(p0.length, 1)
eq_(p1.length, 2)
eq_(p2.length, 3)
eq_(p3.length, 4)
def test_eq(self):
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
u_alias = inspect(aliased(self.classes.User))
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
p3 = PathRegistry.coerce((umapper, umapper.attrs.name))
p4 = PathRegistry.coerce((u_alias, umapper.attrs.addresses))
p5 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
p6 = PathRegistry.coerce(
(amapper, amapper.attrs.user, umapper, umapper.attrs.addresses)
)
p7 = PathRegistry.coerce(
(
amapper,
amapper.attrs.user,
umapper,
umapper.attrs.addresses,
amapper,
amapper.attrs.email_address,
)
)
is_(p1 == p2, True)
is_(p1 == p3, False)
is_(p1 == p4, False)
is_(p1 == p5, False)
is_(p6 == p7, False)
is_(p6 == p7.parent.parent, True)
is_(p1 != p2, False)
is_(p1 != p3, True)
is_(p1 != p4, True)
is_(p1 != p5, True)
def test_eq_non_path(self):
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
u_alias = inspect(aliased(self.classes.User))
p1 = PathRegistry.coerce((umapper,))
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
p3 = PathRegistry.coerce((u_alias, umapper.attrs.addresses))
p4 = PathRegistry.coerce((u_alias, umapper.attrs.addresses, amapper))
p5 = PathRegistry.coerce((u_alias,)).token(":*")
non_object = 54.1432
for obj in [p1, p2, p3, p4, p5]:
with expect_warnings(
"Comparison of PathRegistry to "
"<.* 'float'> is not supported"
):
is_(obj == non_object, False)
with expect_warnings(
"Comparison of PathRegistry to "
"<.* 'float'> is not supported"
):
is_(obj != non_object, True)
def test_contains_mapper(self):
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
assert p1.contains_mapper(umapper)
assert not p1.contains_mapper(amapper)
def test_path(self):
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
p3 = PathRegistry.coerce((amapper, amapper.attrs.email_address))
eq_(p1.path, (umapper, umapper.attrs.addresses))
eq_(p2.path, (umapper, umapper.attrs.addresses, amapper))
eq_(p3.path, (amapper, amapper.attrs.email_address))
def test_registry_set(self):
reg = {}
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
p3 = PathRegistry.coerce((amapper, amapper.attrs.email_address))
p1.set(reg, "p1key", "p1value")
p2.set(reg, "p2key", "p2value")
p3.set(reg, "p3key", "p3value")
eq_(
reg,
{
("p1key", p1.path): "p1value",
("p2key", p2.path): "p2value",
("p3key", p3.path): "p3value",
},
)
def test_registry_get(self):
reg = {}
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
p3 = PathRegistry.coerce((amapper, amapper.attrs.email_address))
reg.update(
{
("p1key", p1.path): "p1value",
("p2key", p2.path): "p2value",
("p3key", p3.path): "p3value",
}
)
eq_(p1.get(reg, "p1key"), "p1value")
eq_(p2.get(reg, "p2key"), "p2value")
eq_(p2.get(reg, "p1key"), None)
eq_(p3.get(reg, "p3key"), "p3value")
eq_(p3.get(reg, "p1key"), None)
def test_registry_contains(self):
reg = {}
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
p3 = PathRegistry.coerce((amapper, amapper.attrs.email_address))
reg.update(
{
("p1key", p1.path): "p1value",
("p2key", p2.path): "p2value",
("p3key", p3.path): "p3value",
}
)
assert p1.contains(reg, "p1key")
assert not p1.contains(reg, "p2key")
assert p3.contains(reg, "p3key")
assert not p2.contains(reg, "fake")
def test_registry_setdefault(self):
reg = {}
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
p1 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
reg.update({("p1key", p1.path): "p1value"})
p1.setdefault(reg, "p1key", "p1newvalue_a")
p1.setdefault(reg, "p1key_new", "p1newvalue_b")
p2.setdefault(reg, "p2key", "p2newvalue")
eq_(
reg,
{
("p1key", p1.path): "p1value",
("p1key_new", p1.path): "p1newvalue_b",
("p2key", p2.path): "p2newvalue",
},
)
def test_serialize(self):
User = self.classes.User
Address = self.classes.Address
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
p1 = PathRegistry.coerce(
(
umapper,
umapper.attrs.addresses,
amapper,
amapper.attrs.email_address,
)
)
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
p3 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
eq_(p1.serialize(), [(User, "addresses"), (Address, "email_address")])
eq_(p2.serialize(), [(User, "addresses"), (Address, None)])
eq_(p3.serialize(), [(User, "addresses")])
def test_deseralize(self):
User = self.classes.User
Address = self.classes.Address
umapper = inspect(self.classes.User)
amapper = inspect(self.classes.Address)
p1 = PathRegistry.coerce(
(
umapper,
umapper.attrs.addresses,
amapper,
amapper.attrs.email_address,
)
)
p2 = PathRegistry.coerce((umapper, umapper.attrs.addresses, amapper))
p3 = PathRegistry.coerce((umapper, umapper.attrs.addresses))
eq_(
PathRegistry.deserialize(
[(User, "addresses"), (Address, "email_address")]
),
p1,
)
eq_(
PathRegistry.deserialize([(User, "addresses"), (Address, None)]),
p2,
)
eq_(PathRegistry.deserialize([(User, "addresses")]), p3)
class PathRegistryInhTest(_poly_fixtures._Polymorphic):
run_setup_mappers = "once"
run_inserts = None
run_deletes = None
def test_plain(self):
Person = _poly_fixtures.Person
Engineer = _poly_fixtures.Engineer
pmapper = inspect(Person)
emapper = inspect(Engineer)
p1 = PathRegistry.coerce((pmapper, emapper.attrs.machines))
# given a mapper and an attribute on a subclass,
# the path converts what you get to be against that subclass
eq_(p1.path, (emapper, emapper.attrs.machines))
def test_plain_compound(self):
Company = _poly_fixtures.Company
Person = _poly_fixtures.Person
Engineer = _poly_fixtures.Engineer
cmapper = inspect(Company)
pmapper = inspect(Person)
emapper = inspect(Engineer)
p1 = PathRegistry.coerce(
(cmapper, cmapper.attrs.employees, pmapper, emapper.attrs.machines)
)
# given a mapper and an attribute on a subclass,
# the path converts what you get to be against that subclass
eq_(
p1.path,
(
cmapper,
cmapper.attrs.employees,
emapper,
emapper.attrs.machines,
),
)
def test_plain_aliased(self):
Person = _poly_fixtures.Person
Engineer = _poly_fixtures.Engineer
emapper = inspect(Engineer)
p_alias = aliased(Person)
p_alias = inspect(p_alias)
p1 = PathRegistry.coerce((p_alias, emapper.attrs.machines))
# plain AliasedClass - the path keeps that AliasedClass directly
# as is in the path
eq_(p1.path, (p_alias, emapper.attrs.machines))
def test_plain_aliased_compound(self):
Company = _poly_fixtures.Company
Person = _poly_fixtures.Person
Engineer = _poly_fixtures.Engineer
cmapper = inspect(Company)
emapper = inspect(Engineer)
c_alias = aliased(Company)
p_alias = aliased(Person)
c_alias = inspect(c_alias)
p_alias = inspect(p_alias)
p1 = PathRegistry.coerce(
(c_alias, cmapper.attrs.employees, p_alias, emapper.attrs.machines)
)
# plain AliasedClass - the path keeps that AliasedClass directly
# as is in the path
eq_(
p1.path,
(
c_alias,
cmapper.attrs.employees,
p_alias,
emapper.attrs.machines,
),
)
def test_with_poly_sub(self):
Company = _poly_fixtures.Company
Person = _poly_fixtures.Person
Engineer = _poly_fixtures.Engineer
emapper = inspect(Engineer)
cmapper = inspect(Company)
p_poly = with_polymorphic(Person, [Engineer])
e_poly_insp = inspect(p_poly.Engineer) # noqa - used by comment below
p_poly_insp = inspect(p_poly)
p1 = PathRegistry.coerce((p_poly_insp, emapper.attrs.machines))
# changes as of #5082: when a with_polymorphic is in the middle
# of a path, the natural path makes sure it uses the base mappers,
# however when it's at the root, the with_polymorphic stays in
# the natural path
# this behavior is the same as pre #5082, it was temporarily changed
# but this proved to be incorrect. The path starts on a
# with_polymorphic(), so a Query will "naturally" construct a path
# that comes from that wp.
eq_(p1.path, (e_poly_insp, emapper.attrs.machines))
eq_(p1.natural_path, (e_poly_insp, emapper.attrs.machines))
# this behavior is new as of the final version of #5082.
# the path starts on a normal entity and has a with_polymorphic
# in the middle, for this to match what Query will generate it needs
# to use the non aliased mappers in the natural path.
p2 = PathRegistry.coerce(
(
cmapper,
cmapper.attrs.employees,
p_poly_insp,
emapper.attrs.machines,
)
)
eq_(
p2.path,
(
cmapper,
cmapper.attrs.employees,
e_poly_insp,
emapper.attrs.machines,
),
)
eq_(
p2.natural_path,
(
cmapper,
cmapper.attrs.employees,
emapper,
emapper.attrs.machines,
),
)
def test_with_poly_base_two(self):
Company = _poly_fixtures.Company
Person = _poly_fixtures.Person
Engineer = _poly_fixtures.Engineer
cmapper = inspect(Company)
pmapper = inspect(Person)
p_poly = with_polymorphic(Person, [Engineer])
e_poly_insp = inspect(p_poly.Engineer) # noqa - used by comment below
p_poly_insp = inspect(p_poly)
p1 = PathRegistry.coerce(
(
cmapper,
cmapper.attrs.employees,
p_poly_insp,
pmapper.attrs.paperwork,
)
)
eq_(
p1.path,
(
cmapper,
cmapper.attrs.employees,
p_poly_insp,
pmapper.attrs.paperwork,
),
)
eq_(
p1.natural_path,
(
cmapper,
cmapper.attrs.employees,
pmapper,
pmapper.attrs.paperwork,
),
)
def test_nonpoly_oftype_aliased_subclass_onroot(self):
Engineer = _poly_fixtures.Engineer
eng_alias = aliased(Engineer)
ea_insp = inspect(eng_alias)
p1 = PathRegistry.coerce((ea_insp, ea_insp.mapper.attrs.paperwork))
eq_(p1.path, (ea_insp, ea_insp.mapper.attrs.paperwork))
eq_(p1.natural_path, (ea_insp, ea_insp.mapper.attrs.paperwork))
def test_nonpoly_oftype_aliased_subclass(self):
Company = _poly_fixtures.Company
Person = _poly_fixtures.Person
Engineer = _poly_fixtures.Engineer
cmapper = inspect(Company)
pmapper = inspect(Person)
eng_alias = aliased(Engineer)
ea_insp = inspect(eng_alias)
p1 = PathRegistry.coerce(
(
cmapper,
cmapper.attrs.employees,
ea_insp,
ea_insp.mapper.attrs.paperwork,
)
)
eq_(
p1.path,
(
cmapper,
cmapper.attrs.employees,
ea_insp,
ea_insp.mapper.attrs.paperwork,
),
)
eq_(
p1.natural_path,
(
cmapper,
cmapper.attrs.employees,
pmapper,
pmapper.attrs.paperwork,
),
)
def test_nonpoly_oftype_subclass(self):
Company = _poly_fixtures.Company
Person = _poly_fixtures.Person
Engineer = _poly_fixtures.Engineer
emapper = inspect(Engineer)
cmapper = inspect(Company)
pmapper = inspect(Person)
p1 = PathRegistry.coerce(
(
cmapper,
cmapper.attrs.employees,
emapper,
emapper.attrs.paperwork,
)
)
eq_(
p1.path,
(
cmapper,
cmapper.attrs.employees,
pmapper,
pmapper.attrs.paperwork,
),
)
eq_(
p1.natural_path,
(
cmapper,
cmapper.attrs.employees,
pmapper,
pmapper.attrs.paperwork,
),
)
def test_with_poly_base_one(self):
Person = _poly_fixtures.Person
Engineer = _poly_fixtures.Engineer
pmapper = inspect(Person)
emapper = inspect(Engineer)
p_poly = with_polymorphic(Person, [Engineer])
p_poly = inspect(p_poly)
# "name" is actually on Person, not Engineer
p1 = PathRegistry.coerce((p_poly, emapper.attrs.name))
# polymorphic AliasedClass - because "name" is on Person,
# we get Person, not Engineer
eq_(p1.path, (p_poly, pmapper.attrs.name))
def test_with_poly_use_mapper(self):
Person = _poly_fixtures.Person
Engineer = _poly_fixtures.Engineer
emapper = inspect(Engineer)
p_poly = with_polymorphic(Person, [Engineer], _use_mapper_path=True)
p_poly = inspect(p_poly)
p1 = PathRegistry.coerce((p_poly, emapper.attrs.machines))
# polymorphic AliasedClass with the "use_mapper_path" flag -
# the AliasedClass acts just like the base mapper
eq_(p1.path, (emapper, emapper.attrs.machines))