Use pydantic with the Django REST framework
Project description
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 :heart_eyes:.
[!NOTE] The latest version of
drf_pydanticonly supportspydanticv2. Support forpydanticv1 is available in the1.*version.
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.
[!NOTE] There will be a minor penalty if
validate_pydanticis set toTruedue to pydantic model validation. This is minimal compared to an already-present overhead of DRF itself because pydantic runs its validation in rust while DRF is pure python.
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, 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)
[!NOTE] Models created using
drf_pydanticare fully identical to those created bypydantic. The only change is the addition of thedrf_serializeranddrf_configattributes.
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.
[!NOTE] All
drf_configvalues are properly inherited by child classes, just like pydantic'smodel_config.
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
[!NOTE] When a model is invalid from both DRF's and pydantic's perspectives and exceptions are enabled (
.is_valid(raise_exception=True)), DRF'sValidationErrorwill be raised regardless of thevalidation_errorsetting, because DRF validation always runs first.
[!CAUTION] Setting
validation_errortopydantichas side effects:
- It may break your views because they expect DRF's
ValidationError.- Calling
.is_valid()will always raisepydantic.ValidationErrorif the data is invalid, even without setting.is_valid(raise_exception=True).
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(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.
[!WARNING]
- Accessing
pydantic_instancebefore calling.is_valid()will raise an error.- If
validate_pydanticis disabled, accessing it will also raise an error.
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
[!IMPORTANT] Inheritance order is important:
drf_pydantic.BaseModelmust always come beforepydantic.BaseModel.
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.
[!IMPORTANT] When manually configuring the serializer, you are responsible for setting all properties of the fields (e.g.,
allow_null,required,default, etc.).drf_pydanticdoes not perform any introspection for fields that are manually configured or for any fields if a custom serializer is used.
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
[!IMPORTANT] Added in version
v2.6.0Manual
drf_serializermust have base class ofDrfPydanticSerializerin order for Pydantic Validation to work properly. You can still use standardSerializerfromrest_framework, but automatic pydantic model validation will not work consistently and you will get a warning.
Additional Properties
Additional field properties are mapped as follows (pydantic -> DRF):
description->help_texttitle->labelStringConstraints->min_lengthandmax_lengthandallow_blankpattern-> 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)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file drf_pydantic-2.9.1.tar.gz.
File metadata
- Download URL: drf_pydantic-2.9.1.tar.gz
- Upload date:
- Size: 13.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
76070a9abb132395eb4d2ebe65abb2e842d0efa0cc967c9fa19ef14491dc0f39
|
|
| MD5 |
a9c09b5859d0eb19df96a1d49c6e61b7
|
|
| BLAKE2b-256 |
718de9df8b818817b120f602117a1815b0192eff740f473804a489d5f9db9fa3
|
File details
Details for the file drf_pydantic-2.9.1-py3-none-any.whl.
File metadata
- Download URL: drf_pydantic-2.9.1-py3-none-any.whl
- Upload date:
- Size: 15.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
85458a511cf4006bc610724bce54d58255e9c96221be4a51e343671ff183fea1
|
|
| MD5 |
73c396ac7e2f7769f0b58dd29be71ce7
|
|
| BLAKE2b-256 |
497f1081d3569684919cd25058547262cfae5504b457568349233312a5e23536
|