【问题标题】:How can I transform my input data to fit my desired Pydantic model如何转换我的输入数据以适合我想要的 Pydantic 模型
【发布时间】:2022-01-23 05:19:46
【问题描述】:

假设我有以下关于宠物主人的输入数据。

from types import SimpleNamespace

petowner1 = SimpleNamespace(
    id = 1,
    cats = [
            SimpleNamespace(id=1, name='Princess Peach')
        ],
    dogs = [
        SimpleNamespace(id=1, name='Sparky'),
        SimpleNamespace(id=2, name='Clifford')
    ]
)

petowner1 有一个id、一个cats 列表和一个dogs 列表。 ...但我认为 Owner 拥有一个 Pets 列表更有意义,每个宠物都有一个 type 属性('cat' 或 'dog') .因此,我建立了以下 Pydantic 模型

class Pet(BaseModel):
    id: int
    type: str
    name: str
    
    class Config:
        orm_mode = True
        

class Owner(BaseModel):
    id: int
    pets: List[Pet]

    class Config:
        orm_mode = True

根据我的输入数据,如何填充这些 Pydantic 模型?我的最终目标是做类似的事情

owner = Owner.from_orm(petowner1)
owner.json()

应该输出哪个

{
    'id': 1,
    'pets': [
        {'id': 1, 'type': 'cat', 'name': 'Princess Peach'},
        {'id': 1, 'type': 'dog', 'name': 'Sparky'},
        {'id': 2, 'type': 'dog', 'name': 'Clifford'}
    ]
}

【问题讨论】:

    标签: python pydantic


    【解决方案1】:

    想出了一个解决方案,该解决方案涉及对GetterDict 进行子类化并扩展其get() 方法,如here 所述。

    from typing import Optional, List, Any
    from pydantic import BaseModel
    from pydantic.utils import GetterDict
    
    class Pet(BaseModel):
        id: int
        pet_type: str
        name: str
    
        class Config:
            orm_mode = True
    
    
    class OwnerGetter(GetterDict):
    
        def get(self, key: str, default: Any = None) -> Any:
            if key == 'pets':
                pets = [
                    *[Pet(pet_type="cat", id=x.id, name=x.name) for x in self._obj.cats],
                    *[Pet(pet_type="cat", id=x.id, name=x.name) for x in self._obj.dogs],
                ]
                return pets
    
            else:
                return super(OwnerGetter, self).get(key, default)
    
    
    class Owner(BaseModel):
        id: int
        pets: List[Pet]
    
        class Config:
            orm_mode = True
            getter_dict = OwnerGetter
    

    用法

    owner = Owner.from_orm(petowner1)
    owner.json()
    
    {
      "id": 1,
      "pets": [
        {
          "id": 1,
          "pet_type": "cat",
          "name": "Princess Peach"
        },
        {
          "id": 1,
          "pet_type": "cat",
          "name": "Sparky"
        },
        {
          "id": 2,
          "pet_type": "cat",
          "name": "Clifford"
        }
      ]
    }
    
    

    这里需要注意的是,我们必须使用Pet(pet_type="cat", id=x.id, name=x.name) 而不是Pet.from_orm(...) 来初始化每个Pet。我使用自定义 PetGetter 创建了一个更长的版本,以便我可以使用

    pets = [
      *[Pet.from_orm(SimpleNamespace(pet_type="cat", data=x)) for x in self._obj.cats],
      *[Pet.from_orm(SimpleNamespace(pet_type="dog", data=x)) for x in self._obj.dogs]
    ]
    

    代替

    pets = [
      *[Pet(pet_type="cat", id=x.id, name=x.name) for x in self._obj.cats],
      *[Pet(pet_type="cat", id=x.id, name=x.name) for x in self._obj.dogs]
    ]
    

    【讨论】:

      【解决方案2】:

      你可以使用简单的循环来构造对象:

      class Owner(BaseModel):
          id: int
          pets: List[Pet]
      
          class Config:
              orm_mode = True
      
          @classmethod
          def from_orm(cls, p):
              pets = [
                  *[Pet(id=cat.id, type="cat", name=cat.name) for cat in p.cats],
                  *[Pet(id=dog.id, type="dog", name=dog.name) for dog in p.dogs],
              ]
              return cls(id=p.id, pets=pets)
      
      

      【讨论】:

      • 谢谢。看起来您正在覆盖 BaseModel 的 from_orm() 方法,是吗?如果是这样,是不是有很多 stuff 您要删除 from_orm() 所做的,例如检查 orm_mode 并调用 validate_model(cls, obj)?覆盖 from_orm() 似乎是一个危险的想法。
      • 如果您查看 pydantic 源代码,您会发现 __init__from_orm 看起来几乎相同,不同之处在于 from_orm 调用 __new__ 本身。您在构造对象时具有相同的验证,因此根本没有危险。
      • 有趣。感谢您的帮助。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-23
      • 2021-04-20
      • 1970-01-01
      • 1970-01-01
      • 2022-07-28
      • 1970-01-01
      相关资源
      最近更新 更多