2024年1月15日月曜日

SQLAlchemy2 では Mapped カラムを使ってモデルを定義する

SQLAlchemy2 では Mapped カラムを使ってモデルを定義する

概要

Mapped カラムを使うとより Python のデータクラスっぽくモデルを定義することができます

また declarative_base の使い方も変わったので v2 にあった記載方法を紹介します

環境

  • macOS 11.7.10
  • Python 3.11.6
  • sqlalchemy 2.0.25

サンプルコード

from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column
from sqlalchemy.types import JSON

engine = create_engine("mysql+pymysql://root@localhost/test?charset=utf8mb4")


class Base(DeclarativeBase):
    pass


class User(Base):

    __tablename__ = 'user'

    id: Mapped[int] = mapped_column(primary_key=True)
    profile: Mapped[dict] = mapped_column(JSON())


class UserTable():
    def __init__(self, session: Session):
        self.session = session

    def select_all(self):
        return self.session.query(User).all()


if __name__ == '__main__':
    with Session(engine) as session:
        user_table = UserTable(session)
        records = user_table.select_all()
        for r in records:
            print(r.id)
            print(r.profile)

解説

DeclarativeBase は直接使用できないの必ず DeclarativeBase を継承したベースクラスを作成してそのベースクラスを元にモデルを定義する必要があります

モデルは Mapped と一緒に型を使ってタイプヒトっぽいく記載します
そしてカラムのオプション情報などを mapped_column を使って定義します
単純な文字列を管理するクラスであれば mapped_column を使ったオプション情報は不要です

session も session_maker からは生成せずに直接クラスに engine 情報を渡すことで生成できます
基本は with セッションを使えば自動でクローズしてくれるので with と併用しましょう

JSON を TypedDict に自動バインドする

sqlalchemy v2 では json は dict として扱うので受け取るときに TypedDict として受け取ることもできます

できれば dataclass などのクラスに変換してほしかったのですが単純にやってみたところダメそうだったので自力でやるか他の方法があるのかもしれません

from typing import TypedDict

from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column
from sqlalchemy.types import JSON

engine = create_engine("mysql+pymysql://root@localhost/test?charset=utf8mb4")


class Profile(TypedDict):
    name: str


class Base(DeclarativeBase):
    pass


class User(Base):

    __tablename__ = 'user'

    id: Mapped[int] = mapped_column(primary_key=True)
    profile: Mapped[Profile] = mapped_column(JSON())


class UserTable():
    def __init__(self, session: Session):
        self.session = session

    def select_all(self):
        return self.session.query(User).all()


if __name__ == '__main__':
    with Session(engine) as session:
        user_table = UserTable(session)
        records = user_table.select_all()
        for r in records:
            print(r.id)
            print(r.profile["name"])

参考: v1 でのサンプルコード

ライブラリが v2 でも互換があるので動作しますがそのうち使えなくなるかもしれないです

from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
from sqlalchemy.schema import Column
from sqlalchemy.types import JSON, String

engine = create_engine("mysql+pymysql://root@localhost/test?charset=utf8mb4")
SessionClass = sessionmaker(engine)
db_session = SessionClass()

Base = declarative_base()


class User(Base):

    __tablename__ = 'user'

    id = Column(String(32), primary_key=True)
    profile = Column(JSON())


class UserTable():

    def select_all(self):
        return db_session.query(User).all()


if __name__ == '__main__':
    user_table = UserTable()
    records = user_table.select_all()
    for r in records:
        print(r.id)
        print(r.profile)

最後に

SQLAlchemy v2 では Mapped を使ってモデルをタイプヒントっぽく定義できるようになっています

JSON の扱い方はもう少し検討が必要そうです

参考サイト

0 件のコメント:

コメントを投稿