Skip to main content

Django JSONField with msgspec structs as a Schema. Fork of django-pydantic-field.

Project description

Django + msgspec = 🖤

Django JSONField with msgspec structs as a Schema.

Note: This library is a fork of django-pydantic-field that replaces Pydantic with msgspec for faster serialization and validation.

Why msgspec?

msgspec is a fast serialization library that offers:

  • High performance: 10-75x faster than other serialization libraries
  • Low memory usage: More memory-efficient than alternatives
  • Type validation: Built-in support for Python type hints
  • JSON Schema generation: Automatic schema generation for API documentation

Installation

Install the package with pip:

pip install django-msgspec-field

Usage

import msgspec
from datetime import date
from uuid import UUID

from django.db import models
from django_msgspec_field import SchemaField


class Foo(msgspec.Struct):
    count: int
    size: float = 1.0


class Bar(msgspec.Struct):
    slug: str = "foo_bar"


class MyModel(models.Model):
    # Infer schema from field annotation
    foo_field: Foo = SchemaField()

    # or explicitly pass schema to the field
    bar_list: list[Bar] = SchemaField(schema=list[Bar])

    # msgspec supports many types natively
    raw_date_map: dict[int, date] = SchemaField()
    raw_uids: set[UUID] = SchemaField()


# Usage
model = MyModel(
    foo_field={"count": "5"},
    bar_list=[{}],
    raw_date_map={1: "1970-01-01"},
    raw_uids={"17a25db0-27a4-11ed-904a-5ffb17f92734"}
)
model.save()

assert model.foo_field == Foo(count=5, size=1.0)
assert model.bar_list == [Bar(slug="foo_bar")]
assert model.raw_date_map == {1: date(1970, 1, 1)}
assert model.raw_uids == {UUID("17a25db0-27a4-11ed-904a-5ffb17f92734")}

The schema can be any type supported by msgspec, including:

  • msgspec.Struct classes
  • dataclasses
  • typing.TypedDict
  • Standard Python types (list, dict, set, etc.)
  • Unions, Optionals, and other generic types

Forward referencing annotations

It is also possible to use SchemaField with forward references and string literals:

class MyModel(models.Model):
    foo_field: "Foo" = SchemaField()
    bar_list: list["Bar"] = SchemaField(schema=typing.ForwardRef("list[Bar]"))


class Foo(msgspec.Struct):
    count: int
    size: float = 1.0


class Bar(msgspec.Struct):
    slug: str = "foo_bar"

The exact type resolution will be postponed until the initial access to the field, which usually happens on the first instantiation of the model.

The field performs checks against the passed schema during ./manage.py check command invocation:

  • msgspec.E001: The passed schema could not be resolved.
  • msgspec.E002: default= value could not be serialized to the schema.
  • msgspec.W003: The default value could not be reconstructed due to include/exclude configuration.

typing.Annotated support

SchemaField supports typing.Annotated[...] expressions for adding constraints:

import typing_extensions as te
import msgspec

class MyModel(models.Model):
    annotated_field: te.Annotated[int, msgspec.Meta(gt=0, title="Positive Integer")] = SchemaField()

Django Forms support

Create Django forms that validate against msgspec schemas:

from django import forms
from django_msgspec_field.forms import SchemaField


class Foo(msgspec.Struct):
    slug: str = "foo_bar"


class FooForm(forms.Form):
    field = SchemaField(Foo)


form = FooForm(data={"field": '{"slug": "asdf"}'})
assert form.is_valid()
assert form.cleaned_data["field"] == Foo(slug="asdf")

django_msgspec_field also supports auto-generated fields for ModelForm and modelform_factory:

class MyModelForm(forms.ModelForm):
    class Meta:
        model = MyModel
        fields = ["foo_field"]

form = MyModelForm(data={"foo_field": '{"count": 5}'})
assert form.is_valid()
assert form.cleaned_data["foo_field"] == Foo(count=5)

django-jsonform widgets

django-jsonform offers dynamic form construction based on JSONSchema. django_msgspec_field.forms.SchemaField works with its widgets:

from django_msgspec_field.forms import SchemaField
from django_jsonform.widgets import JSONFormWidget

class FooForm(forms.Form):
    field = SchemaField(Foo, widget=JSONFormWidget)

Override the default form widget for Django Admin:

from django.contrib import admin
from django_jsonform.widgets import JSONFormWidget
from django_msgspec_field.fields import MsgspecSchemaField

@admin.site.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
    formfield_overrides = {
        MsgspecSchemaField: {"widget": JSONFormWidget},
    }

