From: Frazer McLean Date: Thu, 5 Sep 2024 11:29:47 +0000 (-0400) Subject: Fix use of typing.Literal on Python 3.8 and 3.9 X-Git-Tag: rel_2_0_35~8 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9dc96cef939fdf44918641bd1575ba2ccb09c8b1;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Fix use of typing.Literal on Python 3.8 and 3.9 Fixed issue where it was not possible to use ``typing.Literal`` with ``Mapped[]`` on Python 3.8 and 3.9. Pull request courtesy Frazer McLean. Closes: #11825 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/11825 Pull-request-sha: e1e50a97d2a6e0e9ef7ba8dc1a5f07d252e79fa4 Change-Id: Idf04326abcba45813ad555127e81d581a0353587 (cherry picked from commit 509c6d58501679a5844631faf9d7cb751218d7a0) --- diff --git a/doc/build/changelog/unreleased_20/11820.rst b/doc/build/changelog/unreleased_20/11820.rst new file mode 100644 index 0000000000..ae03040a65 --- /dev/null +++ b/doc/build/changelog/unreleased_20/11820.rst @@ -0,0 +1,6 @@ +.. change:: + :tags: bug, orm, typing + :tickets: 11814 + + Fixed issue where it was not possible to use ``typing.Literal`` with + ``Mapped[]`` on Python 3.8 and 3.9. Pull request courtesy Frazer McLean. diff --git a/lib/sqlalchemy/util/typing.py b/lib/sqlalchemy/util/typing.py index 53e3c1732c..a3df977705 100644 --- a/lib/sqlalchemy/util/typing.py +++ b/lib/sqlalchemy/util/typing.py @@ -62,6 +62,13 @@ _KT_contra = TypeVar("_KT_contra", contravariant=True) _VT = TypeVar("_VT") _VT_co = TypeVar("_VT_co", covariant=True) +if compat.py38: + # typing_extensions.Literal is different from typing.Literal until + # Python 3.10.1 + _LITERAL_TYPES = frozenset([typing.Literal, Literal]) +else: + _LITERAL_TYPES = frozenset([Literal]) + if compat.py310: # why they took until py310 to put this in stdlib is beyond me, @@ -356,7 +363,7 @@ def is_non_string_iterable(obj: Any) -> TypeGuard[Iterable[Any]]: def is_literal(type_: _AnnotationScanType) -> bool: - return get_origin(type_) is Literal + return get_origin(type_) in _LITERAL_TYPES def is_newtype(type_: Optional[_AnnotationScanType]) -> TypeGuard[NewType]: diff --git a/test/orm/declarative/test_tm_future_annotations_sync.py b/test/orm/declarative/test_tm_future_annotations_sync.py index f8c6a380a5..a58da96c15 100644 --- a/test/orm/declarative/test_tm_future_annotations_sync.py +++ b/test/orm/declarative/test_tm_future_annotations_sync.py @@ -29,6 +29,7 @@ from typing import TypeVar from typing import Union import uuid +import typing_extensions from typing_extensions import get_args as get_args from typing_extensions import Literal as Literal from typing_extensions import TypeAlias as TypeAlias @@ -119,6 +120,10 @@ _Recursive695_0: TypeAlias = _Literal695 _Recursive695_1: TypeAlias = _Recursive695_0 _Recursive695_2: TypeAlias = _Recursive695_1 +if compat.py38: + _TypingLiteral = typing.Literal["a", "b"] +_TypingExtensionsLiteral = typing_extensions.Literal["a", "b"] + if compat.py312: exec( """ @@ -897,6 +902,22 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): eq_(col.type.enums, ["to-do", "in-progress", "done"]) is_(col.type.native_enum, False) + @testing.requires.python38 + def test_typing_literal_identity(self, decl_base): + """See issue #11820""" + + class Foo(decl_base): + __tablename__ = "footable" + + id: Mapped[int] = mapped_column(primary_key=True) + t: Mapped[_TypingLiteral] + te: Mapped[_TypingExtensionsLiteral] + + for col in (Foo.__table__.c.t, Foo.__table__.c.te): + is_true(isinstance(col.type, Enum)) + eq_(col.type.enums, ["a", "b"]) + is_(col.type.native_enum, False) + @testing.requires.python310 def test_we_got_all_attrs_test_annotated(self): argnames = _py_inspect.getfullargspec(mapped_column) diff --git a/test/orm/declarative/test_typed_mapping.py b/test/orm/declarative/test_typed_mapping.py index 0213a0db3b..ffa83aec25 100644 --- a/test/orm/declarative/test_typed_mapping.py +++ b/test/orm/declarative/test_typed_mapping.py @@ -20,6 +20,7 @@ from typing import TypeVar from typing import Union import uuid +import typing_extensions from typing_extensions import get_args as get_args from typing_extensions import Literal as Literal from typing_extensions import TypeAlias as TypeAlias @@ -110,6 +111,10 @@ _Recursive695_0: TypeAlias = _Literal695 _Recursive695_1: TypeAlias = _Recursive695_0 _Recursive695_2: TypeAlias = _Recursive695_1 +if compat.py38: + _TypingLiteral = typing.Literal["a", "b"] +_TypingExtensionsLiteral = typing_extensions.Literal["a", "b"] + if compat.py312: exec( """ @@ -888,6 +893,22 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): eq_(col.type.enums, ["to-do", "in-progress", "done"]) is_(col.type.native_enum, False) + @testing.requires.python38 + def test_typing_literal_identity(self, decl_base): + """See issue #11820""" + + class Foo(decl_base): + __tablename__ = "footable" + + id: Mapped[int] = mapped_column(primary_key=True) + t: Mapped[_TypingLiteral] + te: Mapped[_TypingExtensionsLiteral] + + for col in (Foo.__table__.c.t, Foo.__table__.c.te): + is_true(isinstance(col.type, Enum)) + eq_(col.type.enums, ["a", "b"]) + is_(col.type.native_enum, False) + @testing.requires.python310 def test_we_got_all_attrs_test_annotated(self): argnames = _py_inspect.getfullargspec(mapped_column)