Showing posts with label pydantic. Show all posts
Showing posts with label pydantic. Show all posts

Friday, 5 November 2021

Pydantic: Error handling

Pydnatic throws ‘ValidationError’ whenever it see an issue while validating the data.

 

validation_error_1.py

from pydantic import BaseModel, ValidationError, conint, constr

class Employee(BaseModel):
    id: int
    name: constr(min_length=3, max_length=10)
    age: conint(gt=18)

try:
    emp1 = Employee(id = 1, name = 'Pt', age = 8)
except ValidationError as e:
    print(e)

 

Output

2 validation errors for Employee
name
  ensure this value has at least 3 characters (type=value_error.any_str.min_length; limit_value=3)
age
  ensure this value is greater than 18 (type=value_error.number.not_gt; limit_value=18)

As you see the output, Pydantic will return the list of errors found in the input data.

 

Different ways to extract the information from ValidaitonError object

Method

Description

e.errors()

method will return list of errors found in the input data.

e.json()

method will return a JSON representation of errors.

str(e)

method will return a human readable representation of the errors.

 

validation_error_2.py

from pydantic import BaseModel, ValidationError, conint, constr

class Employee(BaseModel):
    id: int
    name: constr(min_length=3, max_length=10)
    age: conint(gt=18)

try:
    emp1 = Employee(id = 1, name = 'Pt', age = 8)
except ValidationError as e:
    print(e.errors())
    print('\n\n\n')
    print(e.json())
    print('\n\n\n')
    print(str(e))


Output

[{'loc': ('name',), 'msg': 'ensure this value has at least 3 characters', 'type': 'value_error.any_str.min_length', 'ctx': {'limit_value': 3}}, {'loc': ('age',), 'msg': 'ensure this value is greater than 18', 'type': 'value_error.number.not_gt', 'ctx': {'limit_value': 18}}]




[
  {
    "loc": [
      "name"
    ],
    "msg": "ensure this value has at least 3 characters",
    "type": "value_error.any_str.min_length",
    "ctx": {
      "limit_value": 3
    }
  },
  {
    "loc": [
      "age"
    ],
    "msg": "ensure this value is greater than 18",
    "type": "value_error.number.not_gt",
    "ctx": {
      "limit_value": 18
    }
  }
]




2 validation errors for Employee
name
  ensure this value has at least 3 characters (type=value_error.any_str.min_length; limit_value=3)
age
  ensure this value is greater than 18 (type=value_error.number.not_gt; limit_value=18)


{

    "loc": [

      "age"

    ],

    "msg": "ensure this value is greater than 18",

    "type": "value_error.number.not_gt",

    "ctx": {

      "limit_value": 18

    }

  }

As you see above snippet, each error object contain properties loc, msg, type and ctx. Below table summarizes the properties.

 

Property

Description

loc

Specifies the error's location as a list. The first item in the list will be the field where the error occurred,

msg

Human readable error message

type

Computer readable identifier for the error type

ctx

an optional object which contains values required to render the error message.




 

  

Previous                                                    Next                                                    Home

Tuesday, 2 November 2021

Pydantic: @root_validator: Apply validation on entire model

‘@root_validator’ is used to apply validation on entire model.

 

root_validator_1.py

from pydantic import (
    BaseModel, 
    ValidationError, 
    validator,
    root_validator
)

from typing import Optional

class Box(BaseModel):
    id: int
    weight: int = None
    height: int = None

    @root_validator
    def allValidator(cls, values):
        if(values == None):
            raise ValueError('id, weight and height must set to a value')

        idTemp = values.get('id')
        weightTemp = values.get('weight')
        heightTemp = values.get('height')

        err = '';
        if(idTemp < 1):
            err = err + 'id must > 0. '

        if(weightTemp < 10):
            err = err + 'weigh must >= 10. '

        if(heightTemp < 15):
            err = err + 'height must >= 15'

        if(len(err) > 1):
            raise ValueError(err)

        return values

try:
    b1 = Box(id = -1, weight = 0, height=16)
    print(b1)
except ValidationError as e:
    print(e.json())

 

Output

[
  {
    "loc": [
      "__root__"
    ],
    "msg": "id must > 0. weigh must >= 10. ",
    "type": "value_error"
  }
]

 


 

Previous                                                    Next                                                    Home

Pydantic: validate always

If a field do not set any value, then pydantic validator is not called on that field. You can customize this behaviour by passing the argument ‘always=True’.

 

validator_demo_6.py

from pydantic import BaseModel, ValidationError, validator
from typing import Optional

class Box(BaseModel):
    id: int
    weight: int = None
    height: int = None

    @validator('*', always=True)
    def allValidator(cls, value):
        if(value == None):
            raise ValueError('id, weight and height must set to a value')
        if(value < 1):
            raise ValueError('id, weight and height must > 0')
        return value

try:
    b1 = Box(id = 1, weight = 0)
    print(b1)
except ValidationError as e:
    print(e.json())

 

Output

[
  {
    "loc": [
      "weight"
    ],
    "msg": "id, weight and height must > 0",
    "type": "value_error"
  },
  {
    "loc": [
      "height"
    ],
    "msg": "id, weight and height must set to a value",
    "type": "value_error"
  }
]

 


Previous                                                    Next                                                    Home

Friday, 29 October 2021

Pydantic: Apply validator on every item of a collection (set, list, dict etc.,)

By passing the argument 'each_item=True' to the validator decorator, we can validate every item in the collection.

 

Let’s see it with an example.

 

validator_demo_5.py

from pydantic import BaseModel, ValidationError, validator
from typing import List

class Test(BaseModel):
    even_numbers: List[int]

    @validator('even_numbers', each_item=True)
    def evenNumValidator(cls, value):
        if(value % 2 != 0):
            raise ValueError('number must be even')
        return value

try:
    t1 = Test(even_numbers = [2, 4, 6])
    print(t1)

    t2 = Test(even_numbers = [2, 4, 5, 6, 7])
    print(t2)
except ValidationError as e:
    print(e.json())

 

Output

even_numbers=[2, 4, 6]
[
  {
    "loc": [
      "even_numbers",
      2
    ],
    "msg": "number must be even",
    "type": "value_error"
  },
  {
    "loc": [
      "even_numbers",
      4
    ],
    "msg": "number must be even",
    "type": "value_error"
  }
]

 

On validation failure, it return the failed position element from the list.

 

 

Previous                                                    Next                                                    Home

Wednesday, 27 October 2021

Pydantic: Working with validators

Pydantic provides @validaor decorator to add custom validations to a model.

 

Let’s see it with an example.

class Employee(BaseModel):
    id: int
    name: str
    age: int

    @validator('name')
    def nameValidator(cls, value):
        if(len(value)< 5):
            raise ValueError('name must contain atleast 5 characters')
        return value

 

In the above example, I had written a validator for 'name' property, it throws value error when the name field contains less than 5 characters.

 

validator_demo_1.py

 

from pydantic import BaseModel, ValidationError, validator
from typing import Optional

class Employee(BaseModel):
    id: int
    name: str
    age: int

    @validator('name')
    def nameValidator(cls, value):
        if(len(value)< 5):
            raise ValueError('name must contain atleast 5 characters')
        return value

try:
    emp1 = Employee(id = 1, age = 34, name='kri')
    print(emp1)
except ValidationError as e:
    print(e.json())

 

Output

[
  {
    "loc": [
      "name"
    ],
    "msg": "name must contain atleast 5 characters",
    "type": "value_error"
  }

Points to remember

a.   Validators in Pydantic are modelled as class methods, so the first argument value they receive is the Employee class, not an instance of Employee.

b.   Second argument is the actual field value to validate.

c.    Validation is done in the order fields are defined.

 

Can I add multiple validators to a pydantic model?

Yes, you can.

 

validator_demo_2.py

from pydantic import BaseModel, ValidationError, validator
from typing import Optional

class Employee(BaseModel):
    id: int
    name: str
    age: int

    @validator('name')
    def nameValidator(cls, value):
        if(len(value)< 5):
            raise ValueError('name must contain atleast 5 characters')
        return value
    
    @validator('age')
    def ageValidator(cls, value):
        if(value < 18):
            raise ValueError('age should be >= 18')
        return value

try:
    emp1 = Employee(id = 1, age = 17, name='kri')
    print(emp1)
except ValidationError as e:
    print(e.json())


Output

[
  {
    "loc": [
      "name"
    ],
    "msg": "name must contain atleast 5 characters",
    "type": "value_error"
  },
  {
    "loc": [
      "age"
    ],
    "msg": "age should be >= 18",
    "type": "value_error"
  }
]


Can I apply a validator on multiple fields

Yes, you can.

 

validator_demo_3.py

from pydantic import BaseModel, ValidationError, validator
from typing import Optional

class Person(BaseModel):
    id: int
    name: str
    age: int

    @validator('age', 'id')
    def ageAndIdValidator(cls, value):
        if(value < 1):
            raise ValueError('id and age must > 0')
        return value

try:
    p1 = Person(id = -1, age = -1, name='kri')
    print(p1)
except ValidationError as e:
    print(e.json())


Output

[
  {
    "loc": [
      "id"
    ],
    "msg": "id and age must > 0",
    "type": "value_error"
  },
  {
    "loc": [
      "age"
    ],
    "msg": "id and age must > 0",
    "type": "value_error"
  }
]


In the above example, I applied validator on both age and id.

 

Can I apply a validator on all the fields of a model?

Yes, by passing the special value '*', you can apply a validator on all the fields of a model.

 

validator_demo_4.py

from pydantic import BaseModel, ValidationError, validator
from typing import Optional

class Box(BaseModel):
    id: int
    weight: str
    height: int

    @validator('*')
    def ageAndIdValidator(cls, value):
        if(value < 1):
            raise ValueError('id, weight and height must > 0')
        return value

try:
    b1 = Box(id = 1, weight = 0, height = 0)
    print(b1)
except ValidationError as e:
    print(e.json())


Output

[
  {
    "loc": [
      "weight"
    ],
    "msg": "'<' not supported between instances of 'str' and 'int'",
    "type": "type_error"
  },
  {
    "loc": [
      "height"
    ],
    "msg": "id, weight and height must > 0",
    "type": "value_error"
  }
]


 

Previous                                                    Next                                                    Home