关于 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)
使用非表 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 声明式风格。