单独文件中的 FastAPI / Pydantic 循环引用

FastAPI / Pydantic circular references in separate files

我喜欢在 FastAPI 中使用类似于以下内容的架构:

from __future__ import annotations
from typing import List
from pydantic import BaseModel


class Project(BaseModel):
    members: List[User]


class User(BaseModel):
    projects: List[Project]


Project.update_forward_refs()

但为了保持我的项目结构整洁,我会选择 ofc。喜欢在单独的文件中定义这些。如果不创建循环引用,我怎么能做到这一点?

使用上面的代码,FastAPI 中的模式生成工作正常,我只是不知道如何将它分离到单独的文件中。在后面的步骤中,我将不使用属性,而是使用 @propertys 来为这些对象的子类中的对象定义 getter。但是对于 OpenAPI 文档生成,我需要这个组合 - 我想。

在Python中循环依赖可能起作用的三种情况:

  • 模块顶部:import package.module
  • 模块底部:from package.module import attribute
  • 函数顶部:两者都有效

在您的情况下,第二种情况“模块底部”会有所帮助。 因为你 need to use update_forward_refs 函数来解决像这样的 pydantic 延迟注释:

# project.py
from typing import List
from pydantic import BaseModel


class Project(BaseModel):
    members: "List[User]"


from user import User
Project.update_forward_refs()
# user.py
from typing import List
from pydantic import BaseModel


class User(BaseModel):
    projects: "List[Project]"


from project import Project
User.update_forward_refs()

尽管如此,我强烈反对你故意引入循环依赖

如果我想将 models/schema 拆分成单独的文件,我将为 ProjectBase 模型和 UserBase 模型创建额外的文件,以便 Project 模型和 User 模型可以从它们继承。我会这样做:

#project_base.py
from pydantic import BaseModel

class ProjectBase(BaseModel):
    id: int
    title: str
    
    class Config:
        orm_mode=True

#user_base.py
from pydantic import BaseModel

class UserBase(BaseModel):
    id: int
    title: str
    
    class Config:
        orm_mode=True

#project.py
from typing import List
from .project_base import ProjectBase
from .user_base import UserBase

class Project(ProjectBase):
    members: List[UserBase] = []

#user.py
from typing import List
from .project_base import ProjectBase
from .user_base import UserBase

class User(UserBase):
projects: List[ProjectBase] = []

注意:对于此方法,orm_mode 必须放在 ProjectBase 和 UserBase 中,这样即使它不是字典也可以被 Project 和 User 读取

只需将所有架构 imports 放在文件底部 ,毕竟 类,然后调用 update_forward_refs().

#1/4
from __future__ import annotations # this is important to have at the top
from pydantic import BaseModel

#2/4
class A(BaseModel):
    my_x: X   # a pydantic schema from another file

class B(BaseModel):
    my_y: Y   # a pydantic schema from another file

class C(BaseModel):
    my_z: int

#3/4
from myapp.schemas.x import X   # related schemas we import after all classes
from myapp.schemas.y import Y

#4/4
A.update_forward_refs()   # tell the system that A has a related pydantic schema
B.update_forward_refs()   # tell the system that B has a related pydantic schema
                          # for C we don't need it, because C has just an integer field.

注意: 在每个具有架构导入的文件 中执行此操作。 这将使您能够进行任何组合而不会出现循环导入问题。

注 2: 人们通常将导入和 update_forward_refs() 放在每个 class 之后,然后报告它不起作用。这通常是因为如果应用程序很复杂,您不知道 import 正在调用哪个 class 以及何时调用。因此,如果你把它放在底部,你肯定每个 class 都是 'scanned' 并且对其他人可见。