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

FastAPI+SQLAlchemy如何返回带嵌套子分类的ORM模型结果

问题根源分析

当前返回结果不符合嵌套结构的核心原因:

  • 查询仅选取部分字段,未加载child关联关系,且返回原始行数据而非模型实例
  • SQLAlchemy自关联关系的remote_side配置错误,无法正确关联子分类
  • Pydantic模型的child字段类型定义错误,不支持递归嵌套结构
  • 查询未过滤根分类,导致子分类被当作独立条目返回

分步解决方案

1. 修复SQLAlchemy自关联关系配置

修正remote_side指向父分类的id字段,确保自关联逻辑正确:

from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship

from database._types import created_at, uuid, uuidpk
from database.engine import Base


class CategoriesModel(Base):
    __tablename__ = "categories"

    id: Mapped[uuidpk]
    parent_id: Mapped[uuid | None] = mapped_column(ForeignKey("categories.id"))
    name: Mapped[str]
    created_at: Mapped[created_at]

    child: Mapped[list["CategoriesModel"]] = relationship(
        "CategoriesModel",
        remote_side=[id],  # 修正为指向父分类的id字段
        uselist=True,
        lazy="selectin"  # 默认懒加载方式,也可在查询时指定
    )

2. 修改仓库查询逻辑,加载嵌套关系并过滤根分类

查询完整模型实例,预加载子分类,仅返回根分类(parent_id为None):

from sqlalchemy import select
from sqlalchemy.orm import selectinload
from sqlalchemy.ext.asyncio import AsyncSession

class SQLAlchemyRepository[T]:
    def __init__(self, session: AsyncSession) -> None:
        self._model = get_args(self.__orig_bases__[0])[0]
        self._session = session


class CategoriesRepository(SQLAlchemyRepository[CategoriesModel]):
    async def get_many(self) -> list[CategoriesModel]:
        stmt = (
            select(self._model)
            .where(self._model.parent_id.is_(None))  # 仅查询根分类
            .order_by(self._model.name)
            .options(selectinload(self._model.child))  # 预加载子分类,避免N+1查询
        )
        res = await self._session.execute(stmt)
        return res.scalars().all()  # 返回模型实例列表,而非Row对象

3. 修正Pydantic模型的递归嵌套定义

让child字段递归引用自身,支持嵌套结构序列化:

from datetime import datetime
from pydantic import BaseModel
from _types import uuid


class SQLAlchemyMappedModel(BaseModel):
    class Config:
        from_attributes = True


class CategoryOut(SQLAlchemyMappedModel):
    id: uuid
    name: str
    created_at: datetime
    child: list["CategoryOut"] | None = None  # 递归引用自身,支持多层嵌套

# 解决递归引用的类型解析问题
CategoryOut.model_rebuild()

4. 调整FastAPI接口(优化类型匹配)

显式指定响应模型,移除不必要的提交操作:

from fastapi import APIRouter, Depends, Annotated
from your_uow_module import IUnitOfWork, get_uow
from your_pydantic_module import CategoryOut

router = APIRouter()

@router.get("/", summary="Categories", response_model=list[CategoryOut])
async def get_categories(
    uow: Annotated[IUnitOfWork, Depends(get_uow)],
):
    async with uow:
        res = await uow.categories.get_many()
    return res

验证效果

修改完成后,接口将返回符合预期的嵌套JSON结构:根分类包含对应的子分类,无子分类的条目child字段为None。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 19:32:01