sqltypes.Enum not generic enough

オープン
#11,597 コメント 0 件 リアクション 1 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

  • #13492 @waterWang による — マージされずにクローズ

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
45/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
停滞
技術スタック
python
領域
databases

調査の方向性

lib/sqlalchemy/sql/sqltypes.py の String と Enum の定義から始め、pyright エラーと提案されている型付けの変更について、リンクされている議論を読んでください。Enum(MyEnum, native_enum=False) と column_property を使う例を確認してください。報告されている pyright の指摘なしに、ジェネリックな Enum の型付けでその使用方法をサポートできれば完了です。

索引モデルが issue の本文から書いたものです。

説明

bug datatypes typing
Discussed in https://github.com/sqlalchemy/sqlalchemy/discussions/11548

The generic type for Enum needs to be a typevar for pyright:

from __future__ import annotations

import enum

from sqlalchemy import Enum
from sqlalchemy import func
from sqlalchemy.orm import column_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

class MyEnum(enum.Enum):
    one = "one"
    two = "two"



class Base(DeclarativeBase):
    pass


class A(Base):
    __tablename__ = "a"

    id: Mapped[int] = mapped_column(primary_key=True)
    data: Mapped[MyEnum] = column_property(
        func.some_func(type_=Enum(MyEnum, native_enum=False))
    )


image

seems to pass in mypy

a quick fix looks like:

@@ -1179,8 +1186,9 @@ class SchemaType(SchemaEventTarget, TypeEngineMixin):
         elif dialect.name not in variant_mapping:
             return _we_are_the_impl(variant_mapping["_default"])
 
+_TEE = TypeVar("_TEE", bound=Union[str, enum.Enum])
 
-class Enum(String, SchemaType, Emulated, TypeEngine[Union[str, enum.Enum]]):
+class Enum(String, SchemaType, Emulated, TypeEngine[_TEE]):
     """Generic Enum Type.
 
     The :class:`.Enum` type provides a set of possible string values
@@ -1483,7 +1491,7 @@ class Enum(String, SchemaType, Emulated, TypeEngine[Union[str, enum.Enum]]):
             self.enum_class = None
             return enums, enums
 
-    def _resolve_for_literal(self, value: Any) -> Enum:
+    def _resolve_for_literal(self, value: Any) -> Enum[Any]:
         tv = type(value)
         typ = self._resolve_for_python_type(tv, tv, tv)
         assert typ is not None
@@ -1494,7 +1502,7 @@ class Enum(String, SchemaType, Emulated, TypeEngine[Union[str, enum.Enum]]):
         python_type: Type[Any],
         matched_on: _MatchedOnType,
         matched_on_flattened: Type[Any],
-    ) -> Optional[Enum]:
+    ) -> Optional[Enum[Any]]:
         # "generic form" indicates we were placed in a type map
         # as ``sqlalchemy.Enum(enum.Enum)`` which indicates we need to
         # get enumerated values from the datatype
@@ -1538,7 +1546,7 @@ class Enum(String, SchemaType, Emulated, TypeEngine[Union[str, enum.Enum]]):
 
         kw["length"] = NO_ARG if self.length == 0 else self.length
         return cast(
-            Enum,
+            Enum[Any],
             self._generic_type_affinity(_enums=enum_args, **kw),  # type: ignore  # noqa: E501
         )

to go further we might want to remove String from Enum's inherits, since pyright still complains about the signature

diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py
index c20df07a6..f948ee01d 100644
--- a/lib/sqlalchemy/sql/sqltypes.py
+++ b/lib/sqlalchemy/sql/sqltypes.py
@@ -256,6 +256,13 @@ class String(Concatenable, TypeEngine[str]):
         return dbapi.STRING
 
 
+if TYPE_CHECKING:
+    class StringNoGeneric(Concatenable):
+        pass
+else:
+    StringNoGeneric = String
+
+
 class Text(String):
     """A variably sized string type.
 
@@ -1179,8 +1186,9 @@ class SchemaType(SchemaEventTarget, TypeEngineMixin):
         elif dialect.name not in variant_mapping:
             return _we_are_the_impl(variant_mapping["_default"])
 
+_TEE = TypeVar("_TEE", bound=Union[str, enum.Enum])
 
-class Enum(String, SchemaType, Emulated, TypeEngine[Union[str, enum.Enum]]):
+class Enum(StringNoGeneric, SchemaType, Emulated, TypeEngine[_TEE]):
     """Generic Enum Type.
 
     The :class:`.Enum` type provides a set of possible string values
@@ -1483,7 +1491,7 @@ class Enum(String, SchemaType, Emulated, TypeEngine[Union[str, enum.Enum]]):
             self.enum_class = None
             return enums, enums
 
-    def _resolve_for_literal(self, value: Any) -> Enum:
+    def _resolve_for_literal(self, value: Any) -> Enum[Any]:
         tv = type(value)
         typ = self._resolve_for_python_type(tv, tv, tv)
         assert typ is not None
@@ -1494,7 +1502,7 @@ class Enum(String, SchemaType, Emulated, TypeEngine[Union[str, enum.Enum]]):
         python_type: Type[Any],
         matched_on: _MatchedOnType,
         matched_on_flattened: Type[Any],
-    ) -> Optional[Enum]:
+    ) -> Optional[Enum[Any]]:
         # "generic form" indicates we were placed in a type map
         # as ``sqlalchemy.Enum(enum.Enum)`` which indicates we need to
         # get enumerated values from the datatype
@@ -1538,7 +1546,7 @@ class Enum(String, SchemaType, Emulated, TypeEngine[Union[str, enum.Enum]]):
 
         kw["length"] = NO_ARG if self.length == 0 else self.length
         return cast(
-            Enum,
+            Enum[Any],
             self._generic_type_affinity(_enums=enum_args, **kw),  # type: ignore  # noqa: E501
         )
 

主要言語
Python
スター
12.3k
フォーク
1.8k
平均マージ
2日 10時間
マージ済み PR(30日)
9

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

sqlalchemy/sqlalchemy のほかの issue

sqlalchemy/sqlalchemy の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。