Pydantic – Validation Errors in Python sauber behandeln

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

Banner

Herzlich willkommen zu meinem ersten TechUp! Ich freue mich, euch heute ein cooles Modul vorzustellen: Pydantic. 🎉

In meinem ersten TechUp geht es um Pydantic. Jeder Programmierer kennt diesen Moment: Plötzlich wirft eine Funktion einen Fehler, weil der Typ der übergebenen Daten nicht korrekt ist. Um dieses Problem zu adressieren, wurde Pydantic entwickelt. Heute werde ich euch dieses Modul vorstellen und zeigen, wie es auf unterschiedliche Weise Lösungen bietet. Falls du das Repository sehen möchtest, findest du es hier: https://github.com/b-nova-techhub/pydantic

Zusammenfassend bietet Pydantic folgende Features, die ich mit euch durchgehen werde:

  1. Empfehlungen
  2. Validation
  3. Serialisierung
  4. JSON Schema
  5. Eigene Typen Erstellung

Aber halt! Bevor wir starten, lass mich dir noch ein paar wichtige Eckdaten über Pydantic geben.

Eckdaten

Zuerst kommen wir zu paar Details über Pydantic.😀 Samuel Colvin ist der Gründer von Pydantic. Das Ziel vom Pydantic Python Modul ist die Typisierung zu vereinfachen. Wenn die Daten fehlen oder inkorrekt sind, erzeugt Pydantic Validierungsfehler. Das ist optimal für grössere Firmen die auf eine saubere Datenstruktur angewiesen sind. Der Kern von Pydantic ist in Rust geschrieben. Das bedeutet, dass wir richtig viel Power⚡️ unter der Haube haben und die Sache schnell läuft. Darüber hinaus verfügt Pydantic über eine grosse Community und wird von führenden Unternehmen wie Microsoft und Netflix verwendet. Nicht nur das – Pydantic ist auch Open Source und existiert bereits seit 2018.

Installation

Die installation von Pydantic ist so einfach wie es aussieht. Zuerst führst du folgenden Befehl aus:

1
pip install pydantic

Damit die Features von Pydantic richtig funktionieren, empfehle ich dir auch noch das IntelliJ Plugin Pydantic zu installieren.

img_20.png

Damit hast du Pydantic installiert und kannst loslegen. 🎉🎉🎉

Basic Model

Nach der Installation von Pydantic können wir mit dem ersten Schritt beginnen. Wir erstellen ein Base Model. In unserem Beispiel verwenden wir User als Objekt.

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

Beim Basic Modell definieren wir ganz einfach die Struktur der Daten. Danach können wir in anderen Files die Typen verwenden. In unserem Fall haben wir ein Modell User mit den Typen str int und bool erstellt. Bei Aufruf des User-Objekts mit zwei Parametern, die nicht im Basismodell definiert sind, erhalten wir folgenden Output:

img_26.png

Wie du in der Abbildung erkennen kannst, markiert Pydantic die undefinierten Typen in unserem Fall – hier filepath – gelb. Selbst wenn du alle Typen definiert hast, funktioniert das Programm beim Ausführen dennoch und der fehlende Typ wird ignoriert.

Auch empfiehlt mir Pydantic die Verwendung der zwei Parameter die ich komischerweise vergessen habe 😄 is_active und is_admin. Dies macht es viel einfacher, die erforderlichen Parameter zu setzen. Zudem wird eine Exception ausgelöst, wenn ein Parameter fehlt. (Was bei Dataclasses nicht der Fall wäre.) Wichtig ist, dass du das entsprechende Plugin installiert hast, denn ohne dieses hat das Feature anfangs nicht funktioniert.

Der grösste Vorteil von Pydantic besteht darin, dass Typenfehler automatisch erkannt werden: Wenn wir beispielsweise name in unserem speziellen Fall als Integer definieren, obwohl es im BaseModel als String festgelegt wurde, wird eine Exception ausgelöst. Dies kannst du auch im Bild sehen.

img_23.png

Nicht nur die Einfachheit von Pydantic ist beeindruckend, sondern auch die Vielfalt der angebotenen Typen, die Dataclasses nicht zur Verfügung stellen. Auf der Webseite https://docs.pydantic.dev/1.10/usage/types/ kannst du dir alle verfügbaren Typen ansehen. In meinem Beitrag werde ich speziell die Typen Email und FilePath vorstellen.

Für die Validierung von E-Mails muss zusätzlich ein weiteres Modul installiert werden.

1
pip install email-validator

Danach kannst du einfach in den Imports EmailStr und FilPath verwenden.

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

Bei einer Mail die nicht korrekt ist, wird ein Fehler geworfen. Auch wenn der FilePath nicht existiert wird ein Fehler geworfen. Das ist wirklich sehr angenehm. Das gleiche gilt auch für die anderen Typen die Pydantic anbietet.

