FastAPI - 带有模块化导入的“TypeError: issubclass() arg 1 必须是一个类”

Fel*_*lix 9 python sqlalchemy pydantic fastapi sqlmodel

当使用 FastAPI 和 SQLModel 进行模块化导入时,如果打开 /docs,我会收到以下错误:

类型错误:issubclass() arg 1 必须是一个类

  • Python 3.10.6
  • pydantic 1.10.2
  • 快速API 0.85.2
  • sqlmodel 0.0.8
  • macOS 12.6

这是一个可重现的示例。

用户.py

from typing import List, TYPE_CHECKING, Optional
from sqlmodel import SQLModel, Field

if TYPE_CHECKING:
    from item import Item

class User(SQLModel):
    id: int = Field(default=None, primary_key=True)
    age: Optional[int]
    bought_items: List["Item"] = []
Run Code Online (Sandbox Code Playgroud)

项目.py

from sqlmodel import SQLModel, Field

class Item(SQLModel):
    id: int = Field(default=None, primary_key=True)
    price: float
    name: str
Run Code Online (Sandbox Code Playgroud)

主要.py

from fastapi import FastAPI

from user import User

app = FastAPI()

@app.get("/", response_model=User)
def main():
    return {"message": "working just fine"}
Run Code Online (Sandbox Code Playgroud)

我按照 sqlmodel https://sqlmodel.tiangolo.com/tutorial/code-struction/#make-circular-imports-work的教程进行操作。如果我将模型放在同一个文件中,一切都会正常。由于我的实际模型非常复杂,因此我需要依赖模块化导入。

追溯:

Traceback (most recent call last):
  File "/Users/felix/opt/anaconda3/envs/fastapi_test/lib/python3.10/site-packages/fastapi/utils.py", line 45, in get_model_definitions
    m_schema, m_definitions, m_nested_models = model_process_schema(
  File "pydantic/schema.py", line 580, in pydantic.schema.model_process_schema
  File "pydantic/schema.py", line 621, in pydantic.schema.model_type_schema
  File "pydantic/schema.py", line 254, in pydantic.schema.field_schema
  File "pydantic/schema.py", line 461, in pydantic.schema.field_type_schema
  File "pydantic/schema.py", line 847, in pydantic.schema.field_singleton_schema
  File "pydantic/schema.py", line 698, in pydantic.schema.field_singleton_sub_fields_schema
  File "pydantic/schema.py", line 526, in pydantic.schema.field_type_schema
  File "pydantic/schema.py", line 921, in pydantic.schema.field_singleton_schema
  File "/Users/felix/opt/anaconda3/envs/fastapi_test/lib/python3.10/abc.py", line 123, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
TypeError: issubclass() arg 1 must be a class
Run Code Online (Sandbox Code Playgroud)

Dan*_*erg 11

长话短说

您需要User.update_forward_refs(Item=Item)在 OpenAPI 设置之前调用。


解释

所以,这实际上有点棘手,我还不太确定为什么文档中没有提到这一点。也许我错过了一些东西。反正...

如果您跟踪回溯,您将看到发生错误,因为在函数的第 921 行中pydantic.schema执行field_singleton_schema了检查以查看issubclass(field_type, BaseModel)此时field_type是否实际上不是一个type实例。

一些调试表明,当User生成模型的架构并bought_items处理字段时,就会发生这种情况。此时注释已被处理,并且 的类型参数List仍然是对的前向引用Item。这意味着它不是实际的Item类本身。这就是传递给issubclass并导致错误的内容。

在处理 Pydantic 模型之间的递归或循环关系时,这是一个相当常见的问题,这就是为什么他们如此友善地为此提供了一种特殊的方法。文档的推迟注释部分对此进行了解释。update_forward_refs顾名思义,该方法是为了解决前向引用。

在这种情况下,棘手的是您需要为其提供更新的命名空间来解析引用Item。为此,您实际上需要在范围内拥有真正的Item类,因为这就是该名称空间中需要的内容。你在哪里做并不重要。例如,您可以将User模型导入到item模块中并在那里调用它(显然在 的定义下面Item):

from sqlmodel import SQLModel, Field

from .user import User

class Item(SQLModel):
    id: int = Field(default=None, primary_key=True)
    price: float
    name: str

User.update_forward_refs(Item=Item)
Run Code Online (Sandbox Code Playgroud)

但该调用需要在尝试设置该架构之前进行。因此,您至少需要item在模块中导入该模块main

from fastapi import FastAPI

from .user import User
from . import item

api = FastAPI()

@api.get("/", response_model=User)
def main():
    return {"message": "working just fine"}
Run Code Online (Sandbox Code Playgroud)

此时,拥有一个仅包含模型模块的子包并将所有模块导入__init__.py该子包中可能会更简单。

User.update_forward_refs我给出将调用放在定义下面的示例的原因Item是,当您实际上具有循环关系时,通常会发生这些情况,即,例如,如果您的Item类有一个users字段,其类型为list[User]. 然后你无论如何都必须导入User那里,并且可能只是更新那里的引用。

您的具体示例中,您实际上没有任何循环依赖项,因此严格来说不需要转义TYPE_CHECKING。您可以简单地from .item import Item在内部执行user.py并将实际的类作为bought_items: list[Item]. 但我假设您简化了实际用例,只是忘记了包含循环依赖项。


也许我错过了一些东西,这里的其他人可以找到一种调用方式,而update_forward_refs 无需明确提供Item,但这种方式绝对应该有效。