From: Mike Bayer Date: Tue, 4 Jul 2023 18:13:31 +0000 (-0400) Subject: allow aliased() to receive any FromClause X-Git-Tag: rel_2_0_18~11^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c53805b99dcfdddb5d08ebc219845fbe9925182c;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git allow aliased() to receive any FromClause Fixed some of the typing within the :func:`_orm.aliased` construct to correctly accept a :class:`.Table` object that's been aliased with :meth:`.Table.alias`, as well as general support for :class:`.FromClause` objects to be passed as the "selectable" argument, since this is all supported. Change-Id: I6dfd5c93dc2b2f23895fbd8982633f2ed13b5a52 References: #10061 --- diff --git a/doc/build/changelog/unreleased_20/10061.rst b/doc/build/changelog/unreleased_20/10061.rst new file mode 100644 index 0000000000..a8cbaec29d --- /dev/null +++ b/doc/build/changelog/unreleased_20/10061.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, typing + :tickets: 10061 + + Fixed some of the typing within the :func:`_orm.aliased` construct to + correctly accept a :class:`.Table` object that's been aliased with + :meth:`.Table.alias`, as well as general support for :class:`.FromClause` + objects to be passed as the "selectable" argument, since this is all + supported. diff --git a/lib/sqlalchemy/orm/_orm_constructors.py b/lib/sqlalchemy/orm/_orm_constructors.py index b5586350d0..9e41874dc1 100644 --- a/lib/sqlalchemy/orm/_orm_constructors.py +++ b/lib/sqlalchemy/orm/_orm_constructors.py @@ -2182,7 +2182,7 @@ AliasedType = Annotated[Type[_O], "aliased"] @overload def aliased( element: Type[_O], - alias: Optional[Union[Alias, Subquery]] = None, + alias: Optional[FromClause] = None, name: Optional[str] = None, flat: bool = False, adapt_on_names: bool = False, @@ -2193,7 +2193,7 @@ def aliased( @overload def aliased( element: Union[AliasedClass[_O], Mapper[_O], AliasedInsp[_O]], - alias: Optional[Union[Alias, Subquery]] = None, + alias: Optional[FromClause] = None, name: Optional[str] = None, flat: bool = False, adapt_on_names: bool = False, @@ -2204,7 +2204,7 @@ def aliased( @overload def aliased( element: FromClause, - alias: Optional[Union[Alias, Subquery]] = None, + alias: None = None, name: Optional[str] = None, flat: bool = False, adapt_on_names: bool = False, @@ -2214,7 +2214,7 @@ def aliased( def aliased( element: Union[_EntityType[_O], FromClause], - alias: Optional[Union[Alias, Subquery]] = None, + alias: Optional[FromClause] = None, name: Optional[str] = None, flat: bool = False, adapt_on_names: bool = False, diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index f50dba52bc..4371e6116f 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -114,10 +114,8 @@ if typing.TYPE_CHECKING: from ..sql.base import ReadOnlyColumnCollection from ..sql.elements import BindParameter from ..sql.selectable import _ColumnsClauseElement - from ..sql.selectable import Alias from ..sql.selectable import Select from ..sql.selectable import Selectable - from ..sql.selectable import Subquery from ..sql.visitors import anon_map from ..util.typing import _AnnotationScanType from ..util.typing import ArgsTypeProcotol @@ -1024,7 +1022,7 @@ class AliasedInsp( def _alias_factory( cls, element: Union[_EntityType[_O], FromClause], - alias: Optional[Union[Alias, Subquery]] = None, + alias: Optional[FromClause] = None, name: Optional[str] = None, flat: bool = False, adapt_on_names: bool = False, diff --git a/test/orm/test_core_compilation.py b/test/orm/test_core_compilation.py index f66fc7a48e..fe47bcd856 100644 --- a/test/orm/test_core_compilation.py +++ b/test/orm/test_core_compilation.py @@ -2477,6 +2477,46 @@ class RelNaturalAliasedJoinsDisamTest( ) +class JoinedInhTest( + InheritedTest, _poly_fixtures._Polymorphic, AssertsCompiledSQL +): + __dialect__ = "default" + + def test_load_only_on_sub_table(self): + Company = self.classes.Company + Engineer = self.classes.Engineer + + e1 = aliased(Engineer, inspect(Engineer).local_table) + + q = select(Company.name, e1.primary_language).join( + Company.employees.of_type(e1) + ) + + self.assert_compile( + q, + "SELECT companies.name, engineers.primary_language " + "FROM companies JOIN engineers " + "ON companies.company_id = people.company_id", + ) + + def test_load_only_on_sub_table_aliased(self): + Company = self.classes.Company + Engineer = self.classes.Engineer + + e1 = aliased(Engineer, inspect(Engineer).local_table.alias()) + + q = select(Company.name, e1.primary_language).join( + Company.employees.of_type(e1) + ) + + self.assert_compile( + q, + "SELECT companies.name, engineers_1.primary_language " + "FROM companies JOIN engineers AS engineers_1 " + "ON companies.company_id = people.company_id", + ) + + class RawSelectTest(QueryTest, AssertsCompiledSQL): """older tests from test_query. Here, they are converted to use future selects with ORM compilation. diff --git a/test/typing/plain_files/orm/typed_queries.py b/test/typing/plain_files/orm/typed_queries.py index 8030fcff84..c3468d9528 100644 --- a/test/typing/plain_files/orm/typed_queries.py +++ b/test/typing/plain_files/orm/typed_queries.py @@ -9,6 +9,7 @@ from sqlalchemy import delete from sqlalchemy import func from sqlalchemy import insert from sqlalchemy import Integer +from sqlalchemy import join from sqlalchemy import MetaData from sqlalchemy import Select from sqlalchemy import select @@ -480,3 +481,25 @@ def t_from_statement() -> None: reveal_type(ts2) select(User).from_statement(ts2) + + +def t_aliased_fromclause() -> None: + a1 = aliased(User, user_table) + + a2 = aliased(User, user_table.alias()) + + a3 = aliased(User, join(user_table, user_table.alias())) + + a4 = aliased(user_table) + + # EXPECTED_TYPE: Type[User] + reveal_type(a1) + + # EXPECTED_TYPE: Type[User] + reveal_type(a2) + + # EXPECTED_TYPE: Type[User] + reveal_type(a3) + + # EXPECTED_TYPE: FromClause + reveal_type(a4)