Pydantic

Содержание
Введение
Пример применения
BaseModel
Вывод сообщения об ошибке как JSON
Диапазон допустимых значений
@validator
@root_validator
Полный код примеров
Похожие статьи

Введение

Pydantic это библиотека, с помощью которой можно парсить данные и выполнять валидацию.

Про установку можете прочитать здесь

Свободный перевод того что они пишут о себе + комментарии:

Проверка данных и управление настройками с помощью аннотаций типа python.

pydantic применяет аннотации типа (type hints - смотрите PEP 484 ) во время выполнения (runtime) и предоставляет понятные пользователю сообщения об ошибках, когда данные некорректны.

Определить какими должны быть данные можно с помощью в чистого, канонического python. Затем можно сделать валидацию с помощью pydantic.

Pydantic использует возможность современного Python. Убедитесь, что у вас версия не ниже 3.7

Желательно установить последнюю стабильную версию Python. Если нужно - прочитайте «Руководство по установке Python в Linux»

Тем не менее, если вы планируете обмениваться данными в формате JSON со Swagger или Open API проверьте текущую совместимость библиотек.

Пример использования

Рассмотрим скрипт PydanticDemo.py

from dataclasses import dataclass from typing import Tuple from enum import Enum @dataclass class IceCreamMix: name: str flavor: str toppings: Tuple[str, ...] scoops: int def main(): ice_cream_mix = IceCreamMix( "PB&J", "peanut butter", ("strawberries", "sprinkles"), 2 ) print(ice_cream_mix) if __name__ == '__main__': main()

python PydanticDemo.py

IceCreamMix(name='PB&J', flavor='peanut butter', toppings=('strawberries', 'sprinkles'), scoops=2)

Этот скрипт успешно демонстрирует тип мороженого

Добавим ещё немного ООП

from dataclasses import dataclass from typing import Tuple from enum import Enum class Flavor(str, Enum): chocolate = 'chocolate' vanilla = 'vanilla' strawberry = 'strawberry' mint = 'mint' coffeee = 'coffee' peanut_butter = 'peanut butter' class Topping(str, Enum): sprinkles = 'sprinkles' hot_fudge = 'hot fudge' cookies = 'cookies' brownie = 'brownie' whipped_cream = 'whipped cream' strawberries = 'strawberries' @dataclass class IceCreamMix: name: str flavor: Flavor toppings: Tuple[Topping, ...] scoops: int def main(): ice_cream_mix = IceCreamMix( "PB&J", Flavor.peanut_butter, (Topping.strawberries, Topping.sprinkles), 2 ) print(ice_cream_mix) if __name__ == '__main__': main()

$ python PydanticDemo.py

IceCreamMix(name='PB&J', flavor=<Flavor.peanut_butter: 'peanut butter'>, toppings=(<Topping.strawberries: 'strawberries'>, <Topping.sprinkles: 'sprinkles'>), scoops=2)

Скрипт по-прежнему работает

Что если мы по ошибке выберем несуществующий запах или топпинг

def main(): ice_cream_mix = IceCreamMix( "PB&J", "smells like shit", (Topping.strawberries, 111), 2 )

python PydanticDemo.py

IceCreamMix(name='PB&J', flavor='smells like shit', toppings=(<Topping.strawberries: 'strawberries'>, 111), scoops=2)

Скрипт не замечает подвоха.

Чтобы проверять данные автоматически установите pydantic и внесите всего одно изменение в первую строку

from pydantic.dataclasses import dataclass

python PydanticDemo.py

