Skip to main content

Test Status Test Coverage PyPI version

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_text
  • title -> label
  • StringConstraints -> min_length and max_length
  • pattern -> Uses the specialized RegexField serializer field
  • max_digits and decimal_places are carried over (used for Decimal types, 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)

Source distribution for drf-pydantic 2.8.0
File Size Uploaded
drf_pydantic-2.8.0.tar.gz 12.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for drf-pydantic 2.8.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

2.9.2

2 release files

2.9.1

2 release files

2.9.0

2 release files

This release

2.8.0 This release

2 release files

2.7.1

2 release files

2.7.0

2 release files

2.6.1

2 release files

2.6.0

2 release files

2.5.1

2 release files

2.5.0

2 release files

2.4.0

2 release files

2.3.0

2 release files

2.2.0

2 release files

2.1.0

2 release files

2.0.0

2 release files

1.0.0

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page