From: Federico Caselli Date: Tue, 19 Nov 2024 22:09:06 +0000 (+0100) Subject: Remove fallbacks from the previous typing change X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8d6f44eecbd25c4b4c489b789b7f45ce4e6defca;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Remove fallbacks from the previous typing change Removed 2.0 fallbacks from Iffc34fd42b9769f73ddb4331bd59b6b37391635d Fixes: #11944 Fixes: #11955 Fixes: #11305 Change-Id: I358aa8ea9822d20525989f414447f7f5ecb68711 --- diff --git a/doc/build/orm/declarative_tables.rst b/doc/build/orm/declarative_tables.rst index aba74f5793..a8e8afff90 100644 --- a/doc/build/orm/declarative_tables.rst +++ b/doc/build/orm/declarative_tables.rst @@ -686,6 +686,23 @@ way in which ``Annotated`` may be used with Declarative that is even more open ended. +.. note:: While a ``typing.TypeAliasType`` can be assigned to unions, like in the + case of ``JsonScalar`` defined above, it has a different behavior than normal + unions defined without the ``type ...`` syntax. + The following mapping includes unions that are compatible with ``JsonScalar``, + but they will not be recognized:: + + class SomeClass(TABase): + __tablename__ = "some_table" + + id: Mapped[int] = mapped_column(primary_key=True) + col_a: Mapped[str | float | bool | None] + col_b: Mapped[str | float | bool] + + This raises an error since the union types used by ``col_a`` or ``col_b``, + are not found in ``TABase`` type map and ``JsonScalar`` must be referenced + directly. + .. _orm_declarative_mapped_column_pep593: Mapping Whole Column Declarations to Python Types diff --git a/lib/sqlalchemy/orm/decl_api.py b/lib/sqlalchemy/orm/decl_api.py index a9dc3bb7bf..97da200ef3 100644 --- a/lib/sqlalchemy/orm/decl_api.py +++ b/lib/sqlalchemy/orm/decl_api.py @@ -71,14 +71,10 @@ from ..sql.selectable import FromClause from ..util import hybridmethod from ..util import hybridproperty from ..util import typing as compat_typing -from ..util import warn_deprecated from ..util.typing import CallableReference from ..util.typing import de_optionalize_union_types -from ..util.typing import flatten_newtype from ..util.typing import is_generic from ..util.typing import is_literal -from ..util.typing import is_newtype -from ..util.typing import is_pep695 from ..util.typing import Literal from ..util.typing import LITERAL_TYPES from ..util.typing import Self @@ -1233,7 +1229,7 @@ class registry: ) def _resolve_type( - self, python_type: _MatchedOnType, _do_fallbacks: bool = True + self, python_type: _MatchedOnType ) -> Optional[sqltypes.TypeEngine[Any]]: python_type_type: Type[Any] search: Iterable[Tuple[_MatchedOnType, Type[Any]]] @@ -1278,39 +1274,6 @@ class registry: if resolved_sql_type is not None: return resolved_sql_type - # 2.0 fallbacks - if _do_fallbacks: - python_type_to_check: Any = None - kind = None - if is_pep695(python_type): - # NOTE: assume there aren't type alias types of new types. - python_type_to_check = python_type - while is_pep695(python_type_to_check): - python_type_to_check = python_type_to_check.__value__ - python_type_to_check = de_optionalize_union_types( - python_type_to_check - ) - kind = "TypeAliasType" - if is_newtype(python_type): - python_type_to_check = flatten_newtype(python_type) - kind = "NewType" - - if python_type_to_check is not None: - res_after_fallback = self._resolve_type( - python_type_to_check, False - ) - if res_after_fallback is not None: - assert kind is not None - warn_deprecated( - f"Matching the provided {kind} '{python_type}' on " - "its resolved value without matching it in the " - "type_annotation_map is deprecated; add this type to " - "the type_annotation_map to allow it to match " - "explicitly.", - "2.0", - ) - return res_after_fallback - return None @property diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py index 212b86ca8a..44c193bf73 100644 --- a/lib/sqlalchemy/sql/sqltypes.py +++ b/lib/sqlalchemy/sql/sqltypes.py @@ -60,7 +60,6 @@ from .. import util from ..engine import processors from ..util import langhelpers from ..util import OrderedDict -from ..util import warn_deprecated from ..util.typing import get_args from ..util.typing import is_literal from ..util.typing import is_pep695 @@ -1594,20 +1593,6 @@ class Enum(String, SchemaType, Emulated, TypeEngine[Union[str, enum.Enum]]): enum_args, native_enum = process_literal(python_type) elif is_pep695(python_type): value = python_type.__value__ - if is_pep695(value): - new_value = value - while is_pep695(new_value): - new_value = new_value.__value__ - if is_literal(new_value): - value = new_value - warn_deprecated( - f"Mapping recursive TypeAliasType '{python_type}' " - "that resolve to literal to generate an Enum is " - "deprecated. SQLAlchemy 2.1 will not support this " - "use case. Please avoid using recursing " - "TypeAliasType.", - "2.0", - ) if not is_literal(value): raise exc.ArgumentError( f"Can't associate TypeAliasType '{python_type}' to an " diff --git a/lib/sqlalchemy/util/typing.py b/lib/sqlalchemy/util/typing.py index 7809c9fcad..01569cebda 100644 --- a/lib/sqlalchemy/util/typing.py +++ b/lib/sqlalchemy/util/typing.py @@ -358,13 +358,6 @@ def is_pep695(type_: _AnnotationScanType) -> TypeGuard[TypeAliasType]: return isinstance(type_, TypeAliasType) -def flatten_newtype(type_: NewType) -> Type[Any]: - super_type = type_.__supertype__ - while is_newtype(super_type): - super_type = super_type.__supertype__ - return super_type # type: ignore[return-value] - - def pep695_values(type_: _AnnotationScanType) -> Set[Any]: """Extracts the value from a TypeAliasType, recursively exploring unions and inner TypeAliasType to flatten them into a single set. diff --git a/test/orm/declarative/test_tm_future_annotations_sync.py b/test/orm/declarative/test_tm_future_annotations_sync.py index 0591973427..d435e9547b 100644 --- a/test/orm/declarative/test_tm_future_annotations_sync.py +++ b/test/orm/declarative/test_tm_future_annotations_sync.py @@ -852,6 +852,7 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): "plain", "union", "union_604", + "null", "union_null", "union_null_604", "optional", @@ -875,6 +876,8 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): tat = TypeAliasType("tat", Union[str, int]) elif option.union_604: tat = TypeAliasType("tat", str | int) + elif option.null: + tat = TypeAliasType("tat", None) elif option.union_null: tat = TypeAliasType("tat", Union[str, int, None]) elif option.union_null_604: @@ -915,33 +918,18 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): if in_map.yes: col = declare() - length = 99 - elif ( - in_map.value - and "newtype" not in option.name - or option.optional - or option.plain - ): - with expect_deprecated( - "Matching the provided TypeAliasType 'tat' on its " - "resolved value without matching it in the " - "type_annotation_map is deprecated; add this type to the " - "type_annotation_map to allow it to match explicitly.", - ): - col = declare() - length = 99 if in_map.value else None + is_true(isinstance(col.type, String)) + eq_(col.type.length, 99) + nullable = "null" in option.name or "optional" in option.name + eq_(col.nullable, nullable) + else: with expect_raises_message( exc.ArgumentError, - "Could not locate SQLAlchemy Core type for Python type", + "Could not locate SQLAlchemy Core type for Python type " + f"{tat} inside the 'data' attribute Mapped annotation", ): declare() - return - - is_true(isinstance(col.type, String)) - eq_(col.type.length, length) - nullable = "null" in option.name or "optional" in option.name - eq_(col.nullable, nullable) @testing.requires.python312 def test_pep695_typealias_as_typemap_keys( @@ -1043,16 +1031,12 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): _StrPep695: Enum(enum.Enum), # noqa: F821 } ) - if type_.recursive: - with expect_deprecated( - "Mapping recursive TypeAliasType '.+' that resolve to " - "literal to generate an Enum is deprecated. SQLAlchemy " - "2.1 will not support this use case. Please avoid using " - "recursing TypeAliasType", - ): - Foo = declare() - elif type_.literal: + if type_.literal: Foo = declare() + col = Foo.__table__.c.status + is_true(isinstance(col.type, Enum)) + eq_(col.type.enums, ["to-do", "in-progress", "done"]) + is_(col.type.native_enum, False) else: with expect_raises_message( exc.ArgumentError, @@ -1062,22 +1046,13 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): "'b'.` are supported when generating Enums.", ): declare() - return else: - with expect_deprecated( - "Matching the provided TypeAliasType '.*' on its " - "resolved value without matching it in the " - "type_annotation_map is deprecated; add this type to the " - "type_annotation_map to allow it to match explicitly.", + with expect_raises_message( + exc.ArgumentError, + "Could not locate SQLAlchemy Core type for Python type " + ".+ inside the 'status' attribute Mapped annotation", ): - Foo = declare() - col = Foo.__table__.c.status - if in_map and not type_.not_literal: - is_true(isinstance(col.type, Enum)) - eq_(col.type.enums, ["to-do", "in-progress", "done"]) - is_(col.type.native_enum, False) - else: - is_true(isinstance(col.type, String)) + declare() def test_typing_literal_identity(self, decl_base): """See issue #11820""" @@ -1405,11 +1380,10 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): # .new_type at 0x...> text = ".*NewType.*" - with expect_deprecated( - f"Matching the provided NewType '{text}' on its " - "resolved value without matching it in the " - "type_annotation_map is deprecated; add this type to the " - "type_annotation_map to allow it to match explicitly.", + with expect_raises_message( + exc.ArgumentError, + "Could not locate SQLAlchemy Core type for Python type " + f"{text} inside the 'data_one' attribute Mapped annotation", ): class MyClass(decl_base): @@ -1418,8 +1392,6 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): id: Mapped[int] = mapped_column(primary_key=True) data_one: Mapped[str50] - is_true(isinstance(MyClass.data_one.type, String)) - def test_extract_base_type_from_pep593( self, decl_base: Type[DeclarativeBase] ): diff --git a/test/orm/declarative/test_typed_mapping.py b/test/orm/declarative/test_typed_mapping.py index 79aca8a361..6700cde56c 100644 --- a/test/orm/declarative/test_typed_mapping.py +++ b/test/orm/declarative/test_typed_mapping.py @@ -843,6 +843,7 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): "plain", "union", "union_604", + "null", "union_null", "union_null_604", "optional", @@ -866,6 +867,8 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): tat = TypeAliasType("tat", Union[str, int]) elif option.union_604: tat = TypeAliasType("tat", str | int) + elif option.null: + tat = TypeAliasType("tat", None) elif option.union_null: tat = TypeAliasType("tat", Union[str, int, None]) elif option.union_null_604: @@ -906,33 +909,18 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): if in_map.yes: col = declare() - length = 99 - elif ( - in_map.value - and "newtype" not in option.name - or option.optional - or option.plain - ): - with expect_deprecated( - "Matching the provided TypeAliasType 'tat' on its " - "resolved value without matching it in the " - "type_annotation_map is deprecated; add this type to the " - "type_annotation_map to allow it to match explicitly.", - ): - col = declare() - length = 99 if in_map.value else None + is_true(isinstance(col.type, String)) + eq_(col.type.length, 99) + nullable = "null" in option.name or "optional" in option.name + eq_(col.nullable, nullable) + else: with expect_raises_message( exc.ArgumentError, - "Could not locate SQLAlchemy Core type for Python type", + "Could not locate SQLAlchemy Core type for Python type " + f"{tat} inside the 'data' attribute Mapped annotation", ): declare() - return - - is_true(isinstance(col.type, String)) - eq_(col.type.length, length) - nullable = "null" in option.name or "optional" in option.name - eq_(col.nullable, nullable) @testing.requires.python312 def test_pep695_typealias_as_typemap_keys( @@ -1034,16 +1022,12 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): _StrPep695: Enum(enum.Enum), # noqa: F821 } ) - if type_.recursive: - with expect_deprecated( - "Mapping recursive TypeAliasType '.+' that resolve to " - "literal to generate an Enum is deprecated. SQLAlchemy " - "2.1 will not support this use case. Please avoid using " - "recursing TypeAliasType", - ): - Foo = declare() - elif type_.literal: + if type_.literal: Foo = declare() + col = Foo.__table__.c.status + is_true(isinstance(col.type, Enum)) + eq_(col.type.enums, ["to-do", "in-progress", "done"]) + is_(col.type.native_enum, False) else: with expect_raises_message( exc.ArgumentError, @@ -1053,22 +1037,13 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): "'b'.` are supported when generating Enums.", ): declare() - return else: - with expect_deprecated( - "Matching the provided TypeAliasType '.*' on its " - "resolved value without matching it in the " - "type_annotation_map is deprecated; add this type to the " - "type_annotation_map to allow it to match explicitly.", + with expect_raises_message( + exc.ArgumentError, + "Could not locate SQLAlchemy Core type for Python type " + ".+ inside the 'status' attribute Mapped annotation", ): - Foo = declare() - col = Foo.__table__.c.status - if in_map and not type_.not_literal: - is_true(isinstance(col.type, Enum)) - eq_(col.type.enums, ["to-do", "in-progress", "done"]) - is_(col.type.native_enum, False) - else: - is_true(isinstance(col.type, String)) + declare() def test_typing_literal_identity(self, decl_base): """See issue #11820""" @@ -1396,11 +1371,10 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): # .new_type at 0x...> text = ".*NewType.*" - with expect_deprecated( - f"Matching the provided NewType '{text}' on its " - "resolved value without matching it in the " - "type_annotation_map is deprecated; add this type to the " - "type_annotation_map to allow it to match explicitly.", + with expect_raises_message( + exc.ArgumentError, + "Could not locate SQLAlchemy Core type for Python type " + f"{text} inside the 'data_one' attribute Mapped annotation", ): class MyClass(decl_base): @@ -1409,8 +1383,6 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): id: Mapped[int] = mapped_column(primary_key=True) data_one: Mapped[str50] - is_true(isinstance(MyClass.data_one.type, String)) - def test_extract_base_type_from_pep593( self, decl_base: Type[DeclarativeBase] ):