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
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
)
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]]]
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
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
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 "
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.
"plain",
"union",
"union_604",
+ "null",
"union_null",
"union_null_604",
"optional",
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:
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(
_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,
"'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"""
# <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):
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]
):
"plain",
"union",
"union_604",
+ "null",
"union_null",
"union_null_604",
"optional",
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:
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(
_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,
"'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"""
# <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):
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]
):