From: Mike Bayer Date: Wed, 8 Dec 2021 19:19:11 +0000 (-0500) Subject: Removals: strings for join(), loader_options(). X-Git-Tag: rel_2_0_0b1~605^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9e20d410212296517f8dffd2531d55fee196635b;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Removals: strings for join(), loader_options(). * The :meth:`_orm.Query.join` method no longer accepts strings for relationship names; the long-documented approach of using ``Class.attrname`` for join targets is now standard. * Loader options no longer accept strings for attribute names. The long-documented approach of using ``Class.attrname`` for loader option targets is now standard. It is hoped that a subsequent commit can refactor loader options to no longer need "UnboundLoad" for most cases. Change-Id: If4629882c40523dccbf4459256bf540fb468b618 References: #6986 --- diff --git a/doc/build/changelog/unreleased_20/7257.rst b/doc/build/changelog/unreleased_20/7257.rst index cecc4e627a..c06ef4f0d7 100644 --- a/doc/build/changelog/unreleased_20/7257.rst +++ b/doc/build/changelog/unreleased_20/7257.rst @@ -26,4 +26,12 @@ :class:`.Table`, and from all DDL/DML/DQL elements that previously could refer to a "bound engine" + * The :meth:`_orm.Query.join` method no longer accepts strings for + relationship names; the long-documented approach of using + ``Class.attrname`` for join targets is now standard. + + * Loader options no longer accept strings for attribute names. The + long-documented approach of using ``Class.attrname`` for loader option + targets is now standard. + * More are in progress as development continues diff --git a/lib/sqlalchemy/orm/context.py b/lib/sqlalchemy/orm/context.py index 4a3d5286ba..8e3dc3134b 100644 --- a/lib/sqlalchemy/orm/context.py +++ b/lib/sqlalchemy/orm/context.py @@ -1457,7 +1457,7 @@ class ORMSelectCompileState(ORMCompileState, SelectState): # legacy ^^^^^^^^^^^^^^^^^^^^^^^^^^^ if ( - isinstance(right, (interfaces.PropComparator, str)) + isinstance(right, interfaces.PropComparator) and onclause is None ): onclause = right @@ -1478,11 +1478,9 @@ class ORMSelectCompileState(ORMCompileState, SelectState): of_type = None if isinstance(onclause, str): - # string given, e.g. query(Foo).join("bar"). - # we look to the left entity or what we last joined - # towards - onclause = _entity_namespace_key( - inspect(self._joinpoint_zero()), onclause + raise sa_exc.ArgumentError( + "ORM mapped attributes as join targets must be " + "stated the attribute itself, not string name" ) # legacy vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv @@ -1495,7 +1493,7 @@ class ORMSelectCompileState(ORMCompileState, SelectState): # to work with the aliased=True flag, which is also something # that probably shouldn't exist on join() due to its high # complexity/usefulness ratio - elif from_joinpoint and isinstance( + if from_joinpoint and isinstance( onclause, interfaces.PropComparator ): jp0 = self._joinpoint_zero() diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index bd80749d23..a2e247f147 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -2186,25 +2186,6 @@ class Query( in the 1.4 series for the following features: - * Joining on relationship names rather than attributes:: - - session.query(User).join("addresses") - - **Why it's legacy**: the string name does not provide enough context - for :meth:`_query.Query.join` to always know what is desired, - notably in that there is no indication of what the left side - of the join should be. This gives rise to flags like - ``from_joinpoint`` as well as the ability to place several - join clauses in a single :meth:`_query.Query.join` call - which don't solve the problem fully while also - adding new calling styles that are unnecessary and expensive to - accommodate internally. - - **Modern calling pattern**: Use the actual relationship, - e.g. ``User.addresses`` in the above case:: - - session.query(User).join(User.addresses) - * Automatic aliasing with the ``aliased=True`` flag:: session.query(Node).join(Node.children, aliased=True).\ @@ -2337,7 +2318,6 @@ class Query( onclause, ( elements.ColumnElement, - str, interfaces.PropComparator, types.FunctionType, ), @@ -2361,7 +2341,7 @@ class Query( # this checks for an extremely ancient calling form of # reversed tuples. - if isinstance(prop[0], (str, interfaces.PropComparator)): + if isinstance(prop[0], interfaces.PropComparator): prop = (prop[1], prop[0]) _props.append(prop) @@ -2400,11 +2380,7 @@ class Query( legacy=True, apply_propagate_attrs=self, ), - ( - coercions.expect(roles.OnClauseRole, prop[1], legacy=True) - # if not isinstance(prop[1], str) - # else prop[1] - ) + (coercions.expect(roles.OnClauseRole, prop[1], legacy=True)) if len(prop) == 2 else None, None, diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py index 363307bfd6..a5c5397739 100644 --- a/lib/sqlalchemy/orm/strategy_options.py +++ b/lib/sqlalchemy/orm/strategy_options.py @@ -15,7 +15,6 @@ from .base import _is_aliased_class from .base import _is_mapped_class from .base import InspectionAttr from .interfaces import LoaderOption -from .interfaces import MapperProperty from .interfaces import PropComparator from .path_registry import _DEFAULT_TOKEN from .path_registry import _WILDCARD_TOKEN @@ -304,7 +303,6 @@ class Load(Generative, LoaderOption): if isinstance(attr, str): default_token = attr.endswith(_DEFAULT_TOKEN) - attr_str_name = attr if attr.endswith(_WILDCARD_TOKEN) or default_token: if default_token: self.propagate_to_loaders = False @@ -321,137 +319,97 @@ class Load(Generative, LoaderOption): self.path = path return path - if existing_of_type: - ent = inspect(existing_of_type) - else: - ent = path.entity - - util.warn_deprecated_20( - "Using strings to indicate column or " - "relationship paths in loader options is deprecated " - "and will be removed in SQLAlchemy 2.0. Please use " - "the class-bound attribute directly.", + raise sa_exc.ArgumentError( + "Strings are not accepted for attribute names in loader " + "options; please use class-bound attributes directly." ) - try: - # use getattr on the class to work around - # synonyms, hybrids, etc. - attr = getattr(ent.class_, attr) - except AttributeError as err: + + insp = inspect(attr) + + if insp.is_mapper or insp.is_aliased_class: + # TODO: this does not appear to be a valid codepath. "attr" + # would never be a mapper. This block is present in 1.2 + # as well however does not seem to be accessed in any tests. + if not orm_util._entity_corresponds_to_use_path_impl( + attr.parent, path[-1] + ): if raiseerr: - util.raise_( - sa_exc.ArgumentError( - 'Can\'t find property named "%s" on ' - "%s in this Query." % (attr, ent) - ), - replace_context=err, + raise sa_exc.ArgumentError( + "Attribute '%s' does not " + "link from element '%s'" % (attr, path.entity) ) else: return None - else: - try: - attr = found_property = attr.property - except AttributeError as ae: - if not isinstance(attr, MapperProperty): - util.raise_( - sa_exc.ArgumentError( - 'Expected attribute "%s" on %s to be a ' - "mapped attribute; " - "instead got %s object." - % (attr_str_name, ent, type(attr)) + elif insp.is_property: + prop = found_property = attr + path = path[prop] + elif insp.is_attribute: + prop = found_property = attr.property + + if not orm_util._entity_corresponds_to_use_path_impl( + attr.parent, path[-1] + ): + if raiseerr: + raise sa_exc.ArgumentError( + 'Attribute "%s" does not ' + 'link from element "%s".%s' + % ( + attr, + path.entity, + ( + " Did you mean to use " + "%s.of_type(%s)?" + % (path[-2], attr.class_.__name__) + if len(path) > 1 + and path.entity.is_mapper + and attr.parent.is_aliased_class + else "" ), - replace_context=ae, - ) - else: - raise - - path = path[attr] - else: - insp = inspect(attr) - - if insp.is_mapper or insp.is_aliased_class: - # TODO: this does not appear to be a valid codepath. "attr" - # would never be a mapper. This block is present in 1.2 - # as well however does not seem to be accessed in any tests. - if not orm_util._entity_corresponds_to_use_path_impl( - attr.parent, path[-1] - ): - if raiseerr: - raise sa_exc.ArgumentError( - "Attribute '%s' does not " - "link from element '%s'" % (attr, path.entity) ) - else: - return None - elif insp.is_property: - prop = found_property = attr - path = path[prop] - elif insp.is_attribute: - prop = found_property = attr.property + ) + else: + return None - if not orm_util._entity_corresponds_to_use_path_impl( - attr.parent, path[-1] - ): - if raiseerr: - raise sa_exc.ArgumentError( - 'Attribute "%s" does not ' - 'link from element "%s".%s' - % ( - attr, - path.entity, - ( - " Did you mean to use " - "%s.of_type(%s)?" - % (path[-2], attr.class_.__name__) - if len(path) > 1 - and path.entity.is_mapper - and attr.parent.is_aliased_class - else "" - ), - ) - ) - else: - return None + if attr._extra_criteria and not self._extra_criteria: + # in most cases, the process that brings us here will have + # already established _extra_criteria. however if not, + # and it's present on the attribute, then use that. + self._extra_criteria = attr._extra_criteria - if attr._extra_criteria and not self._extra_criteria: - # in most cases, the process that brings us here will have - # already established _extra_criteria. however if not, - # and it's present on the attribute, then use that. - self._extra_criteria = attr._extra_criteria + if getattr(attr, "_of_type", None): + ac = attr._of_type + ext_info = of_type_info = inspect(ac) - if getattr(attr, "_of_type", None): - ac = attr._of_type - ext_info = of_type_info = inspect(ac) + if polymorphic_entity_context is None: + polymorphic_entity_context = self.context - if polymorphic_entity_context is None: - polymorphic_entity_context = self.context + existing = path.entity_path[prop].get( + polymorphic_entity_context, "path_with_polymorphic" + ) - existing = path.entity_path[prop].get( - polymorphic_entity_context, "path_with_polymorphic" + if not ext_info.is_aliased_class: + ac = orm_util.with_polymorphic( + ext_info.mapper.base_mapper, + ext_info.mapper, + aliased=True, + _use_mapper_path=True, + _existing_alias=inspect(existing) + if existing is not None + else None, ) - if not ext_info.is_aliased_class: - ac = orm_util.with_polymorphic( - ext_info.mapper.base_mapper, - ext_info.mapper, - aliased=True, - _use_mapper_path=True, - _existing_alias=inspect(existing) - if existing is not None - else None, - ) - - ext_info = inspect(ac) + ext_info = inspect(ac) - path.entity_path[prop].set( - polymorphic_entity_context, "path_with_polymorphic", ac - ) + path.entity_path[prop].set( + polymorphic_entity_context, "path_with_polymorphic", ac + ) - path = path[prop][ext_info] + path = path[prop][ext_info] - self._of_type = of_type_info + self._of_type = of_type_info - else: - path = path[prop] + else: + path = path[prop] if for_strategy is not None: found_property._get_strategy(for_strategy) diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index e7a485d0bf..140464b87c 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -1856,13 +1856,10 @@ def with_parent(instance, prop, from_entity=None): An instance which has some :func:`_orm.relationship`. :param property: - String property name, or class-bound attribute, which indicates + Class-bound attribute, which indicates what relationship from the instance should be used to reconcile the parent/child relationship. - .. deprecated:: 1.4 Using strings is deprecated and will be removed - in SQLAlchemy 2.0. Please use the class-bound attribute directly. - :param from_entity: Entity in which to consider as the left side. This defaults to the "zero" entity of the :class:`_query.Query` itself. @@ -1871,13 +1868,9 @@ def with_parent(instance, prop, from_entity=None): """ if isinstance(prop, str): - util.warn_deprecated_20( - "Using strings to indicate relationship names in the ORM " - "with_parent() function is deprecated and will be removed " - "SQLAlchemy 2.0. Please use the class-bound attribute directly." + raise sa_exc.ArgumentError( + "with_parent() accepts class-bound mapped attributes, not strings" ) - mapper = object_mapper(instance) - prop = getattr(mapper.class_, prop).property elif isinstance(prop, attributes.QueryableAttribute): if prop._of_type: from_entity = prop._of_type diff --git a/lib/sqlalchemy/sql/coercions.py b/lib/sqlalchemy/sql/coercions.py index 07da49c4e0..38eed4d2a9 100644 --- a/lib/sqlalchemy/sql/coercions.py +++ b/lib/sqlalchemy/sql/coercions.py @@ -610,20 +610,6 @@ class OnClauseImpl(_CoerceLiterals, _ColumnCoercions, RoleImpl): _coerce_consts = True - def _implicit_coercions( - self, original_element, resolved, argname=None, legacy=False, **kw - ): - if legacy and isinstance(resolved, str): - return resolved - else: - return super(OnClauseImpl, self)._implicit_coercions( - original_element, - resolved, - argname=argname, - legacy=legacy, - **kw - ) - def _text_coercion(self, element, argname=None, legacy=False): if legacy and isinstance(element, str): util.warn_deprecated_20( @@ -925,9 +911,8 @@ class JoinTargetImpl(RoleImpl): _skip_clauseelement_for_target_match = True - def _literal_coercion(self, element, legacy=False, **kw): - if isinstance(element, str): - return element + def _literal_coercion(self, element, argname=None, **kw): + self._raise_for_expected(element, argname) def _implicit_coercions( self, original_element, resolved, argname=None, legacy=False, **kw @@ -937,14 +922,6 @@ class JoinTargetImpl(RoleImpl): # #6550, unless JoinTargetImpl._skip_clauseelement_for_target_match # were set to False. return original_element - elif legacy and isinstance(resolved, str): - util.warn_deprecated_20( - "Using strings to indicate relationship names in " - "Query.join() is deprecated and will be removed in " - "SQLAlchemy 2.0. Please use the class-bound attribute " - "directly." - ) - return resolved elif legacy and isinstance(resolved, roles.WhereHavingRole): return resolved elif legacy and resolved._is_select_statement: diff --git a/test/aaa_profiling/test_orm.py b/test/aaa_profiling/test_orm.py index 2ba6c993cc..113286d1ba 100644 --- a/test/aaa_profiling/test_orm.py +++ b/test/aaa_profiling/test_orm.py @@ -396,7 +396,10 @@ class DeferOptionsTest(NoCache, fixtures.MappedTest): A = self.classes.A s = fixture_session() s.query(A).options( - *[defer(letter) for letter in ["x", "y", "z", "p", "q", "r"]] + *[ + defer(getattr(A, letter)) + for letter in ["x", "y", "z", "p", "q", "r"] + ] ).all() diff --git a/test/orm/test_deprecations.py b/test/orm/test_deprecations.py index 89e9a89e67..535196c253 100644 --- a/test/orm/test_deprecations.py +++ b/test/orm/test_deprecations.py @@ -1,5 +1,4 @@ from contextlib import nullcontext -import pickle from unittest.mock import call from unittest.mock import Mock @@ -46,13 +45,10 @@ from sqlalchemy.orm import exc as orm_exc from sqlalchemy.orm import foreign from sqlalchemy.orm import instrumentation from sqlalchemy.orm import joinedload -from sqlalchemy.orm import Load -from sqlalchemy.orm import load_only from sqlalchemy.orm import mapper from sqlalchemy.orm import relation from sqlalchemy.orm import relationship from sqlalchemy.orm import scoped_session -from sqlalchemy.orm import selectinload from sqlalchemy.orm import Session from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import strategy_options @@ -65,7 +61,6 @@ from sqlalchemy.orm import with_polymorphic from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.orm.collections import collection from sqlalchemy.orm.util import polymorphic_union -from sqlalchemy.testing import assert_raises from sqlalchemy.testing import assert_raises_message from sqlalchemy.testing import assertions from sqlalchemy.testing import AssertsCompiledSQL @@ -84,12 +79,8 @@ from sqlalchemy.testing.schema import Table from sqlalchemy.testing.util import resolve_lambda from . import _fixtures from .inheritance import _poly_fixtures -from .inheritance._poly_fixtures import _Polymorphic -from .inheritance._poly_fixtures import Company -from .inheritance._poly_fixtures import Engineer from .inheritance._poly_fixtures import Manager from .inheritance._poly_fixtures import Person -from .test_ac_relationships import PartitionByFixture from .test_deferred import InheritanceTest as _deferred_InheritanceTest from .test_dynamic import _DynamicFixture from .test_events import _RemoveListeners @@ -125,21 +116,11 @@ undefer_needs_chaining = ( "Please use method chaining" ) -join_strings_dep = "Using strings to indicate relationship names in Query.join" join_tuple_form = ( r"Query.join\(\) will no longer accept tuples as " "arguments in SQLAlchemy 2.0." ) -opt_strings_dep = ( - "Using strings to indicate column or relationship " - "paths in loader options" -) - -wparent_strings_dep = ( - r"Using strings to indicate relationship names " - r"in the ORM with_parent\(\) function" -) query_wparent_dep = ( r"The Query.with_parent\(\) method is considered legacy as of the 1.x" @@ -284,89 +265,6 @@ class GetTest(QueryTest): q.get(5) -class CustomJoinTest(QueryTest): - run_setup_mappers = None - - def test_double_same_mappers_flag_alias(self): - """test aliasing of joins with a custom join condition""" - - ( - addresses, - items, - order_items, - orders, - Item, - User, - Address, - Order, - users, - ) = ( - self.tables.addresses, - self.tables.items, - self.tables.order_items, - self.tables.orders, - self.classes.Item, - self.classes.User, - self.classes.Address, - self.classes.Order, - self.tables.users, - ) - - self.mapper_registry.map_imperatively(Address, addresses) - self.mapper_registry.map_imperatively( - Order, - orders, - properties={ - "items": relationship( - Item, - secondary=order_items, - lazy="select", - order_by=items.c.id, - ) - }, - ) - self.mapper_registry.map_imperatively(Item, items) - self.mapper_registry.map_imperatively( - User, - users, - properties=dict( - addresses=relationship(Address, lazy="select"), - open_orders=relationship( - Order, - primaryjoin=and_( - orders.c.isopen == 1, users.c.id == orders.c.user_id - ), - lazy="select", - viewonly=True, - ), - closed_orders=relationship( - Order, - primaryjoin=and_( - orders.c.isopen == 0, users.c.id == orders.c.user_id - ), - lazy="select", - viewonly=True, - ), - ), - ) - q = fixture_session().query(User) - - with assertions.expect_deprecated_20( - join_aliased_dep, - join_strings_dep, - join_chain_dep, - raise_on_any_unexpected=True, - ): - eq_( - q.join("open_orders", "items", aliased=True) - .filter(Item.id == 4) - .join("closed_orders", "items", aliased=True) - .filter(Item.id == 3) - .all(), - [User(id=7)], - ) - - class PickleTest(fixtures.MappedTest): @classmethod def define_tables(cls, metadata): @@ -446,73 +344,6 @@ class PickleTest(fixtures.MappedTest): sess.expunge_all() return sess, User, Address, Dingaling - def test_became_bound_options(self): - sess, User, Address, Dingaling = self._option_test_fixture() - - for opt in [ - sa.orm.joinedload("addresses"), - sa.orm.defer("name"), - sa.orm.joinedload("addresses").joinedload(Address.dingaling), - ]: - with assertions.expect_deprecated_20(opt_strings_dep): - context = sess.query(User).options(opt)._compile_context() - opt = [ - v - for v in context.attributes.values() - if isinstance(v, sa.orm.Load) - ][0] - - opt2 = pickle.loads(pickle.dumps(opt)) - eq_(opt.path, opt2.path) - eq_(opt.local_opts, opt2.local_opts) - - u1 = sess.query(User).options(opt).first() - pickle.loads(pickle.dumps(u1)) - - @testing.combinations( - lambda: sa.orm.joinedload("addresses"), - lambda: sa.orm.defer("name"), - lambda Address: sa.orm.joinedload("addresses").joinedload( - Address.dingaling - ), - lambda: sa.orm.joinedload("addresses").raiseload("*"), - ) - def test_unbound_options(self, test_case): - sess, User, Address, Dingaling = self._option_test_fixture() - - opt = testing.resolve_lambda(test_case, User=User, Address=Address) - opt2 = pickle.loads(pickle.dumps(opt)) - eq_(opt.path, opt2.path) - - with assertions.expect_deprecated_20(opt_strings_dep): - u1 = sess.query(User).options(opt).first() - pickle.loads(pickle.dumps(u1)) - - @testing.combinations( - lambda User: sa.orm.Load(User).joinedload("addresses"), - lambda User: sa.orm.Load(User).joinedload("addresses").raiseload("*"), - lambda User: sa.orm.Load(User).defer("name"), - lambda User, Address: sa.orm.Load(User) - .joinedload("addresses") - .joinedload(Address.dingaling), - lambda User, Address: sa.orm.Load(User) - .joinedload("addresses", innerjoin=True) - .joinedload(Address.dingaling), - ) - def test_bound_options(self, test_case): - sess, User, Address, Dingaling = self._option_test_fixture() - - with assertions.expect_deprecated_20(opt_strings_dep): - opt = testing.resolve_lambda(test_case, User=User, Address=Address) - - opt2 = pickle.loads(pickle.dumps(opt)) - eq_(opt.path, opt2.path) - eq_(opt.context.keys(), opt2.context.keys()) - eq_(opt.local_opts, opt2.local_opts) - - u1 = sess.query(User).options(opt).first() - pickle.loads(pickle.dumps(u1)) - class SynonymTest(QueryTest, AssertsCompiledSQL): __dialect__ = "default" @@ -581,36 +412,6 @@ class SynonymTest(QueryTest, AssertsCompiledSQL): ) cls.mapper_registry.map_imperatively(Keyword, keywords) - def test_options_syn_of_syn_string(self): - User, Order = self.classes.User, self.classes.Order - - s = fixture_session() - - def go(): - with testing.expect_deprecated_20(opt_strings_dep): - result = ( - s.query(User) - .filter_by(name="jack") - .options(joinedload("orders_syn_2")) - .all() - ) - eq_( - result, - [ - User( - id=7, - name="jack", - orders=[ - Order(description="order 1"), - Order(description="order 3"), - Order(description="order 5"), - ], - ) - ], - ) - - self.assert_sql_count(testing.db, go, 1) - class DeprecatedQueryTest(_fixtures.FixtureTest, AssertsCompiledSQL): __dialect__ = "default" @@ -785,111 +586,6 @@ class DeprecatedQueryTest(_fixtures.FixtureTest, AssertsCompiledSQL): "JOIN dingalings ON addresses.id = dingalings.address_id", ) - def test_str_join_target(self): - User = self.classes.User - - s = fixture_session() - - with testing.expect_deprecated_20(join_strings_dep): - q1 = s.query(User).join("addresses") - - self.assert_compile( - q1, - "SELECT users.id AS users_id, users.name AS " - "users_name FROM users JOIN addresses " - "ON users.id = addresses.user_id", - ) - - def test_str_rel_loader_opt(self): - User = self.classes.User - - s = fixture_session() - - q1 = s.query(User).options(joinedload("addresses")) - - with testing.expect_deprecated_20(opt_strings_dep): - self.assert_compile( - q1, - "SELECT users.id AS users_id, users.name AS users_name, " - "addresses_1.id AS addresses_1_id, addresses_1.user_id " - "AS addresses_1_user_id, addresses_1.email_address AS " - "addresses_1_email_address FROM users LEFT OUTER JOIN " - "addresses AS addresses_1 ON users.id = addresses_1.user_id " - "ORDER BY addresses_1.id", - ) - - def test_dotted_options(self): - User = self.classes.User - - sess = fixture_session() - - with testing.expect_deprecated_20( - "Using strings to indicate column or relationship " - "paths in loader options" - ): - q2 = ( - sess.query(User) - .order_by(User.id) - .options(sa.orm.joinedload("orders")) - .options(sa.orm.joinedload("orders.items")) - .options(sa.orm.joinedload("orders.items.keywords")) - ) - u = q2.all() - - def go(): - u[0].orders[1].items[0].keywords[1] - - self.sql_count_(0, go) - - def test_str_col_loader_opt(self): - User = self.classes.User - - s = fixture_session() - - q1 = s.query(User).options(defer("name")) - - with testing.expect_deprecated_20( - "Using strings to indicate column or relationship " - "paths in loader options" - ): - self.assert_compile(q1, "SELECT users.id AS users_id FROM users") - - def test_str_with_parent(self): - User = self.classes.User - Address = self.classes.Address - - s = fixture_session() - - u1 = User(id=1) - - with testing.expect_deprecated_20( - r"Using strings to indicate relationship names in the ORM " - r"with_parent\(\)", - ): - q1 = s.query(Address).filter(with_parent(u1, "addresses")) - - self.assert_compile( - q1, - "SELECT addresses.id AS addresses_id, addresses.user_id " - "AS addresses_user_id, addresses.email_address " - "AS addresses_email_address FROM addresses " - "WHERE :param_1 = addresses.user_id", - ) - - with testing.expect_deprecated_20( - r"Using strings to indicate relationship names in the ORM " - r"with_parent\(\)", - ): - q1 = s.query(Address).with_parent(u1, "addresses") - - self.assert_compile( - q1, - "SELECT addresses.id AS addresses_id, addresses.user_id " - "AS addresses_user_id, addresses.email_address " - "AS addresses_email_address FROM addresses " - "WHERE :param_1 = addresses.user_id", - ) - def test_invalid_column(self): User = self.classes.User @@ -1035,7 +731,7 @@ class DeprecatedQueryTest(_fixtures.FixtureTest, AssertsCompiledSQL): result = ( sess.query(User) .select_entity_from(query) - .options(contains_eager("addresses")) + .options(contains_eager(User.addresses)) .all() ) assert self.static.user_address_result == result @@ -1070,7 +766,7 @@ class DeprecatedQueryTest(_fixtures.FixtureTest, AssertsCompiledSQL): result = ( sess.query(User) .select_entity_from(query) - .options(contains_eager("addresses", alias=adalias)) + .options(contains_eager(User.addresses, alias=adalias)) .all() ) assert self.static.user_address_result == result @@ -1140,7 +836,7 @@ class DeprecatedQueryTest(_fixtures.FixtureTest, AssertsCompiledSQL): result = ( sess.query(User) .select_entity_from(sel) - .join("addresses") + .join(User.addresses) .add_entity(Address) .order_by(User.id) .order_by(Address.id) @@ -1174,7 +870,7 @@ class DeprecatedQueryTest(_fixtures.FixtureTest, AssertsCompiledSQL): result = ( sess.query(User) .select_entity_from(sel) - .join(adalias, "addresses") + .join(adalias, User.addresses) .add_entity(adalias) .order_by(User.id) .order_by(adalias.id) @@ -1208,6 +904,7 @@ class DeprecatedQueryTest(_fixtures.FixtureTest, AssertsCompiledSQL): self.classes.Keyword, self.classes.User, ) + Order, Item = self.classes("Order", "Item") sess = fixture_session() sel = users.select().where(users.c.id.in_([7, 8])) @@ -1216,7 +913,9 @@ class DeprecatedQueryTest(_fixtures.FixtureTest, AssertsCompiledSQL): eq_( sess.query(User) .select_entity_from(sel) - .join("orders", "items", "keywords") + .join(User.orders) + .join(Order.items) + .join(Item.keywords) .filter(Keyword.name.in_(["red", "big", "round"])) .all(), [User(name="jack", id=7)], @@ -1226,7 +925,9 @@ class DeprecatedQueryTest(_fixtures.FixtureTest, AssertsCompiledSQL): eq_( sess.query(User) .select_entity_from(sel) - .join("orders", "items", "keywords", aliased=True) + .join(User.orders, aliased=True) + .join(Order.items, aliased=True) + .join(Item.keywords, aliased=True) .filter(Keyword.name.in_(["red", "big", "round"])) .all(), [User(name="jack", id=7)], @@ -1258,7 +959,7 @@ class DeprecatedQueryTest(_fixtures.FixtureTest, AssertsCompiledSQL): with self._expect_implicit_subquery(): eq_( sess.query(User) - .options(joinedload("addresses")) + .options(joinedload(User.addresses)) .select_entity_from(sel) .order_by(User.id) .all(), @@ -1282,7 +983,7 @@ class DeprecatedQueryTest(_fixtures.FixtureTest, AssertsCompiledSQL): with self._expect_implicit_subquery(): eq_( sess.query(User) - .options(joinedload("addresses")) + .options(joinedload(User.addresses)) .select_entity_from(sel) .filter(User.id == 8) .order_by(User.id) @@ -1306,7 +1007,7 @@ class DeprecatedQueryTest(_fixtures.FixtureTest, AssertsCompiledSQL): with self._expect_implicit_subquery(): eq_( sess.query(User) - .options(joinedload("addresses")) + .options(joinedload(User.addresses)) .select_entity_from(sel) .order_by(User.id)[1], User( @@ -1565,47 +1266,6 @@ class SelfReferentialEagerTest(fixtures.MappedTest): Column("data", String(30)), ) - def test_eager_loading_with_deferred(self): - nodes = self.tables.nodes - - class Node(fixtures.ComparableEntity): - def append(self, node): - self.children.append(node) - - self.mapper_registry.map_imperatively( - Node, - nodes, - properties={ - "children": relationship( - Node, lazy="joined", join_depth=3, order_by=nodes.c.id - ), - "data": deferred(nodes.c.data), - }, - ) - sess = fixture_session() - n1 = Node(data="n1") - n1.append(Node(data="n11")) - n1.append(Node(data="n12")) - sess.add(n1) - sess.flush() - sess.expunge_all() - - def go(): - with assertions.expect_deprecated_20( - "Using strings to indicate column or relationship paths" - ): - eq_( - Node( - data="n1", - children=[Node(data="n11"), Node(data="n12")], - ), - sess.query(Node) - .options(undefer("data"), undefer("children.data")) - .first(), - ) - - self.assert_sql_count(testing.db, go, 1) - class LazyLoadOptSpecificityTest(fixtures.DeclarativeMappedTest): """test for [ticket:3963]""" @@ -1646,38 +1306,6 @@ class LazyLoadOptSpecificityTest(fixtures.DeclarativeMappedTest): self.assert_sql_count(testing.db, go, expected) - def test_string_options_aliased_whatever(self): - A, B, C = self.classes("A", "B", "C") - s = fixture_session() - aa = aliased(A) - q = ( - s.query(aa, A) - .filter(aa.id == 1) - .filter(A.id == 2) - .filter(aa.id != A.id) - .options(joinedload("bs").joinedload("cs")) - ) - with assertions.expect_deprecated_20( - "Using strings to indicate column or relationship paths" - ): - self._run_tests(q, 1) - - def test_string_options_unaliased_whatever(self): - A, B, C = self.classes("A", "B", "C") - s = fixture_session() - aa = aliased(A) - q = ( - s.query(A, aa) - .filter(aa.id == 2) - .filter(A.id == 1) - .filter(aa.id != A.id) - .options(joinedload("bs").joinedload("cs")) - ) - with assertions.expect_deprecated_20( - "Using strings to indicate column or relationship paths" - ): - self._run_tests(q, 1) - class DynamicTest(_DynamicFixture, _fixtures.FixtureTest): def test_negative_slice_access_raises(self): @@ -1799,7 +1427,7 @@ class FromSelfTest(QueryTest, AssertsCompiledSQL): with self._from_self_deprecated(): q = ( sess.query(User, Address.email_address) - .join("addresses") + .join(User.addresses) .distinct() .from_self(User) .order_by(desc(Address.email_address)) @@ -2399,7 +2027,7 @@ class FromSelfTest(QueryTest, AssertsCompiledSQL): .query(User) .filter(User.id.in_([8, 9])) .from_self() - .join("addresses") + .join(User.addresses) .add_entity(Address) .order_by(User.id, Address.id) .all(), @@ -2541,7 +2169,7 @@ class FromSelfTest(QueryTest, AssertsCompiledSQL): .filter(User.id == Address.user_id) .filter(Address.id.in_([2, 5])) .from_self() - .options(joinedload("addresses")) + .options(joinedload(User.addresses)) .first(), ( User(id=8, addresses=[Address(), Address(), Address()]), @@ -3461,22 +3089,20 @@ class NonPrimaryRelationshipLoaderTest(_fixtures.FixtureTest): closed_mapper = User.closed_orders.entity open_mapper = User.open_orders.entity - with testing.expect_deprecated_20(wparent_strings_dep): - eq_( - [Order(id=1), Order(id=5)], - fixture_session() - .query(closed_mapper) - .with_parent(user, property="closed_orders") - .all(), - ) - with testing.expect_deprecated_20(wparent_strings_dep): - eq_( - [Order(id=3)], - fixture_session() - .query(open_mapper) - .with_parent(user, property="open_orders") - .all(), - ) + eq_( + [Order(id=1), Order(id=5)], + fixture_session() + .query(closed_mapper) + .filter(with_parent(user, User.closed_orders)) + .all(), + ) + eq_( + [Order(id=3)], + fixture_session() + .query(open_mapper) + .filter(with_parent(user, User.open_orders)) + .all(), + ) class ViewonlyFlagWarningTest(fixtures.MappedTest): @@ -3766,7 +3392,7 @@ class InstancesTest(QueryTest, AssertsCompiledSQL): ): result = ( q.options( - contains_alias("ulist"), contains_eager("addresses") + contains_alias("ulist"), contains_eager(User.addresses) ) .from_statement(query) .all() @@ -3797,7 +3423,7 @@ class InstancesTest(QueryTest, AssertsCompiledSQL): r"Using the Query.instances\(\) method without a context" ): result = list( - q.options(contains_eager("addresses")).instances( + q.options(contains_eager(User.addresses)).instances( sess.execute(selectquery) ) ) @@ -3845,7 +3471,7 @@ class InstancesTest(QueryTest, AssertsCompiledSQL): ): result = list( q.options( - contains_eager("addresses", alias=adalias) + contains_eager(User.addresses, alias=adalias) ).instances(sess.connection().execute(selectquery)) ) assert self.static.user_address_result == result @@ -3861,6 +3487,8 @@ class InstancesTest(QueryTest, AssertsCompiledSQL): self.classes.User, ) + Order = self.classes.Order + sess = fixture_session() q = sess.query(User) @@ -3887,8 +3515,10 @@ class InstancesTest(QueryTest, AssertsCompiledSQL): ): result = list( q.options( - contains_eager("orders", alias=oalias), - contains_eager("orders.items", alias=ialias), + contains_eager(User.orders, alias=oalias), + defaultload(User.orders).contains_eager( + Order.items, alias=ialias + ), ).instances(sess.connection().execute(query)) ) assert self.static.user_order_result == result @@ -3928,38 +3558,6 @@ class TestDeprecation20(fixtures.TestBase): class DistinctOrderByImplicitTest(QueryTest, AssertsCompiledSQL): __dialect__ = "default" - def test_columns_augmented_roundtrip_one(self): - User, Address = self.classes.User, self.classes.Address - - sess = fixture_session() - with testing.expect_deprecated_20(join_strings_dep): - q = ( - sess.query(User) - .join("addresses") - .distinct() - .order_by(desc(Address.email_address)) - ) - with testing.expect_deprecated( - "ORDER BY columns added implicitly due to " - ): - eq_([User(id=7), User(id=9), User(id=8)], q.all()) - - def test_columns_augmented_roundtrip_two(self): - User, Address = self.classes.User, self.classes.Address - - sess = fixture_session() - with testing.expect_deprecated_20(join_strings_dep): - q = ( - sess.query(User) - .join("addresses") - .distinct() - .order_by(desc(Address.email_address).label("foo")) - ) - with testing.expect_deprecated( - "ORDER BY columns added implicitly due to " - ): - eq_([User(id=7), User(id=9), User(id=8)], q.all()) - def test_columns_augmented_roundtrip_three(self): User, Address = self.classes.User, self.classes.Address @@ -4173,7 +3771,7 @@ class MixedEntitiesTest(QueryTest, AssertsCompiledSQL): with testing.expect_deprecated(r"Query.values?\(\) is deprecated"): q2 = ( - q.join("addresses") + q.join(User.addresses) .filter(User.name.like("%e%")) .order_by(User.id, Address.id) .values(User.name, Address.email_address) @@ -4190,7 +3788,7 @@ class MixedEntitiesTest(QueryTest, AssertsCompiledSQL): with testing.expect_deprecated(r"Query.values?\(\) is deprecated"): q2 = ( - q.join("addresses") + q.join(User.addresses) .filter(User.name.like("%e%")) .order_by(desc(Address.email_address)) .slice(1, 3) @@ -4201,7 +3799,7 @@ class MixedEntitiesTest(QueryTest, AssertsCompiledSQL): adalias = aliased(Address) with testing.expect_deprecated(r"Query.values?\(\) is deprecated"): q2 = ( - q.join(adalias, "addresses") + q.join(adalias, User.addresses) .filter(User.name.like("%e%")) .order_by(adalias.email_address) .values(User.name, adalias.email_address) @@ -4327,130 +3925,6 @@ class MixedEntitiesTest(QueryTest, AssertsCompiledSQL): class JoinTest(QueryTest, AssertsCompiledSQL): __dialect__ = "default" - @testing.combinations( - "string_relationship", - "string_relationship_only", - ) - def test_filter_by_from_join(self, onclause_type): - User, Address = self.classes("User", "Address") - (address_table,) = self.tables("addresses") - (user_table,) = self.tables("users") - - sess = fixture_session() - q = sess.query(User) - - with assertions.expect_deprecated_20(join_strings_dep): - if onclause_type == "string_relationship": - q = q.join(Address, "addresses") - elif onclause_type == "string_relationship_only": - q = q.join("addresses") - else: - assert False - - q2 = q.filter_by(email_address="foo") - - self.assert_compile( - q2, - "SELECT users.id AS users_id, users.name AS users_name " - "FROM users JOIN addresses ON users.id = addresses.user_id " - "WHERE addresses.email_address = :email_address_1", - ) - - q2 = q.reset_joinpoint().filter_by(name="user") - self.assert_compile( - q2, - "SELECT users.id AS users_id, users.name AS users_name " - "FROM users JOIN addresses ON users.id = addresses.user_id " - "WHERE users.name = :name_1", - ) - - def test_implicit_joins_from_aliases(self): - Item, User, Order = ( - self.classes.Item, - self.classes.User, - self.classes.Order, - ) - - sess = fixture_session() - OrderAlias = aliased(Order) - - with testing.expect_deprecated_20(join_strings_dep): - eq_( - sess.query(OrderAlias) - .join("items") - .filter_by(description="item 3") - .order_by(OrderAlias.id) - .all(), - [ - Order( - address_id=1, - description="order 1", - isopen=0, - user_id=7, - id=1, - ), - Order( - address_id=4, - description="order 2", - isopen=0, - user_id=9, - id=2, - ), - Order( - address_id=1, - description="order 3", - isopen=1, - user_id=7, - id=3, - ), - ], - ) - - with testing.expect_deprecated_20(join_strings_dep, join_aliased_dep): - eq_( - sess.query(User, OrderAlias, Item.description) - .join(OrderAlias, "orders") - .join("items", from_joinpoint=True) - .filter_by(description="item 3") - .order_by(User.id, OrderAlias.id) - .all(), - [ - ( - User(name="jack", id=7), - Order( - address_id=1, - description="order 1", - isopen=0, - user_id=7, - id=1, - ), - "item 3", - ), - ( - User(name="jack", id=7), - Order( - address_id=1, - description="order 3", - isopen=1, - user_id=7, - id=3, - ), - "item 3", - ), - ( - User(name="fred", id=9), - Order( - address_id=4, - description="order 2", - isopen=0, - user_id=9, - id=2, - ), - "item 3", - ), - ], - ) - def test_orderby_arg_bug(self): User, users, Order = ( self.classes.User, @@ -4463,106 +3937,13 @@ class JoinTest(QueryTest, AssertsCompiledSQL): with testing.expect_deprecated_20(join_aliased_dep): ( sess.query(User) - .join("orders", aliased=True) + .join(User.orders, aliased=True) .order_by(Order.id) .reset_joinpoint() .order_by(users.c.id) .all() ) - def test_aliased(self): - """test automatic generation of aliased joins.""" - - Item, Order, User, Address = ( - self.classes.Item, - self.classes.Order, - self.classes.User, - self.classes.Address, - ) - - sess = fixture_session() - - # test a basic aliasized path - with testing.expect_deprecated(join_aliased_dep, join_strings_dep): - q = ( - sess.query(User) - .join("addresses", aliased=True) - .filter_by(email_address="jack@bean.com") - ) - assert [User(id=7)] == q.all() - - with testing.expect_deprecated(join_aliased_dep, join_strings_dep): - q = ( - sess.query(User) - .join("addresses", aliased=True) - .filter(Address.email_address == "jack@bean.com") - ) - assert [User(id=7)] == q.all() - - with testing.expect_deprecated(join_aliased_dep, join_strings_dep): - q = ( - sess.query(User) - .join("addresses", aliased=True) - .filter( - or_( - Address.email_address == "jack@bean.com", - Address.email_address == "fred@fred.com", - ) - ) - ) - assert [User(id=7), User(id=9)] == q.all() - - # test two aliasized paths, one to 'orders' and the other to - # 'orders','items'. one row is returned because user 7 has order 3 and - # also has order 1 which has item 1 - # this tests a o2m join and a m2m join. - with testing.expect_deprecated( - join_aliased_dep, join_strings_dep, join_chain_dep - ): - q = ( - sess.query(User) - .join("orders", aliased=True) - .filter(Order.description == "order 3") - .join("orders", "items", aliased=True) - .filter(Item.description == "item 1") - ) - assert q.count() == 1 - assert [User(id=7)] == q.all() - - with testing.expect_deprecated(join_strings_dep, join_chain_dep): - # test the control version - same joins but not aliased. rows are - # not returned because order 3 does not have item 1 - q = ( - sess.query(User) - .join("orders") - .filter(Order.description == "order 3") - .join("orders", "items") - .filter(Item.description == "item 1") - ) - assert [] == q.all() - assert q.count() == 0 - - # the left half of the join condition of the any() is aliased. - with testing.expect_deprecated(join_aliased_dep, join_strings_dep): - q = ( - sess.query(User) - .join("orders", aliased=True) - .filter(Order.items.any(Item.description == "item 4")) - ) - assert [User(id=7)] == q.all() - - # test that aliasing gets reset when join() is called - with testing.expect_deprecated(join_aliased_dep, join_strings_dep): - q = ( - sess.query(User) - .join("orders", aliased=True) - .filter(Order.description == "order 3") - .join("orders", aliased=True) - .filter(Order.description == "order 5") - ) - assert q.count() == 1 - assert [User(id=7)] == q.all() - def test_does_filter_aliasing_work(self): User, Address = self.classes("User", "Address") @@ -4584,135 +3965,6 @@ class JoinTest(QueryTest, AssertsCompiledSQL): "WHERE addresses_1.email_address = :email_address_1", ) - def test_overlapping_paths_two(self): - User = self.classes.User - - sess = fixture_session() - - # test overlapping paths. User->orders is used by both joins, but - # rendered once. - with testing.expect_deprecated_20(join_strings_dep, join_chain_dep): - self.assert_compile( - sess.query(User) - .join("orders", "items") - .join("orders", "address"), - "SELECT users.id AS users_id, users.name AS users_name " - "FROM users " - "JOIN orders " - "ON users.id = orders.user_id " - "JOIN order_items AS order_items_1 " - "ON orders.id = order_items_1.order_id " - "JOIN items ON items.id = order_items_1.item_id JOIN " - "addresses " - "ON addresses.id = orders.address_id", - ) - - def test_overlapping_paths_three(self): - User = self.classes.User - - for aliased_ in (True, False): - # load a user who has an order that contains item id 3 and address - # id 1 (order 3, owned by jack) - - warnings = (join_strings_dep, join_chain_dep) - if aliased_: - warnings += (join_aliased_dep,) - - with testing.expect_deprecated_20(*warnings): - result = ( - fixture_session() - .query(User) - .join("orders", "items", aliased=aliased_) - .filter_by(id=3) - .join("orders", "address", aliased=aliased_) - .filter_by(id=1) - .all() - ) - assert [User(id=7, name="jack")] == result - - def test_overlapping_paths_multilevel(self): - User = self.classes.User - - s = fixture_session() - - with testing.expect_deprecated_20(join_strings_dep, join_chain_dep): - q = ( - s.query(User) - .join("orders") - .join("addresses") - .join("orders", "items") - .join("addresses", "dingaling") - ) - self.assert_compile( - q, - "SELECT users.id AS users_id, users.name AS users_name " - "FROM users JOIN orders ON users.id = orders.user_id " - "JOIN addresses ON users.id = addresses.user_id " - "JOIN order_items AS order_items_1 ON orders.id = " - "order_items_1.order_id " - "JOIN items ON items.id = order_items_1.item_id " - "JOIN dingalings ON addresses.id = dingalings.address_id", - ) - - def test_from_joinpoint(self): - Item, User, Order = ( - self.classes.Item, - self.classes.User, - self.classes.Order, - ) - - sess = fixture_session() - - for oalias, ialias in [ - (True, True), - (False, False), - (True, False), - (False, True), - ]: - with testing.expect_deprecated(join_aliased_dep, join_strings_dep): - eq_( - sess.query(User) - .join("orders", aliased=oalias) - .join("items", from_joinpoint=True, aliased=ialias) - .filter(Item.description == "item 4") - .all(), - [User(name="jack")], - ) - - # use middle criterion - with testing.expect_deprecated(join_aliased_dep, join_strings_dep): - eq_( - sess.query(User) - .join("orders", aliased=oalias) - .filter(Order.user_id == 9) - .join("items", from_joinpoint=True, aliased=ialias) - .filter(Item.description == "item 4") - .all(), - [], - ) - - orderalias = aliased(Order) - itemalias = aliased(Item) - with testing.expect_deprecated(join_aliased_dep, join_strings_dep): - eq_( - sess.query(User) - .join(orderalias, "orders") - .join(itemalias, "items", from_joinpoint=True) - .filter(itemalias.description == "item 4") - .all(), - [User(name="jack")], - ) - with testing.expect_deprecated(join_aliased_dep, join_strings_dep): - eq_( - sess.query(User) - .join(orderalias, "orders") - .join(itemalias, "items", from_joinpoint=True) - .filter(orderalias.user_id == 9) - .filter(itemalias.description == "item 4") - .all(), - [], - ) - def test_multi_tuple_form_legacy_one(self): """test the 'tuple' form of join, now superseded by the two-element join() form. @@ -4770,138 +4022,6 @@ class JoinTest(QueryTest, AssertsCompiledSQL): "order_items_1.item_id WHERE items.description = :description_1", ) - def test_multi_tuple_form_legacy_three(self): - """test the 'tuple' form of join, now superseded - by the two-element join() form. - - - """ - - Order, User = ( - self.classes.Order, - self.classes.User, - ) - - sess = fixture_session() - - # the old "backwards" form - with testing.expect_deprecated_20(join_tuple_form, join_strings_dep): - q = ( - sess.query(User) - .join(("orders", Order)) - .filter_by(description="foo") - ) - self.assert_compile( - q, - "SELECT users.id AS users_id, users.name AS users_name " - "FROM users JOIN orders ON users.id = orders.user_id " - "WHERE orders.description = :description_1", - ) - - def test_multi_tuple_form_legacy_three_point_five(self): - """test the 'tuple' form of join, now superseded - by the two-element join() form. - - - """ - - Order, User = ( - self.classes.Order, - self.classes.User, - ) - - sess = fixture_session() - - with testing.expect_deprecated_20(join_strings_dep): - q = ( - sess.query(User) - .join(Order, "orders") - .filter_by(description="foo") - ) - self.assert_compile( - q, - "SELECT users.id AS users_id, users.name AS users_name " - "FROM users JOIN orders ON users.id = orders.user_id " - "WHERE orders.description = :description_1", - ) - - def test_multi_tuple_form_legacy_four(self): - User, Order, Item, Keyword = self.classes( - "User", "Order", "Item", "Keyword" - ) - - sess = fixture_session() - - # ensure when the tokens are broken up that from_joinpoint - # is set between them - - expected = ( - "SELECT users.id AS users_id, users.name AS users_name " - "FROM users JOIN orders ON users.id = orders.user_id " - "JOIN order_items AS order_items_1 ON orders.id = " - "order_items_1.order_id JOIN items ON items.id = " - "order_items_1.item_id JOIN item_keywords AS item_keywords_1 " - "ON items.id = item_keywords_1.item_id " - "JOIN keywords ON keywords.id = item_keywords_1.keyword_id" - ) - - with testing.expect_deprecated_20(join_tuple_form, join_strings_dep): - q = sess.query(User).join( - (Order, "orders"), (Item, "items"), (Keyword, "keywords") - ) - self.assert_compile(q, expected) - - with testing.expect_deprecated_20(join_strings_dep): - q = sess.query(User).join("orders", "items", "keywords") - self.assert_compile(q, expected) - - def test_single_name(self): - User = self.classes.User - - sess = fixture_session() - - with testing.expect_deprecated_20(join_strings_dep): - self.assert_compile( - sess.query(User).join("orders"), - "SELECT users.id AS users_id, users.name AS users_name " - "FROM users JOIN orders ON users.id = orders.user_id", - ) - - with testing.expect_deprecated_20(join_strings_dep): - assert_raises( - sa_exc.InvalidRequestError, - sess.query(User).join("user")._compile_context, - ) - - with testing.expect_deprecated_20(join_strings_dep, join_chain_dep): - self.assert_compile( - sess.query(User).join("orders", "items"), - "SELECT users.id AS users_id, users.name AS users_name " - "FROM users " - "JOIN orders ON users.id = orders.user_id " - "JOIN order_items AS order_items_1 " - "ON orders.id = order_items_1.order_id JOIN items " - "ON items.id = order_items_1.item_id", - ) - - # test overlapping paths. User->orders is used by both joins, but - # rendered once. - with testing.expect_deprecated_20(join_strings_dep, join_chain_dep): - self.assert_compile( - sess.query(User) - .join("orders", "items") - .join("orders", "address"), - "SELECT users.id AS users_id, users.name AS users_name " - "FROM users " - "JOIN orders " - "ON users.id = orders.user_id " - "JOIN order_items AS order_items_1 " - "ON orders.id = order_items_1.order_id " - "JOIN items ON items.id = order_items_1.item_id JOIN " - "addresses " - "ON addresses.id = orders.address_id", - ) - def test_single_prop_5(self): ( Order, @@ -5088,99 +4208,6 @@ class JoinTest(QueryTest, AssertsCompiledSQL): # or: query.join(path_to_some_joined_table_mapper).join(target, # sql_expression) - def test_overlap_with_aliases(self): - orders, User, users = ( - self.tables.orders, - self.classes.User, - self.tables.users, - ) - - oalias = orders.alias("oalias") - - with testing.expect_deprecated_20(join_strings_dep, join_chain_dep): - result = ( - fixture_session() - .query(User) - .select_from(users.join(oalias)) - .filter( - oalias.c.description.in_(["order 1", "order 2", "order 3"]) - ) - .join("orders", "items") - .order_by(User.id) - .all() - ) - assert [User(id=7, name="jack"), User(id=9, name="fred")] == result - - with testing.expect_deprecated_20(join_strings_dep, join_chain_dep): - result = ( - fixture_session() - .query(User) - .select_from(users.join(oalias)) - .filter( - oalias.c.description.in_(["order 1", "order 2", "order 3"]) - ) - .join("orders", "items") - .filter_by(id=4) - .all() - ) - assert [User(id=7, name="jack")] == result - - def test_reset_joinpoint(self): - User = self.classes.User - - for aliased_ in (True, False): - warnings = ( - join_strings_dep, - join_chain_dep, - ) - if aliased_: - warnings += (join_aliased_dep,) - # load a user who has an order that contains item id 3 and address - # id 1 (order 3, owned by jack) - - with fixture_session() as sess: - with testing.expect_deprecated_20(*warnings): - result = ( - sess.query(User) - .join("orders", "items", aliased=aliased_) - .filter_by(id=3) - .reset_joinpoint() - .join("orders", "address", aliased=aliased_) - .filter_by(id=1) - .all() - ) - assert [User(id=7, name="jack")] == result - - with fixture_session() as sess: - with testing.expect_deprecated_20(*warnings): - result = ( - sess.query(User) - .join( - "orders", "items", aliased=aliased_, isouter=True - ) - .filter_by(id=3) - .reset_joinpoint() - .join( - "orders", "address", aliased=aliased_, isouter=True - ) - .filter_by(id=1) - .all() - ) - assert [User(id=7, name="jack")] == result - - with fixture_session() as sess: - with testing.expect_deprecated_20(*warnings): - result = ( - sess.query(User) - .outerjoin("orders", "items", aliased=aliased_) - .filter_by(id=3) - .reset_joinpoint() - .outerjoin("orders", "address", aliased=aliased_) - .filter_by(id=1) - .all() - ) - assert [User(id=7, name="jack")] == result - class AliasFromCorrectLeftTest( fixtures.DeclarativeMappedTest, AssertsCompiledSQL @@ -5237,29 +4264,6 @@ class AliasFromCorrectLeftTest( obj_id = Column(Integer, ForeignKey("object.id")) obj = relationship("Object", backref="x_list") - def test_join_prop_to_string(self): - A, B, X = self.classes("A", "B", "X") - - s = fixture_session() - - with testing.expect_deprecated_20(join_strings_dep): - q = s.query(B).join(B.a_list, "x_list").filter(X.name == "x1") - - with _aliased_join_warning(): - self.assert_compile( - q, - "SELECT object.type AS object_type, b.id AS b_id, " - "object.id AS object_id, object.name AS object_name " - "FROM object JOIN b ON object.id = b.id " - "JOIN a_b_association AS a_b_association_1 " - "ON b.id = a_b_association_1.b_id " - "JOIN (" - "object AS object_1 " - "JOIN a AS a_1 ON object_1.id = a_1.id" - ") ON a_1.id = a_b_association_1.a_id " - "JOIN x ON object_1.id = x.obj_id WHERE x.name = :name_1", - ) - def test_join_prop_to_prop(self): A, B, X = self.classes("A", "B", "X") @@ -5343,19 +4347,6 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL): sess.flush() sess.close() - def test_join_1(self): - Node = self.classes.Node - sess = fixture_session() - - with testing.expect_deprecated_20(join_strings_dep, join_aliased_dep): - node = ( - sess.query(Node) - .join("children", aliased=True) - .filter_by(data="n122") - .first() - ) - assert node.data == "n12" - def test_join_2(self): Node = self.classes.Node sess = fixture_session() @@ -5368,203 +4359,6 @@ class SelfReferentialTest(fixtures.MappedTest, AssertsCompiledSQL): ) assert ret == [("n12",)] - def test_join_3_filter_by(self): - Node = self.classes.Node - sess = fixture_session() - with testing.expect_deprecated_20( - join_strings_dep, join_aliased_dep, join_chain_dep - ): - q = ( - sess.query(Node) - .join("children", "children", aliased=True) - .filter_by(data="n122") - ) - self.assert_compile( - q, - "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id, " - "nodes.data AS nodes_data FROM nodes JOIN nodes AS nodes_1 " - "ON nodes.id = nodes_1.parent_id JOIN nodes AS nodes_2 " - "ON nodes_1.id = nodes_2.parent_id WHERE nodes_2.data = :data_1", - checkparams={"data_1": "n122"}, - ) - node = q.first() - eq_(node.data, "n1") - - def test_join_3_filter(self): - Node = self.classes.Node - sess = fixture_session() - with testing.expect_deprecated_20( - join_strings_dep, join_aliased_dep, join_chain_dep - ): - q = ( - sess.query(Node) - .join("children", "children", aliased=True) - .filter(Node.data == "n122") - ) - self.assert_compile( - q, - "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id, " - "nodes.data AS nodes_data FROM nodes JOIN nodes AS nodes_1 " - "ON nodes.id = nodes_1.parent_id JOIN nodes AS nodes_2 " - "ON nodes_1.id = nodes_2.parent_id WHERE nodes_2.data = :data_1", - checkparams={"data_1": "n122"}, - ) - node = q.first() - eq_(node.data, "n1") - - def test_join_4_filter_by(self): - Node = self.classes.Node - sess = fixture_session() - - with testing.expect_deprecated_20(join_strings_dep, join_aliased_dep): - q = ( - sess.query(Node) - .filter_by(data="n122") - .join("parent", aliased=True) - .filter_by(data="n12") - .join("parent", aliased=True, from_joinpoint=True) - .filter_by(data="n1") - ) - - self.assert_compile( - q, - "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id, " - "nodes.data AS nodes_data FROM nodes JOIN nodes AS nodes_1 " - "ON nodes_1.id = nodes.parent_id JOIN nodes AS nodes_2 " - "ON nodes_2.id = nodes_1.parent_id WHERE nodes.data = :data_1 " - "AND nodes_1.data = :data_2 AND nodes_2.data = :data_3", - checkparams={"data_1": "n122", "data_2": "n12", "data_3": "n1"}, - ) - - node = q.first() - eq_(node.data, "n122") - - def test_join_4_filter(self): - Node = self.classes.Node - sess = fixture_session() - - with testing.expect_deprecated_20(join_strings_dep, join_aliased_dep): - q = ( - sess.query(Node) - .filter(Node.data == "n122") - .join("parent", aliased=True) - .filter(Node.data == "n12") - .join("parent", aliased=True, from_joinpoint=True) - .filter(Node.data == "n1") - ) - - self.assert_compile( - q, - "SELECT nodes.id AS nodes_id, nodes.parent_id AS nodes_parent_id, " - "nodes.data AS nodes_data FROM nodes JOIN nodes AS nodes_1 " - "ON nodes_1.id = nodes.parent_id JOIN nodes AS nodes_2 " - "ON nodes_2.id = nodes_1.parent_id WHERE nodes.data = :data_1 " - "AND nodes_1.data = :data_2 AND nodes_2.data = :data_3", - checkparams={"data_1": "n122", "data_2": "n12", "data_3": "n1"}, - ) - - node = q.first() - eq_(node.data, "n122") - - def test_string_or_prop_aliased_one(self): - """test that join('foo') behaves the same as join(Cls.foo) in a self - referential scenario. - - """ - - Node = self.classes.Node - - sess = fixture_session() - nalias = aliased( - Node, sess.query(Node).filter_by(data="n1").subquery() - ) - - with testing.expect_deprecated_20(join_aliased_dep): - q1 = ( - sess.query(nalias) - .join(nalias.children, aliased=True) - .join(Node.children, from_joinpoint=True) - .filter(Node.data == "n1") - ) - - with testing.expect_deprecated_20(join_aliased_dep, join_strings_dep): - q2 = ( - sess.query(nalias) - .join(nalias.children, aliased=True) - .join("children", from_joinpoint=True) - .filter(Node.data == "n1") - ) - - for q in (q1, q2): - self.assert_compile( - q, - "SELECT anon_1.id AS anon_1_id, anon_1.parent_id AS " - "anon_1_parent_id, anon_1.data AS anon_1_data FROM " - "(SELECT nodes.id AS id, nodes.parent_id AS parent_id, " - "nodes.data AS data FROM nodes WHERE nodes.data = :data_1) " - "AS anon_1 JOIN nodes AS nodes_1 ON anon_1.id = " - "nodes_1.parent_id JOIN nodes " - "ON nodes_1.id = nodes.parent_id " - "WHERE nodes_1.data = :data_2", - use_default_dialect=True, - checkparams={"data_1": "n1", "data_2": "n1"}, - ) - - def test_string_or_prop_aliased_two(self): - Node = self.classes.Node - - sess = fixture_session() - nalias = aliased( - Node, sess.query(Node).filter_by(data="n1").subquery() - ) - - with testing.expect_deprecated_20(join_aliased_dep): - q1 = ( - sess.query(Node) - .filter(Node.data == "n1") - .join(nalias.children, aliased=True) - .filter(nalias.data == "n2") - .join(Node.children, aliased=True, from_joinpoint=True) - .filter(Node.data == "n3") - .join(Node.children, from_joinpoint=True) - .filter(Node.data == "n4") - ) - - with testing.expect_deprecated_20(join_aliased_dep, join_strings_dep): - q2 = ( - sess.query(Node) - .filter(Node.data == "n1") - .join(nalias.children, aliased=True) - .filter(nalias.data == "n2") - .join("children", aliased=True, from_joinpoint=True) - .filter(Node.data == "n3") - .join("children", from_joinpoint=True) - .filter(Node.data == "n4") - ) - - for q in (q1, q2): - self.assert_compile( - q, - "SELECT nodes.id AS nodes_id, nodes.parent_id " - "AS nodes_parent_id, nodes.data AS nodes_data " - "FROM (SELECT nodes.id AS id, nodes.parent_id AS parent_id, " - "nodes.data AS data FROM nodes WHERE nodes.data = :data_1) " - "AS anon_1 JOIN nodes AS nodes_1 " - "ON anon_1.id = nodes_1.parent_id JOIN nodes AS nodes_2 " - "ON nodes_1.id = nodes_2.parent_id JOIN nodes " - "ON nodes_2.id = nodes.parent_id WHERE nodes.data = :data_2 " - "AND anon_1.data = :data_3 AND nodes_2.data = :data_4 " - "AND nodes_2.data = :data_5", - use_default_dialect=True, - checkparams={ - "data_1": "n1", - "data_2": "n1", - "data_3": "n2", - "data_4": "n3", - "data_5": "n4", - }, - ) - class InheritedJoinTest( fixtures.NoCache, @@ -5575,74 +4369,6 @@ class InheritedJoinTest( run_setup_mappers = "once" __dialect__ = "default" - def test_load_only_alias_subclass(self): - Manager = self.classes.Manager - - s = fixture_session() - m1 = aliased(Manager, flat=True) - q = ( - s.query(m1) - .order_by(m1.person_id) - .options(load_only("status", "manager_name")) - ) - with assertions.expect_deprecated_20(opt_strings_dep): - self.assert_compile( - q, - "SELECT managers_1.person_id AS managers_1_person_id, " - "people_1.person_id AS people_1_person_id, " - "people_1.type AS people_1_type, " - "managers_1.status AS managers_1_status, " - "managers_1.manager_name AS managers_1_manager_name " - "FROM people AS people_1 JOIN managers AS " - "managers_1 ON people_1.person_id = managers_1.person_id " - "ORDER BY managers_1.person_id", - ) - - def test_load_only_alias_subclass_bound(self): - Manager = self.classes.Manager - - s = fixture_session() - m1 = aliased(Manager, flat=True) - with assertions.expect_deprecated_20(opt_strings_dep): - q = ( - s.query(m1) - .order_by(m1.person_id) - .options(Load(m1).load_only("status", "manager_name")) - ) - self.assert_compile( - q, - "SELECT managers_1.person_id AS managers_1_person_id, " - "people_1.person_id AS people_1_person_id, " - "people_1.type AS people_1_type, " - "managers_1.status AS managers_1_status, " - "managers_1.manager_name AS managers_1_manager_name " - "FROM people AS people_1 JOIN managers AS " - "managers_1 ON people_1.person_id = managers_1.person_id " - "ORDER BY managers_1.person_id", - ) - - def test_load_only_of_type_with_polymorphic(self): - Company, Person, Manager = self.classes("Company", "Person", "Manager") - s = fixture_session() - - wp = with_polymorphic(Person, [Manager], flat=True) - - # needs to be explicit, we don't currently dig onto all the - # sub-entities in the wp - with assertions.expect_deprecated_20(opt_strings_dep): - assert_raises_message( - sa.exc.ArgumentError, - r'Can\'t find property named "status" on ' - r"with_polymorphic\(Person, \[Manager\]\) in this Query.", - s.query(Company) - .options( - joinedload(Company.employees.of_type(wp)).load_only( - "status" - ) - ) - ._compile_context, - ) - def test_join_to_selectable(self): people, Company, engineers, Engineer = ( self.tables.people, @@ -5708,71 +4434,6 @@ class InheritedJoinTest( use_default_dialect=True, ) - def test_prop_with_polymorphic_1(self): - Person, Manager, Paperwork = ( - self.classes.Person, - self.classes.Manager, - self.classes.Paperwork, - ) - - sess = fixture_session() - - with testing.expect_deprecated_20(join_strings_dep, w_polymorphic_dep): - self.assert_compile( - sess.query(Person) - .with_polymorphic(Manager) - .order_by(Person.person_id) - .join("paperwork") - .filter(Paperwork.description.like("%review%")), - "SELECT people.person_id AS people_person_id, " - "people.company_id AS" - " people_company_id, " - "people.name AS people_name, people.type AS people_type, " - "managers.person_id AS managers_person_id, " - "managers.status AS managers_status, managers.manager_name AS " - "managers_manager_name FROM people " - "LEFT OUTER JOIN managers " - "ON people.person_id = managers.person_id " - "JOIN paperwork " - "ON people.person_id = paperwork.person_id " - "WHERE paperwork.description LIKE :description_1 " - "ORDER BY people.person_id", - use_default_dialect=True, - ) - - def test_prop_with_polymorphic_2(self): - Person, Manager, Paperwork = ( - self.classes.Person, - self.classes.Manager, - self.classes.Paperwork, - ) - - sess = fixture_session() - - with testing.expect_deprecated_20( - join_strings_dep, w_polymorphic_dep, join_aliased_dep - ): - self.assert_compile( - sess.query(Person) - .with_polymorphic(Manager) - .order_by(Person.person_id) - .join("paperwork", aliased=True) - .filter(Paperwork.description.like("%review%")), - "SELECT people.person_id AS people_person_id, " - "people.company_id AS people_company_id, " - "people.name AS people_name, people.type AS people_type, " - "managers.person_id AS managers_person_id, " - "managers.status AS managers_status, " - "managers.manager_name AS managers_manager_name " - "FROM people LEFT OUTER JOIN managers " - "ON people.person_id = managers.person_id " - "JOIN paperwork AS paperwork_1 " - "ON people.person_id = paperwork_1.person_id " - "WHERE paperwork_1.description " - "LIKE :description_1 ORDER BY people.person_id", - use_default_dialect=True, - ) - def test_with_poly_loader_criteria_warning(self): Person, Manager = ( self.classes.Person, @@ -5803,7 +4464,7 @@ class InheritedJoinTest( with _aliased_join_deprecation(): eq_( sess.query(Company) - .join(people.join(engineers), "employees") + .join(people.join(engineers), Company.employees) .filter(Engineer.primary_language == "java") .all(), [self.c1], @@ -5828,7 +4489,7 @@ class InheritedJoinTest( with _aliased_join_deprecation(): eq_( sess.query(Company) - .join(people.join(engineers), "employees") + .join(people.join(engineers), Company.employees) .filter(Engineer.primary_language == "java") .all(), [self.c1], @@ -5843,7 +4504,7 @@ class InheritedJoinTest( with _aliased_join_deprecation(): eq_( sess.query(Company) - .join(people.join(engineers), "employees") + .join(people.join(engineers), Company.employees) .join(Engineer.machines) .all(), [self.c1, self.c2], @@ -5858,7 +4519,7 @@ class InheritedJoinTest( with _aliased_join_deprecation(): eq_( sess.query(Company) - .join(people.join(engineers), "employees") + .join(people.join(engineers), Company.employees) .join(Engineer.machines) .filter(Engineer.name == "dilbert") .all(), @@ -5876,7 +4537,7 @@ class InheritedJoinTest( with _aliased_join_deprecation(): eq_( sess.query(Company) - .join(people.join(engineers), "employees") + .join(people.join(engineers), Company.employees) .join(Engineer.machines) .filter(Machine.name.ilike("%thinkpad%")) .all(), @@ -5955,63 +4616,19 @@ class MultiplePathTest(fixtures.MappedTest, AssertsCompiledSQL): ), Column("data", String(30)), ) - - Table( - "t1t2_1", - metadata, - Column("t1id", Integer, ForeignKey("t1.id")), - Column("t2id", Integer, ForeignKey("t2.id")), - ) - - Table( - "t1t2_2", - metadata, - Column("t1id", Integer, ForeignKey("t1.id")), - Column("t2id", Integer, ForeignKey("t2.id")), - ) - - def test_basic(self): - t2, t1t2_1, t1t2_2, t1 = ( - self.tables.t2, - self.tables.t1t2_1, - self.tables.t1t2_2, - self.tables.t1, - ) - - class T1: - pass - - class T2: - pass - - self.mapper_registry.map_imperatively( - T1, - t1, - properties={ - "t2s_1": relationship(T2, secondary=t1t2_1), - "t2s_2": relationship(T2, secondary=t1t2_2), - }, + + Table( + "t1t2_1", + metadata, + Column("t1id", Integer, ForeignKey("t1.id")), + Column("t2id", Integer, ForeignKey("t2.id")), ) - self.mapper_registry.map_imperatively(T2, t2) - with testing.expect_deprecated_20(join_strings_dep): - q = ( - fixture_session() - .query(T1) - .join("t2s_1") - .filter(t2.c.id == 5) - .reset_joinpoint() - .join("t2s_2") - ) - self.assert_compile( - q, - "SELECT t1.id AS t1_id, t1.data AS t1_data FROM t1 " - "JOIN t1t2_1 AS t1t2_1_1 " - "ON t1.id = t1t2_1_1.t1id JOIN t2 ON t2.id = t1t2_1_1.t2id " - "JOIN t1t2_2 AS t1t2_2_1 " - "ON t1.id = t1t2_2_1.t1id JOIN t2 ON t2.id = t1t2_2_1.t2id " - "WHERE t2.id = :id_1", - use_default_dialect=True, + Table( + "t1t2_2", + metadata, + Column("t1id", Integer, ForeignKey("t1.id")), + Column("t2id", Integer, ForeignKey("t2.id")), ) @@ -6274,27 +4891,6 @@ class RequirementsTest(fixtures.MappedTest): class DeferredOptionsTest(AssertsCompiledSQL, _fixtures.FixtureTest): __dialect__ = "default" - def test_load_only_synonym(self): - orders, Order = self.tables.orders, self.classes.Order - - self.mapper_registry.map_imperatively( - Order, - orders, - properties={"desc": synonym("description")}, - ) - - opt = load_only("isopen", "desc") - - sess = fixture_session() - q = sess.query(Order).options(opt) - with assertions.expect_deprecated_20(opt_strings_dep): - self.assert_compile( - q, - "SELECT orders.id AS orders_id, orders.description " - "AS orders_description, orders.isopen AS orders_isopen " - "FROM orders", - ) - def test_deep_options(self): users, items, order_items, Order, Item, User, orders = ( self.tables.users, @@ -6353,51 +4949,6 @@ class OptionsTest(PathTest, OptionsQueryTest): strategy_options._UnboundLoad.joinedload, arg, True, {} ) - def test_chained(self): - User = self.classes.User - Order = self.classes.Order - sess = fixture_session() - q = sess.query(User) - opt = self._option_fixture(User.orders).joinedload("items") - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_path_result( - opt, q, [(User, "orders"), (User, "orders", Order, "items")] - ) - - def test_chained_plus_dotted(self): - User = self.classes.User - Order = self.classes.Order - Item = self.classes.Item - sess = fixture_session() - q = sess.query(User) - opt = self._option_fixture("orders.items").joinedload("keywords") - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_path_result( - opt, - q, - [ - (User, "orders"), - (User, "orders", Order, "items"), - (User, "orders", Order, "items", Item, "keywords"), - ], - ) - - def test_with_current_matching_string(self): - Item, User, Order = ( - self.classes.Item, - self.classes.User, - self.classes.Order, - ) - - sess = fixture_session() - q = sess.query(Item)._with_current_path( - self._make_path_registry([User, "orders", Order, "items"]) - ) - - opt = self._option_fixture("orders.items.keywords") - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_path_result(opt, q, [(Item, "keywords")]) - def test_with_current_nonmatching_string(self): Item, User, Order = ( self.classes.Item, @@ -6416,76 +4967,6 @@ class OptionsTest(PathTest, OptionsQueryTest): opt = self._option_fixture("items.keywords") self._assert_path_result(opt, q, []) - def test_path_multilevel_string(self): - Item, User, Order = ( - self.classes.Item, - self.classes.User, - self.classes.Order, - ) - - sess = fixture_session() - q = sess.query(User) - - opt = self._option_fixture("orders.items.keywords") - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_path_result( - opt, - q, - [ - (User, "orders"), - (User, "orders", Order, "items"), - (User, "orders", Order, "items", Item, "keywords"), - ], - ) - - def test_chained_plus_multi(self): - User = self.classes.User - Order = self.classes.Order - Item = self.classes.Item - sess = fixture_session() - q = sess.query(User) - opt = self._option_fixture(User.orders, Order.items).joinedload( - "keywords" - ) - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_path_result( - opt, - q, - [ - (User, "orders"), - (User, "orders", Order, "items"), - (User, "orders", Order, "items", Item, "keywords"), - ], - ) - - def test_multi_entity_opt_on_string(self): - Item = self.classes.Item - Order = self.classes.Order - opt = self._option_fixture("items") - sess = fixture_session() - q = sess.query(Item, Order) - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_path_result(opt, q, []) - - def test_get_path_one_level_string(self): - User = self.classes.User - - sess = fixture_session() - q = sess.query(User) - - opt = self._option_fixture("addresses") - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_path_result(opt, q, [(User, "addresses")]) - - def test_get_path_one_level_with_unrelated(self): - Order = self.classes.Order - - sess = fixture_session() - q = sess.query(Order) - opt = self._option_fixture("addresses") - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_path_result(opt, q, []) - class SubOptionsTest(PathTest, OptionsQueryTest): run_create_tables = False @@ -6539,70 +5020,6 @@ class SubOptionsTest(PathTest, OptionsQueryTest): {path: strat_as_tuple(load) for path, load in attr_b.items()}, ) - def test_invalid_two(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - # these options are "invalid", in that User.orders -> Item.keywords - # is not a path. However, the "normal" option is not generating - # an error for now, which is bad, but we're testing here only that - # it works the same way, so there you go. If and when we make this - # case raise, then both cases should raise in the same way. - sub_opt = joinedload("orders").options( - joinedload("keywords"), joinedload("items") - ) - non_sub_opts = [ - joinedload(User.orders).joinedload(Item.keywords), - defaultload(User.orders).joinedload(Order.items), - ] - sess = fixture_session() - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_opts(sess.query(User), sub_opt, non_sub_opts) - - def test_four_strings(self): - User, Address, Order, Item, SubItem, Keyword = self.classes( - "User", "Address", "Order", "Item", "SubItem", "Keyword" - ) - sub_opt = joinedload("orders").options( - defer("description"), - joinedload("items").options( - joinedload("keywords").options(defer("name")), - defer("description"), - ), - ) - non_sub_opts = [ - joinedload(User.orders), - defaultload(User.orders).defer(Order.description), - defaultload(User.orders).joinedload(Order.items), - defaultload(User.orders) - .defaultload(Order.items) - .joinedload(Item.keywords), - defaultload(User.orders) - .defaultload(Order.items) - .defer(Item.description), - defaultload(User.orders) - .defaultload(Order.items) - .defaultload(Item.keywords) - .defer(Keyword.name), - ] - sess = fixture_session() - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_opts(sess.query(User), sub_opt, non_sub_opts) - - def test_five_strings(self): - User, Address, Order, Item, SubItem, Keyword = self.classes( - "User", "Address", "Order", "Item", "SubItem", "Keyword" - ) - sub_opt = joinedload("orders").options(load_only("description")) - non_sub_opts = [ - joinedload(User.orders), - defaultload(User.orders).load_only(Order.description), - ] - sess = fixture_session() - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_opts(sess.query(User), sub_opt, non_sub_opts) - class OptionsNoPropTest(_fixtures.FixtureTest): """test the error messages emitted when using property @@ -6626,36 +5043,6 @@ class OptionsNoPropTest(_fixtures.FixtureTest): Item.id, "keywords", message ) - def test_option_against_nonexistent_basestring(self): - Item = self.classes.Item - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_eager_with_entity_exception( - [Item], - (joinedload("foo"),), - 'Can\'t find property named "foo" on mapped class ' - "Item->items in this Query.", - ) - - def test_option_against_nonexistent_twolevel_basestring(self): - Item = self.classes.Item - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_eager_with_entity_exception( - [Item], - (joinedload("keywords.foo"),), - 'Can\'t find property named "foo" on mapped class ' - "Keyword->keywords in this Query.", - ) - - def test_option_against_nonexistent_twolevel_chained(self): - Item = self.classes.Item - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_eager_with_entity_exception( - [Item], - (joinedload("keywords").joinedload("foo"),), - 'Can\'t find property named "foo" on mapped class ' - "Keyword->keywords in this Query.", - ) - @testing.fails_if( lambda: True, "PropertyOption doesn't yet check for relation/column on end result", @@ -6684,54 +5071,6 @@ class OptionsNoPropTest(_fixtures.FixtureTest): "does not refer to a mapped entity", ) - def test_option_against_wrong_entity_type_basestring(self): - Item = self.classes.Item - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_loader_strategy_exception( - [Item], - (joinedload("id").joinedload("keywords"),), - 'Can\'t apply "joined loader" strategy to property "Item.id", ' - 'which is a "column property"; this loader strategy is ' - 'intended to be used with a "relationship property".', - ) - - def test_col_option_against_relationship_basestring(self): - Item = self.classes.Item - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_loader_strategy_exception( - [Item], - (load_only("keywords"),), - 'Can\'t apply "column loader" strategy to property ' - '"Item.keywords", which is a "relationship property"; this ' - "loader strategy is intended to be used with a " - '"column property".', - ) - - def test_option_against_multi_non_relation_twolevel_basestring(self): - Item = self.classes.Item - Keyword = self.classes.Keyword - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_loader_strategy_exception( - [Keyword, Item], - (joinedload("id").joinedload("keywords"),), - 'Can\'t apply "joined loader" strategy to property ' - '"Keyword.id", ' - 'which is a "column property"; this loader strategy is ' - "intended " - 'to be used with a "relationship property".', - ) - - def test_option_against_multi_nonexistent_basestring(self): - Item = self.classes.Item - Keyword = self.classes.Keyword - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_eager_with_entity_exception( - [Keyword, Item], - (joinedload("description"),), - 'Can\'t find property named "description" on mapped class ' - "Keyword->keywords in this Query.", - ) - def test_option_against_multi_no_entities_basestring(self): Item = self.classes.Item Keyword = self.classes.Keyword @@ -6742,24 +5081,6 @@ class OptionsNoPropTest(_fixtures.FixtureTest): 'named "keywords".', ) - def test_option_with_mapper_then_column_basestring(self): - Item = self.classes.Item - - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_option([Item, Item.id], "keywords") - - def test_option_with_mapper_basestring(self): - Item = self.classes.Item - - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_option([Item], "keywords") - - def test_option_with_column_then_mapper_basestring(self): - Item = self.classes.Item - - with assertions.expect_deprecated_20(opt_strings_dep): - self._assert_option([Item.id, Item], "keywords") - @classmethod def setup_mappers(cls): users, User, addresses, Address, orders, Order = ( @@ -6858,226 +5179,6 @@ class OptionsNoPropTest(_fixtures.FixtureTest): ) -class OptionsNoPropTestInh(_Polymorphic): - def test_missing_str_attr_of_type_subclass(self): - s = fixture_session() - - with assertions.expect_deprecated_20(opt_strings_dep): - assert_raises_message( - sa.exc.ArgumentError, - r'Can\'t find property named "manager_name" on ' - r"mapped class Engineer->engineers in this Query.$", - s.query(Company) - .options( - joinedload(Company.employees.of_type(Engineer)).load_only( - "manager_name" - ) - ) - ._compile_state, - ) - - -class CacheKeyTest(CacheKeyFixture, _fixtures.FixtureTest): - """In these tests we've moved / adapted all the tests from - test_cache_key that make use of string options or string join(). Because - we are ensuring cache keys are distinct we still keep a lot of the - non-deprecated cases in the lists that we are testing. - - """ - - run_setup_mappers = "once" - run_inserts = None - run_deletes = None - - @classmethod - def setup_mappers(cls): - cls._setup_stock_mapping() - - def _stmt_20(self, *elements): - return tuple( - elem._statement_20() if isinstance(elem, sa.orm.Query) else elem - for elem in elements - ) - - def _deprecated_opt(self, fn): - with assertions.expect_deprecated_20( - opt_strings_dep, raise_on_any_unexpected=True - ): - return fn() - - def _deprecated_join(self, fn): - with assertions.expect_deprecated_20( - join_strings_dep, raise_on_any_unexpected=True - ): - return fn() - - def _deprecated_join_w_aliased(self, fn): - with assertions.expect_deprecated_20( - join_strings_dep, join_aliased_dep, raise_on_any_unexpected=True - ): - return fn() - - def test_bound_options(self): - User, Address, Keyword, Order, Item = self.classes( - "User", "Address", "Keyword", "Order", "Item" - ) - - self._run_cache_key_fixture( - lambda: ( - Load(User).joinedload(User.addresses), - Load(User).joinedload( - User.addresses.of_type(aliased(Address)) - ), - Load(User).joinedload(User.orders), - self._deprecated_opt( - lambda: Load(User).subqueryload("addresses") - ), - self._deprecated_opt(lambda: Load(Address).defer("id")), - Load(Address).defer("*"), - self._deprecated_opt( - lambda: Load(aliased(Address)).defer("id") - ), - Load(User).joinedload(User.addresses).defer(Address.id), - Load(User).joinedload(User.orders).joinedload(Order.items), - Load(User).joinedload(User.orders).subqueryload(Order.items), - Load(User).subqueryload(User.orders).subqueryload(Order.items), - Load(Address).raiseload("*"), - self._deprecated_opt(lambda: Load(Address).raiseload("user")), - ), - compare_values=True, - ) - - def test_bound_options_equiv_on_strname(self): - """Bound loader options resolve on string name so test that the cache - key for the string version matches the resolved version. - - """ - User, Address, Keyword, Order, Item = self.classes( - "User", "Address", "Keyword", "Order", "Item" - ) - - for left, right in [ - ( - Load(User).defer(User.id), - self._deprecated_opt(lambda: Load(User).defer("id")), - ), - ( - Load(User).joinedload(User.addresses), - self._deprecated_opt( - lambda: Load(User).joinedload("addresses") - ), - ), - ( - Load(User).joinedload(User.orders).joinedload(Order.items), - self._deprecated_opt( - lambda: Load(User).joinedload("orders").joinedload("items") - ), - ), - ]: - eq_(left._generate_cache_key(), right._generate_cache_key()) - - def test_orm_query_w_orm_joins(self): - - User, Address, Keyword, Order, Item = self.classes( - "User", "Address", "Keyword", "Order", "Item" - ) - - a1 = aliased(Address) - - self._run_cache_key_fixture( - lambda: self._stmt_20( - fixture_session().query(User).join(User.addresses), - fixture_session().query(User).join(User.orders), - fixture_session() - .query(User) - .join(User.addresses) - .join(User.orders), - self._deprecated_join_w_aliased( - lambda: fixture_session() - .query(User) - .join("addresses") - .join("dingalings", from_joinpoint=True) - ), - self._deprecated_join( - lambda: fixture_session().query(User).join("addresses") - ), - self._deprecated_join( - lambda: fixture_session().query(User).join("orders") - ), - self._deprecated_join( - lambda: fixture_session() - .query(User) - .join("addresses") - .join("orders") - ), - fixture_session().query(User).join(Address, User.addresses), - self._deprecated_join( - lambda: fixture_session().query(User).join(a1, "addresses") - ), - self._deprecated_join_w_aliased( - lambda: fixture_session() - .query(User) - .join(a1, "addresses", aliased=True) - ), - fixture_session().query(User).join(User.addresses.of_type(a1)), - ), - compare_values=True, - ) - - def test_unbound_options(self): - User, Address, Keyword, Order, Item = self.classes( - "User", "Address", "Keyword", "Order", "Item" - ) - - # unbound options dont emit a deprecation warning during cache - # key generation - self._run_cache_key_fixture( - lambda: ( - joinedload(User.addresses), - joinedload(User.addresses.of_type(aliased(Address))), - joinedload("addresses"), - joinedload(User.orders), - joinedload(User.orders.and_(Order.id != 5)), - joinedload(User.orders).selectinload("items"), - joinedload(User.orders).selectinload(Order.items), - defer(User.id), - defer("id"), - defer("*"), - defer(Address.id), - joinedload(User.addresses).defer(Address.id), - joinedload(User.addresses).defer("id"), - subqueryload(User.orders) - .subqueryload(Order.items) - .defer(Item.description), - defaultload(User.orders).defaultload(Order.items), - defaultload(User.orders), - ), - compare_values=True, - ) - - def test_unbound_sub_options(self): - """test #6869""" - - User, Address, Keyword, Order, Item = self.classes( - "User", "Address", "Keyword", "Order", "Item" - ) - - self._run_cache_key_fixture( - lambda: ( - joinedload(User.addresses).options( - joinedload(Address.dingaling) - ), - joinedload(User.addresses).options( - joinedload(Address.dingaling).options(load_only("name")) - ), - joinedload(User.orders).options( - joinedload(Order.items).options(joinedload(Item.keywords)) - ), - ), - compare_values=True, - ) - - class PolyCacheKeyTest(CacheKeyFixture, _poly_fixtures._Polymorphic): run_setup_mappers = "once" run_inserts = None @@ -7161,30 +5262,6 @@ class PolyCacheKeyTest(CacheKeyFixture, _poly_fixtures._Polymorphic): ) -class AliasedClassRelationshipTest( - PartitionByFixture, testing.AssertsCompiledSQL -): - __requires__ = ("window_functions",) - __dialect__ = "default" - - def test_selectinload_w_joinedload_after(self): - """test has been enhanced to also test #7224""" - - A, B, C = self.classes("A", "B", "C") - - s = Session(testing.db) - - opt = selectinload(A.partitioned_bs).joinedload("cs") - - def go(): - for a1 in s.query(A).options(opt): - for b in a1.partitioned_bs: - eq_(len(b.cs), 2) - - with assertions.expect_deprecated_20(opt_strings_dep): - self.assert_sql_count(testing.db, go, 2) - - class ColumnAccessTest(QueryTest, AssertsCompiledSQL): """test access of columns after _from_selectable has been applied""" @@ -7317,7 +5394,7 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): eq_( sess.query(User) .select_entity_from(sel.subquery()) - .join("addresses") + .join(User.addresses) .add_entity(Address) .order_by(User.id) .order_by(Address.id) @@ -7351,7 +5428,7 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): eq_( sess.query(User) .select_entity_from(sel.subquery()) - .join(adalias, "addresses") + .join(adalias, User.addresses) .add_entity(adalias) .order_by(User.id) .order_by(adalias.id) @@ -7504,9 +5581,9 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): sess.query(User) .select_entity_from(sel.subquery()) .options( - joinedload("orders") - .joinedload("items") - .joinedload("keywords") + joinedload(User.orders) + .joinedload(Order.items) + .joinedload(Item.keywords) ) .join(User.orders, Order.items, Item.keywords) .filter(Keyword.name.in_(["red", "big", "round"])) @@ -7621,7 +5698,7 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): with assertions.expect_deprecated_20(sef_dep): eq_( sess.query(User) - .options(joinedload("addresses")) + .options(joinedload(User.addresses)) .select_entity_from(sel.subquery()) .order_by(User.id) .all(), @@ -7645,7 +5722,7 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): with assertions.expect_deprecated_20(sef_dep): eq_( sess.query(User) - .options(joinedload("addresses")) + .options(joinedload(User.addresses)) .select_entity_from(sel.subquery()) .filter(User.id == 8) .order_by(User.id) @@ -7669,7 +5746,7 @@ class SelectFromTest(QueryTest, AssertsCompiledSQL): with assertions.expect_deprecated_20(sef_dep): eq_( sess.query(User) - .options(joinedload("addresses")) + .options(joinedload(User.addresses)) .select_entity_from(sel.subquery()) .order_by(User.id)[1], User( diff --git a/test/orm/test_joins.py b/test/orm/test_joins.py index 988fad6629..351938a512 100644 --- a/test/orm/test_joins.py +++ b/test/orm/test_joins.py @@ -34,6 +34,7 @@ from sqlalchemy.testing import assert_raises_message from sqlalchemy.testing import AssertsCompiledSQL from sqlalchemy.testing import eq_ from sqlalchemy.testing import fixtures +from sqlalchemy.testing.assertions import expect_raises_message from sqlalchemy.testing.fixtures import fixture_session from sqlalchemy.testing.schema import Column from test.orm import _fixtures @@ -1700,6 +1701,52 @@ class JoinTest(QueryTest, AssertsCompiledSQL): stmt.compile, ) + def test_no_strings_for_single_onclause_legacy_query(self): + User = self.classes.User + + sess = fixture_session() + + with expect_raises_message( + sa_exc.ArgumentError, + "Join target, typically a FROM expression, or ORM relationship " + "attribute expected, got 'addresses'", + ): + sess.query(User).join("addresses") + + def test_no_strings_for_single_onclause_newstyle(self): + User = self.classes.User + + with expect_raises_message( + sa_exc.ArgumentError, + "Join target, typically a FROM expression, or ORM relationship " + "attribute expected, got 'addresses'", + ): + select(User).join("addresses") + + def test_no_strings_for_dual_onclause_legacy_query(self): + User = self.classes.User + Address = self.classes.Address + + sess = fixture_session() + + with expect_raises_message( + sa_exc.ArgumentError, + "Join target, typically a FROM expression, or ORM relationship " + "attribute expected, got 'addresses'", + ): + sess.query(User).join(Address, "addresses") + + def test_no_strings_for_dual_onclause_newstyle(self): + User = self.classes.User + Address = self.classes.Address + + with expect_raises_message( + sa_exc.ArgumentError, + r"Textual SQL expression 'addresses' should be explicitly " + r"declared as text\('addresses'\)", + ): + select(User).join(Address, "addresses") + def test_select_from(self): """Test that the left edge of the join can be set reliably with select_from().""" diff --git a/test/orm/test_options.py b/test/orm/test_options.py index dcb2103291..25d5dcc6e5 100644 --- a/test/orm/test_options.py +++ b/test/orm/test_options.py @@ -27,6 +27,7 @@ from sqlalchemy.testing.assertions import assert_raises_message from sqlalchemy.testing.assertions import AssertsCompiledSQL from sqlalchemy.testing.assertions import emits_warning from sqlalchemy.testing.assertions import eq_ +from sqlalchemy.testing.assertions import expect_raises_message from sqlalchemy.testing.fixtures import fixture_session from test.orm import _fixtures from .inheritance._poly_fixtures import _Polymorphic @@ -36,10 +37,6 @@ from .inheritance._poly_fixtures import Manager from .inheritance._poly_fixtures import Person -def _deprecated_strings(): - return testing.expect_deprecated_20("Using strings to indicate") - - class QueryTest(_fixtures.FixtureTest): run_setup_mappers = "once" run_inserts = "once" @@ -153,34 +150,6 @@ class LoadTest(PathTest, QueryTest): self._make_path_registry([User, "name"]), ) - def test_gen_path_string_entity(self): - User = self.classes.User - Address = self.classes.Address - - result = Load(User) - with _deprecated_strings(): - eq_( - result._generate_path( - inspect(User)._path_registry, - "addresses", - None, - "relationship", - ), - self._make_path_registry([User, "addresses", Address]), - ) - - def test_gen_path_string_column(self): - User = self.classes.User - - result = Load(User) - with _deprecated_strings(): - eq_( - result._generate_path( - inspect(User)._path_registry, "name", None, "column" - ), - self._make_path_registry([User, "name"]), - ) - def test_gen_path_invalid_from_col(self): User = self.classes.User @@ -214,21 +183,6 @@ class LoadTest(PathTest, QueryTest): "relationship", ) - def test_gen_path_attr_str_not_mapped(self): - OrderWProp = self.classes.OrderWProp - - sess = fixture_session() - q = sess.query(OrderWProp).options(defer("some_attr")) - - with _deprecated_strings(): - assert_raises_message( - sa.exc.ArgumentError, - r"Expected attribute \"some_attr\" on mapped class " - "OrderWProp->orders to be a mapped attribute; instead " - "got .*property.* object.", - q._compile_state, - ) - def test_gen_path_attr_entity_invalid_noraiseerr(self): User = self.classes.User Order = self.classes.Order @@ -320,38 +274,6 @@ class OfTypePathingTest(PathTest, QueryTest): [(User, "addresses"), (User, "addresses", SubAddr, "sub_attr")], ) - @emits_warning("This declarative base already contains a class") - def test_oftype_only_col_attr_string_unbound(self): - User, Address, SubAddr = self._fixture() - - l1 = defaultload(User.addresses.of_type(SubAddr)).defer("sub_attr") - - sess = fixture_session() - q = sess.query(User) - self._assert_path_result( - l1, - q, - [(User, "addresses"), (User, "addresses", SubAddr, "sub_attr")], - ) - - @emits_warning("This declarative base already contains a class") - def test_oftype_only_col_attr_string_bound(self): - User, Address, SubAddr = self._fixture() - - l1 = ( - Load(User) - .defaultload(User.addresses.of_type(SubAddr)) - .defer("sub_attr") - ) - - sess = fixture_session() - q = sess.query(User) - self._assert_path_result( - l1, - q, - [(User, "addresses"), (User, "addresses", SubAddr, "sub_attr")], - ) - @emits_warning("This declarative base already contains a class") def test_oftype_only_rel_attr_unbound(self): User, Address, SubAddr = self._fixture() @@ -382,36 +304,6 @@ class OfTypePathingTest(PathTest, QueryTest): l1, q, [(User, "addresses"), (User, "addresses", SubAddr, "dings")] ) - @emits_warning("This declarative base already contains a class") - def test_oftype_only_rel_attr_string_unbound(self): - User, Address, SubAddr = self._fixture() - - l1 = defaultload(User.addresses.of_type(SubAddr)).joinedload("dings") - - sess = fixture_session() - q = sess.query(User) - self._assert_path_result( - l1, q, [(User, "addresses"), (User, "addresses", SubAddr, "dings")] - ) - - @emits_warning("This declarative base already contains a class") - def test_oftype_only_rel_attr_string_bound(self): - User, Address, SubAddr = self._fixture() - - l1 = ( - Load(User) - .defaultload(User.addresses.of_type(SubAddr)) - .defer("sub_attr") - ) - - sess = fixture_session() - q = sess.query(User) - self._assert_path_result( - l1, - q, - [(User, "addresses"), (User, "addresses", SubAddr, "sub_attr")], - ) - class WithEntitiesTest(QueryTest, AssertsCompiledSQL): __dialect__ = "default" @@ -509,6 +401,58 @@ class OptionsTest(PathTest, QueryTest): strategy_options._UnboundLoad.joinedload, arg, True, {} ) + @testing.combinations( + lambda: joinedload("addresses"), + lambda: defer("name"), + lambda Address: joinedload("addresses").joinedload(Address.dingaling), + lambda: joinedload("addresses"), + ) + def test_error_for_string_names_unbound(self, test_case): + User, Address = self.classes("User", "Address") + + query = fixture_session().query(User) + + with expect_raises_message( + sa.exc.ArgumentError, + "Strings are not accepted for attribute names in loader " + "options; please use class-bound attributes directly.", + ): + unbound_opt = testing.resolve_lambda( + test_case, User=User, Address=Address + ) + + # TODO: the strings above should raise immediately, we should + # not have to bind_loader for this to occur. + attr = {} + for opt in unbound_opt._to_bind: + opt._bind_loader( + [ + ent.entity_zero + for ent in query._compile_state()._lead_mapper_entities + ], + query._compile_options._current_path, + attr, + False, + ) + + @testing.combinations( + lambda User: Load(User).joinedload("addresses"), + lambda User: Load(User).defer("name"), + lambda User, Address: Load(User) + .joinedload("addresses") + .joinedload(Address.dingaling), + lambda User: Load(User).joinedload("addresses"), + ) + def test_error_for_string_names_bound(self, test_case): + User, Address = self.classes("User", "Address") + + with expect_raises_message( + sa.exc.ArgumentError, + "Strings are not accepted for attribute names in loader " + "options; please use class-bound attributes directly.", + ): + testing.resolve_lambda(test_case, User=User, Address=Address) + def test_get_path_one_level_attribute(self): User = self.classes.User @@ -741,36 +685,6 @@ class OptionsTest(PathTest, QueryTest): ], ) - @emits_warning("This declarative base already contains a class") - def test_of_type_string_attr(self): - User, Address = self.classes.User, self.classes.Address - - sess = fixture_session() - - class SubAddr(Address): - pass - - self.mapper_registry.map_imperatively(SubAddr, inherits=Address) - - q = sess.query(User) - opt = self._option_fixture(User.addresses.of_type(SubAddr), "user") - - u_mapper = inspect(User) - a_mapper = inspect(Address) - self._assert_path_result( - opt, - q, - [ - (u_mapper, u_mapper.attrs.addresses), - ( - u_mapper, - u_mapper.attrs.addresses, - a_mapper, - a_mapper.attrs.user, - ), - ], - ) - @emits_warning("This declarative base already contains a class") def test_of_type_plus_level(self): Dingaling, User, Address = ( diff --git a/test/orm/test_query.py b/test/orm/test_query.py index 4cce3f33f9..d3ca9521f8 100644 --- a/test/orm/test_query.py +++ b/test/orm/test_query.py @@ -6358,6 +6358,21 @@ class TextErrorTest(QueryTest, AssertsCompiledSQL): class ParentTest(QueryTest, AssertsCompiledSQL): __dialect__ = "default" + def test_no_strings(self): + User = self.classes.User + + sess = fixture_session() + q = sess.query(User) + + u1 = q.filter_by(name="jack").one() + + with expect_raises_message( + sa_exc.ArgumentError, + r"with_parent\(\) accepts class-bound mapped " + "attributes, not strings", + ): + with_parent(u1, "orders") + def test_o2m(self): User, Order = ( self.classes.User,