You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FastAPI Users关联模型路由因异步SQLAlchemy懒加载失败

问题描述

在FastAPI项目中使用FastAPI Users库时,为User模型添加与Post模型的一对多关联后,库内置的注册、获取用户等路由出现异常。具体表现为:当UserRead Pydantic模型包含posts字段时,调用POST /auth/register会触发错误:

Error extracting attribute: MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place?

移除UserRead中的posts字段后,路由恢复正常。尝试为User_DB的所有关联设置预加载,但担心这种方式会强制预加载所有关联属性,且FastAPI Users文档未提及该方案,因此寻求在FastAPI Users中正确使用关联关系的方法。

涉及代码

SQLAlchemy模型

class Mixin(MappedAsDataclass, SQLAlchemyBaseUserTable[int]):
    pass

class User_DB(BaseModel_DB, Mixin):
    __tablename__ = "user_table"
    id: Mapped[int] = mapped_column(primary_key=True, init=False)  # type: ignore
    firstname: Mapped[str] = mapped_column()
    posts: Mapped[list["Post_DB"]] = relationship(init=False)
class Post_DB(BaseModel_DB):
    __tablename__ = "post_table"

    id: Mapped[int] = mapped_column(primary_key=True, init=False)
    name: Mapped[str] = mapped_column()
    user_id: Mapped[int] = mapped_column(ForeignKey("user_table.id"))

Pydantic Schema

class UserRead(fastapi_users_schemas.BaseUser[int], BaseSchema):
    firstname: str
    email: str
    posts: list[PostRead]  # 注释此字段路由可正常工作

解决方案

1. 拆分Pydantic Schema(推荐)

创建两套User相关的Schema:一套用于FastAPI Users内置路由(不包含关联字段),另一套用于需要返回关联数据的自定义路由。这样既能保留内置路由的正常功能,又能在需要时返回关联数据。

修改后的Schema代码

# 基础Schema,用于内置路由
class UserReadBase(fastapi_users_schemas.BaseUser[int], BaseSchema):
    firstname: str
    email: str

# 带关联字段的Schema,用于自定义路由
class UserReadWithPosts(UserReadBase):
    posts: list[PostRead]

初始化FastAPI Users时使用基础Schema

# 初始化用户管理器和认证路由
user_manager = UserManager(user_db, auth_backend)
app.include_router(
    fastapi_users.get_auth_router(auth_backend),
    prefix="/auth/jwt",
    tags=["auth"],
)
app.include_router(
    fastapi_users.get_register_router(UserReadBase, UserCreate),
    prefix="/auth",
    tags=["auth"],
)

自定义路由返回带关联的用户数据

在自定义路由中显式预加载posts关联:

@app.get("/users/{user_id}/with-posts", response_model=UserReadWithPosts)
async def get_user_with_posts(user_id: int, session: AsyncSession = Depends(get_async_session)):
    user = await session.get(User_DB, user_id, options=[selectinload(User_DB.posts)])
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user

2. 自定义数据库适配器,按需预加载关联

继承FastAPI Users的AsyncSQLAlchemyUserDatabase,重写获取用户的方法,在需要时预加载指定关联。这种方式可以让内置路由也能返回关联数据,但需要谨慎处理,避免不必要的性能开销。

自定义数据库适配器代码

from fastapi_users_db_sqlalchemy import AsyncSQLAlchemyUserDatabase
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

class CustomAsyncUserDatabase(AsyncSQLAlchemyUserDatabase[int, User_DB]):
    def __init__(self, session: AsyncSession, user_model: Type[User_DB], preload_options=None):
        super().__init__(session, user_model)
        self.preload_options = preload_options or []

    async def get(self, user_id: int) -> Optional[User_DB]:
        statement = select(self.user_model).where(self.user_model.id == user_id).options(*self.preload_options)
        result = await self.session.execute(statement)
        return result.scalar_one_or_none()

# 初始化时传入预加载选项
user_db = CustomAsyncUserDatabase(session, User_DB, preload_options=[selectinload(User_DB.posts)])

3. 调整SQLAlchemy关系配置(不推荐)

可以将关系设置为lazy="selectin",但这种方式会在访问关联字段时自动触发异步查询,虽然能解决错误,但可能导致不可控的IO操作,增加性能隐患,不建议在生产环境使用。

class User_DB(BaseModel_DB, Mixin):
    # ... 其他字段
    posts: Mapped[list["Post_DB"]] = relationship(init=False, lazy="selectin")

内容的提问来源于stack exchange,提问作者Oskar Watsfeldt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 18:33:10