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