Pydantic – Handling Validation Errors in Python Cleanly

17.04.2024Wasili Aebi
Tech Python Framework validation Development Integration Tutorial How-to

Banner

Welcome to my first TechUp! I’m excited to introduce you to a cool module today: Pydantic. 🎉

My first TechUp is about Pydantic. Every programmer knows this moment: Suddenly a function throws an error because the type of the passed data is not correct. To address this problem, Pydantic was developed. Today I will introduce you to this module and show how it offers solutions in different ways. If you want to see the repository, you can find it here: https://github.com/b-nova-techhub/pydantic

In summary, Pydantic offers the following features that I will go through with you:

  1. Recommendations
  2. Validation
  3. Serialization
  4. JSON Schema
  5. Custom Type Creation

But wait! Before we start, let me give you some important key facts about Pydantic.

Key Facts

First, let’s look at some details about Pydantic.😀 Samuel Colvin is the founder of Pydantic. The goal of the Pydantic Python module is to simplify typing. When data is missing or incorrect, Pydantic generates validation errors. This is optimal for larger companies that rely on a clean data structure. The core of Pydantic is written in Rust. This means we have a lot of power⚡️ under the hood and things run fast. Moreover, Pydantic has a large community and is used by leading companies like Microsoft and Netflix. Not only that - Pydantic is also open source and has existed since 2018.

Installation

The installation of Pydantic is as simple as it looks. First, run the following command:

1
pip install pydantic

For Pydantic’s features to work properly, I also recommend installing the IntelliJ Pydantic plugin.

img_20.png

With that, you have Pydantic installed and can get started. 🎉🎉🎉

Basic Model

After installing Pydantic, we can start with the first step. We create a Base Model. In our example, we use User as the object.

1
2
3
4
5
6
7
8
from pydantic import BaseModel, EmailStr, FilePath

class User(BaseModel):
    name: str
    age: int
    email: EmailStr
    is_active: bool = True
    is_admin: bool = False

In the Basic Model, we simply define the structure of the data. Then we can use the types in other files. In our case, we created a User model with the types str, int, and bool. When calling the User object with two parameters that are not defined in the base model, we get the following output:

img_26.png

As you can see in the figure, Pydantic marks the undefined types in our case - here filepath - in yellow. Even if you have defined all types, the program still works when executed and the missing type is ignored.

Pydantic also recommends using the two parameters that I strangely forgot 😄 is_active and is_admin. This makes it much easier to set the required parameters. In addition, an exception is thrown if a parameter is missing. (Which would not be the case with dataclasses.) It’s important that you have installed the corresponding plugin, because without it the feature didn’t work initially.

The biggest advantage of Pydantic is that type errors are automatically detected: For example, if we define name in our special case as an integer, even though it is set as a string in the BaseModel, an exception is thrown. You can also see this in the image.

img_23.png

Not only the simplicity of Pydantic is impressive, but also the variety of types offered that dataclasses do not provide. On the website https://docs.pydantic.dev/1.10/usage/types/ you can see all available types. In my post, I will specifically introduce the Email and FilePath types.

For email validation, an additional module needs to be installed.

1
pip install email-validator

After that, you can simply use EmailStr and FilePath in the imports.

1
2
3
4
5
6
7
8
9
from pydantic import BaseModel, EmailStr, FilePath

class User(BaseModel):
    name: str
    age: int
    email: EmailStr
    is_active: bool
    is_admin: bool
    filepath: FilePath

If an email is not correct, an error is thrown. Also, if the FilePath does not exist, an error is thrown. This is really very convenient. The same applies to the other types that Pydantic offers.

EmailStr

img_8.png

FilePath

img_11.png

Output on Successful Validation

If all types are defined correctly, the data is output in a dictionary.

img_12.png

JSON Schema

If you want your output in JSON format, there is also a way to do that with Pydantic. To do this, simply add your model and model_json.schema(). See below. In my case, I print the JSON directly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import json
from pydantic import BaseModel, EmailStr, FilePath


