mor*_*orf 2 python rest fastapi
我使用 FastAPI 一段时间了,它是一个很棒的框架。\n但是现实生活中的场景可能会令人惊讶,有时需要使用非标准方法。我有一个案例想请您帮忙。
\n有一个奇怪的外部要求,模型响应的格式应如示例中所述:
\n期望的行为:
\nGET /object/1
{status: \xe2\x80\x98success\xe2\x80\x99, data: {object: {id:\xe2\x80\x981\xe2\x80\x99, category: \xe2\x80\x98test\xe2\x80\x99 \xe2\x80\xa6}}}\nRun Code Online (Sandbox Code Playgroud)\nGET /objects
{status: \xe2\x80\x98success\xe2\x80\x99, data: {objects: [...]}}}\nRun Code Online (Sandbox Code Playgroud)\n当前行为:
\nGET/object/1将响应:
{id: 1,field1:"content",... }\nRun Code Online (Sandbox Code Playgroud)\nGET/objects/将发送对象列表,例如:
{\n [\n {id: 1,field1:"content",... },\n {id: 1,field1:"content",... },\n ...\n ]\n}\nRun Code Online (Sandbox Code Playgroud)\n您可以用任何类替换“对象”,这仅用于描述目的。
\n如何编写适合这些要求的通用响应模型?
\n我知道我可以生成包含status:str和 (取决于类)数据结构的响应模型,例如ticket:Ticket或tickets:List[Ticket]。
重点是有很多类,所以我希望有一种更Pythonic 的方法来做到这一点。
\n感谢帮助。
\n通用模型是一种模型,其中一个(或多个)字段用类型变量进行注释。因此,默认情况下未指定该字段的类型,并且必须在子类化和/或初始化期间显式指定。但该字段仍然只是一个属性,并且属性必须有一个名称。一个固定的名字。
从您的示例开始,假设这是您的模型:
{
"status": "...",
"data": {
"object": {...} # type variable
}
}
Run Code Online (Sandbox Code Playgroud)
然后我们可以根据属性的类型将该模型定义为通用模型object。
这可以使用 Pydantic 来完成,GenericModel如下所示:
from typing import Generic, TypeVar
from pydantic import BaseModel
from pydantic.generics import GenericModel
M = TypeVar("M", bound=BaseModel)
class GenericSingleObject(GenericModel, Generic[M]):
object: M
class GenericMultipleObjects(GenericModel, Generic[M]):
objects: list[M]
class BaseGenericResponse(GenericModel):
status: str
class GenericSingleResponse(BaseGenericResponse, Generic[M]):
data: GenericSingleObject[M]
class GenericMultipleResponse(BaseGenericResponse, Generic[M]):
data: GenericMultipleObjects[M]
class Foo(BaseModel):
a: str
b: int
class Bar(BaseModel):
x: float
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,GenericSingleObject反映了我们想要的泛型类型data,而就GenericSingleResponse的类型参数而言是泛型的M,GenericSingleObject这是其属性的类型data。
如果我们现在想要使用我们的通用响应模型之一,我们需要首先使用类型参数(具体模型)来指定它,例如GenericSingleResponse[Foo]。
FastAPI 可以很好地处理这个问题,并且可以生成正确的 OpenAPI 文档。JSON 架构GenericSingleResponse[Foo]如下所示:
{
"title": "GenericSingleResponse[Foo]",
"type": "object",
"properties": {
"status": {
"title": "Status",
"type": "string"
},
"data": {
"$ref": "#/definitions/GenericSingleObject_Foo_"
}
},
"required": [
"status",
"data"
],
"definitions": {
"Foo": {
"title": "Foo",
"type": "object",
"properties": {
"a": {
"title": "A",
"type": "string"
},
"b": {
"title": "B",
"type": "integer"
}
},
"required": [
"a",
"b"
]
},
"GenericSingleObject_Foo_": {
"title": "GenericSingleObject[Foo]",
"type": "object",
"properties": {
"object": {
"$ref": "#/definitions/Foo"
}
},
"required": [
"object"
]
}
}
}
Run Code Online (Sandbox Code Playgroud)
使用 FastAPI 进行演示:
from fastapi import FastAPI
app = FastAPI()
@app.get("/foo/", response_model=GenericSingleResponse[Foo])
async def get_one_foo() -> dict[str, object]:
return {"status": "foo", "data": {"object": {"a": "spam", "b": 123}}}
Run Code Online (Sandbox Code Playgroud)
向该路由发送请求会返回以下内容:
{
"status": "foo",
"data": {
"object": {
"a": "spam",
"b": 123
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果您实际上希望属性名称每次都不同,那么使用静态类型注释显然不再可能。在这种情况下,我们将不得不通过实际动态创建模型类型pydantic.create_model。
在这种情况下,通用性实际上已经没有意义了,因为无论如何类型安全都已经不可能了,至少对于data模型来说是这样。我们仍然可以选择定义一个GenericResponse模型,我们可以通过动态生成的模型来指定该模型,但这将使每个静态类型检查器发疯,因为我们将使用类型变量。尽管如此,它仍可能使代码更加简洁。
我们只需要定义一个算法来导出模型参数:
from typing import Any, Generic, Optional, TypeVar
from pydantic import BaseModel, create_model
from pydantic.generics import GenericModel
M = TypeVar("M", bound=BaseModel)
def create_data_model(
model: type[BaseModel],
plural: bool = False,
custom_plural_name: Optional[str] = None,
**kwargs: Any,
) -> type[BaseModel]:
data_field_name = model.__name__.lower()
if plural:
model_name = f"Multiple{model.__name__}"
if custom_plural_name:
data_field_name = custom_plural_name
else:
data_field_name += "s"
kwargs[data_field_name] = (list[model], ...) # type: ignore[valid-type]
else:
model_name = f"Single{model.__name__}"
kwargs[data_field_name] = (model, ...)
return create_model(model_name, **kwargs)
class GenericResponse(GenericModel, Generic[M]):
status: str
data: M
Run Code Online (Sandbox Code Playgroud)
Foo使用与Bar之前相同的示例:
class Foo(BaseModel):
a: str
b: int
class Bar(BaseModel):
x: float
SingleFoo = create_data_model(Foo)
MultipleBar = create_data_model(Bar, plural=True)
Run Code Online (Sandbox Code Playgroud)
这也可以按预期使用 FastAPI,包括自动生成的架构/文档:
from fastapi import FastAPI
app = FastAPI()
@app.get("/foo/", response_model=GenericResponse[SingleFoo]) # type: ignore[valid-type]
async def get_one_foo() -> dict[str, object]:
return {"status": "foo", "data": {"foo": {"a": "spam", "b": 123}}}
@app.get("/bars/", response_model=GenericResponse[MultipleBar]) # type: ignore[valid-type]
async def get_multiple_bars() -> dict[str, object]:
return {"status": "bars", "data": {"bars": [{"x": 3.14}, {"x": 0}]}}
Run Code Online (Sandbox Code Playgroud)
输出基本上与第一种方法相同。
您必须看看哪一种更适合您。由于动态键/字段名称,我发现第二个选项非常奇怪。但也许这正是您出于某种原因所需要的。
| 归档时间: |
|
| 查看次数: |
4209 次 |
| 最近记录: |