--- /dev/null
+.. change::
+ :tags: usecase, orm
+ :tickets: 12398
+
+ Improved error messages raised when ORM loader strategy options cannot be
+ applied to a query. Messages now render the offending option in a
+ user-friendly form such as ``joinedload(User.orders)`` rather than exposing
+ internal class and path representations, and the "does not apply to root
+ entities" message now includes the option that triggered the error. The
+ same user-friendly rendering is also applied to the "conflicting loader
+ strategy" message and to the ``of_type()`` representation in "does not
+ link" messages. Originating pull request courtesy Jan Vollmer.
]
+class _HasPathString(Protocol):
+ def path_string(self) -> str: ...
+
+
class _ORMAdapterProto(Protocol):
"""protocol for the :class:`.AliasedInsp._orm_adapt_element` method
which is a synonym for :class:`.AliasedInsp._adapt_element`.
import typing
from typing import Any
from typing import Callable
+from typing import cast
from typing import Dict
from typing import Generic
from typing import Literal
from typing import Union
from . import exc
+from ._typing import _HasPathString
from ._typing import _O
from ._typing import insp_is_mapper
from .. import exc as sa_exc
return state_str(state) + "." + attribute
+def entity_str(entity: Any) -> str:
+ """Return a user-facing string for a mapped entity, such as a mapped
+ class, :class:`_orm.Mapper`, or :func:`_orm.aliased` construct.
+
+ Also accepts a :class:`_orm.PathRegistry` directly, which is itself
+ ``inspect()``-able and implements ``path_string()``; in that case
+ this is equivalent to calling
+ :meth:`_orm.PathRegistry.path_string` directly.
+
+ """
+ return cast(_HasPathString, inspection.inspect(entity)).path_string()
+
+
def object_mapper(instance: _T) -> Mapper[_T]:
"""Given an object, return the primary Mapper associated with the object
instance.
getattr(self, "key", "no key"),
)
+ def path_string(self) -> str:
+ """Return a user-facing name for this :class:`.MapperProperty`,
+ for use in a :class:`_orm.PathRegistry` string representation.
+
+ """
+ return f"{self.parent.class_.__name__}.{self.key}"
+
@inspection._self_inspects
class PropComparator(SQLORMOperations[_T_co], Generic[_T_co], ColumnOperators):
),
)
+ def path_string(self) -> str:
+ """Return a user-facing name for this :class:`_orm.Mapper`,
+ for use in a :class:`_orm.PathRegistry` string representation.
+
+ """
+ return self.class_.__name__
+
def _is_orphan(self, state: InstanceState[_O]) -> bool:
orphan_possible = False
for mapper in self.iterate_to_root():
from . import base as orm_base
from ._typing import insp_is_mapper_property
from .. import exc
+from .. import inspection
from .. import util
from ..sql import visitors
from ..sql.cache_key import HasCacheKey
_DEFAULT_TOKEN = "_sa_default"
+@inspection._self_inspects
class PathRegistry(HasCacheKey):
"""Represent query load paths and registry functions.
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.path!r})"
+ def path_string(self) -> str:
+ """Return a user-facing string representation of this path,
+ e.g. ``"User.orders -> Order.items"``.
+
+ """
+
+ raw = self.path
+ parts = []
+ lraw = len(raw)
+ for i in range(0, lraw - 1, 2):
+ entity = raw[i]
+ prop = raw[i + 1]
+ prop_key = getattr(prop, "key", str(prop))
+
+ if (
+ i < lraw - 2
+ and cast(
+ "_InternalEntityType[Any]", raw[i + 2]
+ ).is_aliased_class
+ ):
+ parts.append(
+ f"{orm_base.entity_str(entity)}.{prop_key}."
+ f"of_type({orm_base.entity_str(raw[i + 2])})"
+ )
+ else:
+ parts.append(f"{orm_base.entity_str(entity)}.{prop_key}")
+
+ return (
+ " -> ".join(parts)
+ if parts
+ else orm_base.entity_str(self.path[0]) if self.path else ""
+ )
+
class _CreatesToken(PathRegistry):
__slots__ = ()
prop: StrategizedProperty[Any]
mapper: Optional[Mapper[Any]]
entity: Optional[_InternalEntityType[Any]]
+ parent: _AbstractEntityRegistry
def __init__(
self, parent: _AbstractEntityRegistry, prop: StrategizedProperty[Any]
from typing import Union
from . import util as orm_util
-from ._typing import insp_is_aliased_class
from ._typing import insp_is_attribute
from ._typing import insp_is_mapper
from ._typing import insp_is_mapper_property
from .attributes import QueryableAttribute
+from .base import entity_str
from .base import InspectionAttr
from .interfaces import LoaderOption
from .path_registry import _AbstractEntityRegistry
_OptsType = Dict[str, Any]
_AttrGroupType = Tuple[_AttrType, ...]
+# maps _StrategyKey tuples to the user-facing loader function name,
+# populated by the @_strategy_labels() decorator below
+_STRATEGY_FN_LABELS: Dict[Any, str] = {}
+
class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
__slots__ = ("propagate_to_loaders",)
self.additional_source_entities = ()
def __str__(self) -> str:
- return f"Load({self.path[0]})"
+ return f"Load({entity_str(self.path[0])})"
@classmethod
def _construct_for_existing_path(
if not found_entities:
raise sa_exc.ArgumentError(
"Query has only expression-based entities; "
- f"attribute loader options for {path[0]} can't "
- "be applied here."
+ f"attribute loader option {self._to_option_method_string()} "
+ "can't be applied here."
)
else:
raise sa_exc.ArgumentError(
- f"Mapped class {path[0]} does not apply to any of the "
- f"root entities in this query, e.g. "
+ f"Mapped class {entity_str(path[0])} referenced in "
+ f"option {self._to_option_method_string()} does not apply "
+ f"to any of the root entities in this query, e.g. "
f"""{
", ".join(
- str(x.entity_zero)
+ entity_str(x.entity_zero)
for x in mapper_entities if x.entity_zero
)}. Please """
"specify the full path "
):
raise sa_exc.ArgumentError(
f'Attribute "{self.path[1]}" does not link '
- f'from element "{parent.path[-1]}".'
+ f'from element "{entity_str(parent.path[-1])}".'
)
return self._prepend_path(parent.path)
return replacement
raise sa_exc.InvalidRequestError(
- f"Loader strategies for {replacement.path} conflict"
+ f"Loader strategy replacement "
+ f"{replacement._to_option_method_string()} is in conflict "
+ f"with existing strategy {existing._to_option_method_string()}"
)
+ def _to_option_method_string(self) -> str:
+ """Return a string representation of this :class:`._LoadElement`
+ as it would be written as a loader option method call, e.g.
+ ``"joinedload(User.orders)"``.
+
+ """
+ assert (
+ self.strategy is not None
+ ), "to_option_method_string() requires a strategy to be set"
+
+ for opt_key in self.local_opts:
+ fn = _STRATEGY_FN_LABELS.get((self.strategy, opt_key))
+ if fn:
+ break
+ else:
+ fn = _STRATEGY_FN_LABELS.get((self.strategy, None))
+
+ assert (
+ fn is not None
+ ), f"No _STRATEGY_FN_LABELS entry for strategy {self.strategy!r}"
+
+ return f"{fn}({self.path.path_string()})"
+
class _AttributeStrategyLoad(_LoadElement):
"""Loader strategies against specific relationship or column paths.
return insp, lead_entity, prop
+def _strategy_labels(
+ *strategy_keys: "Any",
+ discriminating_opt: Optional[str] = None,
+) -> Callable[[_FN], _FN]:
+ """Decorator that registers strategy key(s) -> function name in
+ ``_STRATEGY_FN_LABELS``. Apply below ``@loader_unbound_fn`` so that
+ ``fn.__name__`` is still the original function name when the decorator
+ runs.
+
+ :param discriminating_opt: optional local_opts key that distinguishes
+ this function from another function with the same strategy key.
+ When set, the entry is stored under ``(strategy_key, opt_key)``
+ instead of ``(strategy_key, None)``, and ``__str__`` will use this
+ function name when that opt is present in ``local_opts``.
+ """
+
+ def decorator(fn: _FN) -> _FN:
+ for key in strategy_keys:
+ _STRATEGY_FN_LABELS[(key, discriminating_opt)] = fn.__name__
+ return fn
+
+ return decorator
+
+
def loader_unbound_fn(fn: _FN) -> _FN:
"""decorator that applies docstrings between standalone loader functions
and the loader methods on :class:`._AbstractLoad`.
@loader_unbound_fn
+@_strategy_labels((("lazy", "joined"),), discriminating_opt="eager_from_alias")
def contains_eager(*keys: _AttrType, **kw: Any) -> _AbstractLoad:
return _generate_from_keys(Load.contains_eager, keys, True, kw)
@loader_unbound_fn
+@_strategy_labels((("lazy", "joined"),))
def joinedload(*keys: _AttrType, **kw: Any) -> _AbstractLoad:
return _generate_from_keys(Load.joinedload, keys, False, kw)
@loader_unbound_fn
+@_strategy_labels((("lazy", "subquery"),))
def subqueryload(*keys: _AttrType) -> _AbstractLoad:
return _generate_from_keys(Load.subqueryload, keys, False, {})
@loader_unbound_fn
+@_strategy_labels((("lazy", "selectin"),))
def selectinload(
*keys: _AttrType,
recursion_depth: Optional[int] = None,
@loader_unbound_fn
+@_strategy_labels((("lazy", "select"),))
def lazyload(*keys: _AttrType) -> _AbstractLoad:
return _generate_from_keys(Load.lazyload, keys, False, {})
@loader_unbound_fn
+@_strategy_labels((("lazy", "immediate"),))
def immediateload(
*keys: _AttrType, recursion_depth: Optional[int] = None
) -> _AbstractLoad:
@loader_unbound_fn
+@_strategy_labels((("lazy", "noload"),))
def noload(*keys: _AttrType) -> _AbstractLoad:
return _generate_from_keys(Load.noload, keys, False, {})
@loader_unbound_fn
+@_strategy_labels((("lazy", "raise"),), (("lazy", "raise_on_sql"),))
def raiseload(*keys: _AttrType, **kw: Any) -> _AbstractLoad:
return _generate_from_keys(Load.raiseload, keys, False, kw)
@loader_unbound_fn
+@_strategy_labels(
+ (("deferred", True), ("instrument", True)),
+ (("deferred", True), ("instrument", True), ("raiseload", True)),
+)
def defer(key: _AttrType, *, raiseload: bool = False) -> _AbstractLoad:
if raiseload:
kw = {"raiseload": raiseload}
@loader_unbound_fn
+@_strategy_labels((("deferred", False), ("instrument", True)))
def undefer(key: _AttrType) -> _AbstractLoad:
return _generate_from_keys(Load.undefer, (key,), False, {})
@loader_unbound_fn
+@_strategy_labels((("query_expression", True),))
def with_expression(
key: _AttrType, expression: _ColumnExpressionArgument[Any]
) -> _AbstractLoad:
@loader_unbound_fn
+@_strategy_labels((("selectinload_polymorphic", True),))
def selectin_polymorphic(
base_cls: _EntityType[Any], classes: Iterable[Type[Any]]
) -> _AbstractLoad:
def _raise_for_does_not_link(path, attrname, parent_entity):
if len(path) > 1:
path_is_of_type = path[-1].entity is not path[-2].mapper.class_
- if insp_is_aliased_class(parent_entity):
- parent_entity_str = str(parent_entity)
- else:
- parent_entity_str = parent_entity.class_.__name__
raise sa_exc.ArgumentError(
f'ORM mapped entity or attribute "{attrname}" does not '
- f'link from relationship "{path[-2]}%s".%s'
+ f'link from relationship "{entity_str(path[-2])}%s".%s'
% (
- f".of_type({path[-1]})" if path_is_of_type else "",
+ (
+ f".of_type({entity_str(path[-1])})"
+ if path_is_of_type
+ else ""
+ ),
(
" Did you mean to use "
- f'"{path[-2]}'
- f'.of_type({parent_entity_str})" or "loadopt.options('
+ f'"{entity_str(path[-2])}'
+ f'.of_type({entity_str(parent_entity)})" or '
+ '"loadopt.options('
f"selectin_polymorphic({path[-2].mapper.class_.__name__}, "
- f'[{parent_entity_str}]), ...)" ?'
+ f'[{entity_str(parent_entity)}]), ...)" ?'
if not path_is_of_type
and not path[-1].is_aliased_class
and orm_util._entity_corresponds_to(
else:
raise sa_exc.ArgumentError(
f'ORM mapped attribute "{attrname}" does not '
- f'link mapped class "{path[-1]}"'
+ f'link mapped class "{entity_str(path[-1])}"'
)
)
def __str__(self):
+ return self.path_string()
+
+ def path_string(self) -> str:
+ """Return a user-facing name for this :class:`.AliasedInsp`,
+ for use in a :class:`_orm.PathRegistry` string representation.
+
+ """
if self._is_with_polymorphic:
return "with_polymorphic(%s, [%s])" % (
self._target.__name__,
q2 = q.options(undefer(Order.user_id))
with expect_raises_message(
sa.exc.InvalidRequestError,
- r"Loader strategies for ORM Path\[Mapper\[Order\(orders\)\] -> "
- r"Order.user_id\] conflict",
+ r"Loader strategy replacement undefer\(Order.user_id\) "
+ r"is in conflict with existing strategy defer\(Order.user_id\)",
):
q2.all()
assert_raises_message(
sa.exc.ArgumentError,
- r"Mapped class Mapper\[Manager\(managers\)\] does not apply to "
+ r"Mapped class Manager referenced in option "
+ r"undefer\(Manager.status\) does not apply to "
"any of the root entities in this query, e.g. "
r"with_polymorphic\(Person, \[Manager\]\).",
s.query(wp).options(load_only(Manager.status))._compile_context,
assert_raises_message(
sa.exc.ArgumentError,
- r"Mapped class Mapper\[Order\(orders\)\] does not apply to any of "
- "the "
- r"root entities in this query, e.g. Mapper\[User\(users\)\]. "
+ r"Mapped class Order referenced in option "
+ r"joinedload\(Order.items\) "
+ r"does not apply to any of the root entities in this query, "
+ r"e.g. User. "
"Please specify the full path from one of the root entities "
"to the target attribute.",
sess.query(User)
# for up front
with expect_raises_message(
sa.exc.ArgumentError,
- r"Mapped class Mapper\[User\(users\)\] does not apply to "
+ r"Mapped class User referenced in option "
+ r"joinedload\(User.addresses\) does not apply to "
"any of the root entities in this query",
):
row = sess.execute(
from sqlalchemy.orm import defaultload
from sqlalchemy.orm import defer
from sqlalchemy.orm import exc as orm_exc
+from sqlalchemy.orm import immediateload
from sqlalchemy.orm import joinedload
+from sqlalchemy.orm import lazyload
from sqlalchemy.orm import Load
from sqlalchemy.orm import load_only
from sqlalchemy.orm import loading
+from sqlalchemy.orm import noload
+from sqlalchemy.orm import query_expression
+from sqlalchemy.orm import raiseload
from sqlalchemy.orm import relationship
+from sqlalchemy.orm import selectinload
from sqlalchemy.orm import strategy_options
from sqlalchemy.orm import subqueryload
from sqlalchemy.orm import synonym
from sqlalchemy.orm import undefer
from sqlalchemy.orm import util as orm_util
+from sqlalchemy.orm import with_expression
from sqlalchemy.orm import with_polymorphic
from sqlalchemy.testing import fixtures
from sqlalchemy.testing import is_not
result = Load(User)
eq_(
str(result),
- "Load(Mapper[User(users)])",
+ "Load(User)",
)
result = Load(aliased(User))
with expect_raises_message(
sa.exc.ArgumentError,
- r"Mapped class Mapper\[User\(users\)\] does not apply to any "
+ r"Mapped class User referenced in option "
+ r"contains_eager\(User.addresses\) does not apply to any "
"of the root entities in this query",
):
self._assert_path_result(opt, q, [])
Item.id,
Item.keywords,
r"Query has only expression-based entities; attribute loader "
- r"options for Mapper\[Item\(items\)\] can't be applied here.",
+ r"option joinedload\(Item.keywords\) can't be applied here.",
)
def test_option_against_nonexistent_PropComparator(self):
self._assert_eager_with_entity_exception(
[Keyword],
(joinedload(Item.keywords),),
- r"Mapped class Mapper\[Item\(items\)\] does not apply to any of "
- "the root entities in this query, e.g. "
- r"Mapper\[Keyword\(keywords\)\]. "
- "Please specify the full path from one of "
+ r"Mapped class Item referenced in option "
+ r"joinedload\(Item.keywords\) "
+ "does not apply to any of the root entities in this query, e.g. "
+ r"Keyword. Please specify the full path from one of "
"the root entities to the target attribute. ",
)
[Keyword.id, Item.id],
lambda: (joinedload(Keyword.keywords),),
r"Query has only expression-based entities; attribute loader "
- r"options for Mapper\[Keyword\(keywords\)\] can't be applied "
- "here.",
+ r"option joinedload\(Keyword.keywords\) can't be applied here.",
)
@testing.combinations(True, False, argnames="first_element")
assert_raises_message(
sa.exc.ArgumentError,
- r"Mapped class Mapper\[Manager\(managers\)\] does not apply to "
- "any of "
- r"the root entities in this query, e.g. "
+ r"Mapped class Manager referenced in option "
+ r"undefer\(Manager.status\) does not apply to "
+ r"any of the root entities in this query, e.g. "
r"with_polymorphic\(Person, \[Manager\]\).",
s.query(wp).options(load_only(Manager.status))._compile_state,
)
sa.exc.ArgumentError,
r'ORM mapped entity or attribute "Manager.manager_name" does '
r"not link from "
- r'relationship "Company.employees.'
- r'of_type\(Mapper\[Engineer\(engineers\)\]\)".$',
+ r'relationship "Company.employees.of_type\(Engineer\)".$',
lambda: s.query(Company)
.options(
joinedload(Company.employees.of_type(Engineer)).load_only(
sa.exc.ArgumentError,
r'ORM mapped entity or attribute "Manager.status" does '
r"not link from "
- r'relationship "Company.employees.'
- r'of_type\(Mapper\[Engineer\(engineers\)\]\)".$',
+ r'relationship "Company.employees.of_type\(Engineer\)".$',
lambda: s.query(Company)
.options(
joinedload(Company.employees.of_type(Engineer)).load_only(
joinedload(User.orders),
contains_eager(User.orders),
),
- r"Loader strategies for ORM Path\[Mapper\[User\(users\)\] -> "
- r"User.orders -> Mapper\[Order\(orders\)\]\] conflict",
+ r"Loader strategy replacement contains_eager\(User.orders\) "
+ r"is in conflict with existing strategy "
+ r"joinedload\(User.orders\)",
),
(
lambda User, Order: (
Order.items
),
),
- r"Loader strategies for ORM Path\[Mapper\[User\(users\)\] -> "
- r"User.orders -> Mapper\[Order\(orders\)\]\] conflict",
+ r"Loader strategy replacement "
+ r"joinedload\(User.orders\) "
+ r"is in conflict with existing strategy "
+ r"joinedload\(User.orders\)",
),
(
lambda User: (defer(User.name), undefer(User.name)),
- r"Loader strategies for ORM Path\[Mapper\[User\(users\)\] -> "
- r"User.name\] conflict",
+ r"Loader strategy replacement undefer\(User.name\) "
+ r"is in conflict with existing strategy defer\(User.name\)",
),
)
def test_conflicts(self, make_opt, errmsg):
sess.query(User).options(opt)._compile_context()
else:
sess.query(User).options(opt)._compile_context()
+
+ @testing.combinations(
+ (
+ lambda User: joinedload(User.addresses),
+ "joinedload(User.addresses)",
+ ),
+ (
+ lambda User: contains_eager(User.addresses),
+ "contains_eager(User.addresses)",
+ ),
+ (
+ lambda User: subqueryload(User.addresses),
+ "subqueryload(User.addresses)",
+ ),
+ (
+ lambda User: selectinload(User.addresses),
+ "selectinload(User.addresses)",
+ ),
+ (
+ # rendering of of_type() is also tested in
+ # test_utils.py::PathRegistryTest but do a spot check here too
+ lambda User: selectinload(
+ User.addresses.of_type(aliased(Address))
+ ),
+ "selectinload(User.addresses.of_type(aliased(Address)))",
+ ),
+ (
+ lambda User: lazyload(User.addresses),
+ "lazyload(User.addresses)",
+ ),
+ (
+ lambda User: immediateload(User.addresses),
+ "immediateload(User.addresses)",
+ ),
+ (
+ lambda User: noload(User.addresses),
+ "noload(User.addresses)",
+ ),
+ (
+ lambda User: raiseload(User.addresses),
+ "raiseload(User.addresses)",
+ ),
+ (
+ lambda User: raiseload(User.addresses, sql_only=True),
+ "raiseload(User.addresses)",
+ ),
+ (
+ lambda User: defer(User.name),
+ "defer(User.name)",
+ ),
+ (
+ lambda User: defer(User.name, raiseload=True),
+ "defer(User.name)",
+ ),
+ (
+ lambda User: undefer(User.name),
+ "undefer(User.name)",
+ ),
+ (
+ lambda User: with_expression(User.expr_col, User.name),
+ "with_expression(User.expr_col)",
+ ),
+ )
+ @testing.emits_warning
+ def test_strategy_labels_str(self, make_opt, expected_str):
+ users, addresses, User, Address = (
+ self.tables.users,
+ self.tables.addresses,
+ self.classes.User,
+ self.classes.Address,
+ )
+
+ self.mapper_registry.map_imperatively(
+ User,
+ users,
+ properties={
+ "addresses": relationship(
+ self.mapper_registry.map_imperatively(Address, addresses)
+ ),
+ "expr_col": query_expression(),
+ },
+ )
+
+ opt = testing.resolve_lambda(make_opt, User=User)
+
+ elements = [el for el in opt.context if el.strategy is not None]
+ eq_(str(elements[0]), expected_str)
)
eq_(PathRegistry.deserialize([(User, "addresses")]), p3)
+ @testing.combinations(
+ (
+ lambda umapper: PathRegistry.coerce(
+ (umapper, umapper.attrs.addresses)
+ ),
+ "User.addresses",
+ ),
+ (
+ lambda umapper: PathRegistry.coerce((umapper,)),
+ "User",
+ ),
+ (
+ lambda ualias: PathRegistry.coerce((ualias,)),
+ "aliased(User)",
+ ),
+ (
+ lambda umapper, amapper: PathRegistry.coerce(
+ (umapper, umapper.attrs.addresses, amapper)
+ ),
+ "User.addresses",
+ ),
+ (
+ lambda ualias, umapper, amapper: PathRegistry.coerce(
+ (ualias, umapper.attrs.addresses, amapper)
+ ),
+ "aliased(User).addresses",
+ ),
+ (
+ lambda umapper, aalias: PathRegistry.coerce(
+ (umapper, umapper.attrs.addresses, aalias)
+ ),
+ "User.addresses.of_type(aliased(Address))",
+ ),
+ (
+ lambda: RootRegistry(),
+ "",
+ ),
+ (
+ lambda umapper, omapper, imapper: PathRegistry.coerce(
+ (
+ umapper,
+ umapper.attrs.orders,
+ omapper,
+ omapper.attrs["items"],
+ imapper,
+ )
+ ),
+ "User.orders -> Order.items",
+ ),
+ (
+ lambda umapper, omapper, oalias, imapper: PathRegistry.coerce(
+ (
+ umapper,
+ umapper.attrs.orders,
+ oalias,
+ omapper.attrs["items"],
+ imapper,
+ )
+ ),
+ "User.orders.of_type(aliased(Order)) -> aliased(Order).items",
+ ),
+ (
+ lambda umapper, omapper, owpoly, imapper: PathRegistry.coerce(
+ (
+ umapper,
+ umapper.attrs.orders,
+ owpoly,
+ omapper.attrs["items"],
+ imapper,
+ )
+ ),
+ "User.orders.of_type(with_polymorphic(Order, []))"
+ " -> with_polymorphic(Order, []).items",
+ ),
+ argnames="path_reg,expected",
+ )
+ def test_path_string(self, path_reg, expected):
+ umapper = inspect(self.classes.User)
+ amapper = inspect(self.classes.Address)
+ omapper = inspect(self.classes.Order)
+ imapper = inspect(self.classes.Item)
+ auser = aliased(self.classes.User)
+ ualias = inspect(auser)
+ aaddress = aliased(self.classes.Address)
+ aorder = aliased(self.classes.Order)
+ aalias = inspect(aaddress)
+ oalias = inspect(aorder)
+
+ # this is a "fake" with_polymorphic but we expect it to trip the
+ # _is_with_polymorphic flag. if this changes, then we need to bring
+ # in the full polymorphic fixture
+ wpolyorder = with_polymorphic(self.classes.Order, [self.classes.Order])
+ owpoly = inspect(wpolyorder)
+
+ p1 = testing.resolve_lambda(
+ path_reg,
+ umapper=umapper,
+ amapper=amapper,
+ ualias=ualias,
+ aalias=aalias,
+ oalias=oalias,
+ owpoly=owpoly,
+ omapper=omapper,
+ imapper=imapper,
+ )
+ eq_(p1.path_string(), expected)
+
class PathRegistryInhTest(_poly_fixtures._Polymorphic):
run_setup_mappers = "once"