TL;DR
from sqlalchemy import DateTime, Column, func
from sqlalchemy.orm import Mapped, declarative_base
Base = declarative_base()
class ModelName1(Base):
created_at: Mapped[datetime.datetime] = Column(
DateTime,
default=func.now(),
nullable=False
)
のように、Mapped[<マップされる型>]
とすればよい。
きっかけ
本記事では、SQLAlchemy を使用したモデルに与える、正しい型ヒントを紹介する。
SQLAlchemy を利用しての開発中に、SQLAlchemy の型ヒントがうまく与えられていないことがわかった。
試しに、column_name: Column = Column()
とするとマイグレーション時に以下のような例外が送出される。
sqlalchemy.exc.ArgumentError: Type annotation for "ModelName.uid" can't be
correctly interpreted for Annotated Declarative Table form.
ORM annotations should normally make use of the ``Mapped[]`` generic type,
or other ORM-compatible generic type, as a container for the actual type,
which indicates the intent that the attribute is mapped.
Class variables that are not intended to be mapped by the ORM should use ClassVar[].
To allow Annotated Declarative to disregard legacy annotations which don't
use Mapped[] to pass, set "__allow_unmapped__ = True" on the class or a
superclass this class. (Background on this error at: https://sqlalche.me/e/20/zlpr)
これを翻訳すると、以下のようになる。
sqlalchemy.exc.ArgumentError: "ModelName.uid"の型ヒントは、 Annotated Declarative Table 形式に対して正しく解釈できません。
通常、ORMアノテーションは、実際の型のコンテナとしてMapped[]ジェネリック型、
または他のORM互換のジェネリック型を使用する必要があり、属性がマッピングされることを示します。
ORMによってマッピングされることを意図していないクラス変数は、ClassVar[]を使用してください。
Annotated DeclarativeがMapped[]を使用しないレガシーアノテーションを無視するようにするには、
クラスまたはそのクラスのスーパークラスに"allow_unmapped = True"を設定してください。 (このエラーの背景: https://sqlalche.me/e/20/zlpr)
要するに、ORM にマッピングされる Column は、Mapped[str]
のような形で Mapped[<実際にマップされる型>]
のようにすればよい。
Solution
Column の場合
from datetime import datetime
from sqlalchemy import String, Column, func
from sqlalchemy.orm import Mapped, declarative_base
Base = declarative_base()
class ModelName1(Base):
__tablename__: str = "model_name"
created_at: Mapped[datetime.datetime] = Column(
DateTime,
default=func.now(),
nullable=False
)
updated_at: Mapped[datetime.datetime] = Column(
DateTime,
default=func.now(),
nullable=False
)
data: Mapped[str] = Column(
String,
nullable=False
)
relationship
の場合
from sqlalchemy.orm import Mapped, relationship, declarative_base
Base = declarative_base()
class ModelName1(Base):
model_name_2: Mapped["ModelName2"] = relationship(
"ModelName2",
back_populates="reply"
)
class ModelName2(Base):
model_name_1: Mapped["ModelName1"] = relationship(
"ModelName1",
back_populates="model_name_2",
uselist=False
)
Mapped[]
に与える引数は、str
型の値でも、実際の Model でも構わない。
しかしながら、大抵のモデルは相互依存になるだろううから、str
型が無難だろう。