设置:
# Pydantic Models
class TMDB_Category(BaseModel):
name: str = Field(alias="strCategory")
description: str = Field(alias="strCategoryDescription")
class TMDB_GetCategoriesResponse(BaseModel):
categories: list[TMDB_Category]
@router.get(path="category", response_model=TMDB_GetCategoriesResponse)
async def get_all_categories():
async with httpx.AsyncClient() as client:
response = await client.get(Endpoint.GET_CATEGORIES)
return TMDB_GetCategoriesResponse.parse_obj(response.json())
问题:
创建响应时使用别名,我想避免使用它。我只需要这个别名来正确映射传入数据,但在返回响应时,我想使用实际的字段名称。
实际反应:
{
"categories": [
{
"strCategory": "Beef",
"strCategoryDescription": "Beef is ..."
},
{
"strCategory": "Chicken",
"strCategoryDescription": "Chicken is ..."
}
}
预期回复:
{
"categories": [
{
"name": "Beef",
"description": "Beef is ..."
},
{
"name": "Chicken",
"description": "Chicken is ..."
}
}
更新(2023-10-07):检查问题中的评论以获取其他答案,并在同一问题中查看 pydantic 2.0 或更高版本的此答案。
切换别名和字段名称并使用
allow_population_by_field_name
模型配置选项:
class TMDB_Category(BaseModel):
strCategory: str = Field(alias="name")
strCategoryDescription: str = Field(alias="description")
class Config:
allow_population_by_field_name = True
让别名配置您要返回的字段的名称,但启用
allow_population_by_field_name
以便能够解析使用不同字段名称的数据。
使用配置选项
by_alias
。
from fastapi import FastAPI, Path, Query
from pydantic import BaseModel, Field
app = FastAPI()
class Item(BaseModel):
name: str = Field(..., alias="keck")
@app.post("/item")
async def read_items(
item: Item,
):
return item.dict(by_alias=False)
鉴于要求:
{
"keck": "string"
}
这将会返回
{
"name": "string"
}
另一种选择(可能不会那么流行)是使用除pydantic
之外的反序列化库。例如,Dataclass Wizard 库就是支持这一特定用例的库。如果您需要
Field(alias=...)
提供的相同往返行为,可以将
all
参数传递给
json_field
函数。请注意,使用这样的库,您确实会失去执行完整类型验证的能力,这可以说是 pydantic 的最大优势之一;但它确实以与 pydantic 类似的方式执行类型转换。我觉得验证不那么重要还有几个原因,我在下面列出。
我认为数据验证是很好的原因 总体特点:
dataclass-wizard 库的上述用例的简单示例(依赖于使用 dataclasses
而不是 pydantic 模型):
from dataclasses import dataclass
from dataclass_wizard import JSONWizard, json_field
@dataclass
class TMDB_Category:
name: str = json_field('strCategory')
description: str = json_field('strCategoryDescription')
@dataclass
class TMDB_GetCategoriesResponse(JSONWizard):
categories: list[TMDB_Category]
运行该代码的代码如下所示:
input_dict = {
"categories": [
{
"strCategory": "Beef",
"strCategoryDescription": "Beef is ..."
},
{
"strCategory": "Chicken",
"strCategoryDescription": "Chicken is ..."
}
]
}
c = TMDB_GetCategoriesResponse.from_dict(input_dict)
print(repr(c))
# TMDB_GetCategoriesResponse(categories=[TMDB_Category(name='Beef', description='Beef is ...'), TMDB_Category(name='Chicken', description='Chicken is ...')])
print(c.to_dict())
# {'categories': [{'name': 'Beef', 'description': 'Beef is ...'}, {'name': 'Chicken', 'description': 'Chicken is ...'}]}
衡量性能
from dataclasses import dataclass
from timeit import timeit
from pydantic import BaseModel, Field
from dataclass_wizard import JSONWizard, json_field
# Pydantic Models
class Pydantic_TMDB_Category(BaseModel):
name: str = Field(alias="strCategory")
description: str = Field(alias="strCategoryDescription")
class Pydantic_TMDB_GetCategoriesResponse(BaseModel):
categories: list[Pydantic_TMDB_Category]
# Dataclasses
@dataclass
class TMDB_Category:
name: str = json_field('strCategory', all=True)
description: str = json_field('strCategoryDescription', all=True)
@dataclass
class TMDB_GetCategoriesResponse(JSONWizard):
categories: list[TMDB_Category]
# Input dict which contains sufficient data for testing (100 categories)
input_dict = {
"categories": [
{
"strCategory": f"Beef {i * 2}",
"strCategoryDescription": "Beef is ..." * i
}
for i in range(100)
]
}
n = 10_000
print('=== LOAD (deserialize)')
print('dataclass-wizard: ',
timeit('c = TMDB_GetCategoriesResponse.from_dict(input_dict)',
globals=globals(), number=n))
print('pydantic: ',
timeit('c = Pydantic_TMDB_GetCategoriesResponse.parse_obj(input_dict)',
globals=globals(), number=n))
c = TMDB_GetCategoriesResponse.from_dict(input_dict)
pydantic_c = Pydantic_TMDB_GetCategoriesResponse.parse_obj(input_dict)
print('=== DUMP (serialize)')
print('dataclass-wizard: ',
timeit('c.to_dict()',
globals=globals(), number=n))
print('pydantic: ',
timeit('pydantic_c.dict()',
globals=globals(), number=n))
以及基准测试结果(在 Mac OS Big Sur、Python 3.9.0 上测试):
=== LOAD (deserialize)
dataclass-wizard: 1.742989194
pydantic: 5.31538175
=== DUMP (serialize)
dataclass-wizard: 2.300118940
pydantic: 5.582638598
在他们的文档中,pydantic
声称自己是最快的库,但证明事实并非如此是相当简单的。正如您所看到的,对于上述数据集,
pydantic
在反序列化和序列化过程中大约慢了 2 倍。值得注意的是,
pydantic
已经相当快了。
免责声明:我是该库的创建者(和维护者)。
alias
才能拥有
validation_alias
。
class TMDB_Category(BaseModel):
name: str = Field(validation_alias="strCategory")
description: str = Field(validation_alias="strCategoryDescription")
可以使用 serialization_alias
设置序列化别名。文档。
from pydantic import BaseModel, Field
class TMDB_Category(BaseModel):
name: str = Field(alias="strCategory")
description: str = Field(alias="strCategoryDescription")
data = {
"strCategory": "Beef",
"strCategoryDescription": "Beef is ..."
}
obj = TMDB_Category.parse_obj(data)
# {'name': 'Beef', 'description': 'Beef is ...'}
print(obj.dict())
pattern
迁移到
patterns
列表,同时优雅地处理旧版本的数据)。我能找到的最好的解决方案是在
__init__
方法中进行字段映射。用 OP 的话来说,这就像:
class TMDB_Category(BaseModel):
name: str
description: str
def __init__(self, **data):
if "strCategory" in data:
data["name"] = data.pop("strCategory")
if "strCategoryDescription" in data:
data["description"] = data.pop("strCategoryDescription")
super().__init__(**data)
然后我们有:
>>> TMDB_Category(strCategory="name", strCategoryDescription="description").json()
'{"name": "name", "description": "description"}'
如果您需要使用字段别名来执行此操作,但仍然在代码中使用名称/描述字段,一种选择是更改 Hernán Alarcón 的解决方案以使用属性:
class TMDB_Category(BaseModel):
strCategory: str = Field(alias="name")
strCategoryDescription: str = Field(alias="description")
class Config:
allow_population_by_field_name = True
@property
def name(self):
return self.strCategory
@name.setter
def name(self, value):
self.strCategory = value
@property
def description(self):
return self.strCategoryDescription
@description.setter
def description(self, value):
self.strCategoryDescription = value
这仍然有点尴尬,因为代表使用“别名”名称:
>>> TMDB_Category(name="name", description="description")
TMDB_Category(strCategory='name', strCategoryDescription='description')