django-pydantic-field

repository·master·Indexed 42 days ago

https://github.com/surenkov/django-pydantic-field

A Django package providing type-safe Pydantic schemas for JSONFields. It enables automatic parsing, validation, and seamless integration with Django Forms, the Django Admin interface, and Django REST Framework. Version 0.5.4 supports Pydantic v2 with backward compatibility for v1 models via django_pydantic_field.v1.

Tokens
8.2K
Snippets
31
Records
35
Agent score
52%

What's inside django-pydantic-field

  1. Handle Forward References in SchemaField

    master

    If a schema is defined later in the code, you can use string literals or typing.ForwardRef to resolve it lazily. Resolution occurs only when the field is first accessed.

    import typing
    from django.db import models
    from django_pydantic_field import SchemaField
    import pydantic
    
    class MyModel(models.Model):
        foo = SchemaField(typing.ForwardRef("DeferredFoo"))
        another_foo: "DeferredFoo" = SchemaField()
    
    class DeferredFoo(pydantic.BaseModel):
        ...
  2. Use django-jsonform in Django Admin

    master

    To provide a better UI in the Django Admin, you can override the formfield widget to use JSONFormWidget from the django-jsonform package. This uses the Pydantic model's JSON schema to provide a dynamic editor.

    from django.contrib import admin
    from django_pydantic_field import fields
    from django_jsonform.widgets import JSONFormWidget
    
    class MyModelAdmin(admin.ModelAdmin):
        formfield_overrides = {
            fields.PydanticSchemaField: {"widget": JSONFormWidget},
        }
  3. Use Pydantic v1 models in a Pydantic v2 environment

    master

    The package detects your Pydantic version automatically. If you are using Pydantic v2 but need to use legacy v1 models, import SchemaField from django_pydantic_field.v1 and use pydantic.v1 for your models.

    from pydantic import v1 as pydantic_v1
    from django_pydantic_field.v1 import SchemaField as SchemaFieldV1
    
    class LegacySchema(pydantic_v1.BaseModel):
        ...
    
    class LegacyModel(models.Model):
        legacy_field = SchemaFieldV1(LegacySchema)
  4. Understand the BaseSchemaAdapter abstraction

    master

    The BaseSchemaAdapter is the core abstraction used to bridge Django fields and Pydantic models. It handles the lifecycle of a schema, including resolution (handling forward references), validation (Python objects and JSON), and serialization (dumping to Python or JSON).

    An adapter can exist in two states:

    1. Unbound: Created via from_type(). It holds a specific schema directly.
    2. Bound: Created via from_annotation(). It doesn't hold the schema immediately but instead looks it up from a Django model's type annotations when it is 'bound' to a specific attribute.

    Key lifecycle methods include:

    • validate_python(value, **kwargs): Validates a Python object.
    • validate_json(value, **kwargs): Validates a JSON string or bytes.
    • dump_python(value, **kwargs): Serializes a value to a Python-native object.
    • dump_json(value, **kwargs): Serializes a value to a JSON-encoded byte string.
    • json_schema(): Returns the JSON schema representation.
    # Conceptual usage of an adapter's interface
    adapter = SchemaAdapter.from_type(MyPydanticModel)
    
    # Validation
    validated_obj = adapter.validate_python({'field': 'value'})
    
    # Serialization
    json_bytes = adapter.dump_json({'field': 'value'})
  5. Use Annotated with validation rules

    master

    You can use typing.Annotated combined with Pydantic's Field to apply validation rules directly within your Django model definitions.

    from typing import Annotated
    from pydantic import Field
    from django_pydantic_field import SchemaField
    
    class AdvancedModel(models.Model):
        # Annotated with validation rules
        positive_ints: Annotated[list[int], Field(min_length=1)] = SchemaField()
  6. Use SchemaField in Django Models

    master

    You can define SchemaField in two ways:

    1. Django-like style: Pass the schema class as the first argument.
    2. Annotation-based style: Use Python type annotations (Pydantic-like).

    Data assigned to these fields is automatically parsed into Pydantic model instances. The field supports standard Python types, list[T], and null=True (which correctly infers Optional[T] for type checkers).

    import pydantic
    from django.db import models
    from django_pydantic_field import SchemaField
    
    class Foo(pydantic.BaseModel):
        count: int
        slug: str = "default"
    
    class MyModel(models.Model):
        # Django-like style (explicit schema)
        bar = SchemaField(Foo, default={"count": 5})
    
        # Annotation-based style (Pydantic-like)
        foo: Foo = SchemaField()
    
        # Supports standard Python types and annotations
        items: list[Foo] = SchemaField(default=list)
    
        # null=True correctly infers t.Optional[Foo] for type checkers
        optional_foo = SchemaField(Foo, null=True, default=None)
    
    model = MyModel(foo={"count": 42})
    model.save()
    
    # Data is automatically parsed into Pydantic models
    assert isinstance(model.foo, Foo)
    assert model.foo.count == 42
  7. Integrate SchemaField with Django Forms

    master

    Use django_pydantic_field.forms.SchemaField to create Django forms that validate input data against a Pydantic schema.

    from django import forms
    from django_pydantic_field.forms import SchemaField
    import pydantic
    
    class Foo(pydantic.BaseModel):
        slug: str
        count: int
    
    class FooForm(forms.Form):
        field = SchemaField(Foo)
    
    form = FooForm(data={"field": '{"slug": "asdf", "count": 1}'})
    assert form.is_valid()
  8. Use SchemaField with Django REST Framework

    master

    The package provides several tools for DRF integration:

    • Serializers: Use django_pydantic_field.rest_framework.SchemaField within a DRF Serializer.
    • Typed Views: Use SchemaParser[T] and SchemaRenderer[T] as decorator arguments to handle Pydantic models directly in @api_view functions.
    • OpenAPI Generation: Use AutoSchema to automatically generate OpenAPI definitions for views using these components.
    from rest_framework import serializers, generics
    from rest_framework.decorators import api_view, parser_classes, renderer_classes
    from rest_framework.response import Response
    from django_pydantic_field.rest_framework import SchemaField, SchemaParser, SchemaRenderer, AutoSchema
    import pydantic
    
    class Foo(pydantic.BaseModel):
        count: int
        slug: str
    
    # 1. Serializer usage
    class MySerializer(serializers.Serializer):
        pydantic_field = SchemaField(Foo)
    
    # 2. Typed View usage
    @api_view(["POST"])
    @parser_classes([SchemaParser[Foo]])
    @renderer_classes([SchemaRenderer[list[Foo]]])
    def foo_view(request):
        instance: Foo = request.data
        return Response([instance])
    
    # 3. OpenAPI Generation
    class SampleView(generics.RetrieveAPIView):
        serializer_class = MySerializer
        schema = AutoSchema()
  9. System Check Error Codes

    master

    The field performs validation during python manage.py check. The following error/warning codes are used:

    • pydantic.E001: Schema resolution errors.
    • pydantic.E002: Default value serialization errors.
    • pydantic.W003: Data integrity warnings for include/exclude configurations.
  10. Handle Pydantic validation errors in Django

    master

    When PydanticSchemaField encounters data that does not match the schema during assignment or retrieval, it raises a django.core.exceptions.ValidationError. The error message contains the details of the Pydantic validation failure.

    During Django's system check phase, the following errors/warnings may be raised:

    • pydantic.E001: Cannot resolve the schema (ImproperlyConfiguredSchema).
    • pydantic.E002: Default value cannot be adapted to the schema.
    • pydantic.W003: Export arguments (include/exclude) may lead to data integrity problems (e.g., if you exclude a field that is required by the schema).
  11. Handle ImproperlyConfiguredSchema errors

    master

    The ImproperlyConfiguredSchema exception is raised when a schema cannot be resolved or prepared. Common causes include:

    • Attempting to access an adapter that is not bound to a model attribute and has no explicit schema.
    • A bound adapter where the corresponding annotation is missing on the parent model.
    • Errors during the internal _prepare_schema process (e.g., failing to resolve forward references).