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)

使用非表 SQLModel 类定义创建、更新和公共 API 模式,不要将仅用于请求或响应的字段混入表模型。

日期时间

在 SQLModel 0.0.45 及更高版本中,时间戳字段使用 datetime。SQLModel 会自动选择 UTCDateTime:写入时将带时区的值规范化为 UTC,读取时返回带时区的 UTC 日期时间,即使使用 SQLite 和 MySQL/MariaDB 也是如此。

不要仅为启用时区支持而添加 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 中,导入 timezone 而不是 UTC,并使用 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.exec(select(...)),不要使用 session.execute(...) 或 session.query(...)。SQLModel 的 exec() 会处理标量结果,因此 Agent 在查询模型后不应再添加 .scalars()。

通过 session.get(Model, id) 按主键查询。

在 session.exec(...) 后使用常规结果方法:列表使用 .all();获取可选的首行使用 .first();必须恰好存在一行时使用 .one();允许零行或一行时使用 .one_or_none()。

创建或修改对象后,如果返回的数据需要数据库默认值或生成的 ID,请提交并刷新:

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

关系

使用 SQLModel 关系属性,不要临时创建 SQLAlchemy 表或使用 secondary=。

一对多:

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 表模型放在同一文件中,以简化关系注解和元数据顺序。如果必须将模型拆分到多个文件,请使用 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 表示输入,用包含所有可选字段的 HeroUpdate 表示 PATCH,用 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 是常见选择;使用时,请显式创建元数据并使用直接会话:

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,并在适当时在关系上使用 passive_deletes。

ondelete="SET 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=...)。不要仅为了一个自定义列就将整个模型切换为 SQLAlchemy 声明式风格。

评论

支持 Markdown