mcpField
S
sqlmodel

sqlmodel

github.com/fastapi開発18.5k167 回インストール2 週間前に更新
リポジトリ

sqlmodel について

sqlmodel は、SQLModel の API によるモデル定義、セッション管理、データ検索を案内するコーディングスキルです。リレーション、検証、日時処理、アプリケーション構成のパターンも扱います。

主な機能

  1. 01SQLModel のモデルとフィールドのパターン
  2. 02セッションとクエリの指針
  3. 03リレーションのマッピングパターン
  4. 04日時とタイムゾーンの処理
  5. 05作成・更新・削除のパターン
  6. 06FastAPI と SQLite の構成案内

活用シーン

  1. 01データベーステーブルと API モデルの定義
  2. 02クエリの作成とセッション管理
  3. 03関連レコードのモデル化
  4. 04タイムスタンプ項目の一貫した処理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/fastapi/sqlmodel --skill sqlmodel

インストール後、Claude Code で/sqlmodelを入力して読み込まれたか確認します。

詳細

SQLModelのパターン

まずSQLModelのAPIを使用してください。タスクでSQLAlchemyのみの機能が明示的に必要な場合を除き、生のSQLAlchemyパターンを標準として使用しないでください。

インポート

sqlmodelからのインポートを優先してください。

from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select

通常のSQLModelコードでは、declarative_base()、Mapped[...]、mapped_column()、relationship()、sessionmaker()などのSQLAlchemyの宣言型デフォルトを使用しないでください。

モデル

SQLModel, table=TrueとField()を使ってテーブルモデルを定義してください。

class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    team_id: int | None = Field(default=None, foreign_key="team.id")

生成される値にはField(default_factory=...)を使用してください。

id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)

作成・更新・公開APIのスキーマには、テーブルモデルにリクエスト/レスポンス専用のフィールドを混在させず、テーブルではないSQLModelクラスを使用してください。

日時

SQLModel 0.0.45以降では、タイムスタンプフィールドにdatetimeを使用してください。SQLModelは自動的にUTCDateTimeを選択し、書き込み時にはタイムゾーン付きの値をUTCに正規化します。また、SQLiteやMySQL/MariaDBを含め、読み取り時にはタイムゾーン付きのUTC日時を返します。

タイムゾーン対応を有効にするだけの目的で、sa_type=DateTime(timezone=True)やsa_column=Column(DateTime(timezone=True))を追加しないでください。これらのオーバーライドはSQLModelのUTC処理を迂回します。明示的なカラム型が必要な場合は、sqlmodelからUTCDateTimeをインポートしてください。

Python 3.11以降でタイムスタンプを生成する場合:

from datetime import UTC, datetime

from sqlmodel import Field, SQLModel


class Event(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))

Python 3.10では、UTCの代わりにtimezoneをインポートし、lambda: datetime.now(timezone.utc)を使用してください。

  • 書き込み、更新、クエリフィルターには、タイムゾーン付きの日時を渡してください。UTCタイムスタンプには、タイムゾーンを指定しないdatetime.now()やdatetime.utcnow()を使用しないでください。タイムゾーンのないデータベースパラメーターは、ステートメントの実行時に拒否されます。
  • 入力検証でタイムゾーンのない値を拒否する必要がある場合は、PydanticのAwareDatetimeを使用してください。ストレージには同じUTC処理が適用されます。テーブルモデルの入力を検証するには、Model.model_validate(data)を使用してください。テーブルモデルを直接構築すると現在はPydanticの検証がスキップされ、通常のdatetime検証ではタイムゾーンのない値も許容されます。
  • 意図的にタイムゾーンのない値を保存する場合は、PydanticのNaiveDatetimeを使用してください。これはDateTime(timezone=False)を選択し、Pydanticの検証時にタイムゾーンのない値を要求します。

既存のアプリケーションをアップグレードする場合は、日時の移行ガイドに従ってください。モデルを変更しても、既存のカラムは変更されず、過去のデータも変換されません。

セッションとクエリ

エンジンから直接セッションを開いてください。

with Session(engine) as session:
    heroes = session.exec(select(Hero)).all()

一般的なSQLModelの例では、sessionmaker()を作成しないでください。

session.execute(...)やsession.query(...)ではなく、session.exec(select(...))を使用してください。SQLModelのexec()はスカラー結果を処理するため、エージェントはモデルのselectの後に.scalars()を追加する必要はありません。

主キーで検索する場合は、session.get(Model, id)を使用してください。

session.exec(...)の後は、通常の結果メソッドを使用してください。一覧には.all()、最初の行が存在する場合としない場合があるときは.first()、必ず1行存在する必要があるときは.one()、0行または1行が有効なときは.one_or_none()を使用します。

オブジェクトの作成または変更後、返されるデータにDBのデフォルト値または生成されたIDが必要な場合は、コミットしてリフレッシュしてください。

session.add(hero)
session.commit()
session.refresh(hero)

リレーションシップ

アドホックなSQLAlchemyテーブルやsecondary=ではなく、SQLModelのリレーションシップ属性を使用してください。

一対多:

class Team(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    heroes: list["Hero"] = Relationship(back_populates="team")


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    team_id: int | None = Field(default=None, foreign_key="team.id")
    team: Team | None = Relationship(back_populates="heroes")

多対多:

class HeroTeamLink(SQLModel, table=True):
    team_id: int | None = Field(default=None, foreign_key="team.id", primary_key=True)
    hero_id: int | None = Field(default=None, foreign_key="hero.id", primary_key=True)


class Team(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    heroes: list["Hero"] = Relationship(back_populates="teams", link_model=HeroTeamLink)


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    teams: list[Team] = Relationship(back_populates="heroes", link_model=HeroTeamLink)

リンクテーブルに追加フィールドがある場合は、両側へのリレーションシップを持つ完全なSQLModelテーブルとしてモデル化し、リンクオブジェクトを直接操作してください。

新しいアプリを始める場合は、リレーションシップのアノテーションとメタデータの順序を簡潔に保つため、関連するSQLModelテーブルモデルを1つのファイルにまとめることを優先してください。モデルを複数のファイルに分ける必要がある場合は、TYPE_CHECKINGインポートと文字列アノテーションを使用してください。

if TYPE_CHECKING:
    from .team_model import Team

team: Optional["Team"] = Relationship(back_populates="heroes")

SQLModelのファーストクラスのパラメーターとして公開されていないSQLAlchemyのリレーションシップオプションには、Relationship(sa_relationship_kwargs={...})またはRelationship(sa_relationship_args=[...])を優先してください。SQLModelのリレーションシップラッパーでマッピングを表現できない場合に限り、回避策としてsa_relationship=relationship(...)を使用してください。

作成と更新

信頼できる内部値には、直接構築を使用します。

hero = Hero(name="Deadpond", secret_name="Dive Wilson")

データモデルからテーブルオブジェクトを構築するには、Model.model_validate(...) を使用します。

db_hero = Hero.model_validate(hero_create)

FastAPI では、モデルを分割するパターンを使用します。共通フィールドには HeroBase、テーブルには Hero(HeroBase, table=True)、入力には HeroCreate、PATCH 用のすべてのフィールドをオプションにしたモデルには HeroUpdate、出力には HeroPublic を使用し、必要な場合に限り HeroPublicWithTeam などのリレーションシップ固有の公開モデルを使用します。

部分更新では、指定されたフィールドのみをダンプし、既存のオブジェクトをその場で更新します。

hero_data = hero_update.model_dump(exclude_unset=True)
db_hero.sqlmodel_update(hero_data)
session.add(db_hero)
session.commit()
session.refresh(db_hero)

メタデータとアプリのセットアップ

すべてのテーブルモデルクラスをインポートした後にのみ、SQLModel.metadata.create_all(engine) を呼び出します。FastAPI の例では、Session(engine) を生成する依存関係を使用します。

テストや小規模な例では、SQLite がよく使われます。SQLite を使用する場合は、メタデータを明示的に作成し、直接セッションを使用します。

engine = create_engine("sqlite:///:memory:")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
    ...

FastAPI で SQLite を使用する場合は、次を含めます。

connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, connect_args=connect_args)

削除

カスケード処理には、SQLModel のリレーションシップヘルパーを使用します。

heroes: list["Hero"] = Relationship(back_populates="team", cascade_delete=True)
team_id: int | None = Field(default=None, foreign_key="team.id", ondelete="CASCADE")

SET NULL などのデータベースレベルの削除動作には、ondelete を NULL 許容の外部キーと組み合わせ、適切な場合はリレーションシップに passive_deletes を使用します。

ondelete="SET NULL" を使用するには、外部キーが NULL 許容である必要があります。

team_id: int | None = Field(default=None, foreign_key="team.id", ondelete="SET NULL")

SQLAlchemy のエスケープハッチ

通常の Field() パラメーターでカラムや型の要件を満たせない場合に限り、SQLModel の Field(sa_type=...)、Field(sa_column=...)、Field(sa_column_args=...)、または Field(sa_column_kwargs=...) を使用します。カスタムカラムを1つ追加するために、モデル全体を SQLAlchemy の宣言的スタイルに切り替えないでください。

コメント