]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Fix use of typing.Literal on Python 3.8 and 3.9
authorFrazer McLean <frazer@frazermclean.co.uk>
Thu, 5 Sep 2024 11:29:47 +0000 (07:29 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 11 Sep 2024 15:38:47 +0000 (11:38 -0400)
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)

doc/build/changelog/unreleased_20/11820.rst [new file with mode: 0644]
lib/sqlalchemy/util/typing.py
test/orm/declarative/test_tm_future_annotations_sync.py
test/orm/declarative/test_typed_mapping.py

diff --git a/doc/build/changelog/unreleased_20/11820.rst b/doc/build/changelog/unreleased_20/11820.rst
new file mode 100644 (file)
index 0000000..ae03040
--- /dev/null
@@ -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.
index 53e3c1732c72695149ab1d23c00c9410af8edf83..a3df97770542af215626b414be16cfc43f76d98e 100644 (file)
@@ -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]:
index f8c6a380a53142e1485a9afbcea8eb2053908f3f..a58da96c15143d5d4efd8319dff52595e7fc861a 100644 (file)
@@ -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)
index 0213a0db3b0b501045811ec91765692735d45b8b..ffa83aec25d8cdceb4e7892e7cd29bc6a455abf6 100644 (file)
@@ -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)