Dynamic Data Models in Python Using Pydantic's Discriminator
Written on
Introduction to Pydantic
In Python development, validating data is crucial for maintaining the integrity and reliability of applications. Whether you're developing a web API, creating a data-centric application, or managing user input, ensuring data accuracy is essential. This is where the Pydantic library excels.
Pydantic is a robust Python library that has gained traction for its efficient and elegant approach to data validation and parsing. It allows developers to define data models in a simple, declarative manner, making it easy to validate and sanitize input data. If you've ever struggled with extensive validation code or intricate data structures, Pydantic can significantly simplify your workflow.
Scope of the Article
This article assumes a basic understanding of how to use Pydantic, as we won't be starting from the ground up. Pydantic has excellent documentation available online, along with a wealth of resources to help newcomers get acquainted with the library.
Today, we will delve into one of Pydantic’s powerful features known as the discriminator, which can be incredibly useful in various scenarios.
Understanding Pydantic's Discriminator
A discriminator in Pydantic is a valuable feature that enables differentiation between various subclasses of a parent model based on the value of a specific field. This is especially useful when working with polymorphic data structures or complex hierarchies.
By incorporating a discriminator field into your Pydantic model, you can dictate which subclass to instantiate during data parsing. This flexibility is particularly beneficial when handling diverse data formats like JSON with varying structures.
This feature not only streamlines data parsing but also enhances code organization and maintainability, making Pydantic an even more powerful tool for complex data validation scenarios.
Use Case Example
Let's consider two models of data that are quite similar but differ in a few fields. For our example, we will create models for dogs and cats.
Dog Model:
from pydantic import BaseModel
class Dog(BaseModel):
age: int
color: str
height: int
endless_love: bool # A boolean indicating endless love
Cat Model:
from pydantic import BaseModel
class Cat(BaseModel):
age: int
color: str
height: int
hairy: bool # A boolean indicating if the cat is hairy
lives: int # The number of lives the cat has
Notice that both models share three common fields: age, color, and height. While this might not be an issue with just a couple of models, managing dozens or hundreds of data models in larger projects could lead to unnecessary redundancy.
Solution with Pydantic's Discriminator
Pydantic offers a solution to this issue by allowing you to define a "parent" model encompassing the shared fields. You can then instantiate one of the subclasses with their unique fields based on a discriminator field specified in the parent class. This leads to an object that combines the parent model's fields with its subclass-specific fields.
Here’s how you can define a parent class for animals:
from typing import Union
from pydantic import BaseModel, Field
class Animal(BaseModel):
pet: Union[Cat, Dog] = Field(..., discriminator='pet_type')
age: int
color: str
height: int
Now, the two initial data models can be reshaped as follows:
from typing_extensions import Literal
from pydantic import BaseModel
class Dog(BaseModel):
pet_type: Literal['dog']
endless_love: bool
class Cat(BaseModel):
pet_type: Literal['cat']
hairy: bool
lives: int
Demo Implementation
Let’s implement this setup by creating an animal object for both a dog and a cat and printing their models:
dog_animal = Animal(
pet={
'pet_type': 'dog',
'endless_love': True
},
age=3,
color="black",
height=50
)
cat_animal = Animal(
pet={
'pet_type': 'cat',
'lives': 9,
'hairy': False
},
age=2,
color="white",
height=35
)
print(dog_animal)
print(cat_animal)
Upon execution, the output will show the properties age, color, and height defined through the Animal model for both objects.
The discriminator field on the pet attribute receives the pet_type value, enabling the instantiation of the correct model for each object. This means that each model is accurately parsed and validated through Pydantic.
Error Handling
Let’s explore what happens if we mistakenly omit a property:
try:
dog_animal = Animal(
pet={
'pet_type': 'dog',},
age=3,
color="black",
height=50
)
except ValidationError as e:
print(e)
try:
cat_animal = Animal(
pet={
'pet_type': 'cat',
'hairy': False
},
age=2,
color="white",
height=35
)
except ValidationError as e:
print(e)
When executed, this snippet will display validation errors indicating that required fields are missing for both subclasses.
Here’s the complete code snippet for anyone interested in trying it out:
from typing_extensions import Literal
from typing import Union
from pydantic import BaseModel, Field, ValidationError
class Dog(BaseModel):
pet_type: Literal['dog']
endless_love: bool
class Cat(BaseModel):
pet_type: Literal['cat']
lives: int
hairy: bool
class Animal(BaseModel):
pet: Union[Cat, Dog] = Field(..., discriminator='pet_type')
age: int
color: str
height: int
dog_animal = Animal(
pet={
'pet_type': 'dog',
'endless_love': True
},
age=3,
color="black",
height=50
)
cat_animal = Animal(
pet={
'pet_type': 'cat',
'lives': 9,
'hairy': False
},
age=2,
color="white",
height=35
)
print(dog_animal)
print(cat_animal)
try:
dog_animal = Animal(
pet={
'pet_type': 'dog',},
age=3,
color="black",
height=50
)
except ValidationError as e:
print(e)
try:
cat_animal = Animal(
pet={
'pet_type': 'cat',
'hairy': False
},
age=2,
color="white",
height=35
)
except ValidationError as e:
print(e)
Conclusion
In summary, Pydantic serves as an excellent tool for modeling, validating, and managing data structures in Python. The discriminator feature we explored is just one of its many capabilities, showcasing its potential to simplify complex data handling.
I hope you found this article helpful! If you did, please share your thoughts in the comments and feel free to follow for more content like this.
Video Resources
Learn more about Pydantic and its applications:
This video provides insights into why Pydantic is essential for Python developers in 2024.
This tutorial addresses some of Python's significant challenges and how Pydantic can help solve them.