Use pydantic with Django REST framework
Introduction
Pydantic is a Python library used to perform data serialization and validation.
Django REST framework is a framework built on top of Django used to write REST APIs.
If you develop DRF APIs and rely on pydantic for data validation/(de)serialization,
then drf-pydantic is for you 😍.
Performance
Translation between pydantic models and DRF serializers is done during class
creation (e.g., when you first import the model). This means there will be
zero runtime impact when using drf_pydantic in your application.
Installation
pip install drf-pydantic
Usage
General
Use drf_pydantic.BaseModel instead of pydantic.BaseModel when creating your models:
from drf_pydantic import BaseModel
class MyModel(BaseModel):
name: str
addresses: list[str]
MyModel.drf_serializer is equivalent to the following DRF Serializer class:
class MyModelSerializer:
name = CharField(allow_null=False, required=True)
addresses = ListField(
allow_empty=True,
allow_null=False,
child=CharField(allow_null=False),
required=True,
)
Whenever you need a DRF serializer, you can get it from the model like this:
my_value = MyModel.drf_serializer(data={"name": "Van", "addresses": ["Gym"]})
my_value.is_valid(raise_exception=True)
Pydantic Validation
By default, the generated serializer only uses DRF's validation; however, pydantic
models are often more complex and their numerous validation rules cannot be fully
translated to DRF. To enable pydantic validators to run whenever the generated
DRF serializer validates its data (e.g., via .is_valid()),
set "validate_pydantic": True within the drf_config property of your model:
from drf_pydantic import BaseModel
class MyModel(BaseModel):
name: str
addresses: list[str]
drf_config = {"validate_pydantic": True}
my_serializer = MyModel.drf_serializer(data={"name": "Van", "addresses": []})
my_serializer.is_valid() # this will also validate MyModel
With this option enabled, every time you validate data using your DRF serializer,
the parent pydantic model is also validated. If it fails, its
ValidationError exception will be wrapped within DRF's ValidationError.
Per-field and non-field (object-level) errors are wrapped
similarly to how DRF handles them. This ensures your complex pydantic validation logic
is properly evaluated wherever a DRF serializer is used.
Updating Field Values
By default, drf_pydantic updates values in the DRF serializer
with those from the validated pydantic model:
from drf_pydantic import BaseModel
class MyModel(BaseModel):
name: str
addresses: list[str]
@pydantic.field_validator("name")
@classmethod
def validate_name(cls, v):
assert isinstance(v, str)
return v.strip().title()
drf_config = {"validate_pydantic": True}
my_serializer = MyModel.drf_serializer(data={"name": "van herrington", "addresses": []})
my_serializer.is_valid()
print(my_serializer.validated_data) # {"name": "Van Herrington", "addresses": []}
This is handy when you dynamically modify field values within your
pydantic validators. You can disable this behavior by setting
"backpopulate_after_validation": False:
class MyModel(BaseModel):
...
drf_config = {"validate_pydantic": True, "backpopulate_after_validation": False}
Validation Errors
By default, pydantic's ValidationError is wrapped within DRF's ValidationError.
If you want to raise pydantic's ValidationError directly,
set "validation_error": "pydantic" in the drf_config property of your model:
import pydantic
from drf_pydantic import BaseModel
class MyModel(BaseModel):
name: str
addresses: list[str]
@pydantic.field_validator("name")
@classmethod
def validate_name(cls, v):
assert isinstance(v, str)
if v != "Billy":
raise ValueError("Wrong door")
return v
drf_config = {"validate_pydantic": True, "validation_error": "pydantic"}
my_serializer = MyModel.drf_serializer(data={"name": "Van", "addresses": []})
my_serializer.is_valid() # this will raise pydantic.ValidationError
Existing Models
If you have an existing code base and want to add the drf_serializer
attribute only to some of your models, you can extend your existing pydantic models
by adding drf_pydantic.BaseModel as a parent class to the models you want to extend.
Your existing pydantic models:
from pydantic import BaseModel
class Pet(BaseModel):
name: str
class Dog(Pet):
breed: str
Update your Dog model and get serializer via the drf_serializer:
from drf_pydantic import BaseModel as DRFBaseModel
from pydantic import BaseModel
class Pet(BaseModel):
name: str
class Dog(DRFBaseModel, Pet):
breed: str
Dog.drf_serializer
Nested Models
If you have nested models and want to generate a serializer for only one of them,
you don't need to update all models. Simply update the model you need,
and drf_pydantic will automatically generate serializers
for all standard nested pydantic models:
from drf_pydantic import BaseModel as DRFBaseModel
from pydantic import BaseModel
class Apartment(BaseModel):
floor: int
tenant: str
class Building(BaseModel):
address: str
apartments: list[Apartment]
class Block(DRFBaseModel):
buildings: list[Building]
Block.drf_serializer
Manual Serializer Configuration
If drf_pydantic doesn't generate the serializer you need,
you can configure the DRF serializer fields for each pydantic field manually,
or create a custom serializer for the model altogether.
Per-Field Configuration
from typing import Annotated
from drf_pydantic import BaseModel
from rest_framework.serializers import IntegerField
class Person(BaseModel):
name: str
age: Annotated[float, IntegerField(min_value=0, max_value=100)]
Custom Serializer
In the example below, Person will use MyCustomSerializer as its DRF serializer.
Employee will have its own serializer generated by drf_pydantic since it doesn't
inherit a user-defined drf_serializer attribute.
Company will use Person's manually defined serializer for its ceo field.
from drf_pydantic import BaseModel, DrfPydanticSerializer
from rest_framework.serializers import CharField, IntegerField
class MyCustomSerializer(DrfPydanticSerializer):
name = CharField(allow_null=False, required=True)
age = IntegerField(allow_null=False, required=True)
class Person(BaseModel):
name: str
age: float
drf_serializer = MyCustomSerializer
class Employee(Person):
salary: float
class Company(BaseModel):
ceo: Person
Additional Properties
Additional field properties are mapped as follows (pydantic -> DRF):
description->help_texttitle->labelStringConstraints->min_lengthandmax_lengthpattern-> Uses the specializedRegexFieldserializer fieldmax_digitsanddecimal_placesare carried over (used forDecimaltypes, with the current decimal context precision)ge/gt->min_value(only for numeric types)le/lt->max_value(only for numeric types)
Metadata
Release files for drf-pydantic 2.8.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| drf_pydantic-2.8.0.tar.gz | 12.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| drf_pydantic-2.8.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 26.5 kB
Release files / drf_pydantic-2.8.0.tar.gz
| Download URL | drf_pydantic-2.8.0.tar.gz |
|---|---|
| Size | 12.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
16c2dbead529311629f421bb360cf286a406878a5e882106a59876dc677666fe
|
|
BLAKE2b-256 checksum How to use checksums |
09cc10da0dfb667a40b92e4f6bce037b121b5911bc67dc1caa2f249d7a633bad
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.7.21
|
Release files / drf_pydantic-2.8.0-py3-none-any.whl
| Download URL | drf_pydantic-2.8.0-py3-none-any.whl |
|---|---|
| Size | 14.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
99898da4a9acc24d7298ed5613cd4115156607be3aa39d5dfcde0be2045f5bff
|
|
BLAKE2b-256 checksum How to use checksums |
53243d0e22356100eda57d8ef32c25f98c87d9ea0991ba7e7dbd7b71d5eaa2b7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.7.21
|