From 0f98ead274df103a5a623baa3898580932e1c8e2 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Tue, 26 Oct 2021 15:37:16 -0400 Subject: [PATCH] deprecation warnings: with_parent, aliased, from_joinpoint most of the work for aliased / from_joinpoint has been done already as I added all new tests for these and moved most aliased/from_joinpoint to test/orm/test_deprecations.py already Change-Id: Ia23e332dec183de17b2fb9d89d946af8d5e89ae7 --- lib/sqlalchemy/exc.py | 15 +- lib/sqlalchemy/orm/query.py | 1 + lib/sqlalchemy/testing/assertions.py | 2 +- lib/sqlalchemy/testing/warnings.py | 3 - lib/sqlalchemy/util/deprecations.py | 9 +- test/base/test_except.py | 2 + test/orm/inheritance/test_deprecations.py | 260 +++++++++++++++++ test/orm/inheritance/test_polymorphic_rel.py | 82 +----- test/orm/inheritance/test_single.py | 32 -- test/orm/test_cascade.py | 3 +- test/orm/test_deprecations.py | 289 +++++++++++++++++++ test/orm/test_lambdas.py | 20 -- test/orm/test_lazy_relations.py | 5 +- test/orm/test_naturalpks.py | 5 +- test/orm/test_query.py | 148 +++------- test/orm/test_relationships.py | 12 +- 16 files changed, 631 insertions(+), 257 deletions(-) create mode 100644 test/orm/inheritance/test_deprecations.py diff --git a/lib/sqlalchemy/exc.py b/lib/sqlalchemy/exc.py index 9b9c82acaa..7fa77120c6 100644 --- a/lib/sqlalchemy/exc.py +++ b/lib/sqlalchemy/exc.py @@ -685,8 +685,9 @@ class SADeprecationWarning(HasDescriptionCode, DeprecationWarning): "Indicates the version that started raising this deprecation warning" -class RemovedIn20Warning(SADeprecationWarning): - """Issued for usage of APIs specifically deprecated in SQLAlchemy 2.0. +class Base20DeprecationWarning(SADeprecationWarning): + """Issued for usage of APIs specifically deprecated or legacy in + SQLAlchemy 2.0. .. seealso:: @@ -701,11 +702,19 @@ class RemovedIn20Warning(SADeprecationWarning): def __str__(self): return ( - super(RemovedIn20Warning, self).__str__() + super(Base20DeprecationWarning, self).__str__() + " (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)" ) +class LegacyAPIWarning(Base20DeprecationWarning): + """indicates an API that is in 'legacy' status, a long term deprecation.""" + + +class RemovedIn20Warning(Base20DeprecationWarning): + """indicates an API that will be fully removed in SQLAlchemy 2.0.""" + + class MovedIn20Warning(RemovedIn20Warning): """Subtype of RemovedIn20Warning to indicate an API that moved only.""" diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index d48e34923a..eddb9c29c1 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -1711,6 +1711,7 @@ class Query( we will attempt to derive an expression from based on string name. """ + if self._legacy_setup_joins: _last_joined_entity = self._last_joined_entity if _last_joined_entity is not None: diff --git a/lib/sqlalchemy/testing/assertions.py b/lib/sqlalchemy/testing/assertions.py index 986dbb5e9b..6bf14aecde 100644 --- a/lib/sqlalchemy/testing/assertions.py +++ b/lib/sqlalchemy/testing/assertions.py @@ -86,7 +86,7 @@ def expect_deprecated(*messages, **kw): def expect_deprecated_20(*messages, **kw): - return _expect_warnings(sa_exc.RemovedIn20Warning, messages, **kw) + return _expect_warnings(sa_exc.Base20DeprecationWarning, messages, **kw) def emits_warning_on(db, *messages): diff --git a/lib/sqlalchemy/testing/warnings.py b/lib/sqlalchemy/testing/warnings.py index 40d03f0afe..f29d656921 100644 --- a/lib/sqlalchemy/testing/warnings.py +++ b/lib/sqlalchemy/testing/warnings.py @@ -69,9 +69,6 @@ def setup_filters(): # ORM Query # r"The Query\.get\(\) method", - r"The Query\.with_parent\(\) method", - r"The Query\.with_parent\(\) method", - r"The ``aliased`` and ``from_joinpoint`` keyword arguments", r"The Query.with_polymorphic\(\) method is considered " "legacy as of the 1.x series", # diff --git a/lib/sqlalchemy/util/deprecations.py b/lib/sqlalchemy/util/deprecations.py index c1acde39be..e1138aaefc 100644 --- a/lib/sqlalchemy/util/deprecations.py +++ b/lib/sqlalchemy/util/deprecations.py @@ -98,13 +98,18 @@ def deprecated_20_cls( if alternative: message += " " + alternative + if becomes_legacy: + warning_cls = exc.LegacyAPIWarning + else: + warning_cls = exc.RemovedIn20Warning + def decorate(cls): return _decorate_cls_with_warning( cls, constructor, - exc.RemovedIn20Warning, + warning_cls, message, - exc.RemovedIn20Warning.deprecated_since, + warning_cls.deprecated_since, message, ) diff --git a/test/base/test_except.py b/test/base/test_except.py index be6f448bd8..767fd233c0 100644 --- a/test/base/test_except.py +++ b/test/base/test_except.py @@ -507,6 +507,8 @@ ALL_EXC = [ ( [ sa_exceptions.SADeprecationWarning, + sa_exceptions.Base20DeprecationWarning, + sa_exceptions.LegacyAPIWarning, sa_exceptions.RemovedIn20Warning, sa_exceptions.MovedIn20Warning, sa_exceptions.SAWarning, diff --git a/test/orm/inheritance/test_deprecations.py b/test/orm/inheritance/test_deprecations.py new file mode 100644 index 0000000000..bf8219cb02 --- /dev/null +++ b/test/orm/inheritance/test_deprecations.py @@ -0,0 +1,260 @@ +from sqlalchemy import ForeignKey +from sqlalchemy import Integer +from sqlalchemy import String +from sqlalchemy import testing +from sqlalchemy.orm import relationship +from sqlalchemy.testing import eq_ +from sqlalchemy.testing import fixtures +from sqlalchemy.testing.assertions import expect_deprecated_20 +from sqlalchemy.testing.fixtures import fixture_session +from sqlalchemy.testing.schema import Column +from sqlalchemy.testing.schema import Table +from ._poly_fixtures import _Polymorphic +from ._poly_fixtures import _PolymorphicAliasedJoins +from ._poly_fixtures import _PolymorphicJoins +from ._poly_fixtures import _PolymorphicPolymorphic +from ._poly_fixtures import _PolymorphicUnions +from ._poly_fixtures import Company +from ._poly_fixtures import Engineer +from ._poly_fixtures import Manager +from ._poly_fixtures import Paperwork +from ._poly_fixtures import Person + + +aliased_jp_dep = ( + r"The ``aliased`` and ``from_joinpoint`` keyword arguments " + r"to Query.join\(\) are deprecated" +) + + +class _PolymorphicTestBase(fixtures.NoCache): + __backend__ = True + __dialect__ = "default_enhanced" + + @classmethod + def setup_mappers(cls): + super(_PolymorphicTestBase, cls).setup_mappers() + global people, engineers, managers, boss + global companies, paperwork, machines + people, engineers, managers, boss, companies, paperwork, machines = ( + cls.tables.people, + cls.tables.engineers, + cls.tables.managers, + cls.tables.boss, + cls.tables.companies, + cls.tables.paperwork, + cls.tables.machines, + ) + + @classmethod + def insert_data(cls, connection): + super(_PolymorphicTestBase, cls).insert_data(connection) + + global all_employees, c1_employees, c2_employees + global c1, c2, e1, e2, e3, b1, m1 + c1, c2, all_employees, c1_employees, c2_employees = ( + cls.c1, + cls.c2, + cls.all_employees, + cls.c1_employees, + cls.c2_employees, + ) + e1, e2, e3, b1, m1 = cls.e1, cls.e2, cls.e3, cls.b1, cls.m1 + + def test_join_from_polymorphic_flag_aliased_one(self): + sess = fixture_session() + with expect_deprecated_20(aliased_jp_dep): + eq_( + sess.query(Person) + .order_by(Person.person_id) + .join(Person.paperwork, aliased=True) + .filter(Paperwork.description.like("%review%")) + .all(), + [b1, m1], + ) + + def test_join_from_polymorphic_flag_aliased_two(self): + sess = fixture_session() + with expect_deprecated_20(aliased_jp_dep): + eq_( + sess.query(Person) + .order_by(Person.person_id) + .join(Person.paperwork, aliased=True) + .filter(Paperwork.description.like("%#2%")) + .all(), + [e1, m1], + ) + + def test_join_from_with_polymorphic_flag_aliased_one(self): + sess = fixture_session() + with expect_deprecated_20(aliased_jp_dep): + eq_( + sess.query(Person) + .with_polymorphic(Manager) + .join(Person.paperwork, aliased=True) + .filter(Paperwork.description.like("%review%")) + .all(), + [b1, m1], + ) + + def test_join_from_with_polymorphic_flag_aliased_two(self): + sess = fixture_session() + with expect_deprecated_20(aliased_jp_dep): + eq_( + sess.query(Person) + .with_polymorphic([Manager, Engineer]) + .order_by(Person.person_id) + .join(Person.paperwork, aliased=True) + .filter(Paperwork.description.like("%#2%")) + .all(), + [e1, m1], + ) + + def test_join_to_polymorphic_flag_aliased(self): + sess = fixture_session() + with expect_deprecated_20(aliased_jp_dep): + eq_( + sess.query(Company) + .join(Company.employees, aliased=True) + .filter(Person.name == "vlad") + .one(), + c2, + ) + + def test_polymorphic_any_flag_alias_two(self): + sess = fixture_session() + # test that the aliasing on "Person" does not bleed into the + # EXISTS clause generated by any() + any_ = Company.employees.any(Person.name == "wally") + with expect_deprecated_20(aliased_jp_dep): + eq_( + sess.query(Company) + .join(Company.employees, aliased=True) + .filter(Person.name == "dilbert") + .filter(any_) + .all(), + [c1], + ) + + def test_join_from_polymorphic_flag_aliased_three(self): + sess = fixture_session() + with expect_deprecated_20(aliased_jp_dep): + eq_( + sess.query(Engineer) + .order_by(Person.person_id) + .join(Person.paperwork, aliased=True) + .filter(Paperwork.description.like("%#2%")) + .all(), + [e1], + ) + + +class PolymorphicTest(_PolymorphicTestBase, _Polymorphic): + pass + + +class PolymorphicPolymorphicTest( + _PolymorphicTestBase, _PolymorphicPolymorphic +): + pass + + +class PolymorphicUnionsTest(_PolymorphicTestBase, _PolymorphicUnions): + pass + + +class PolymorphicAliasedJoinsTest( + _PolymorphicTestBase, _PolymorphicAliasedJoins +): + pass + + +class PolymorphicJoinsTest(_PolymorphicTestBase, _PolymorphicJoins): + pass + + +class RelationshipToSingleTest( + testing.AssertsCompiledSQL, fixtures.MappedTest +): + __dialect__ = "default" + + @classmethod + def define_tables(cls, metadata): + Table( + "employees", + metadata, + Column( + "employee_id", + Integer, + primary_key=True, + test_needs_autoincrement=True, + ), + Column("name", String(50)), + Column("manager_data", String(50)), + Column("engineer_info", String(50)), + Column("type", String(20)), + Column("company_id", Integer, ForeignKey("companies.company_id")), + ) + + Table( + "companies", + metadata, + Column( + "company_id", + Integer, + primary_key=True, + test_needs_autoincrement=True, + ), + Column("name", String(50)), + ) + + @classmethod + def setup_classes(cls): + class Company(cls.Comparable): + pass + + class Employee(cls.Comparable): + pass + + class Manager(Employee): + pass + + class Engineer(Employee): + pass + + class JuniorEngineer(Engineer): + pass + + def test_of_type_aliased_fromjoinpoint(self): + Company, Employee, Engineer = ( + self.classes.Company, + self.classes.Employee, + self.classes.Engineer, + ) + companies, employees = self.tables.companies, self.tables.employees + + self.mapper_registry.map_imperatively( + Company, companies, properties={"employee": relationship(Employee)} + ) + self.mapper_registry.map_imperatively( + Employee, employees, polymorphic_on=employees.c.type + ) + self.mapper_registry.map_imperatively( + Engineer, inherits=Employee, polymorphic_identity="engineer" + ) + + sess = fixture_session() + + with expect_deprecated_20(aliased_jp_dep): + self.assert_compile( + sess.query(Company).outerjoin( + Company.employee.of_type(Engineer), + aliased=True, + from_joinpoint=True, + ), + "SELECT companies.company_id AS companies_company_id, " + "companies.name AS companies_name FROM companies " + "LEFT OUTER JOIN employees AS employees_1 ON " + "companies.company_id = employees_1.company_id " + "AND employees_1.type IN ([POSTCOMPILE_type_1])", + ) diff --git a/test/orm/inheritance/test_polymorphic_rel.py b/test/orm/inheritance/test_polymorphic_rel.py index b9d9ad932b..2e52e162bb 100644 --- a/test/orm/inheritance/test_polymorphic_rel.py +++ b/test/orm/inheritance/test_polymorphic_rel.py @@ -386,18 +386,7 @@ class _PolymorphicTestBase(fixtures.NoCache): [m1], ) - def test_join_from_polymorphic_flag_aliased_one(self): - sess = fixture_session() - eq_( - sess.query(Person) - .order_by(Person.person_id) - .join(Person.paperwork, aliased=True) - .filter(Paperwork.description.like("%review%")) - .all(), - [b1, m1], - ) - - def test_join_from_polymorphic_flag_aliased_one_future(self): + def test_join_from_polymorphic_aliased_one_future(self): sess = fixture_session(future=True) pa = aliased(Paperwork) @@ -426,17 +415,6 @@ class _PolymorphicTestBase(fixtures.NoCache): [b1, m1], ) - def test_join_from_polymorphic_flag_aliased_two(self): - sess = fixture_session() - eq_( - sess.query(Person) - .order_by(Person.person_id) - .join(Person.paperwork, aliased=True) - .filter(Paperwork.description.like("%#2%")) - .all(), - [e1, m1], - ) - def test_join_from_polymorphic_explicit_aliased_two(self): sess = fixture_session() pa = aliased(Paperwork) @@ -449,17 +427,6 @@ class _PolymorphicTestBase(fixtures.NoCache): [e1, m1], ) - def test_join_from_polymorphic_flag_aliased_three(self): - sess = fixture_session() - eq_( - sess.query(Engineer) - .order_by(Person.person_id) - .join(Person.paperwork, aliased=True) - .filter(Paperwork.description.like("%#2%")) - .all(), - [e1], - ) - def test_join_from_polymorphic_explicit_aliased_three(self): sess = fixture_session() pa = aliased(Paperwork) @@ -539,17 +506,6 @@ class _PolymorphicTestBase(fixtures.NoCache): [m1], ) - def test_join_from_with_polymorphic_flag_aliased_one(self): - sess = fixture_session() - eq_( - sess.query(Person) - .with_polymorphic(Manager) - .join(Person.paperwork, aliased=True) - .filter(Paperwork.description.like("%review%")) - .all(), - [b1, m1], - ) - def test_join_from_with_polymorphic_explicit_aliased_one(self): sess = fixture_session() pa = aliased(Paperwork) @@ -562,18 +518,6 @@ class _PolymorphicTestBase(fixtures.NoCache): [b1, m1], ) - def test_join_from_with_polymorphic_flag_aliased_two(self): - sess = fixture_session() - eq_( - sess.query(Person) - .with_polymorphic([Manager, Engineer]) - .order_by(Person.person_id) - .join(Person.paperwork, aliased=True) - .filter(Paperwork.description.like("%#2%")) - .all(), - [e1, m1], - ) - def test_join_from_with_polymorphic_explicit_aliased_two(self): sess = fixture_session() pa = aliased(Paperwork) @@ -612,16 +556,6 @@ class _PolymorphicTestBase(fixtures.NoCache): c2, ) - def test_join_to_polymorphic_flag_aliased(self): - sess = fixture_session() - eq_( - sess.query(Company) - .join(Company.employees, aliased=True) - .filter(Person.name == "vlad") - .one(), - c2, - ) - def test_join_to_polymorphic_explicit_aliased(self): sess = fixture_session() ea = aliased(Person) @@ -639,20 +573,6 @@ class _PolymorphicTestBase(fixtures.NoCache): any_ = Company.employees.any(Person.name == "vlad") eq_(sess.query(Company).filter(any_).all(), [c2]) - def test_polymorphic_any_flag_alias_two(self): - sess = fixture_session() - # test that the aliasing on "Person" does not bleed into the - # EXISTS clause generated by any() - any_ = Company.employees.any(Person.name == "wally") - eq_( - sess.query(Company) - .join(Company.employees, aliased=True) - .filter(Person.name == "dilbert") - .filter(any_) - .all(), - [c1], - ) - def test_polymorphic_any_explicit_alias_two(self): sess = fixture_session() # test that the aliasing on "Person" does not bleed into the diff --git a/test/orm/inheritance/test_single.py b/test/orm/inheritance/test_single.py index 066f781d88..025f717d7d 100644 --- a/test/orm/inheritance/test_single.py +++ b/test/orm/inheritance/test_single.py @@ -922,38 +922,6 @@ class RelationshipToSingleTest( [Company(name="c1")], ) - def test_of_type_aliased_fromjoinpoint(self): - Company, Employee, Engineer = ( - self.classes.Company, - self.classes.Employee, - self.classes.Engineer, - ) - companies, employees = self.tables.companies, self.tables.employees - - self.mapper_registry.map_imperatively( - Company, companies, properties={"employee": relationship(Employee)} - ) - self.mapper_registry.map_imperatively( - Employee, employees, polymorphic_on=employees.c.type - ) - self.mapper_registry.map_imperatively( - Engineer, inherits=Employee, polymorphic_identity="engineer" - ) - - sess = fixture_session() - self.assert_compile( - sess.query(Company).outerjoin( - Company.employee.of_type(Engineer), - aliased=True, - from_joinpoint=True, - ), - "SELECT companies.company_id AS companies_company_id, " - "companies.name AS companies_name FROM companies " - "LEFT OUTER JOIN employees AS employees_1 ON " - "companies.company_id = employees_1.company_id " - "AND employees_1.type IN ([POSTCOMPILE_type_1])", - ) - def test_join_explicit_onclause_no_discriminator(self): # test issue #3462 Company, Employee, Engineer = ( diff --git a/test/orm/test_cascade.py b/test/orm/test_cascade.py index 3e42368df2..8749a01473 100644 --- a/test/orm/test_cascade.py +++ b/test/orm/test_cascade.py @@ -18,6 +18,7 @@ from sqlalchemy.orm import object_mapper from sqlalchemy.orm import relationship from sqlalchemy.orm import Session from sqlalchemy.orm import util as orm_util +from sqlalchemy.orm import with_parent from sqlalchemy.orm.attributes import instance_state from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.orm.decl_api import declarative_base @@ -2002,7 +2003,7 @@ class M2OCascadeDeleteOrphanTestOne(fixtures.MappedTest): assert p1 not in sess sess.flush() eq_( - sess.query(Pref).with_parent(someuser).all(), + sess.query(Pref).filter(with_parent(someuser, User.pref)).all(), [Pref(data="someotherpref")], ) diff --git a/test/orm/test_deprecations.py b/test/orm/test_deprecations.py index c66259e478..f6854cf271 100644 --- a/test/orm/test_deprecations.py +++ b/test/orm/test_deprecations.py @@ -134,6 +134,10 @@ wparent_strings_dep = ( r"in the ORM with_parent\(\) function" ) +query_wparent_dep = ( + r"The Query.with_parent\(\) method is considered legacy as of the 1.x" +) + sef_dep = ( r"The Query.select_entity_from\(\) method is considered " "legacy as of the 1.x" @@ -4714,6 +4718,27 @@ class JoinTest(QueryTest, AssertsCompiledSQL): assert q.count() == 1 assert [User(id=7)] == q.all() + def test_does_filter_aliasing_work(self): + User, Address = self.classes("User", "Address") + + s = fixture_session() + + # aliased=True is to be deprecated, other filter lambdas + # that go into effect include polymorphic filtering. + with testing.expect_deprecated(join_aliased_dep): + q = ( + s.query(lambda: User) + .join(lambda: User.addresses, aliased=True) + .filter(lambda: Address.email_address == "foo") + ) + self.assert_compile( + q, + "SELECT users.id AS users_id, users.name AS users_name " + "FROM users JOIN addresses AS addresses_1 " + "ON users.id = addresses_1.user_id " + "WHERE addresses_1.email_address = :email_address_1", + ) + def test_overlapping_paths_two(self): User = self.classes.User @@ -8161,3 +8186,267 @@ class JoinLateralTest(fixtures.MappedTest, AssertsCompiledSQL): "generate_series(:generate_series_1, anon_1.bookcase_shelves) " "AS anon_2 ON true", ) + + +class ParentTest(QueryTest, AssertsCompiledSQL): + __dialect__ = "default" + + def test_o2m(self): + User, orders, Order = ( + self.classes.User, + self.tables.orders, + self.classes.Order, + ) + + sess = fixture_session() + q = sess.query(User) + + u1 = q.filter_by(name="jack").one() + + # test auto-lookup of property + with assertions.expect_deprecated_20(query_wparent_dep): + o = sess.query(Order).with_parent(u1).all() + assert [ + Order(description="order 1"), + Order(description="order 3"), + Order(description="order 5"), + ] == o + + # test with explicit property + with assertions.expect_deprecated_20(query_wparent_dep): + o = sess.query(Order).with_parent(u1, property=User.orders).all() + assert [ + Order(description="order 1"), + Order(description="order 3"), + Order(description="order 5"), + ] == o + + with assertions.expect_deprecated_20(query_wparent_dep): + # test generative criterion + o = sess.query(Order).with_parent(u1).filter(orders.c.id > 2).all() + assert [ + Order(description="order 3"), + Order(description="order 5"), + ] == o + + def test_select_from(self): + User, Address = self.classes.User, self.classes.Address + + sess = fixture_session() + u1 = sess.query(User).get(7) + with assertions.expect_deprecated_20(query_wparent_dep): + q = sess.query(Address).select_from(Address).with_parent(u1) + self.assert_compile( + q, + "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", + {"param_1": 7}, + ) + + def test_from_entity_query_entity(self): + User, Address = self.classes.User, self.classes.Address + + sess = fixture_session() + u1 = sess.query(User).get(7) + with assertions.expect_deprecated_20(query_wparent_dep): + q = sess.query(User, Address).with_parent( + u1, User.addresses, from_entity=Address + ) + self.assert_compile( + q, + "SELECT users.id AS users_id, users.name AS users_name, " + "addresses.id AS addresses_id, addresses.user_id " + "AS addresses_user_id, " + "addresses.email_address AS addresses_email_address " + "FROM users, addresses " + "WHERE :param_1 = addresses.user_id", + {"param_1": 7}, + ) + + def test_select_from_alias(self): + User, Address = self.classes.User, self.classes.Address + + sess = fixture_session() + u1 = sess.query(User).get(7) + a1 = aliased(Address) + with assertions.expect_deprecated_20(query_wparent_dep): + q = sess.query(a1).with_parent(u1) + self.assert_compile( + q, + "SELECT 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 addresses AS addresses_1 " + "WHERE :param_1 = addresses_1.user_id", + {"param_1": 7}, + ) + + def test_select_from_alias_explicit_prop(self): + User, Address = self.classes.User, self.classes.Address + + sess = fixture_session() + u1 = sess.query(User).get(7) + a1 = aliased(Address) + with assertions.expect_deprecated_20(query_wparent_dep): + q = sess.query(a1).with_parent(u1, User.addresses) + self.assert_compile( + q, + "SELECT 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 addresses AS addresses_1 " + "WHERE :param_1 = addresses_1.user_id", + {"param_1": 7}, + ) + + def test_select_from_alias_from_entity(self): + User, Address = self.classes.User, self.classes.Address + + sess = fixture_session() + u1 = sess.query(User).get(7) + a1 = aliased(Address) + a2 = aliased(Address) + with assertions.expect_deprecated_20(query_wparent_dep): + q = sess.query(a1, a2).with_parent( + u1, User.addresses, from_entity=a2 + ) + self.assert_compile( + q, + "SELECT 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, " + "addresses_2.id AS addresses_2_id, " + "addresses_2.user_id AS addresses_2_user_id, " + "addresses_2.email_address AS addresses_2_email_address " + "FROM addresses AS addresses_1, " + "addresses AS addresses_2 WHERE :param_1 = addresses_2.user_id", + {"param_1": 7}, + ) + + def test_select_from_alias_of_type(self): + User, Address = self.classes.User, self.classes.Address + + sess = fixture_session() + u1 = sess.query(User).get(7) + a1 = aliased(Address) + a2 = aliased(Address) + with assertions.expect_deprecated_20(query_wparent_dep): + q = sess.query(a1, a2).with_parent(u1, User.addresses.of_type(a2)) + self.assert_compile( + q, + "SELECT 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, " + "addresses_2.id AS addresses_2_id, " + "addresses_2.user_id AS addresses_2_user_id, " + "addresses_2.email_address AS addresses_2_email_address " + "FROM addresses AS addresses_1, " + "addresses AS addresses_2 WHERE :param_1 = addresses_2.user_id", + {"param_1": 7}, + ) + + def test_noparent(self): + Item, User = self.classes.Item, self.classes.User + + sess = fixture_session() + q = sess.query(User) + + u1 = q.filter_by(name="jack").one() + + with assertions.expect_deprecated_20(query_wparent_dep): + with assertions.expect_raises_message( + sa_exc.InvalidRequestError, + "Could not locate a property which relates " + "instances of class 'Item' to instances of class 'User'", + ): + q = sess.query(Item).with_parent(u1) + + def test_m2m(self): + Item, Keyword = self.classes.Item, self.classes.Keyword + + sess = fixture_session() + i1 = sess.query(Item).filter_by(id=2).one() + with assertions.expect_deprecated_20(query_wparent_dep): + k = sess.query(Keyword).with_parent(i1).all() + assert [ + Keyword(name="red"), + Keyword(name="small"), + Keyword(name="square"), + ] == k + + def test_with_transient(self): + User, Order = self.classes.User, self.classes.Order + + sess = fixture_session() + + q = sess.query(User) + u1 = q.filter_by(name="jack").one() + utrans = User(id=u1.id) + with assertions.expect_deprecated_20(query_wparent_dep): + o = sess.query(Order).with_parent(utrans, User.orders) + eq_( + [ + Order(description="order 1"), + Order(description="order 3"), + Order(description="order 5"), + ], + o.all(), + ) + + def test_with_pending_autoflush(self): + Order, User = self.classes.Order, self.classes.User + + sess = fixture_session() + + o1 = sess.query(Order).first() + opending = Order(id=20, user_id=o1.user_id) + sess.add(opending) + with assertions.expect_deprecated_20(query_wparent_dep): + eq_( + sess.query(User).with_parent(opending, Order.user).one(), + User(id=o1.user_id), + ) + + def test_with_pending_no_autoflush(self): + Order, User = self.classes.Order, self.classes.User + + sess = fixture_session(autoflush=False) + + o1 = sess.query(Order).first() + opending = Order(user_id=o1.user_id) + sess.add(opending) + with assertions.expect_deprecated_20(query_wparent_dep): + eq_( + sess.query(User).with_parent(opending, Order.user).one(), + User(id=o1.user_id), + ) + + def test_unique_binds_union(self): + """bindparams used in the 'parent' query are unique""" + User, Address = self.classes.User, self.classes.Address + + sess = fixture_session() + u1, u2 = sess.query(User).order_by(User.id)[0:2] + + with assertions.expect_deprecated_20(query_wparent_dep): + q1 = sess.query(Address).with_parent(u1, User.addresses) + with assertions.expect_deprecated_20(query_wparent_dep): + q2 = sess.query(Address).with_parent(u2, User.addresses) + + self.assert_compile( + q1.union(q2), + "SELECT anon_1.addresses_id AS anon_1_addresses_id, " + "anon_1.addresses_user_id AS anon_1_addresses_user_id, " + "anon_1.addresses_email_address AS " + "anon_1_addresses_email_address FROM (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 UNION SELECT " + "addresses.id AS addresses_id, addresses.user_id AS " + "addresses_user_id, addresses.email_address " + "AS addresses_email_address " + "FROM addresses WHERE :param_2 = addresses.user_id) AS anon_1", + checkparams={"param_1": 7, "param_2": 8}, + ) diff --git a/test/orm/test_lambdas.py b/test/orm/test_lambdas.py index 30048585bc..5274271d9f 100644 --- a/test/orm/test_lambdas.py +++ b/test/orm/test_lambdas.py @@ -305,26 +305,6 @@ class LambdaTest(QueryTest, AssertsCompiledSQL): ): self.assert_sql_count(testing.db, fn, 2) - def test_does_filter_aliasing_work(self, plain_fixture): - User, Address = plain_fixture - - s = Session(testing.db, future=True) - - # aliased=True is to be deprecated, other filter lambdas - # that go into effect include polymorphic filtering. - q = ( - s.query(lambda: User) - .join(lambda: User.addresses, aliased=True) - .filter(lambda: Address.email_address == "foo") - ) - self.assert_compile( - q, - "SELECT users.id AS users_id, users.name AS users_name " - "FROM users JOIN addresses AS addresses_1 " - "ON users.id = addresses_1.user_id " - "WHERE addresses_1.email_address = :email_address_1", - ) - @testing.combinations( lambda s, User, Address: s.query(lambda: User).join(lambda: Address), lambda s, User, Address: s.query(lambda: User).join( diff --git a/test/orm/test_lazy_relations.py b/test/orm/test_lazy_relations.py index 2bea9b1106..412637ec6a 100644 --- a/test/orm/test_lazy_relations.py +++ b/test/orm/test_lazy_relations.py @@ -23,6 +23,7 @@ from sqlalchemy.orm import configure_mappers from sqlalchemy.orm import exc as orm_exc from sqlalchemy.orm import relationship from sqlalchemy.orm import Session +from sqlalchemy.orm import with_parent from sqlalchemy.testing import assert_raises from sqlalchemy.testing import eq_ from sqlalchemy.testing import fixtures @@ -658,14 +659,14 @@ class LazyTest(_fixtures.FixtureTest): [Order(id=1), Order(id=5)], fixture_session() .query(closed_mapper) - .with_parent(user, property=User.closed_orders) + .filter(with_parent(user, User.closed_orders)) .all(), ) eq_( [Order(id=3)], fixture_session() .query(open_mapper) - .with_parent(user, property=User.open_orders) + .filter(with_parent(user, User.open_orders)) .all(), ) diff --git a/test/orm/test_naturalpks.py b/test/orm/test_naturalpks.py index 9f31c4b800..fba335d285 100644 --- a/test/orm/test_naturalpks.py +++ b/test/orm/test_naturalpks.py @@ -12,8 +12,9 @@ from sqlalchemy import Integer from sqlalchemy import String from sqlalchemy import testing from sqlalchemy import TypeDecorator +from sqlalchemy.orm import make_transient from sqlalchemy.orm import relationship -from sqlalchemy.orm.session import make_transient +from sqlalchemy.orm import with_parent from sqlalchemy.testing import assert_raises from sqlalchemy.testing import assert_raises_message from sqlalchemy.testing import eq_ @@ -621,7 +622,7 @@ class NaturalPKTest(fixtures.MappedTest): u2 = sess.query(User).get(u2.username) u2.username = "wendy" sess.flush() - r = sess.query(Item).with_parent(u2).all() + r = sess.query(Item).filter(with_parent(u2, User.items)).all() eq_(Item(itemname="item2"), r[0]) def test_manytoone_deferred_relationship_expr(self): diff --git a/test/orm/test_query.py b/test/orm/test_query.py index 1971964b13..a504a4eb1c 100644 --- a/test/orm/test_query.py +++ b/test/orm/test_query.py @@ -6352,9 +6352,8 @@ class ParentTest(QueryTest, AssertsCompiledSQL): __dialect__ = "default" def test_o2m(self): - User, orders, Order = ( + User, Order = ( self.classes.User, - self.tables.orders, self.classes.Order, ) @@ -6363,22 +6362,6 @@ class ParentTest(QueryTest, AssertsCompiledSQL): u1 = q.filter_by(name="jack").one() - # test auto-lookup of property - o = sess.query(Order).with_parent(u1).all() - assert [ - Order(description="order 1"), - Order(description="order 3"), - Order(description="order 5"), - ] == o - - # test with explicit property - o = sess.query(Order).with_parent(u1, property=User.orders).all() - assert [ - Order(description="order 1"), - Order(description="order 3"), - Order(description="order 5"), - ] == o - o = sess.query(Order).filter(with_parent(u1, User.orders)).all() assert [ Order(description="order 1"), @@ -6386,25 +6369,16 @@ class ParentTest(QueryTest, AssertsCompiledSQL): Order(description="order 5"), ] == o - # test generative criterion - o = sess.query(Order).with_parent(u1).filter(orders.c.id > 2).all() - assert [ - Order(description="order 3"), - Order(description="order 5"), - ] == o - - # test against None for parent? this can't be done with the current - # API since we don't know what mapper to use - # assert - # sess.query(Order).with_parent(None, property='addresses').all() - # == [Order(description="order 5")] - def test_select_from(self): User, Address = self.classes.User, self.classes.Address sess = fixture_session() u1 = sess.query(User).get(7) - q = sess.query(Address).select_from(Address).with_parent(u1) + q = ( + sess.query(Address) + .select_from(Address) + .filter(with_parent(u1, User.addresses)) + ) self.assert_compile( q, "SELECT addresses.id AS addresses_id, " @@ -6433,49 +6407,13 @@ class ParentTest(QueryTest, AssertsCompiledSQL): {"param_1": 7}, ) - def test_from_entity_query_entity(self): - User, Address = self.classes.User, self.classes.Address - - sess = fixture_session() - u1 = sess.query(User).get(7) - q = sess.query(User, Address).with_parent( - u1, User.addresses, from_entity=Address - ) - self.assert_compile( - q, - "SELECT users.id AS users_id, users.name AS users_name, " - "addresses.id AS addresses_id, addresses.user_id " - "AS addresses_user_id, " - "addresses.email_address AS addresses_email_address " - "FROM users, addresses " - "WHERE :param_1 = addresses.user_id", - {"param_1": 7}, - ) - def test_select_from_alias(self): User, Address = self.classes.User, self.classes.Address sess = fixture_session() u1 = sess.query(User).get(7) a1 = aliased(Address) - q = sess.query(a1).with_parent(u1) - self.assert_compile( - q, - "SELECT 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 addresses AS addresses_1 " - "WHERE :param_1 = addresses_1.user_id", - {"param_1": 7}, - ) - - def test_select_from_alias_explicit_prop(self): - User, Address = self.classes.User, self.classes.Address - - sess = fixture_session() - u1 = sess.query(User).get(7) - a1 = aliased(Address) - q = sess.query(a1).with_parent(u1, User.addresses) + q = sess.query(a1).filter(with_parent(u1, User.addresses.of_type(a1))) self.assert_compile( q, "SELECT addresses_1.id AS addresses_1_id, " @@ -6493,7 +6431,9 @@ class ParentTest(QueryTest, AssertsCompiledSQL): u1 = sess.query(User).get(7) a1 = aliased(Address) a2 = aliased(Address) - q = sess.query(a1, a2).with_parent(u1, User.addresses, from_entity=a2) + q = sess.query(a1, a2).filter( + with_parent(u1, User.addresses, from_entity=a2) + ) self.assert_compile( q, "SELECT addresses_1.id AS addresses_1_id, " @@ -6514,7 +6454,9 @@ class ParentTest(QueryTest, AssertsCompiledSQL): u1 = sess.query(User).get(7) a1 = aliased(Address) a2 = aliased(Address) - q = sess.query(a1, a2).with_parent(u1, User.addresses.of_type(a2)) + q = sess.query(a1, a2).filter( + with_parent(u1, User.addresses.of_type(a2)) + ) self.assert_compile( q, "SELECT addresses_1.id AS addresses_1_id, " @@ -6536,26 +6478,30 @@ class ParentTest(QueryTest, AssertsCompiledSQL): u1 = q.filter_by(name="jack").one() - try: - q = sess.query(Item).with_parent(u1) - assert False - except sa_exc.InvalidRequestError as e: - assert ( - str(e) == "Could not locate a property which relates " - "instances of class 'Item' to instances of class 'User'" - ) + # TODO: this can perhaps raise an error, then again it's doing what's + # asked... + q = sess.query(Item).filter(with_parent(u1, User.orders)) + self.assert_compile( + q, + "SELECT items.id AS items_id, " + "items.description AS items_description " + "FROM items, orders WHERE :param_1 = orders.user_id", + ) def test_m2m(self): Item, Keyword = self.classes.Item, self.classes.Keyword sess = fixture_session() i1 = sess.query(Item).filter_by(id=2).one() - k = sess.query(Keyword).with_parent(i1).all() - assert [ - Keyword(name="red"), - Keyword(name="small"), - Keyword(name="square"), - ] == k + k = sess.query(Keyword).filter(with_parent(i1, Item.keywords)).all() + eq_( + k, + [ + Keyword(name="red"), + Keyword(name="small"), + Keyword(name="square"), + ], + ) def test_with_transient(self): User, Order = self.classes.User, self.classes.Order @@ -6565,16 +6511,6 @@ class ParentTest(QueryTest, AssertsCompiledSQL): q = sess.query(User) u1 = q.filter_by(name="jack").one() utrans = User(id=u1.id) - o = sess.query(Order).with_parent(utrans, User.orders) - eq_( - [ - Order(description="order 1"), - Order(description="order 3"), - Order(description="order 5"), - ], - o.all(), - ) - o = sess.query(Order).filter(with_parent(utrans, User.orders)) eq_( [ @@ -6593,10 +6529,6 @@ class ParentTest(QueryTest, AssertsCompiledSQL): o1 = sess.query(Order).first() opending = Order(id=20, user_id=o1.user_id) sess.add(opending) - eq_( - sess.query(User).with_parent(opending, Order.user).one(), - User(id=o1.user_id), - ) eq_( sess.query(User).filter(with_parent(opending, Order.user)).one(), User(id=o1.user_id), @@ -6611,7 +6543,7 @@ class ParentTest(QueryTest, AssertsCompiledSQL): opending = Order(user_id=o1.user_id) sess.add(opending) eq_( - sess.query(User).with_parent(opending, Order.user).one(), + sess.query(User).filter(with_parent(opending, Order.user)).one(), User(id=o1.user_id), ) @@ -6622,8 +6554,8 @@ class ParentTest(QueryTest, AssertsCompiledSQL): sess = fixture_session() u1, u2 = sess.query(User).order_by(User.id)[0:2] - q1 = sess.query(Address).with_parent(u1, User.addresses) - q2 = sess.query(Address).with_parent(u2, User.addresses) + q1 = sess.query(Address).filter(with_parent(u1, User.addresses)) + q2 = sess.query(Address).filter(with_parent(u2, User.addresses)) self.assert_compile( q1.union(q2), @@ -6870,7 +6802,9 @@ class WithTransientOnNone(_fixtures.FixtureTest, AssertsCompiledSQL): sess = fixture_session() - q = sess.query(User).with_parent(Address(user_id=None), Address.user) + q = sess.query(User).filter( + with_parent(Address(user_id=None), Address.user) + ) with expect_warnings("Got None for value of column"): self.assert_compile( q, @@ -6884,8 +6818,10 @@ class WithTransientOnNone(_fixtures.FixtureTest, AssertsCompiledSQL): User, Address = self.classes.User, self.classes.Address s = fixture_session() - q = s.query(User).with_parent( - Address(user_id=None, email_address=None), Address.special_user + q = s.query(User).filter( + with_parent( + Address(user_id=None, email_address=None), Address.special_user + ) ) with expect_warnings("Got None for value of column"): @@ -7105,7 +7041,7 @@ class SynonymTest(QueryTest, AssertsCompiledSQL): u1 = q.filter_by(**{nameprop: "jack"}).one() - o = sess.query(Order).with_parent(u1, property=orderprop).all() + o = sess.query(Order).filter(with_parent(u1, orderprop)).all() assert [ Order(description="order 1"), Order(description="order 3"), diff --git a/test/orm/test_relationships.py b/test/orm/test_relationships.py index 2547a981a2..2d3a6309b0 100644 --- a/test/orm/test_relationships.py +++ b/test/orm/test_relationships.py @@ -628,8 +628,10 @@ class DirectSelfRefFKTest(fixtures.MappedTest, AssertsCompiledSQL): self._descendants_fixture(data=False) Entity = self.classes.Entity sess = fixture_session() + + da = aliased(Entity) self.assert_compile( - sess.query(Entity).join(Entity.descendants, aliased=True), + sess.query(Entity).join(Entity.descendants.of_type(da)), "SELECT entity.path AS entity_path FROM entity JOIN entity AS " "entity_1 ON entity_1.path LIKE entity.path || :path_1", ) @@ -1451,13 +1453,15 @@ class CompositeSelfRefFKTest(fixtures.MappedTest, AssertsCompiledSQL): def _test_join_aliasing(self, sess): Employee = self.classes.Employee + ea = aliased(Employee) eq_( [ n for n, in sess.query(Employee.name) - .join(Employee.reports_to, aliased=True) - .filter_by(name="emp5") - .reset_joinpoint() + .join(Employee.reports_to.of_type(ea)) + .filter(ea.name == "emp5") + # broken until #7244 is fixed due to of_type() usage + # .filter_by(name="emp5") .order_by(Employee.name) ], ["emp6", "emp7"], -- 2.47.2