Django REST Framework support

from rest_framework import generics, serializers
from django_msgspec_field.rest_framework import SchemaField, AutoSchema


class MyModelSerializer(serializers.ModelSerializer):
    foo_field = SchemaField(schema=Foo)

    class Meta:
        model = MyModel
        fields = '__all__'


class SampleView(generics.RetrieveAPIView):
    serializer_class = MyModelSerializer

    # optional support of OpenAPI schema generation for msgspec fields
    schema = AutoSchema()

Global approach with typed parser and renderer classes:

from rest_framework import views
from rest_framework.decorators import api_view, parser_classes, renderer_classes
from django_msgspec_field.rest_framework import SchemaRenderer, SchemaParser, AutoSchema


@api_view(["POST"])
@parser_classes([SchemaParser[Foo]])
@renderer_classes([SchemaRenderer[list[Foo]]])
def foo_view(request):
    assert isinstance(request.data, Foo)

    count = request.data.count + 1
    return Response([Foo(count=count)])


class FooClassBasedView(views.APIView):
    parser_classes = [SchemaParser[Foo]]
    renderer_classes = [SchemaRenderer[list[Foo]]]

    # optional support of OpenAPI schema generation
    schema = AutoSchema()

    def get(self, request, *args, **kwargs):
        assert isinstance(request.data, Foo)
        return Response([request.data])

    def put(self, request, *args, **kwargs):
        assert isinstance(request.data, Foo)
        count = request.data.count + 1
        return Response([request.data])

Migrating from django-pydantic-field

If you're migrating from django-pydantic-field, here are the key changes:

  1. Replace imports:

    # Before (pydantic)
    from django_pydantic_field import SchemaField
    import pydantic
    
    class Foo(pydantic.BaseModel):
        count: int
    
    # After (msgspec)
    from django_msgspec_field import SchemaField
    import msgspec
    
    class Foo(msgspec.Struct):
        count: int
    
  2. Schema definitions: Replace pydantic.BaseModel with msgspec.Struct

  3. Config options: Remove pydantic.ConfigDict - msgspec uses different configuration methods

  4. Validators: Replace Pydantic validators with msgspec constraints using msgspec.Meta

Contributing

To get django-msgspec-field up and running in development mode:

  1. Clone this repo: git clone https://github.com/quertenmont/django-msgspec-field.git
  2. Install uv: curl -LsSf https://astral.sh/uv/install.sh | sh
  3. Install dependencies: uv sync --all-extras
  4. Setup pre-commit: pre-commit install
  5. Run tests: uv run pytest
  6. Run linting: uv run ruff check .
  7. Run type checking: uv run mypy django_msgspec_field

License

Released under MIT License.


Supporting

  • :star: Star this project on GitHub
  • :octocat: Follow me on GitHub
  • :blue_heart: Follow me on Twitter
  • :moneybag: Sponsor me on Github

You can also support me via:


Acknowledgement

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

django_msgspec_field-0.1.6.tar.gz (19.7 kB view details)

Uploaded Source

Built Distribution

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

django_msgspec_field-0.1.6-py3-none-any.whl (28.0 kB view details)

Uploaded Python 3

File details

Details for the file django_msgspec_field-0.1.6.tar.gz.

File metadata

  • Download URL: django_msgspec_field-0.1.6.tar.gz
  • Upload date:
  • Size: 19.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for django_msgspec_field-0.1.6.tar.gz
Algorithm Hash digest
SHA256 6864ac8f9c27bb99974a4515ee4c20f49f0e8d435a588484aae6a2671105b507
MD5 b0143396422625f8e7a5437453f06544
BLAKE2b-256 d6d0eda7ea58be69e88b61c9babf517f14e36556e6c44a6315d4dc44707667c7

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_msgspec_field-0.1.6.tar.gz:

Publisher: create-release.yml on quertenmont/django-msgspec-field

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_msgspec_field-0.1.6-py3-none-any.whl.

File metadata

File hashes

Hashes for django_msgspec_field-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 0bca1e4f231fd5b156cc0c96375c6790512fd4ac23725d274484d718c90b81cf
MD5 2a0aeb557c2cd827078db3b5ccd5b288
BLAKE2b-256 dffd1c4b1b42d3035e7a3bce2c7f2783d17dec85dc79a078a8e68a9613b88aeb

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_msgspec_field-0.1.6-py3-none-any.whl:

Publisher: create-release.yml on quertenmont/django-msgspec-field

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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