Skip to main content

Use pydantic with the Django REST framework

Project description

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 :heart_eyes:.

[!NOTE] The latest version of drf_pydantic only supports pydantic v2. Support for pydantic v1 is available in the 1.* 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_pydantic is set to True due 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_pydantic are fully identical to those created by pydantic. The only change is the addition of the drf_serializer and drf_config attributes.

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_config values are properly inherited by child classes, just like pydantic's model_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's ValidationError will be raised regardless of the validation_error setting, because DRF validation always runs first.

[!CAUTION] Setting validation_error to pydantic has side effects:

  1. It may break your views because they expect DRF's ValidationError.
  2. Calling .is_valid() will always raise pydantic.ValidationError if 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_instance before calling .is_valid() will raise an error.
  • If validate_pydantic is 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.BaseModel must always come before pydantic.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_pydantic does 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.0

Manual drf_serializer must have base class of DrfPydanticSerializer in order for Pydantic Validation to work properly. You can still use standard Serializer from rest_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_text
  • title -> label
  • StringConstraints -> min_length and max_length and allow_blank
  • 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)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

drf_pydantic-2.9.1.tar.gz (13.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

drf_pydantic-2.9.1-py3-none-any.whl (15.0 kB view details)

Uploaded Python 3

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

Hashes for drf_pydantic-2.9.1.tar.gz
Algorithm Hash digest
SHA256 76070a9abb132395eb4d2ebe65abb2e842d0efa0cc967c9fa19ef14491dc0f39
MD5 a9c09b5859d0eb19df96a1d49c6e61b7
BLAKE2b-256 718de9df8b818817b120f602117a1815b0192eff740f473804a489d5f9db9fa3

See more details on using hashes here.

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

Hashes for drf_pydantic-2.9.1-py3-none-any.whl
Algorithm Hash digest
SHA256 85458a511cf4006bc610724bce54d58255e9c96221be4a51e343671ff183fea1
MD5 73c396ac7e2f7769f0b58dd29be71ce7
BLAKE2b-256 497f1081d3569684919cd25058547262cfae5504b457568349233312a5e23536

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page