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(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_text
  • title -> label
  • default / default_factory -> default (the factory is called each time a default is needed, not once)
  • Optional[...] -> allow_null=True
  • min_length / max_length (including StringConstraints) -> min_length and max_length; allow_blank is True for strings unless min_length is above 0 (it is always False for EmailStr and URL types)
  • pattern -> Uses the specialized RegexField serializer field (allow_blank is True only if the pattern matches an empty string)
  • max_digits and decimal_places are carried over for Decimal types; if max_digits is not set, the current decimal context precision is used, and if decimal_places is not set, the number of decimal places is not limited
  • ge / gt -> min_value (only for numeric types; gt is treated as ge with a warning because DRF doesn't support exclusive bounds)
  • le / lt -> max_value (only for numeric types; lt is treated as le with a warning)
  • alias is 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)

Source distribution for drf-pydantic 2.9.3
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)

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

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