--- /dev/null
+.. change::
+ :tags: usecase, orm
+ :tickets: 10807
+
+ Added preliminary support for Python 3.12 pep-695 type alias structures,
+ when resolving custom type maps for ORM Annotated Declarative mappings.
+
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 Self
elif is_newtype(python_type):
python_type_type = flatten_newtype(python_type)
search = ((python_type, python_type_type),)
+ elif is_pep695(python_type):
+ python_type_type = python_type.__value__
+ flattened = None
+ search = ((python_type, python_type_type),)
else:
python_type_type = cast("Type[Any]", python_type)
flattened = None
from .. import exc
from .. import util
from ..util.typing import Self
+from ..util.typing import TypeAliasType
from ..util.typing import TypeGuard
# these are back-assigned by sqltypes.
_TE = TypeVar("_TE", bound="TypeEngine[Any]")
_CT = TypeVar("_CT", bound=Any)
-_MatchedOnType = Union["GenericProtocol[Any]", NewType, Type[Any]]
+_MatchedOnType = Union[
+ "GenericProtocol[Any]", TypeAliasType, NewType, Type[Any]
+]
class _NoValueInList(Enum):
lambda: util.py311, "Python 3.11 or above required"
)
+ @property
+ def python312(self):
+ return exclusions.only_if(
+ lambda: util.py312, "Python 3.12 or above required"
+ )
+
@property
def cpython(self):
return exclusions.only_if(
from typing_extensions import TypeAlias as TypeAlias # 3.10
from typing_extensions import TypeGuard as TypeGuard # 3.10
from typing_extensions import Self as Self # 3.11
-
+ from typing_extensions import TypeAliasType as TypeAliasType # 3.12
_T = TypeVar("_T", bound=Any)
_KT = TypeVar("_KT")
_AnnotationScanType = Union[
- Type[Any], str, ForwardRef, NewType, "GenericProtocol[Any]"
+ Type[Any], str, ForwardRef, NewType, TypeAliasType, "GenericProtocol[Any]"
]
return hasattr(type_, "__args__") and hasattr(type_, "__origin__")
+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):
=lib
install_requires =
- typing-extensions >= 4.2.0
+ typing-extensions >= 4.6.0
[options.extras_require]
asyncio =
from typing import Set
from typing import Type
from typing import TYPE_CHECKING
+from typing import TypedDict
from typing import TypeVar
from typing import Union
import uuid
from typing_extensions import get_args as get_args
from typing_extensions import Literal as Literal
+from typing_extensions import TypeAlias as TypeAlias
from sqlalchemy import BIGINT
from sqlalchemy import BigInteger
from sqlalchemy.util.typing import Annotated
+class _SomeDict1(TypedDict):
+ type: Literal["1"]
+
+
+class _SomeDict2(TypedDict):
+ type: Literal["2"]
+
+
+_UnionTypeAlias: TypeAlias = Union[_SomeDict1, _SomeDict2]
+
+_StrTypeAlias: TypeAlias = str
+
+_StrPep695: TypeAlias = Union[_SomeDict1, _SomeDict2]
+_UnionPep695: TypeAlias = str
+
+if compat.py312:
+ exec(
+ """
+type _UnionPep695 = _SomeDict1 | _SomeDict2
+type _StrPep695 = str
+""",
+ globals(),
+ )
+
+
def expect_annotation_syntax_error(name):
return expect_raises_message(
sa_exc.ArgumentError,
is_true(MyClass.__table__.c.data_two.nullable)
eq_(MyClass.__table__.c.data_three.type.length, 50)
+ def test_plain_typealias_as_typemap_keys(
+ self, decl_base: Type[DeclarativeBase]
+ ):
+ decl_base.registry.update_type_annotation_map(
+ {_UnionTypeAlias: JSON, _StrTypeAlias: String(30)}
+ )
+
+ class Test(decl_base):
+ __tablename__ = "test"
+ id: Mapped[int] = mapped_column(primary_key=True)
+ data: Mapped[_StrTypeAlias]
+ structure: Mapped[_UnionTypeAlias]
+
+ eq_(Test.__table__.c.data.type.length, 30)
+ is_(Test.__table__.c.structure.type._type_affinity, JSON)
+
+ @testing.requires.python312
+ def test_pep695_typealias_as_typemap_keys(
+ self, decl_base: Type[DeclarativeBase]
+ ):
+ """test #10807"""
+
+ decl_base.registry.update_type_annotation_map(
+ {_UnionPep695: JSON, _StrPep695: String(30)}
+ )
+
+ class Test(decl_base):
+ __tablename__ = "test"
+ id: Mapped[int] = mapped_column(primary_key=True)
+ data: Mapped[_StrPep695] # type: ignore
+ structure: Mapped[_UnionPep695] # type: ignore
+
+ eq_(Test.__table__.c.data.type.length, 30)
+ is_(Test.__table__.c.structure.type._type_affinity, JSON)
+
@testing.requires.python310
def test_we_got_all_attrs_test_annotated(self):
argnames = _py_inspect.getfullargspec(mapped_column)
from typing import Set
from typing import Type
from typing import TYPE_CHECKING
+from typing import TypedDict
from typing import TypeVar
from typing import Union
import uuid
from typing_extensions import get_args as get_args
from typing_extensions import Literal as Literal
+from typing_extensions import TypeAlias as TypeAlias
from sqlalchemy import BIGINT
from sqlalchemy import BigInteger
from sqlalchemy.util.typing import Annotated
+class _SomeDict1(TypedDict):
+ type: Literal["1"]
+
+
+class _SomeDict2(TypedDict):
+ type: Literal["2"]
+
+
+_UnionTypeAlias: TypeAlias = Union[_SomeDict1, _SomeDict2]
+
+_StrTypeAlias: TypeAlias = str
+
+_StrPep695: TypeAlias = Union[_SomeDict1, _SomeDict2]
+_UnionPep695: TypeAlias = str
+
+if compat.py312:
+ exec(
+ """
+type _UnionPep695 = _SomeDict1 | _SomeDict2
+type _StrPep695 = str
+""",
+ globals(),
+ )
+
+
def expect_annotation_syntax_error(name):
return expect_raises_message(
sa_exc.ArgumentError,
is_true(MyClass.__table__.c.data_two.nullable)
eq_(MyClass.__table__.c.data_three.type.length, 50)
+ def test_plain_typealias_as_typemap_keys(
+ self, decl_base: Type[DeclarativeBase]
+ ):
+ decl_base.registry.update_type_annotation_map(
+ {_UnionTypeAlias: JSON, _StrTypeAlias: String(30)}
+ )
+
+ class Test(decl_base):
+ __tablename__ = "test"
+ id: Mapped[int] = mapped_column(primary_key=True)
+ data: Mapped[_StrTypeAlias]
+ structure: Mapped[_UnionTypeAlias]
+
+ eq_(Test.__table__.c.data.type.length, 30)
+ is_(Test.__table__.c.structure.type._type_affinity, JSON)
+
+ @testing.requires.python312
+ def test_pep695_typealias_as_typemap_keys(
+ self, decl_base: Type[DeclarativeBase]
+ ):
+ """test #10807"""
+
+ decl_base.registry.update_type_annotation_map(
+ {_UnionPep695: JSON, _StrPep695: String(30)}
+ )
+
+ class Test(decl_base):
+ __tablename__ = "test"
+ id: Mapped[int] = mapped_column(primary_key=True)
+ data: Mapped[_StrPep695] # type: ignore
+ structure: Mapped[_UnionPep695] # type: ignore
+
+ eq_(Test.__table__.c.data.type.length, 30)
+ is_(Test.__table__.c.structure.type._type_affinity, JSON)
+
@testing.requires.python310
def test_we_got_all_attrs_test_annotated(self):
argnames = _py_inspect.getfullargspec(mapped_column)