[英]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
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
You need to call User.update_forward_refs(Item=Item)
before the OpenAPI setup.您需要在 OpenAPI 设置之前调用User.update_forward_refs(Item=Item)
。
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.