FastAPI+SQLModel中User与Signatory关联报InvalidRequestError求助
问题:FastAPI+SQLModel多模型关联引发InvalidRequestError
使用FastAPI结合SQLModel开发项目,Signatory模型通过外键关联User模型,运行时触发SQLAlchemy的InvalidRequestError,提示无法解析Signatory类。
相关代码
user_model.py
from datetime import datetime from typing import List, Optional from sqlmodel import Field, SQLModel, Relationship from app.models.signatory_model import Signatory from app.models.document_model import Document class UserBase(SQLModel): email: str = Field(index=True, unique=True) is_active: bool = True is_superuser: bool = False class User(UserBase, table=True): id: int | None = Field(default=None, primary_key=True) first_name: str last_name: str created_at: datetime = Field(default_factory=datetime.now) updated_at: datetime = Field(default_factory=datetime.now) user_as_signatory: Optional["Signatory"] = Relationship(back_populates="user") signatories: List["Signatory"] = Relationship(back_populates="creator") documents: List["Document"] = Relationship(back_populates="owner")
signatory_model.py
from datetime import datetime from typing import TYPE_CHECKING, Optional, List from sqlalchemy import Enum from sqlmodel import Field, Relationship, SQLModel from app.models.document_model import Document, DocumentSignatoryLink if TYPE_CHECKING: from .user_model import User class SignatoryBase(SQLModel, table=True): id: int = Field(default=None, primary_key=True) first_name: str last_name: str email: str created_at: datetime = Field(default_factory=datetime.now) updated_at: datetime = Field(default_factory=datetime.now) class Signatory(SignatoryBase): user_id: Optional[int] = Field(default=None, foreign_key="user.id") creator_id: int = Field(default=None, foreign_key="user.id") documents: List["Document"] = Relationship( back_populates="signatories", link_model=DocumentSignatoryLink, ) user: Optional["User"] = Relationship(back_populates="user_as_signatory") creator: "User" = Relationship(back_populates="signatories")
报错信息
backend_1 | Traceback (most recent call last): backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/clsregistry.py", line 515, in _resolve_name backend_1 | rval = d[token] backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/util/_collections.py", line 344, in __missing__ backend_1 | self[key] = val = self.creator(key) backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/clsregistry.py", line 483, in _access_cls backend_1 | return self.fallback[key] backend_1 | KeyError: 'Signatory' backend_1 | backend_1 | The above exception was the direct cause of the following exception: backend_1 | backend_1 | Traceback (most recent call last): backend_1 | File "/app/app/initial_data.py", line 23, in <module> backend_1 | main() backend_1 | File "/app/app/initial_data.py", line 18, in main backend_1 | init() backend_1 | File "/app/app/initial_data.py", line 13, in init backend_1 | init_db(session) backend_1 | File "/app/app/core/db.py", line 31, in init_db backend_1 | user = session.exec(user_query).first() backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlmodel/orm/session.py", line 68, in exec backend_1 | results = super().execute( backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 2306, in execute backend_1 | return self._execute_internal( backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 2191, in _execute_internal backend_1 | result: Result[Any] = compile_state_cls.orm_execute_statement( backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/context.py", line 293, in orm_execute_statement backend_1 | result = conn.execute( backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1421, in execute backend_1 | return meth( backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/elements.py", line 514, in _execute_on_connection backend_1 | return connection._execute_clauseelement( backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1635, in _execute_clauseelement backend_1 | compiled_sql, extracted_params, cache_hit = elem._compile_w_cache( backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/elements.py", line 702, in _compile_w_cache backend_1 | compiled_sql = self._compiler( backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/elements.py", line 315, in _compiler backend_1 | return dialect.statement_compiler(dialect, self, **kw) backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/compiler.py", line 1424, in __init__ backend_1 | Compiled.__init__(self, dialect, statement, **kwargs) backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/compiler.py", line 865, in __init__ backend_1 | self.string = self.process(self.statement, **compile_kwargs) backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/compiler.py", line 910, in process backend_1 | return obj._compiler_dispatch(self, **kwargs) backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/visitors.py", line 141, in _compiler_dispatch backend_1 | return meth(self, **kw) # type: ignore # noqa: E501 backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/compiler.py", line 4683, in visit_select backend_1 | compile_state = select_stmt._compile_state_factory( backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/base.py", line 684, in create_for_statement backend_1 | return klass.create_for_statement(statement, compiler, **kw) backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/context.py", line 1091, in create_for_statement backend_1 | _QueryEntity.to_compile_state( backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/context.py", line 2546, in to_compile_state backend_1 | _MapperEntity( backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/context.py", line 2626, in __init__ backend_1 | entity._post_inspect backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/util/langhelpers.py", line 1252, in __get__ backend_1 | obj.__dict__[self.__name__] = result = self.fget(obj) backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/mapper.py", line 2711, in _post_inspect backend_1 | self._check_configure() backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/mapper.py", line 2388, in _check_configure backend_1 | _configure_registries({self.registry}, cascade=True) backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/mapper.py", line 4203, in _configure_registries backend_1 | _do_configure_registries(registries, cascade) backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/mapper.py", line 4244, in _do_configure_registries backend_1 | mapper._post_configure_properties() backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/mapper.py", line 2405, in _post_configure_properties backend_1 | prop.init() backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/interfaces.py", line 579, in init backend_1 | self.do_init() backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/relationships.py", line 1642, in do_init backend_1 | self._setup_entity() backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/relationships.py", line 1856, in _setup_entity backend_1 | self._clsregistry_resolve_name(argument)(), backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/clsregistry.py", line 519, in _resolve_name backend_1 | self._raise_for_name(name, err) backend_1 | File "/usr/local/lib/python3.10/site-packages/sqlalchemy/orm/clsregistry.py", line 500, in _raise_for_name backend_1 | raise exc.InvalidRequestError( backend_1 | sqlalchemy.exc.InvalidRequestError: When initializing mapper Mapper[User(user)], expression 'Signatory' failed to locate a name ('Signatory'). If this is a class name, consider adding this relationship() to the <class 'app.models.user_model.User'> class after both dependent classes have been defined. esign_backend_1 exited with code 1
问题根源
- 循环导入:
user_model.py直接导入Signatory类,导致加载User模型时,Signatory类尚未完成初始化,SQLAlchemy无法解析该类。 - 错误的表模型定义:
SignatoryBase设置了table=True,导致SQLModel尝试为基类和子类分别创建表,引发模型解析混乱。 - 模型注册时机:应用启动时可能未确保所有关联模型被加载,导致SQLAlchemy元数据中缺少
Signatory的注册信息。
修复方案
1. 修复循环导入
修改user_model.py的导入逻辑,仅在类型检查阶段导入关联模型,运行时使用字符串引用:
from datetime import datetime from typing import List, Optional, TYPE_CHECKING from sqlmodel import Field, SQLModel, Relationship # 仅类型检查时导入,避免运行时循环依赖 if TYPE_CHECKING: from app.models.signatory_model import Signatory from app.models.document_model import Document class UserBase(SQLModel): email: str = Field(index=True, unique=True) is_active: bool = True is_superuser: bool = False class User(UserBase, table=True): id: int | None = Field(default=None, primary_key=True) first_name: str last_name: str created_at: datetime = Field(default_factory=datetime.now) updated_at: datetime = Field(default_factory=datetime.now) # 保持字符串引用不变 user_as_signatory: Optional["Signatory"] = Relationship(back_populates="user") signatories: List["Signatory"] = Relationship(back_populates="creator") documents: List["Document"] = Relationship(back_populates="owner")
2. 修正表模型定义
将SignatoryBase改为非表基类,仅Signatory作为实际映射数据库的表模型:
from datetime import datetime from typing import TYPE_CHECKING, Optional, List from sqlalchemy import Enum from sqlmodel import Field, Relationship, SQLModel from app.models.document_model import Document, DocumentSignatoryLink if TYPE_CHECKING: from .user_model import User # 移除table=True,作为数据结构基类 class SignatoryBase(SQLModel): id: int = Field(default=None, primary_key=True) first_name: str last_name: str email: str created_at: datetime = Field(default_factory=datetime.now) updated_at: datetime = Field(default_factory=datetime.now) # 子类设置table=True,作为实际表模型 class Signatory(SignatoryBase, table=True): user_id: Optional[int] = Field(default=None, foreign_key="user.id") creator_id: int = Field(default=None, foreign_key="user.id") documents: List["Document"] = Relationship( back_populates="signatories", link_model=DocumentSignatoryLink, ) user: Optional["User"] = Relationship(back_populates="user_as_signatory") creator: "User" = Relationship(back_populates="signatories")
3. 确保模型全量加载
在FastAPI应用的入口文件(如main.py)中,显式导入所有模型类,确保SQLAlchemy元数据中注册所有模型:
from fastapi import FastAPI from app.core.db import engine from app.models import user_model, signatory_model, document_model # 导入所有模型 from sqlmodel import SQLModel # 创建数据库表(如果需要) SQLModel.metadata.create_all(engine) app = FastAPI() # ... 其他路由和逻辑
修复说明
- 循环导入修复:
TYPE_CHECKING是Python的常量,仅在类型检查时为True,运行时不会执行该分支的导入,彻底避免循环依赖问题。 - 表模型修正:基类仅用于共享字段定义,不应映射数据库表,子类作为实际表模型,确保SQLModel只创建一张
signatory表。 - 模型加载:显式导入所有模型可以确保SQLAlchemy在应用启动时将所有模型注册到元数据中,避免关联解析失败。
内容的提问来源于stack exchange,提问作者E.Mohammed
相关产品推荐
相关产品推荐

