一、背景

最近在使用 FastAPI + SQLModel + Pydantic v2 + SQLAlchemy 2.0 构建数据平台时,遇到一个非常诡异的错误:

1
2
3
4
5
6
sqlalchemy.exc.InvalidRequestError: 
When initializing mapper Mapper[DataSource(data_source)],
expression "relationship('list[DataSourceConfig]')"
seems to be using a generic class as the argument to relationship();
please state the generic argument using an annotation,
e.g. "configs: Mapped[list['DataSourceConfig']] = relationship()"

起初我以为是类型注解写错、版本不兼容、或者 Relationship 用法不对。
反复试了各种写法,都没能解决。

直到——我注释掉了文件顶部的一行:

1
# from __future__ import annotations

一切立刻恢复正常。

于是,问题的罪魁祸首浮出水面:
__future__.annotationsSQLModel 的关系反射机制 不兼容。


二、问题重现

1
2
3
4
5
6
7
8
9
10
11
from __future__ import annotations
from sqlmodel import SQLModel, Field, Relationship

class DataSource(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
configs: list["DataSourceConfig"] = Relationship(back_populates="source")

class DataSourceConfig(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
source_id: int = Field(foreign_key="data_source.id")
source: "DataSource" = Relationship(back_populates="configs")

在启动 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
2
relationship("list[YourModel]")
InvalidRequestError: When initializing mapper Mapper[YourModel(...)]

或者:

1
ValueError: <class 'sqlalchemy.orm.base.Mapped'> has no matching SQLAlchemy type

并且你的模型文件顶部有:

1
from __future__ import annotations

那几乎可以肯定,就是这个问题。


五、解决方案 ✅

最直接的办法:

🚫 删除这行导入

1
# from __future__ import annotations

然后显式使用字符串前向引用:

1
2
configs: list["DataSourceConfig"] = Relationship(back_populates="source")
source: Optional["DataSource"] = Relationship(back_populates="configs")

即可恢复正常。


六、总结与经验法则

框架组合 是否推荐使用 from __future__ import annotations 原因
SQLAlchemy 2.0 (纯 ORM) ✅ 推荐 完全支持延迟解析
Pydantic v2 ✅ 推荐 类型系统兼容
SQLModel 0.0.27 (含 Pydantic v2) 🚫 不推荐 关系反射机制未适配
旧版 SQLModel (Pydantic v1) 🚫 不推荐 问题更严重

🧠 经验法则

SQLModel 项目中不要使用 from __future__ import annotations
只需在关系字段上加字符串前向引用即可避免循环依赖。


七、参考与后续


💡 写在最后

__future__.annotations 是 Python 类型系统的未来(字面意义上),
但 SQLModel 目前还停留在 2021。

在 SQLModel 更新前,最简单的办法就是:

把那一行注释掉,问题迎刃而解。