]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Remove fallbacks from the previous typing change
authorFederico Caselli <cfederico87@gmail.com>
Tue, 19 Nov 2024 22:09:06 +0000 (23:09 +0100)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 3 Jan 2025 16:21:58 +0000 (11:21 -0500)
Removed 2.0 fallbacks from Iffc34fd42b9769f73ddb4331bd59b6b37391635d

Fixes: #11944
Fixes: #11955
Fixes: #11305
Change-Id: I358aa8ea9822d20525989f414447f7f5ecb68711

doc/build/orm/declarative_tables.rst
lib/sqlalchemy/orm/decl_api.py
lib/sqlalchemy/sql/sqltypes.py
lib/sqlalchemy/util/typing.py
test/orm/declarative/test_tm_future_annotations_sync.py
test/orm/declarative/test_typed_mapping.py

index aba74f57932ae4f3f745118fa6804e696e03b203..a8e8afff905e653f8ba896bc2e49e3c2b1033011 100644 (file)
@@ -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
index a9dc3bb7bfeed2175d5b0473a7f09c07f01a0768..97da200ef3a302d3d20296cd9282afbb98a2df73 100644 (file)
@@ -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
index 212b86ca8ab6cafee20d0fa3d387e9c7d0ac40f7..44c193bf73a681912e7800631bca242662008f3c 100644 (file)
@@ -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 "
index 7809c9fcad7cd9766e3abcbfbb2312bc2f9c453b..01569cebdaf3adbfa8e087c53c49829e11ff69af 100644 (file)
@@ -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.
index 05919734270dd8a74fef5463815fba9f947ae990..d435e9547b492be4cfc0e4342bc39bf73fd7e652 100644 (file)
@@ -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):
             # <function NewType.<locals>.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]
     ):
index 79aca8a3613b499656a8303545a8e80692e8b9b6..6700cde56c0a1b42a8a03bbf16bcf00b0e4c09f9 100644 (file)
@@ -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):
             # <function NewType.<locals>.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]
     ):