是否可以更改pydantic中的输出别名?

问题描述 投票:0回答:6

设置:

# 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 ..."
    }
}
python json json-deserialization fastapi pydantic
6个回答
47
投票

更新(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
以便能够解析使用不同字段名称的数据。


11
投票

使用配置选项

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

3
投票

另一种选择(可能不会那么流行)是使用除pydantic

之外的反序列化库。例如,Dataclass Wizard 库就是支持这一特定用例的库。如果您需要 
Field(alias=...)
 提供的相同往返行为,可以将 
all
 参数传递给 
json_field
 函数。请注意,使用这样的库,您确实会失去执行完整类型验证的能力,这可以说是 pydantic 的最大优势之一;但它确实以与 pydantic 类似的方式执行类型转换。我觉得验证不那么重要还有几个原因,我在下面列出。

我认为数据验证是很好的原因 总体特点:

    如果您自己构建并传递输入,您很可能相信您知道自己在做什么,并且传递了正确的数据类型。
  • 如果您从另一个 API 获取输入,那么假设该 API 有不错的文档,您可以从他们的文档中获取示例响应,并使用它来建模您的类结构。如果 API 清楚地记录了其响应结构,那么您通常不需要任何验证。
  • 数据验证需要时间,因此与仅执行类型转换并捕获可能发生的任何错误而不事先验证输入类型相比,它可能会稍微减慢该过程。
为了证明这一点,这里有一个使用

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 ...'}]}
衡量性能

如果有人好奇,我已经设置了一个快速基准测试来比较 pydantic 与仅数据类的反序列化和序列化时间:

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
已经相当快了。


免责声明:我是该库的创建者(和维护者)。


3
投票
您需要更改

alias

 才能拥有 
validation_alias

class TMDB_Category(BaseModel): name: str = Field(validation_alias="strCategory") description: str = Field(validation_alias="strCategoryDescription")
可以使用 

serialization_alias

 设置序列化别名。 
文档


0
投票
也许你可以使用这种方法

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())
    

0
投票
我试图做类似的事情(将字段

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')
    
© www.soinside.com 2019 - 2024. All rights reserved.