Python/Pydantic - использование списка с объектами json

У меня есть рабочая модель для получения json набор данных с использованием pydantic. Набор данных модели выглядит так:

data = {'thing_number': 123, 
        'thing_description': 'duck',
        'thing_amount': 4.56}

Я бы хотел иметь список jsonфайлы в качестве набора данных и иметь возможность проверить их. В конечном итоге список будет преобразован в записи вpandasдля дальнейшей обработки. Моя цель - проверить произвольно длинный списокjson записи, которые выглядят примерно так:

bigger_data = [{'thing_number': 123, 
                'thing_description': 'duck',
                'thing_amount': 4.56}, 
               {'thing_number': 456, 
                'thing_description': 'cow',
                'thing_amount': 7.89}]

Базовая настройка, которую я сейчас имею, выглядит следующим образом. Обратите внимание, что добавлениеclass ItemList является частью попытки заставить произвольную длину работать.

from typing import List
from pydantic import BaseModel
from pydantic.schema import schema
import json

class Item(BaseModel):
    thing_number: int
    thing_description: str
    thing_amount: float

class ItemList(BaseModel):
    each_item: List[Item]                                                                           

Затем базовый код создаст то, что я думаю, я ищу в объекте массива, который будет принимать Item объекты.

item_schema = schema([ItemList])
print(json.dumps(item_schema, indent=2)) 

    {
      "definitions": {
        "Item": {
          "title": "Item",
          "type": "object",
          "properties": {
            "thing_number": {
              "title": "Thing_Number",
              "type": "integer"
            },
            "thing_description": {
              "title": "Thing_Description",
              "type": "string"
            },
            "thing_amount": {
              "title": "Thing_Amount",
              "type": "number"
            }
          },
          "required": [
            "thing_number",
            "thing_description",
            "thing_amount"
          ]
        },
        "ItemList": {
          "title": "ItemList",
          "type": "object",
          "properties": {
            "each_item": {
              "title": "Each_Item",
              "type": "array",
              "items": {
                "$ref": "#/definitions/Item"
              }
            }
          },
          "required": [
            "each_item"
          ]
        }
      }
    }

Настройка работает с одним передаваемым элементом json:

item = Item(**data)                                                      

print(item)

Item thing_number=123 thing_description='duck' thing_amount=4.56

Но когда я пытаюсь передать один элемент в ItemList модель возвращает ошибку:

item_list = ItemList(**data)

---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
<ipython-input-94-48efd56e7b6c> in <module>
----> 1 item_list = ItemList(**data)

/opt/conda/lib/python3.7/site-packages/pydantic/main.cpython-37m-x86_64-linux-gnu.so in pydantic.main.BaseModel.__init__()

/opt/conda/lib/python3.7/site-packages/pydantic/main.cpython-37m-x86_64-linux-gnu.so in pydantic.main.validate_model()

ValidationError: 1 validation error for ItemList
each_item
  field required (type=value_error.missing)

Я также пробовал пройти bigger_dataв массив, думая, что он должен начинаться как список. это также возвращает ошибку - - Хотя я, по крайней мере, лучше понимаю ошибку словаря, я не могу понять, как ее исправить.

item_list2 = ItemList(**data_big)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-100-8fe9a5414bd6> in <module>
----> 1 item_list2 = ItemList(**data_big)

TypeError: MetaModel object argument after ** must be a mapping, not list

Спасибо.

Другие вещи, которые я пробовал

Я попытался передать данные в конкретный ключ с немного большей удачей (может быть?).

item_list2 = ItemList(each_item=data_big)

---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
<ipython-input-111-07e5c12bf8b4> in <module>
----> 1 item_list2 = ItemList(each_item=data_big)

/opt/conda/lib/python3.7/site-packages/pydantic/main.cpython-37m-x86_64-linux-gnu.so in pydantic.main.BaseModel.__init__()

/opt/conda/lib/python3.7/site-packages/pydantic/main.cpython-37m-x86_64-linux-gnu.so in pydantic.main.validate_model()

ValidationError: 6 validation errors for ItemList
each_item -> 0 -> thing_number
  field required (type=value_error.missing)
each_item -> 0 -> thing_description
  field required (type=value_error.missing)
each_item -> 0 -> thing_amount
  field required (type=value_error.missing)
