Pydantic

Contents
Introduction
Installing Pydantic
Example
BaseModel
Show Error Message as JSON
Check Range of Allowed Values
@validator
@root_validator
Final Code of this Demo
Related Articles

Intro

Pydantic is using modern Python features. Please ensure that you have at least Python 3.7 installed.

It is recommended to install latest stable Python version. Consider visiting «Install Python on Linux» manul for guidance.

However if you plan to use Open API or Swagger consider studying current compatibility between libraries.

Example

Create a Python script and 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)

This script shows type of icecream

Lets add some more OOP

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)

Script is still working find

What if we use unexisting flavor or topping

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

python PydanticDemo.py

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

Script does not understand that the value is wrong.

To check values automatically install pydantic and make a change in the first line

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 did not allow our code to run. Lets study the output with more detail

pydantic.error_wrappers.ValidationError: 2 validation errors for IceCreamMix

The quantity of erros is shown as well as the class where it happened

It would help us with the debug if we did know where the errors are

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

The quantity of erros is shown as well as the class where it happened

It would help us with the debug if we did know where the errors are

Use allowed values for Flavor and Topping but change scoops value from 2 to '2' so now it is a string not integer

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 is still equal to 2 because Pydantic suppurts type coercion

BaseModel

To get access to extra features such as Serialization and JSON support use BaseModel class

Just a reminder that originally we had

from dataclasses import dataclass

Then

from pydantic.dataclasses import dataclass

Now we need

from pydantic import BaseModel

Please remove @dataclass decorator before class IceCreamMix:

class IceCreamMix: needs to be changed to class IceCreamMix(BaseModel):

and attributes names should be added to the code that creates an object of class IceCreamMix e.g: name = "PB&J" flavor = Flavor.peanut_butter etc.

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)

Everything works in a same way as it used to.

Now we can have output in JSON format

print(ice_cream_mix.json())

python PydanticDemo.py

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

Notice JSON that we have in the output

Now it is possible to copy it, change if needed and create more objects fro JSON with parse_raw() method

Example:

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}

If we make an accident mistake in the attribute value - pydantic will help us to notice it.

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/andrei/python/pydantic/PydanticDemo.py", line 45, in <module> main() File "/home/andrei/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

Error messages are also can be formatted as JSON. Need to import ValidationError from pydantic and use 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" ] } } ]

Allowed values range

Lets say you want to limit scoopes quantity to be in range 0 to 5 (excluding borders) and make in mandatory

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 scoops are set, so no error here

Try 5 scoops

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/andrei/python/pydantic/PydanticDemo.py", line 50, in <module> main() File "/home/andrei/python/pydantic/PydanticDemo.py", line 41, in main print(ice_cream_mix.json()) UnboundLocalError: local variable 'ice_cream_mix' referenced before assignment

Do not pay attention to Traceback - it is because of validation failure → object is not created. Will not happen if scoops value is coorect

Validation with validator decorator

One more usefull method is using @validator

It is designed to monitor a signle attribute.

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

If we run this code with two toppings there are no errors. Lets add three more to have five.

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

Now we can reduce to 4 and check that there are no errors.

@root_validator

Is used when whole model should be monitored. For example to monitor multiple attributes combinations.

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

Create one more class - Container

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

We will create a new condition: if topping is hot_fudge cone and waffle_cone are not allowed - only cup is allowed

Will validate with @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",

We should have hot fudge from the previous example, if not - add it to the code

python PydanticDemo.py

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

It was the short review of pydantic library.

Thank you for your attention, the full code of this example is below.

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

Second part of Pydantic Demo

Related Articles
Pydantic models
Python
enumerate
Pydantic models

Search on this site

Subscribe to @aofeed channel for updates

Visit Channel

@aofeed

Feedbak and Questions in Telegram

@aofeedchat