Pydantic Data Validation

A better way to validate

·

2 min read

The last blog discussed a method of data validation, exemplified through a POST request, that utilized a class whose __init__ method attempted to assign parameters and threw an error if a parameter did not exist. Unfortunately, that class structure was a bit simplistic and also lacked features such as too many parameters being passed through.

However, Pydantic provides a better method to ensure your data looks exactly as it should. Here is a simple example to show.

Pydantic models are easy to set up. Just import and pass the BaseModel to your class.

from pydantic import BaseModel
class Person(BaseModel): 
    first_name: str
    last_name: str

This is very similar to a class with the dataclass decorator and now you can initialize your model:

>>> person = Person(first_name="nav", last_name="610") 
>>> person.first_name
'nav'

I can also add validators to my model, such that on initialization, the input parameters are checked against my custom validators:

class Person(BaseModel): 
    first_name: str
    last_name: str
    age: Optional[int]
    email: Optional[str]

    @validator('email')
    def validate_email(cls, email): 
        if email and not any(email.endswith(provider) for provider in ['@gmail.com', '@yahoo.com', '@outlook.com']): 
            raise ValidationError
        return email

I've added two extra parameters, age and email, and can validate the email upon initialization. If the email doesn't end properly, a ValidationError is raised

Also, I can define a configuration such that extra data cannot be passed through.

class Person(BaseModel): 
    first_name: str
    last_name: str
    age: Optional[int]
    email: Optional[str]

    @validator('email')
    def validate_email(cls, email): 
        if email and not any(email.endswith(provider) for provider in ['@gmail.com', '@yahoo.com', '@outlook.com']): 
            raise ValidationError
        return email

    class Config:
        extra = Extra.forbid

This configuration forbids the passing of extra parameters and will throw the following Validation error if extra parameters are passed through:

1 validation error for Person
blah
  extra fields not permitted (type=value_error.extra)

Now I can use the same POST request example from the previous blog and get the following:

from flask import Flask 
from flask import request
from flask import jsonify
from pydantic import BaseModel, ValidationError, validator, Extra
from typing import Optional

app = Flask(__name__)

class Person(BaseModel): 
    first_name: str
    last_name: str
    age: Optional[int]
    email: Optional[str]

    @validator('email')
    def validate_email(cls, email): 
        if email and not any(email.endswith(provider) for provider in ['@gmail.com', '@yahoo.com', '@outlook.com']): 
            raise ValidationError
        return email

    class Config:
        extra = Extra.forbid

@app.route("/create", methods=["POST"])
def create(): 
    payload = request.get_json()
    person: Person = Person(**payload)
    return jsonify({"status": "ok"})


if __name__ == "__main__": 
    app.run(host="0.0.0.0", debug=True, port=8888)

A POST request with very easy data validation!