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

Posted

技术标签:

【中文标题】单独文件中的 FastAPI / Pydantic 循环引用【英文标题】:FastAPI / Pydantic circular references in separate files 【发布时间】:2020-12-04 19:42:22 【问题描述】:

我希望在 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()

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

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

【问题讨论】:

请帮我理解清楚,因为我知道您想将class User 存储在models_user.py 中,而您想将class Project 存储在models_project.py 中,对吗? 是的,计划是这样的 【参考方案1】:

循环依赖可能在 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()

不过,我强烈建议您不要有意引入循环依赖项

【讨论】:

嗨,亚历克斯,感谢您的回答和输入 - 我同意我需要找到另一种方法来进行建模!【参考方案2】:

如果我想将模型/模式拆分为单独的文件,我将为 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 中,这样即使不是 dict 也可以被 Project 和 User 读取

【讨论】:

【参考方案3】:

只需将所有架构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 都会被“扫描”并对其他人可见。

【讨论】:

以上是关于单独文件中的 FastAPI / Pydantic 循环引用的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 pydantic/fastapi 接受具有数组\单个项目的单个路由对象?

FastAPI/Pydantic 接受任意发布请求正文?

如何在 FastAPI 中使用 Pydantic 模型和表单数据?

[FastAPI-14]pydantic多个请求体

FastAPI Web框架 [Pydantic]

FastAPI Web框架 [Pydantic]