class User(BaseModel):
    name: str
    age: int
    email: EmailStr
    is_active: bool
    is_admin: bool
    filepath: FilePath


user_model_schema = User.model_json_schema()
print(json.dumps(user_model_schema, indent=2))

img_2.png

Creating Custom Types

If you want to define your own type, Pydantic also offers the possibility to do so. For this, you first have to import from typing import Annotated. Then you can create your own types with Annotated. Unfortunately, the import on the website is wrong, so I recommend looking at the GitHub repos to make sure you do it correctly. For example, Annotated[int, Field(gt=0)] is deprecated. Instead, Annotated[int, (Gt(0)] should be used.

https://github.com/annotated-types/annotated-types With Gt, it is defined that only numbers greater than the defined number, in our case 0, are accepted. For us to also get an exception, a ValidationError must be added. The TypeAdapter inserts the type into the model and makes it usable. As can be seen in the following code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from typing import Annotated
from annotated_types import Gt
from pydantic import BaseModel, EmailStr, FilePath, TypeAdapter

positivInt = Annotated[int, Gt(0)]

ta = TypeAdapter(positivInt)

class User(BaseModel):
    name: str
    age: int
    email: EmailStr
    is_active: bool = True
    is_admin: bool = False
    filepath: FilePath
    bought: positivInt

By creating our own type, we have now created a positivInt. This type only accepts numbers greater than 0. Optimal for an e-commerce project where the number of products must not be less than 0 🎉🙂 .

Custom Type Output

In my example, I defined bought=0. Since 0 is not greater than 0, an error is thrown. If the number is higher than 0, the output is displayed as usual.

1
2
3
4
from util_own_types import User

user = User(name="waebi", age=25, email=wasili.aebi@b-nova.com, filepath="./test.txt", bought=0)
print(user.dict())

img_28.png

Dataclass Modifications

Have you already built your project with dataclasses but want to use features of Pydantic? Pydantic offers the possibility to do this with a wrapper around dataclasses. To create this, you simply have to import from pydantic.dataclasses import dataclass. Then you can simply write @dataclass above your dataclasses and you already have some features of Pydantic. Of course, Pydantic’s Basemodel itself offers more features than dataclasses. Therefore, I recommend using Pydantic models. However, it is a good way to integrate validations into existing projects.

Here is an example without Pydantic :)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import dataclasses

@dataclasses.dataclass
class User:
    name: str
    age: int
    email: str
    is_active: bool
    is_admin: bool
    filepath: str

Here is an example with Pydantic as a wrapper. With Pydantic added, you have the advantage of validation. You can also use the EmailStr type, for example, through an import. Everything is visible in the following code. The big advantage is that you then also get validation errors.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from pydantic.dataclasses import dataclass

@dataclass
class User:
    name: str
    age: int
    email: EmailStr
    is_active: bool
    is_admin: bool
    filepath: str

img_27.png

Why Pydantic and not Dataclasses?

If you are working on a large project or would like to use types like Email, Filepath or others, I recommend using Pydantic. As I showed you in the Techup, you can also generate a JSON output, which is of course very useful for some projects. With dataclasses, you would have to write the validation yourself. In Pydantic, this happens automatically.

Conclusion

My conclusion about Pydantic is that it is a very good module. It is easy to use, has a large tech community, and offers many types. It is important to orient yourself primarily on GitHub for the documentation. On the website, some things are wrong or do not work as specified. If you have a larger project, it will certainly be a very good tool for typing and validation.

I hope I was able to help you with my Techup and wish you a lot of fun programming with Pydantic. :) If you are interested in more details about Pydantic, let me know :) If you have any questions, just get in touch!

This techup has been translated automatically by Gemini

Wasili Aebi

With tireless drive and unwavering enthusiasm, I dive deep into the world of IT to learn as much as possible. As a Padawan developer still in training, I am constantly looking for new challenges and exciting projects in languages such as Python, Node.js, Java, Go, and Ballerina.