EmailStr

img_8.png

FilePath

img_11.png

Output bei erfolgreicher Validierung

Wurden alle Typen richtig definiert, werden die Daten in einem Dictionary ausgegeben.

img_12.png

JSON Schema

Willst du deinen Output in JSON Format so gibt es auch eine Möglichkeit dazu mit Pydantic. Dazu fügst du einfach dein Model und model_json.schema() hinzu. siehe unten. In meinem Fall printe ich das JSON direkt aus.

 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

Eigene Typen erstellen

Möchtest du ein eigenen Typ definieren bietet Pydantic auch die Möglichkeit dazu. Dafür musst du einfach zuerst from typing import Annotated importieren. Danach kannst du mit Annotated deinen eigenen Typen erstellen. Leider ist auf Website der Import falsch und deshalb empfehle ich die Github Repos anzuschauen um sicherzustellen das ihr es richtig macht. Zum Beispiel ist Annotated[int, Field(gt=0)] veraltet. Stattdessen sollte Annotated[int, (Gt(0)]verwendet werden.

https://github.com/annotated-types/annotated-types Mit Gt wird definiert, dass nur Zahlen die grösser als die definierte Zahl also in unserem Fall 0 akzeptiert werden. Damit wir auch eine Exception erhalten muss ein ValidationError hinzugefügt werden. Durch den TypeAdapter wird der Typ in das Modell eingefügt und benutzbar. Wie in dem folgenden Code ersichtlich ist.

 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

Durch das Erstellen des eigenen Typs haben wir jetzt ein positivInt erstellt. Dieser Typ akzeptiert nur Zahlen die grösser als 0 sind. Optimal für ein E-Commerce Projekt, bei welchem die Anzahl der Produkte nicht kleiner als 0 sein darf 🎉🙂 .

Eigeneerstellte Typ Ausgabe

In meinem Beispiel habe ich bought=0 definiert. Da 0 nicht grösser als 0 ist, wird ein Fehler geworfen. Ist die Zahl höher als 0 wird die Ausgabe wie gewohnt ausgegeben.

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 Modifikationen

Hast du dein Projekt schon mit Dataclasses aufgebaut willst aber Features von Pydantic nutzen? Dafür bietet Pydantic mit einem Wrapper um Dataclasses die Möglichkeit dazu. Um das zu erstellen musst du einfach from pydantic.dataclasses import dataclass importieren. Danach kannst du einfach @dataclass über deine Dataclasses schreiben und schon hast du manche Features von Pydantic. Natürlich bietet das Basemodel von Pydantic selber mehr Features als Dataclasses. Deshalb empfehle ich die Verwendung von Pydantic Modellen. Jedoch ist es eine gute Möglichkeit Validierungen in bestehende Projekte zu integrieren.

Hier ein Beispiel ohne 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

Hier ein Beispiel mit Pydantic als Wrapper. Mit Pydantic dazu hat man den Vorteil der Validierung. Auch kannst du zum Beispiel den Typ EmailStr durch ein Import verwenden. Alles ersichtlich im folgenden Code. Der grosse Vorteil ist darin, dass du dann auch Validierungsfehler erhältst.

 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

Warum Pydantic und nicht Dataclasses?

Wenn du an einem grossen Projekt arbeitest oder Typen wie Email, Filepath oder andere gerne nutzen möchtest, empfehle ich dir Pydantic zu verwenden. Auch kannst du wie ich dir im Techup gezeigt habe einen JSON Output erzeugen was natürlich für manche Projekte sehr nützlich ist. Bei Dataclasses müsstest du die Validierung selber schreiben. In Pydantic passiert das automatisch.

Fazit

Mein Fazit zu Pydantic ist, dass es ein sehr gutes Modul ist. Es ist einfach zu verwenden, hat eine grosse Tech-Gemeinschaft und bietet viele Typen an. Wichtig ist, dass man sich vor allem in Github für die Dokumentation orientiert. Auf der Website sind manche Sachen falsch oder funktionieren nicht so, wie sie angegeben sind. Falls ihr ein grösseres Projekt habt wird es sicherlich ein sehr gutes Tool zur Typisierung und Validierung sein.

Ich hoffe, dass ich dir mit meinem Techup weiterhelfen konnte und wünsche dir viel Spass beim Programmieren mit Pydantic. :) Wenn dich weitere Details zu Pydantic interessieren, so lass es mich wissen :) Falls du Fragen hast, melde dich einfach!

Wasili Aebi

Mit unermüdlichem Antrieb und unerschütterlichem Enthusiasmus tauche ich tief in die IT-Welt ein, um so viel wie möglich zu lernen. Als Padawan-Entwickler, der sich noch in der Ausbildung befindet, suche ich ständig nach neuen Herausforderungen und spannenden Projekten in Sprachen wie Python, Node.js, Java, Go und Ballerina.