Traceback (most recent call last): File "PydanticDemo.py", line 41, in <module> main() File "PydanticDemo.py", line 31, in main ice_cream_mix = IceCreamMix( File "<string>", line 7, in __init__ File "C:\Users\Andrei\python\pydantic\venv\lib\site-packages\pydantic\dataclasses.py", line 99, in _pydantic_post_init raise validation_error pydantic.error_wrappers.ValidationError: 2 validation errors for IceCreamMix flavor value is not a valid enumeration member; permitted: 'chocolate', 'vanilla', 'strawberry', 'mint', 'coffee', 'peanut butter' (type=type_error.enum; enum_values=[<Flavor.chocolate: 'chocolate'>, <Flavor.vanilla: 'vanilla'>, <Flavor.strawberry: 'strawberry'>, <Flavor.mint: 'mint'>, <Flavor.coffeee: 'coffee'>, <Flavor.peanut_butter: 'peanut butter'>]) toppings -> 1 value is not a valid enumeration member; permitted: 'sprinkles', 'hot fudge', 'cookies', 'brownie', 'whipped cream', 'strawberries' (type=type_error.enum; enum_values=[<Topping.sprinkles: 'sprinkles'>, <Topping.hot_fudge: 'hot fudge'>, <Topping.cookies: 'cookies'>, <Topping.brownie: 'brownie'>, <Topping.whipped_cream: 'whipped cream'>, <Topping.strawberries: 'strawberries'>])

pydantic не пропустил наш код. Разберём выдачу подробнее

pydantic.error_wrappers.ValidationError: 2 validation errors for IceCreamMix

Указано количество ошибок и класс. Это помогло бы с дебагом, если бы мы не знали заранее где ошибки и сколько их

flavor value is not a valid enumeration member; permitted: 'chocolate', 'vanilla', 'strawberry', 'mint', 'coffee', 'peanut butter' (type=type_error.enum; enum_values=[<Flavor.chocolate: 'chocolate'>, <Flavor.vanilla: 'vanilla'>, <Flavor.strawberry: 'strawberry'>, <Flavor.mint: 'mint'>, <Flavor.coffeee: 'coffee'>, <Flavor.peanut_butter: 'peanut butter'>])

Pydantic подсказывает допустимые значения.

Тоже самое и с топпингами, где вместо допустимого значения стоит 111

toppings -> 1 value is not a valid enumeration member; permitted: 'sprinkles', 'hot fudge', 'cookies', 'brownie', 'whipped cream', 'strawberries' (type=type_error.enum; enum_values=[<Topping.sprinkles: 'sprinkles'>, <Topping.hot_fudge: 'hot fudge'>, <Topping.cookies: 'cookies'>, <Topping.brownie: 'brownie'>, <Topping.whipped_cream: 'whipped cream'>, <Topping.strawberries: 'strawberries'>])

Верните корректные значения для Flavor и Topping но замените scoops с 2 на '2'

def main(): ice_cream_mix = IceCreamMix( "PB&J", Flavor.peanut_butter, (Topping.strawberries, Topping.sprinkles), '2' )

python PydanticDemo.py

IceCreamMix(name='PB&J', flavor=<Flavor.peanut_butter: 'peanut butter'>, toppings=(<Topping.strawberries: 'strawberries'>, <Topping.sprinkles: 'sprinkles'>), scoops=2)

scoops по-прежнему равно двум

Pydantic поддерживает приведение типа (type coercion)

BaseModel

Чтобы получить доступ к дополнительным возможностям таким как сериализация (Serialization) и поддержка JSON воспользуемся классом BaseModel

Просто напомню, что сперва у нас было

from dataclasses import dataclass

Затем

from pydantic.dataclasses import dataclass

А сейчас нужно сделать

from pydantic import BaseModel

И убрать декоратор @dataclass перед class IceCreamMix:

class IceCreamMix: нужно заменить на class IceCreamMix(BaseModel):

а также добавить имена аттрибутов код, создающий объект класса IceCreamMix то есть name = "PB&J" flavor = Flavor.peanut_butter и так далее

strawberries = 'strawberries' class IceCreamMix(BaseModel): name: str flavor: Flavor toppings: Tuple[Topping, ...] scoops: int def main(): ice_cream_mix = IceCreamMix( name = "PB&J", flavor = Flavor.peanut_butter, toppings = (Topping.strawberries, Topping.sprinkles), scoops = 2 )

python PydanticDemo.py

IceCreamMix(name='PB&J', flavor=<Flavor.peanut_butter: 'peanut butter'>, toppings=(<Topping.strawberries: 'strawberries'>, <Topping.sprinkles: 'sprinkles'>), scoops=2)

Всё работает так же, как и до изменений.

Теперь можно вывести результат в виде JSON

print(ice_cream_mix.json())

python PydanticDemo.py

{"name": "PB&J", "flavor": "peanut butter", "toppings": ["strawberries", "brownie"], "scoops": 2}

Обратите внимание на JSON который вы получили выше.

Его можно скопировать, затем если нужно изменить и создать ещё один объект прямо из JSON с помощью метода parse_raw()

Например:

another_mix = IceCreamMix.parse_raw('{"name": "New mix", "flavor": "mint", "toppings": ["cookies", "hot fudge"], "scoops": 2}') print(another_mix.json())

{"name": "New mix", "flavor": "mint", "toppings": ["cookies", "hot fudge"], "scoops": 2}

Если случайно ошибиться со значением аттрибута - pydantic не даст соврать

another_mix = IceCreamMix.parse_raw('{"name": "New mix", "flavor": "novichoke", "toppings": ["cookies", "hot fudge"], "scoops": 2}') print(another_mix.json())

python PydanticDemo.py

Traceback (most recent call last): File "/home/avorotyn/python/pydantic/PydanticDemo.py", line 45, in <module> main() File "/home/avorotyn/python/pydantic/PydanticDemo.py", line 40, in main another_mix = IceCreamMix.parse_raw('{"name": "New mix", "flavor": "novichoke", "toppings": ["cookies", "hot fudge"], "scoops": 2}') File "pydantic/main.py", line 543, in pydantic.main.BaseModel.parse_raw File "pydantic/main.py", line 520, in pydantic.main.BaseModel.parse_obj File "pydantic/main.py", line 362, in pydantic.main.BaseModel.__init__ pydantic.error_wrappers.ValidationError: 1 validation error for IceCreamMix flavor value is not a valid enumeration member; permitted: 'chocolate', 'vanilla', 'strawberry', 'mint', 'coffee', 'peanut butter' (type=type_error.enum; enum_values=[<Flavor.chocolate: 'chocolate'>, <Flavor.vanilla: 'vanilla'>, <Flavor.strawberry: 'strawberry'>, <Flavor.mint: 'mint'>, <Flavor.coffeee: 'coffee'>, <Flavor.peanut_butter: 'peanut butter'>])

ValidationError как JSON

В JSON можно также оформить сообщения об ошибках. Нужно импортировать из pydantic ValidationError и воспользоваться try: except

from pydantic import BaseModel, ValidationError

def main(): try: ice_cream_mix = IceCreamMix( name = "PB&J", flavor = "spring", toppings = (Topping.strawberries, Topping.sprinkles), scoops = 2 ) except ValidationError as e: print(e.json())

python PydanticDemo.py

[ { "loc": [ "flavor" ], "msg": "value is not a valid enumeration member; permitted: 'chocolate', 'vanilla', 'strawberry', 'mint', 'coffee', 'peanut butter'", "type": "type_error.enum", "ctx": { "enum_values": [ "chocolate", "vanilla", "strawberry", "mint", "coffee", "peanut butter" ] } } ]

Границы допустимых значений

Допустим вы хотите, чтобы число ложечек было обязательным аттрибутом со значениями от 0 до 5 не включая границы

from pydantic import BaseModel, ValidationError, Field

strawberries = 'strawberries' class IceCreamMix(BaseModel): name: str flavor: Flavor toppings: Tuple[Topping, ...] scoops: int = Field(..., gt=0, lt=5)

python PydanticDemo.py

{"name": "PB&J", "flavor": "peanut butter", "toppings": ["strawberries", "brownie"], "scoops": 2}

Задано 2 ложечки, так что ошибок нет.

Попробуем 5 ложечек

def main(): try: ice_cream_mix = IceCreamMix( name = "PB&J", flavor = "spring", toppings = (Topping.strawberries, Topping.sprinkles), scoops = 5 ) except ValidationError as e: print(e.json())

python PydanticDemo.py

[ { "loc": [ "scoops" ], "msg": "ensure this value is less than 5", "type": "value_error.number.not_lt", "ctx": { "limit_value": 5 } } ] Traceback (most recent call last): File "/home/avorotyn/python/pydantic/PydanticDemo.py", line 50, in <module> main() File "/home/avorotyn/python/pydantic/PydanticDemo.py", line 41, in main print(ice_cream_mix.json()) UnboundLocalError: local variable 'ice_cream_mix' referenced before assignment

Не обращайте внимание на Traceback - можно было вложить print в try, но если всё ок, то объёкт создается и этой ошибки нет, а если не ок, то pydantic ловит несоответствие и выдает value_error

Валидация с помощью декоратора validator

Ещё один полезный способ установки ограничений - с помощью @validator

Он применяется если нужно следить за каким-то одним аттрибутом

from pydantic import BaseModel, ValidationError, Field, validator

class IceCreamMix(BaseModel): name: str flavor: Flavor toppings: Tuple[Topping, ...] scoops: int = Field(..., gt=0, lt=5) @validator('toppings') def check_toppings(cls, toppings): if len(toppings) > 4: raise ValueError('Too many toppings') return toppings

Если запустить этот код с двумя топпингами никаких ошибок не будет, поэтому сразу добавим ещё три, чтобы в сумме стало пять.

def main(): try: ice_cream_mix = IceCreamMix( name = "PB&J", flavor = "spring", toppings = (Topping.strawberries, Topping.brownie,Topping.sprinkles,Topping.hot_fudge,Topping.whipped_cream),

python PydanticDemo.py

[ { "loc": [ "toppings" ], "msg": "Too many toppings", "type": "value_error" } ]

Теперь можно уменьшить число топпингов до четырёх и убедиться что ошибки нет.

@root_validator

Применяется когда нужно следить за всей моделью. Например за сочетаниями разных аттрибутов.

from pydantic import BaseModel, ValidationError, Field, validator, root_validator

Создайте ещё один класс - Container

strawberries = 'strawberries' class Container(str, Enum): cup = 'cup' cone = 'cone' waffle_cone = 'waffle cone' class IceCreamMix(BaseModel): name: str flavor: Flavor

Зададим условие: если топпинг это hot_fudge то никакой рожок давать нельзя - можно только чашку (cup)

Валидацию будем делать через @root_validator

@validator('toppings') def check_toppings(cls, toppings): if len(toppings) > 4: raise ValueError('Too many toppings') return toppings @root_validator def check_cone_toppings(cls, toppings): container = values.get('container') toppings = values.get('toppings') if container == Container.cone or container == Container.waffle_cone: if Topping.hot_fudge in toppings: raise ValueError('Cones cannot have hot fudge') return values def main(): try: ice_cream_mix = IceCreamMix( name = "PB&J", container = Container.waffle_cone, flavor = "spring",

У вас как раз должен был остаться топпинг hot fudge с прошлого примера, если нет - добавьте и запустите

python PydanticDemo.py

[ { "loc": [ "__root__" ], "msg": "Cones cannot have hot fudge", "type": "value_error" } ]

Окончательный код примера

Краткий обзор возможностей pydantic подошёл к концу.

Спасибо за внимание, ниже полный код к этой статье.

from pydantic import BaseModel, \ ValidationError, Field, validator, root_validator from typing import Tuple from enum import Enum class Flavor(str, Enum): chocolate = 'chocolate' vanilla = 'vanilla' strawberry = 'strawberry' mint = 'mint' coffeee = 'coffee' peanut_butter = 'peanut butter' class Topping(str, Enum): sprinkles = 'sprinkles' hot_fudge = 'hot fudge' cookies = 'cookies' brownie = 'brownie' whipped_cream = 'whipped cream' strawberries = 'strawberries' class Container(str, Enum): cup = 'cup' cone = 'cone' waffle_cone = 'waffle cone' class IceCreamMix(BaseModel): name: str container: Container flavor: Flavor toppings: Tuple[Topping, ...] scoops: int = Field(..., gt=0, lt=5) @validator('toppings') def check_toppings(cls, toppings): if len(toppings) > 4: raise ValueError('Too many toppings') return toppings @root_validator def check_cone_toppings(cls, values): container = values.get('container') toppings = values.get('toppings') if container == Container.cone or container == Container.waffle_cone: if Topping.hot_fudge in toppings: raise ValueError('Cones cannot have hot fudge') return values def main(): try: ice_cream_mix = IceCreamMix( name="PB&J", container=Container.waffle_cone, flavor=Flavor.peanut_butter, # flavor='unknown flavour' toppings=(Topping.strawberries, Topping.brownie, Topping.sprinkles), # на validator # toppings=(Topping.strawberries, Topping.brownie, # Topping.sprinkles,Topping.cookies, Topping.sprinkles), # на root_validator # toppings=(Topping.strawberries, Topping.brownie, # Topping.sprinkles,Topping.hot_fudge), scoops=2 # scoops=5 ) print(ice_cream_mix.json()) except ValidationError as e: print(e.json()) if __name__ == '__main__': main()

Вторая часть статьи

Похожие статьи
Pydantic models
Python
enumerate
Изображение баннера

Поиск по сайту

Подпишитесь на Telegram канал @aofeed чтобы следить за выходом новых статей и обновлением старых

Перейти на канал

@aofeed

Задать вопрос в Телеграм-группе

@aofeedchat

Контакты и сотрудничество:
Рекомендую наш хостинг beget.ru
Пишите на info@urn.su если Вы:
1. Хотите написать статью для нашего сайта или перевести статью на свой родной язык.
2. Хотите разместить на сайте рекламу, подходящую по тематике.
3. Реклама на моём сайте имеет максимальный уровень цензуры. Если Вы увидели рекламный блок недопустимый для просмотра детьми школьного возраста, вызывающий шок или вводящий в заблуждение - пожалуйста свяжитесь с нами по электронной почте
4. Нашли на сайте ошибку, неточности, баг и т.д. ... .......
5. Статьи можно расшарить в соцсетях, нажав на иконку сети: