Monday, September 13, 2021

Python - Type Checking - pydantic

Reference:

https://betterprogramming.pub/the-beginners-guide-to-pydantic-ba33b26cde89 

pydantic enforces type hints at runtime, and provides user friendly errors when data is invalid.

Defining an object in pydantic is as simple as creating a new class which inherits from theBaseModel. When you create a new object from the class, pydantic guarantees that the fields of the resultant model instance will conform to the field types defined on the model.

from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel

Declare a new class which inherits the BaseModel as follow:

class User(BaseModel):
id: int
username : str
password : str
confirm_password : str
alias = 'anonymous'
timestamp: Optional[datetime] = None
friends: List[int] = []

pydantic uses the built-in type hinting syntax to determine the data type of each variable. 

The next step is to instantiate a new object from the User class.

data = {'id': '1234', 'username': 'wai foong', 'password': 'Password123', 'confirm_password': 'Password123', 'timestamp': '2020-08-03 10:30', 'friends': [1, '2', b'3']}user = User(**data)

You should get the following output when you print out the user variable. You can notice that id has been automatically converted to an integer, even though the input is a string. Likewise, bytes are automatically converted to integers, as shown by the friends field.

id=1234 username='wai foong' password='Password123' confirm_password='Password123' timestamp=datetime.datetime(2020, 8, 3, 10, 30) friends=[1, 2, 3] alias='anonymous'

Methods and attributes under BaseModel

Classes that inherit the BaseModel will have the following methods and attributes:

  • dict() — returns a dictionary of the model’s fields and values
  • json() — returns a JSON string representation dictionary
  • copy() — returns a deep copy of the model
  • parse_obj() — a utility for loading any object into a model with error handling if the object is not a dictionary
  • parse_raw() — a utility for loading strings of numerous formats
  • parse_field() — similar to parse_raw() but meant for files
  • from_orm() — loads data into a model from an arbitrary class
  • schema() — returns a dictionary representing the model as JSON schema
  • schema_json() — returns a JSON string representation of schema()
  • construct() — a class method for creating models without running validation
  • __fields_set__ — Set of names of fields which were set when the model instance was initialized
  • __fields__ — a dictionary of the model’s fields
  • __config__ — the configuration class for the model

ValidationError

In order to get better details on the error, it is highly recommended to wrap it inside a try-catch block, as follows:

from pydantic import BaseModel, ValidationError# ... codes for User classdata = {'id': 'a random string', 'username': 'wai foong', 'password': 'Password123', 'confirm_password': 'Password123', 'timestamp': '2020-08-03 10:30', 'friends': [1, '2', b'3']}try:
user = User(**data)
except ValidationError as e:
print(e.json())

It will print out the following JSON, which indicates that the input for id is not a valid integer.

Root Validator

Validation can also be performed on the entire model's data.


As with field validators, root validators can have pre=True, in which case they're called before field validation occurs (and are provided with the raw input data), or pre=False (the default), in which case they're called after field validation.

Field validation will not occur if pre=True root validators raise an error. As with field validators, "post" (i.e. pre=False) root validators by default will be called even if prior validators fail; this behaviour can be changed by setting the skip_on_failure=True keyword argument to the validator. The values argument will be a dict containing the values which passed field validation and field defaults where applicable.

Validator

Furthermore, you can create your own custom validators using the validator decorator inside your inherited class. Let’s have a look at the following example which determine if the id is of four digits and whether the confirm_password matches the password field.

from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, ValidationError, validator
class User(BaseModel):
id: int
username : str
password : str
confirm_password : str
alias = 'anonymous'
timestamp: Optional[datetime] = None
friends: List[int] = []
@validator('id')
def id_must_be_4_digits(cls, v):
if len(str(v)) != 4:
raise ValueError('must be 4 digits')
return v
@validator('confirm_password')
def passwords_match(cls, v, values, **kwargs):
if 'password' in values and v != values['password']:
raise ValueError('passwords do not match')
return v

No comments:

Post a Comment