sqlmodel 外键
一、背景
最近在使用 FastAPI + SQLModel + Pydantic v2 + SQLAlchemy 2.0 构建数据平台时,遇到一个非常诡异的错误:
1 | sqlalchemy.exc.InvalidRequestError: |
起初我以为是类型注解写错、版本不兼容、或者 Relationship 用法不对。
反复试了各种写法,都没能解决。
直到——我注释掉了文件顶部的一行:
1 | # from __future__ import annotations |
一切立刻恢复正常。
于是,问题的罪魁祸首浮出水面:__future__.annotations 与 SQLModel 的关系反射机制 不兼容。
二、问题重现
1 | from __future__ import annotations |
在启动 FastAPI 时,你会看到:
1 | InvalidRequestError: relationship("list[DataSourceConfig]") ... |
三、根本原因分析
在 Python 3.10+ 中,from __future__ import annotations 的作用是:
将所有类型注解延迟求值,保存为字符串,而不是立即解析成真实对象。
这对现代框架(如 SQLAlchemy 2.0、Pydantic v2)来说完全没问题,
但 SQLModel 还在使用一套「静态反射」逻辑来识别关系字段。
它看到的是字符串 "list[DataSourceConfig]",
于是直接把它传给 SQLAlchemy 的 relationship() 函数。
SQLAlchemy 再去解析 list[DataSourceConfig] 这个“类名”——当然不存在,于是报错。
四、错误特征
如果你在项目中看到以下报错栈:
1 | relationship("list[YourModel]") |
或者:
1 | ValueError: <class 'sqlalchemy.orm.base.Mapped'> has no matching SQLAlchemy type |
并且你的模型文件顶部有:
1 | from __future__ import annotations |
那几乎可以肯定,就是这个问题。
五、解决方案 ✅
最直接的办法:
🚫 删除这行导入
1 | # from __future__ import annotations |
然后显式使用字符串前向引用:
1 | configs: list["DataSourceConfig"] = Relationship(back_populates="source") |
即可恢复正常。
六、总结与经验法则
| 框架组合 | 是否推荐使用 from __future__ import annotations |
原因 |
|---|---|---|
| SQLAlchemy 2.0 (纯 ORM) | ✅ 推荐 | 完全支持延迟解析 |
| Pydantic v2 | ✅ 推荐 | 类型系统兼容 |
| SQLModel 0.0.27 (含 Pydantic v2) | 🚫 不推荐 | 关系反射机制未适配 |
| 旧版 SQLModel (Pydantic v1) | 🚫 不推荐 | 问题更严重 |
🧠 经验法则
SQLModel 项目中不要使用
from __future__ import annotations。
只需在关系字段上加字符串前向引用即可避免循环依赖。
七、参考与后续
- SQLAlchemy 2.0 Declarative Typing
- Pydantic v2 migration guide
- SQLModel GitHub issue: “relationship(‘list[…]’) when using future.annotations”
💡 写在最后
__future__.annotations 是 Python 类型系统的未来(字面意义上),
但 SQLModel 目前还停留在 2021。
在 SQLModel 更新前,最简单的办法就是:
把那一行注释掉,问题迎刃而解。