{"id":14972635,"url":"https://github.com/georgebv/drf-pydantic","last_synced_at":"2025-05-16T18:06:45.582Z","repository":{"id":58424859,"uuid":"531759331","full_name":"georgebv/drf-pydantic","owner":"georgebv","description":"Use pydantic with the Django REST framework","archived":false,"fork":false,"pushed_at":"2025-03-29T23:28:17.000Z","size":181,"stargazers_count":92,"open_issues_count":3,"forks_count":14,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-05-07T16:17:39.824Z","etag":null,"topics":["django","django-framework","django-rest-framework","djangorestframework","drf","drf-spectacular","pydantic","pydantic-django"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/georgebv.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-09-02T02:48:46.000Z","updated_at":"2025-04-28T10:52:21.000Z","dependencies_parsed_at":"2024-12-04T08:04:07.243Z","dependency_job_id":"307d45b9-ae50-409b-a9d5-c69520de05d6","html_url":"https://github.com/georgebv/drf-pydantic","commit_stats":{"total_commits":75,"total_committers":7,"mean_commits":"10.714285714285714","dds":"0.41333333333333333","last_synced_commit":"3ebe8e4bb4574bc82543191377963961ddad0a3c"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/georgebv%2Fdrf-pydantic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/georgebv%2Fdrf-pydantic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/georgebv%2Fdrf-pydantic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/georgebv%2Fdrf-pydantic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/georgebv","download_url":"https://codeload.github.com/georgebv/drf-pydantic/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254582905,"owners_count":22095518,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["django","django-framework","django-rest-framework","djangorestframework","drf","drf-spectacular","pydantic","pydantic-django"],"created_at":"2024-09-24T13:47:15.165Z","updated_at":"2025-05-16T18:06:45.562Z","avatar_url":"https://github.com/georgebv.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/georgebv/drf-pydantic/actions/workflows/test.yml\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://github.com/georgebv/drf-pydantic/actions/workflows/test.yml/badge.svg?event=pull_request\" alt=\"Test Status\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://codecov.io/gh/georgebv/drf-pydantic\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://codecov.io/gh/georgebv/drf-pydantic/branch/main/graph/badge.svg?token=GN9rxzIFMc\" alt=\"Test Coverage\"/\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://badge.fury.io/py/drf-pydantic\" target=\"_blank\"\u003e\n    \u003cimg src=\"https://badge.fury.io/py/drf-pydantic.svg\" alt=\"PyPI version\" height=\"20\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003e\n    Use pydantic with Django REST framework\n  \u003c/i\u003e\n\u003c/p\u003e\n\n- [Introduction](#introduction)\n  - [Performance](#performance)\n- [Installation](#installation)\n- [Usage](#usage)\n  - [General](#general)\n  - [Pydantic Validation](#pydantic-validation)\n    - [Updating Field Values](#updating-field-values)\n    - [Validation Errors](#validation-errors)\n  - [Existing Models](#existing-models)\n  - [Nested Models](#nested-models)\n  - [Manual Serializer Configuration](#manual-serializer-configuration)\n    - [Per-Field Configuration](#per-field-configuration)\n    - [Custom Serializer](#custom-serializer)\n- [Additional Properties](#additional-properties)\n\n# Introduction\n\n[Pydantic](https://pydantic-docs.helpmanual.io) is a Python library used to perform\ndata serialization and validation.\n\n[Django REST framework](https://www.django-rest-framework.org) is a framework built\non top of [Django](https://www.djangoproject.com/) used to write REST APIs.\n\nIf you develop DRF APIs and rely on pydantic for data validation/(de)serialization,\nthen `drf-pydantic` is for you :heart_eyes:.\n\n\u003e [!NOTE]\n\u003e The latest version of `drf_pydantic` only supports `pydantic` v2.\n\u003e Support for `pydantic` v1 is available in the `1.*` version.\n\n## Performance\n\nTranslation between `pydantic` models and `DRF` serializers is done during class\ncreation (e.g., when you first import the model). This means there will be\nzero runtime impact when using `drf_pydantic` in your application.\n\n\u003e [!NOTE]\n\u003e There will be a minor penalty if `validate_pydantic` is set to `True` due to pydantic\n\u003e model validation. This is minimal compared to an-already present overhead of DRF\n\u003e itself because pydantic runs its validation in rust while DRF is pure python.\n\n# Installation\n\n```shell\npip install drf-pydantic\n```\n\n# Usage\n\n## General\n\nUse `drf_pydantic.BaseModel` instead of `pydantic.BaseModel` when creating your models:\n\n```python\nfrom drf_pydantic import BaseModel\n\nclass MyModel(BaseModel):\n    name: str\n    addresses: list[str]\n```\n\n`MyModel.drf_serializer` is equivalent to the following DRF Serializer class:\n\n```python\nclass MyModelSerializer:\n    name = CharField(allow_null=False, required=True)\n    addresses = ListField(\n        allow_empty=True,\n        allow_null=False,\n        child=CharField(allow_null=False),\n        required=True,\n    )\n```\n\nWhenever you need a DRF serializer, you can get it from the model like this:\n\n```python\nmy_value = MyModel.drf_serializer(data={\"name\": \"Van\", \"addresses\": [\"Gym\"]})\nmy_value.is_valid(raise_exception=True)\n```\n\n\u003e [!NOTE]\n\u003e Models created using `drf_pydantic` are fully identical to those created by\n\u003e `pydantic`. The only change is the addition of the `drf_serializer`\n\u003e and `drf_config` attributes.\n\n## Pydantic Validation\n\nBy default, the generated serializer only uses DRF's validation; however, pydantic\nmodels are often more complex and their numerous validation rules cannot be fully\ntranslated to DRF. To enable pydantic validators to run whenever the generated\nDRF serializer validates its data (e.g., via `.is_valid()`),\nset `\"validate_pydantic\": True` within the `drf_config` property of your model:\n\n```python\nfrom drf_pydantic import BaseModel\n\nclass MyModel(BaseModel):\n    name: str\n    addresses: list[str]\n\n    drf_config = {\"validate_pydantic\": True}\n\n\nmy_serializer = MyModel.drf_serializer(data={\"name\": \"Van\", \"addresses\": []})\nmy_serializer.is_valid()  # this will also validate MyModel\n```\n\nWith this option enabled, every time you validate data using your DRF serializer,\nthe parent pydantic model is also validated. If it fails, its\n`ValidationError` exception will be wrapped within DRF's ValidationError.\nPer-field and non-field (object-level) errors are wrapped\nsimilarly to how DRF handles them. This ensures your complex pydantic validation logic\nis properly evaluated wherever a DRF serializer is used.\n\n\u003e [!NOTE]\n\u003e All `drf_config` values are properly inherited by child classes,\n\u003e just like pydantic's `model_config`.\n\n### Updating Field Values\n\nBy default, `drf_pydantic` updates values in the DRF serializer\nwith those from the validated pydantic model:\n\n```python\nfrom drf_pydantic import BaseModel\n\nclass MyModel(BaseModel):\n    name: str\n    addresses: list[str]\n\n    @pydantic.field_validator(\"name\")\n    @classmethod\n    def validate_name(cls, v):\n        assert isinstance(v, str)\n        return v.strip().title()\n\n    drf_config = {\"validate_pydantic\": True}\n\n\nmy_serializer = MyModel.drf_serializer(data={\"name\": \"van herrington\", \"addresses\": []})\nmy_serializer.is_valid()\nprint(my_serializer.data)  # {\"name\": \"Van Herrington\", \"addresses\": []}\n```\n\nThis is handy when you dynamically modify field values within your\npydantic validators. You can disable this behavior by setting\n`\"backpopulate_after_validation\": False`:\n\n```python\nclass MyModel(BaseModel):\n    ...\n\n    drf_config = {\"validate_pydantic\": True, \"backpopulate_after_validation\": False}\n```\n\n### Validation Errors\n\nBy default, pydantic's `ValidationError` is wrapped within DRF's `ValidationError`.\nIf you want to raise pydantic's `ValidationError` directly,\nset `\"validation_error\": \"pydantic\"` in the `drf_config` property of your model:\n\n```python\nimport pydantic\n\nfrom drf_pydantic import BaseModel\n\nclass MyModel(BaseModel):\n    name: str\n    addresses: list[str]\n\n    @pydantic.field_validator(\"name\")\n    @classmethod\n    def validate_name(cls, v):\n        assert isinstance(v, str)\n        if v != \"Billy\":\n            raise ValueError(\"Wrong door\")\n        return v\n\n    drf_config = {\"validate_pydantic\": True, \"validation_error\": \"pydantic\"}\n\n\nmy_serializer = MyModel.drf_serializer(data={\"name\": \"Van\", \"addresses\": []})\nmy_serializer.is_valid()  # this will raise pydantic.ValidationError\n```\n\n\u003e [!NOTE]\n\u003e When a model is invalid from both DRF's and pydantic's perspectives and\n\u003e exceptions are enabled (`.is_valid(raise_exception=True)`),\n\u003e DRF's `ValidationError` will be raised regardless of the `validation_error` setting,\n\u003e because DRF validation always runs first.\n\n\u003e [!CAUTION]\n\u003e Setting `validation_error` to `pydantic` has side effects:\n\u003e\n\u003e 1. It may break your views because they expect DRF's `ValidationError`.\n\u003e 2. Calling `.is_valid()` will always raise `pydantic.ValidationError` if the data\n\u003e    is invalid, even without setting `.is_valid(raise_exception=True)`.\n\n## Existing Models\n\nIf you have an existing code base and want to add the `drf_serializer`\nattribute only to some of your models, you can extend your existing pydantic models\nby adding `drf_pydantic.BaseModel` as a parent class to the models you want to extend.\n\nYour existing pydantic models:\n\n```python\nfrom pydantic import BaseModel\n\nclass Pet(BaseModel):\n    name: str\n\nclass Dog(Pet):\n    breed: str\n```\n\nUpdate your `Dog` model and get serializer via the `drf_serializer`:\n\n```python\nfrom drf_pydantic import BaseModel as DRFBaseModel\nfrom pydantic import BaseModel\n\nclass Pet(BaseModel):\n    name: str\n\nclass Dog(DRFBaseModel, Pet):\n    breed: str\n\nDog.drf_serializer\n```\n\n\u003e [!IMPORTANT]\n\u003e Inheritance order is important: `drf_pydantic.BaseModel` must always come before\n\u003e `pydantic.BaseModel`.\n\n## Nested Models\n\nIf you have nested models and want to generate a serializer for only one of them,\nyou don't need to update all models. Simply update the model you need,\nand `drf_pydantic` will automatically generate serializers\nfor all standard nested pydantic models:\n\n```python\nfrom drf_pydantic import BaseModel as DRFBaseModel\nfrom pydantic import BaseModel\n\nclass Apartment(BaseModel):\n    floor: int\n    tenant: str\n\nclass Building(BaseModel):\n    address: str\n    apartments: list[Apartment]\n\nclass Block(DRFBaseModel):\n    buildings: list[Building]\n\nBlock.drf_serializer\n```\n\n## Manual Serializer Configuration\n\nIf `drf_pydantic` doesn't generate the serializer you need,\nyou can configure the DRF serializer fields for each pydantic field manually,\nor create a custom serializer for the model altogether.\n\n\u003e [!IMPORTANT]\n\u003e When manually configuring the serializer, you are responsible for setting all\n\u003e properties of the fields (e.g., `allow_null`, `required`, `default`, etc.).\n\u003e `drf_pydantic` does not perform any introspection for fields that are manually\n\u003e configured or for any fields if a custom serializer is used.\n\n### Per-Field Configuration\n\n```python\nfrom typing import Annotated\n\nfrom drf_pydantic import BaseModel\nfrom rest_framework.serializers import IntegerField\n\n\nclass Person(BaseModel):\n    name: str\n    age: Annotated[float, IntegerField(min_value=0, max_value=100)]\n```\n\n### Custom Serializer\n\nIn the example below, `Person` will use `MyCustomSerializer` as its DRF serializer.\n`Employee` will have its own serializer generated by `drf_pydantic` since it doesn't\ninherit a user-defined `drf_serializer` attribute.\n`Company` will use `Person`'s manually defined serializer for its `ceo` field.\n\n```python\nfrom drf_pydantic import BaseModel, DrfPydanticSerializer\nfrom rest_framework.serializers import CharField, IntegerField\n\n\nclass MyCustomSerializer(DrfPydanticSerializer):\n    name = CharField(allow_null=False, required=True)\n    age = IntegerField(allow_null=False, required=True)\n\n\nclass Person(BaseModel):\n    name: str\n    age: float\n\n    drf_serializer = MyCustomSerializer\n\n\nclass Employee(Person):\n    salary: float\n\n\nclass Company(BaseModel):\n    ceo: Person\n```\n\n\u003e [!IMPORTANT]\n\u003e Added in version `v2.6.0`\n\u003e\n\u003e Manual `drf_serializer` must have base class of `DrfPydanticSerializer`\n\u003e in order for [Pydantic Validation](#pydantic-validation) to work properly.\n\u003e You can still use standard `Serializer` from `rest_framework`, but automatic\n\u003e pydantic model validation will not work consistently and you will get a warning.\n\n# Additional Properties\n\nAdditional field properties are mapped as follows (`pydantic` -\u003e `DRF`):\n\n- `description` -\u003e `help_text`\n- `title` -\u003e `label`\n- `StringConstraints` -\u003e `min_length` and `max_length`\n- `pattern` -\u003e Uses the specialized `RegexField` serializer field\n- `max_digits` and `decimal_places` are carried over\n  (used for `Decimal` types, with the current decimal context precision)\n- `ge` / `gt` -\u003e `min_value` (only for numeric types)\n- `le` / `lt` -\u003e `max_value` (only for numeric types)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeorgebv%2Fdrf-pydantic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgeorgebv%2Fdrf-pydantic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeorgebv%2Fdrf-pydantic/lists"}