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(Serializer):
name = CharField(allow_null=False, required=True, allow_blank=True)
addresses = ListField(
allow_empty=True,
allow_null=False,
child=CharField(allow_null=False, allow_blank=True),
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:
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)
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. Nested pydantic models (including those inside lists,
tuples, and dicts) are converted to plain dicts, just like DRF's nested serializers
produce. 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
Accessing the Validated Pydantic Instance
When validate_pydantic is enabled and .is_valid() has been called successfully,
the generated serializer exposes the fully validated pydantic model instance via the
pydantic_instance property:
serializer = MyModel.drf_serializer(data={"name": "Van", "addresses": []})
serializer.is_valid(raise_exception=True)
print(repr(serializer.pydantic_instance)) # MyModel(name='Van', addresses=[])
This lets you work directly with your original pydantic model (including any
mutations applied in validators) instead of DRF's validated_data dictionary.
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 its serializer via 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 because it doesn't
declare drf_serializer itself (a manually set serializer is not inherited).
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->labeldefault/default_factory->default(the factory is called each time a default is needed, not once)Optional[...]->allow_null=Truemin_length/max_length(includingStringConstraints) ->min_lengthandmax_length;allow_blankisTruefor strings unlessmin_lengthis above 0 (it is alwaysFalseforEmailStrand URL types)pattern-> Uses the specializedRegexFieldserializer field (allow_blankisTrueonly if the pattern matches an empty string)max_digitsanddecimal_placesare carried over forDecimaltypes; ifmax_digitsis not set, the current decimal context precision is used, and ifdecimal_placesis not set, the number of decimal places is not limitedge/gt->min_value(only for numeric types;gtis treated asgewith a warning because DRF doesn't support exclusive bounds)le/lt->max_value(only for numeric types;ltis treated aslewith a warning)aliasis not used: serializer fields are named after the pydantic field names
Metadata
Release files for drf-pydantic 2.9.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| drf_pydantic-2.9.3.tar.gz | 14.0 kB | Without provenance | Details for drf_pydantic-2.9.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| drf_pydantic-2.9.3-py3-none-any.whl | Python 3 | none | any | Details for drf_pydantic-2.9.3-py3-none-any.whl |
Total release size: 30.0 kB
Release files / drf_pydantic-2.9.3.tar.gz
| Download URL | drf_pydantic-2.9.3.tar.gz |
|---|---|
| Size | 14.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
411764ea80355a7fd7c666dc6b731705fd5573c5bcd9c856f2f5d65af8b09d2f
|
|
BLAKE2b-256 checksum How to use checksums |
ae57d2a907a1de8c6c3ce5e9ccdd1f1f774d59bcfc7f6039423ef190548e7c94
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / drf_pydantic-2.9.3-py3-none-any.whl
| Download URL | drf_pydantic-2.9.3-py3-none-any.whl |
|---|---|
| Size | 16.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fd168468125c4bee9fe11d1e90434d4c239a3136f6249da3a497405f74af2bee
|
|
BLAKE2b-256 checksum How to use checksums |
8a99ca3683f4584d35b9d1d4893bb2ea5f8d3ce9f7127fc60c7dfa13f4e6c7c8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for drf-pydantic 2.9.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.