each_item -> 1 -> thing_number
  field required (type=value_error.missing)
each_item -> 1 -> thing_description
  field required (type=value_error.missing)
each_item -> 1 -> thing_amount
  field required (type=value_error.missing)

4 ответа

Решение
from typing import List
from pydantic import BaseModel
import json


class Item(BaseModel):
    thing_number: int
    thing_description: str
    thing_amount: float


class ItemList(BaseModel):
    each_item: List[Item]

Используйте свой код с each_item в виде списка элементов

a_duck = Item(thing_number=123, thing_description="duck", thing_amount=4.56)
print(a_duck.json())

a_list = ItemList(each_item=[a_duck])

print(a_list.json())

Создайте следующий вывод:

{"thing_number": 123, "thing_description": "duck", "thing_amount": 4.56}
{"each_item": [{"thing_number": 123, "thing_description": "duck", "thing_amount": 4.56}]}

используя их как "entry json":

a_json_duck = {"thing_number": 123, "thing_description": "duck", "thing_amount": 4.56}
a_json_list = {
    "each_item": [
        {"thing_number": 123, "thing_description": "duck", "thing_amount": 4.56}
    ]
}

print(Item(**a_json_duck))
print(ItemList(**a_json_list))

Работает нормально и генерирует:

Item thing_number=123 thing_description='duck' thing_amount=4.56
ItemList each_item=[<Item thing_number=123 thing_description='duck' thing_amount=4.56>]

У нас остались только данные:

just_datas = [
    {"thing_number": 123, "thing_description": "duck", "thing_amount": 4.56},
    {"thing_number": 456, "thing_description": "cow", "thing_amount": 7.89},
]
item_list = ItemList(each_item=just_datas)
print(item_list)
print(type(item_list.each_item[1]))
print(item_list.each_item[1])

Те работают как ожидалось:

ItemList each_item=[<Item thing_number=123 thing_description='duck'thing_amount=4.56>,<Item thin…
<class '__main__.Item'>
Item thing_number=456 thing_description='cow' thing_amount=7.89

Так что, если мне что-то не хватает, pydantic librairy работает должным образом.

Моя версия pydantic: 0.30 python 3.7.4

Чтение из похожего файла:

json_data_file = """[
{"thing_number": 123, "thing_description": "duck", "thing_amount": 4.56},
{"thing_number": 456, "thing_description": "cow", "thing_amount": 7.89}]"""

from io import StringIO
item_list2 = ItemList(each_item=json.load(StringIO(json_data_file)))

Работают тоже нормально.

Чтобы избежать "each_item" в ItemList, вы можете использовать __root__ Ключевое слово Pydantic:

from typing import List
from pydantic import BaseModel

class Item(BaseModel):
    thing_number: int
    thing_description: str
    thing_amount: float

class ItemList(BaseModel):
    __root__: List[Item]    # ⯇-- __root__

Чтобы построить item_list:

just_data = [
    {"thing_number": 123, "thing_description": "duck", "thing_amount": 4.56},
    {"thing_number": 456, "thing_description": "cow", "thing_amount": 7.89},
]
item_list = ItemList(__root__=just_data)

a_json_duck = {"thing_number": 123, "thing_description": "duck", "thing_amount": 4.56}
item_list.__root__.append(a_json_duck)

Веб-фреймворки, поддерживающие Pydantic, часто оптимизируют такие ItemList как массив JSON без промежуточных __root__ ключевое слово.

Следующее также работает и не требует корневого типа.

Чтобы преобразовать из List[dict] к:

      items = parse_obj_as(List[Item], bigger_data)

Чтобы преобразовать из JSON в:

      items = parse_raw_as(List[Item], bigger_data_json)

Чтобы преобразовать из List[Item] в JSON str:

      bigger_data_json = json.dumps(items, default=pydantic_encoder)

в чем заключалась хитрость для меняfastapi.encoders.jsonable_encoder(взгляните на https://fastapi.tiangolo.com/tutorial/encoder/)

Итак, в вашем случае я добавил «одиночные» элементы в списокresultто естьresult.append(Item(thing_number=123, thing_description="duck", thing_amount=4.56))

и наконецfastapi.JSONResponse(content=fastapi.encoders.jsonable_encoder(result))

Другие вопросы по тегам