SQLAlchemy+FastAPI:如何实现Owner关联猫狗的合并宠物结构返回?
问题描述
我一直在StackOverflow上查找该问题的解决方案,但始终未能找到(若有相关链接欢迎提供),同时也查阅了SQLAlchemy、FastAPI和Pydantic的官方文档。
我正在使用SQLAlchemy + FastAPI + VueJS技术栈搭建网站,遇到了返回特定JSON结构的问题,具体细节如下:
数据库表结构(model.py)
from sqlalchemy import Column, Integer, ForeignKey from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship Base = declarative_base() class Owner(Base): __tablename__ = "owner" id = Column(Integer, primary_key=True) # 其他Owner表字段 dog_relationship = relationship("Dog", backref="owner") cat_relationship = relationship("Cat", backref="owner") class Dog(Base): __tablename__="dog" id = Column(Integer, primary_key=True) # 与Cat表相同的字段,额外包含专属字段 ownerID = Column(Integer, ForeignKey('owner.id'), index=True) class Cat(Base): __tablename__="cat" id = Column(Integer, primary_key=True) # 与Dog表相同的字段,无Dog的专属字段 ownerID = Column(Integer, ForeignKey('owner.id'), index=True)
注:已建立Owner与Dog、Cat的一对多关联关系。
Pydantic Schema配置(schemas.py)
from pydantic import BaseModel, Field, Annotated from typing import Union, Optional, Literal, List class OwnerBase(BaseModel): # 匹配Owner表字段 class OwnerReturn(OwnerBase): id : int class Config: from_attributes = True class DogBase(BaseModel): # 匹配Dog表字段 pet_type: Literal["Dog"] ownerID : int class DogReturn(DogBase): id: int owner : OwnerReturn class Config: from_attributes = True class CatBase(BaseModel): # 匹配Cat表字段 pet_type: Literal["Cat"] ownerID : int class CatReturn(CatBase): id: int owner : OwnerReturn class Config: from_attributes = True # 用于合并Dog和Cat的联合类型 CombinedDogCats = Annotated[Union[DogReturn, CatReturn], Field(discriminator="pet_type")] # 最初的返回模型,不符合PrimeVue表格需求 class OwnerReturnWithDogsCats(OwnerReturn): dog_relationship : list[DogBase] cat_relationship : list[CatBase] # 合并后的宠物模型,Cat的Dog专属字段返回null class CombinedPets(BaseModel): # 包含Dog所有字段,其中Dog专属字段设为Optional[int] = None id: int # 其他公共字段 pet_type: Literal["Dog", "Cat"] ownerID: int # Dog专属字段,Cat返回null dog_specific_field1: Optional[int] = None dog_specific_field2: Optional[int] = None
接口定义
from fastapi import APIRouter, Depends, Request from sqlalchemy.orm import Session from .schemas import CombinedPets from .models import Owner from .database import get_db router = APIRouter() @router.get('/', response_model=list[CombinedPets]) def get_CombinedPets(request: Request, db:Session = Depends(get_db)): # 查询所有Owner记录 owners = db.query(Owner).all() dogs_cats_list: list[CombinedPets] = [] # 遍历Owner,收集所有宠物 for owner in owners: for dog in owner.dog_relationship: dogs_cats_list.append(dog) for cat in owner.cat_relationship: dogs_cats_list.append(cat) return dogs_cats_list
当前返回与期望结构
- 普通查询接口返回:
[ { "owner info", "dog_relationship": [{"dog info"}], "cat_relationship": [{"cat info"}] } ]
- get_CombinedPets接口返回:
[ {"dog info"}, {"cat info"}, ... ]
- 期望返回结构:
[ { "owner info", "combinedPets": [ {"pet_type": "Dog", "dog info", "dog专属字段": 值}, {"pet_type": "Cat", "cat info", "dog专属字段": null}, ... ] }, ... ]
核心疑问
CombinedPets模型的结构符合单条宠物的需求,但Owner表并无名为combinedPets的关联关系。请问是否可通过SQLAlchemy + FastAPI的配置实现该需求?还是需要手动构造包含combinedPets的字典?手动构造时担心无法准确区分宠物所属的Owner。
解决方案
方式一:通过Pydantic模型直接构造(推荐)
不需要修改SQLAlchemy模型,直接在Pydantic中定义包含combinedPets的返回模型,然后手动组合数据:
- 定义新的返回模型:
class OwnerWithCombinedPets(OwnerReturn): combinedPets: List[CombinedPets] class Config: from_attributes = True
- 修改接口实现:
@router.get('/', response_model=list[OwnerWithCombinedPets]) def get_owners_with_combined_pets(request: Request, db:Session = Depends(get_db)): owners = db.query(Owner).all() result = [] for owner in owners: # 组合当前Owner的所有宠物 combined_pets = [] # 转换Dog为CombinedPets for dog in owner.dog_relationship: combined_pets.append(CombinedPets.from_orm(dog)) # 转换Cat为CombinedPets,自动填充Dog专属字段为null for cat in owner.cat_relationship: combined_pets.append(CombinedPets.from_orm(cat)) # 构造Owner返回对象 owner_data = OwnerWithCombinedPets.from_orm(owner) owner_data.combinedPets = combined_pets result.append(owner_data) return result
这种方式完全通过Pydantic的from_orm方法转换ORM对象,确保宠物与所属Owner的关联准确——所有宠物都是从当前Owner的关联关系中直接获取,不会混淆所属关系。
方式二:SQLAlchemy混合属性(hybrid_property)
可以在Owner模型中添加一个混合属性,直接返回合并后的宠物列表:
- 修改Owner模型:
from sqlalchemy.ext.hybrid import hybrid_property class Owner(Base): __tablename__ = "owner" id = Column(Integer, primary_key=True) # 其他Owner表字段 dog_relationship = relationship("Dog", backref="owner") cat_relationship = relationship("Cat", backref="owner") @hybrid_property def combinedPets(self): return self.dog_relationship + self.cat_relationship
- 定义对应的Pydantic模型:
class OwnerWithCombinedPets(OwnerReturn): combinedPets: List[CombinedPets] class Config: from_attributes = True
- 简化接口实现:
@router.get('/', response_model=list[OwnerWithCombinedPets]) def get_owners_with_combined_pets(request: Request, db:Session = Depends(get_db)): owners = db.query(Owner).all() return [OwnerWithCombinedPets.from_orm(owner) for owner in owners]
这种方式通过SQLAlchemy的混合属性让Owner模型直接拥有combinedPets属性,Pydantic可以自动识别并转换,代码更简洁。需要注意的是,这个混合属性是在Python层面合并的列表,不会影响数据库查询逻辑。
内容的提问来源于stack exchange,提问作者Yoili Youth
相关产品推荐
相关产品推荐

