简体   繁体   English

FastAPI - “TypeError: issubclass() arg 1 must be a class” 带有模块化导入

[英]FastAPI - "TypeError: issubclass() arg 1 must be a class" with modular imports

When working with modular imports with FastAPI and SQLModel, I am getting the following error if I open /docs:使用 FastAPI 和 SQLModel 进行模块化导入时,如果我打开 /docs,我会收到以下错误:

TypeError: issubclass() arg 1 must be a class TypeError: issubclass() arg 1 必须是 class

  • Python 3.10.6 Python 3.10.6
  • pydantic 1.10.2狂妄自大的 1.10.2
  • fastapi 0.85.2 fastapi 0.85.2
  • sqlmodel 0.0.8 sql模型0.0.8
  • macOS 12.6 macOS 12.6

Here is a reproducible example.这是一个可重现的例子。

user.py用户.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"] = []

item.py项目.py

from sqlmodel import SQLModel, Field

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

main.py主程序

from fastapi import FastAPI

from user import User

app = FastAPI()

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

I followed along the tutorial from sqlmodel https://sqlmodel.tiangolo.com/tutorial/code-structure/#make-circular-imports-work .我遵循了 sqlmodel https://sqlmodel.tiangolo.com/tutorial/code-structure/#make-circular-imports-work中的教程。 If I would put the models in the same file, it all works fine.如果我将模型放在同一个文件中,一切正常。 As my actual models are quite complex, I need to rely on the modular imports though.由于我的实际模型非常复杂,因此我需要依赖模块化导入。

Traceback:追溯:

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

TL;DR长话短说

You need to call User.update_forward_refs(Item=Item) before the OpenAPI setup.您需要在 OpenAPI 设置之前调用User.update_forward_refs(Item=Item)


Explanation解释

So, this is actually quite a bit trickier and I am not quite sure yet, why this is not mentioned in the docs.所以,这实际上有点棘手,我还不太确定,为什么文档中没有提到这一点。 Maybe I am missing something.也许我错过了什么。 Anyway...反正...

If you follow the traceback, you'll see that the error occurs because in line 921 of pydantic.schema in the field_singleton_schema function a check is performed to see if issubclass(field_type, BaseModel) and at that point field_type is not in fact a type instance.如果您按照回溯,您会看到错误发生是因为在pydantic.schema的第 921 行中的field_singleton_schema function 执行检查以查看issubclass(field_type, BaseModel)并且此时field_type实际上不是type实例。

A bit of debugging reveals that this occurs, when the schema for the User model is being generated and the bought_items field is being processed.一些调试表明,当生成User model 的模式并处理bought_items字段时,会发生这种情况。 At that point the annotation is processed and the type argument for List is still aforward reference to Item .那时注释被处理并且List的类型参数仍然是对Item前向引用 Meaning it is not the actual Item class itself.这意味着它不是实际的Item class 本身。 And that is what is passed to issubclass and causes the error.这就是传递给issubclass并导致错误的原因。

This is a fairly common problem, when dealing with recursive or circular relationships between Pydantic models, which is why they were so kind to provide a special method just for that.在处理 Pydantic 模型之间的递归或循环关系时,这是一个相当普遍的问题,这就是为什么他们非常友好地为此提供了一种特殊的方法。 It is explained in the Postponed annotations section of the documentation.它在文档的推迟注释部分进行了解释。 The method is update_forward_refs and as the name suggests, it is there to resolve forward references.该方法是update_forward_refs ,顾名思义,它用于解析前向引用。

What is tricky in this case, is that you need to provide it with an updated namespace to resolve the Item reference.在这种情况下棘手的是,您需要为其提供更新的命名空间以解析Item引用。 To do that you need to actually have the real Item class in scope because that is what needs to be in that namespace.为此,您实际上需要在 scope 中拥有真正的Item class,因为这是该命名空间中需要的内容。 Where you do it does not really matter.你在哪里做并不重要。 You could for example import User model into your item module and call it there (obviously below the definition of Item ):例如,您可以将User model 导入您的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)

But that call needs to happen before an attempt is made to set up that schema.但是该调用需要在尝试设置该模式之前发生。 Thus you'll at least need to import the item module in your main module:因此,您至少需要在main模块中导入item模块:

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"}

