sqlmodel について
sqlmodel は、SQLModel の API によるモデル定義、セッション管理、データ検索を案内するコーディングスキルです。リレーション、検証、日時処理、アプリケーション構成のパターンも扱います。
主な機能
- 01SQLModel のモデルとフィールドのパターン
- 02セッションとクエリの指針
- 03リレーションのマッピングパターン
- 04日時とタイムゾーンの処理
- 05作成・更新・削除のパターン
- 06FastAPI と SQLite の構成案内
活用シーン
- 01データベーステーブルと API モデルの定義
- 02クエリの作成とセッション管理
- 03関連レコードのモデル化
- 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 の宣言的スタイルに切り替えないでください。