At that point it is probably simpler to have a sub-package with just the model modules and import all of them in the __init__.py of that sub-package.到那时,拥有一个仅包含 model 模块的子包并将所有这些模块导入该子包的__init__.py中可能会更简单。

The reason I gave the example of putting the User.update_forward_refs call in below your Item definition is that these situations typically occur, when you actually have a circular relationship, ie if your Item class had a users field for example, which was typed as list[User] .我给出将User.update_forward_refs调用放在Item定义下方的示例的原因是,当您实际具有循环关系时,这些情况通常会发生,即如果您的Item class 有一个users字段,例如,它被键入为list[User] Then you'd have to import User there anyway and might as well just update the references there.然后你无论如何都必须在那里导入User并且可能只更新那里的引用。

In your specific example, you don't actually have any circular dependencies, so there is strictly speaking no need for the TYPE_CHECKING escape.您的具体示例中,您实际上没有任何循环依赖关系,因此严格来说不需要TYPE_CHECKING转义。 You can simply do from.item import Item inside user.py and put the actual class in your annotation as bought_items: list[Item] .你可以简单地做from.item import Item inside user.py并将实际的 class 作为bought_items: list[Item]放在你的注释中。 But I assume you simplified the actual use case and simply forgot to include the circular dependency.但我假设您简化了实际用例,只是忘记了包含循环依赖。


Maybe I am missing something and someone else here can find a way to call update_forward_refs without the need to provide Item explicitly, but this way should definitely work.也许我遗漏了一些东西,这里的其他人可以找到一种方法来调用update_forward_refs无需显式提供Item ,但这种方式应该绝对有效。

For anyone ending up here who (just like me) got the same error but couldn't resolve it using the solution above, my script looked like this.对于最终来到这里的任何人(就像我一样)遇到同样的错误但无法使用上面的解决方案解决它,我的脚本看起来像这样。 It seems that SQLModel relies on the pydantic.BaseModel so this solution also applies here.似乎SQLModel依赖于pydantic.BaseModel所以这个解决方案也适用于此。

from pydantic import BaseModel

class Model(BaseModel):
    values: list[int, ...]

class SubModel(Model):
    values = list[int, int, int]

It took me a long time to realize what my mistake was, but in SubModel I used = (assignment) whereas I should have used : (type hint).我花了很长时间才意识到我的错误是什么,但在SubModel中我使用了= (赋值),而我应该使用: (类型提示)。

The strangest thing was that it did work in a docker container (Linux) but not locally (Windows).最奇怪的是,它确实在 docker 容器 (Linux) 中工作,但在本地 (Windows) 中不工作。 Also, mypy did not pick up on this.另外,mypy 没有注意到这一点。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 TypeError: issubclass() arg 1 必须是 Django 测试中的一个类 - TypeError: issubclass() arg 1 must be a class in Django tests 类型错误:issubclass() arg 2 必须是一个类或类的元组 - TypeError: issubclass() arg 2 must be a class or tuple of classes 在 pydantic.validators.find_validators TypeError: issubclass() arg 1 must be a class - in pydantic.validators.find_validators TypeError: issubclass() arg 1 must be a class 错误 Django cms 返回 TypeError: issubclass() arg 1 must be a class - Error Django cms return TypeError: issubclass() arg 1 must be a class TypeError: issubclass() arg 1 必须是 class,而我很确定 arg 1 是 class - TypeError: issubclass() arg 1 must be a class, while I am quite sure arg 1 is a class assertRaises中的“ issubclass()arg 2必须是类或元组” - “issubclass() arg 2 must be a class or tuple” in assertRaises 测试套件Python-issubclass()arg 1必须是一个类 - Test Suite Python - issubclass() arg 1 must be a class python-openstackclient issubclass()arg 1必须是一个类 - python-openstackclient issubclass() arg 1 must be a class 有人可以解释一下这个错误到底是什么意思,TypeError: issubclass() arg 1 must be a class - Can someone explain what exactly this error means,TypeError: issubclass() arg 1 must be a class 自定义多输入原始错误返回“TypeError: issubclass() arg 1 must be a class” - Custom Multiple Input Primitive Bug returns “TypeError: issubclass() arg 1 must be a class”
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM