django-ninja Documentation

Fast, Async-ready, Openapi, type hints based framework for building APIs

9,044 stars Python Python 1 file ยท ~8,573 tokens #django#django-ninja#openapi#pydantic#python#rest-api#swagger#swagger-ui
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy django-ninja on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name django-ninja --restart always vitalik/django-ninja:latest
Configured Services & Ports
โš™๏ธ tester
image: build locally
docker-compose.yml
yaml (docker-compose.yml)
version: "3"
services:
  tester:
    build:
      context: ../../
      dockerfile: tests/env-matrix/Dockerfile

File: README.md

^ Please read ^


Django Ninja - Fast Django REST Framework

Django Ninja is a web framework for building APIs with Django and Python 3.6+ type hints.

Key features:

  • Easy: Designed to be easy to use and intuitive.
  • FAST execution: Very high performance thanks to Pydantic and async support.
  • Fast to code: Type hints and automatic docs lets you focus only on business logic.
  • Standards-based: Based on the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema.
  • Django friendly: (obviously) has good integration with the Django core and ORM.
  • Production ready: Used by multiple companies on live projects (If you use django-ninja and would like to publish your feedback, please email [email protected]).

Documentation: https://django-ninja.dev


Installation

text
pip install django-ninja

Usage

In your django project next to urls.py create new api.py file:

python
from ninja import NinjaAPI

api = NinjaAPI()


@api.get("/add")
def add(request, a: int, b: int):
    return {"result": a + b}

Now go to urls.py and add the following:

python hl_lines="3 7"
...
from .api import api

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/", api.urls),  # <---------- !
]

That's it !

Now you've just created an API that:

  • receives an HTTP GET request at /api/add
  • takes, validates and type-casts GET parameters a and b
  • decodes the result to JSON
  • generates an OpenAPI schema for defined operation

Interactive API docs

Now go to http://127.0.0.1:8000/api/docs

You will see the automatic interactive API documentation (provided by Swagger UI or Redoc):

Sponsors

Become a sponsor

What next?


File: docs/docs/guides/input/body.md

Request Body

Request bodies are typically used with โ€œcreateโ€ and โ€œupdateโ€ operations (POST, PUT, PATCH).
For example, when creating a resource using POST or PUT, the request body usually contains the representation of the resource to be created.

To declare a request body, you need to use Django Ninja Schema.

!!! info
Under the hood Django Ninja uses Pydantic models with all their power and benefits.
The alias Schema was chosen to avoid confusion in code when using Django models, as Pydantic's model class is called Model by default, and conflicts with Django's Model class.

Import Schema

First, you need to import Schema from ninja:

python hl_lines="2"
{!./src/tutorial/body/code01.py!}

Create your data model

Then you declare your data model as a class that inherits from Schema.

Use standard Python types for all the attributes:

python hl_lines="5 6 7 8 9"
{!./src/tutorial/body/code01.py!}

Note: if you use None as the default value for an attribute, it will become optional in the request body.
For example, this model above declares a JSON "object" (or Python dict) like:

json
{
    "name": "Katana",
    "description": "An optional description",
    "price": 299.00,
    "quantity": 10
}

...as description is optional (with a default value of None), this JSON "object" would also be valid:

json
{
    "name": "Katana",
    "price": 299.00,
    "quantity": 10
}

Declare it as a parameter

To add it to your path operation, declare it the same way you declared the path and query parameters:

python hl_lines="13"
{!./src/tutorial/body/code01.py!}

... and declare its type as the model you created, Item.

Results

With just that Python type declaration, Django Ninja will:

  • Read the body of the request as JSON.
  • Convert the corresponding types (if needed).
  • Validate the data.
    • If the data is invalid, it will return a nice and meaningful error, indicating exactly where and what the incorrect data was.
  • Give you the received data in the parameter item.
    • Because you declared it in the function to be of type Item, you will also have all the editor support
      (completion, etc.) for all the attributes and their types.
  • Generate JSON Schema definitions for
    your models, and you can also use them anywhere else you like if it makes sense for your project.
  • Those schemas will be part of the generated OpenAPI schema, and used by the automatic documentation UI's.

Automatic docs

The JSON Schemas of your models will be part of your OpenAPI generated schema, and will be shown in the interactive API docs:

... and they will be also used in the API docs inside each path operation that needs them:

Editor support

In your editor, inside your function you will get type hints and completion everywhere (this wouldn't happen if you received a dict instead of a Schema object):

The previous screenshots were taken with Visual Studio Code.

You would get the same editor support with PyCharm and most of the other Python editors.

Request body + path parameters

You can declare path parameters and body requests at the same time.

Django Ninja will recognize that the function parameters that match path parameters should be taken from the path, and that function parameters that are declared with Schema should be taken from the request body.

python hl_lines="11 12"
{!./src/tutorial/body/code02.py!}

Request body + path + query parameters

You can also declare body, path and query parameters, all at the same time.

Django Ninja will recognize each of them and take the data from the correct place.

python hl_lines="11 12"
{!./src/tutorial/body/code03.py!}

The function parameters will be recognized as follows:

  • If the parameter is also declared in the path, it will be used as a path parameter.
  • If the parameter is of a singular type (like int, float, str, bool, etc.), it will be interpreted as a query parameter.
  • If the parameter is declared to be of the type of Schema (or Pydantic BaseModel), it will be interpreted as a request body.

File: docs/docs/guides/input/file-params.md

File uploads

Handling files are no different from other parameters.

python hl_lines="1 2 5"
from ninja import NinjaAPI, File
from ninja.files import UploadedFile

@api.post("/upload")
def upload(request, file: File[UploadedFile]):
    data = file.read()
    return {'name': file.name, 'len': len(data)}

UploadedFile is an alias to Django's UploadFile and has all the methods and attributes to access the uploaded file:

  • read()
  • multiple_chunks(chunk_size=None)
  • chunks(chunk_size=None)
  • name
  • size
  • content_type
  • content_type_extra
  • charset
  • etc.

Uploading array of files

To upload several files at the same time, just declare a List of UploadedFile:

python hl_lines="1 6"
from typing import List
from ninja import NinjaAPI, File
from ninja.files import UploadedFile

@api.post("/upload-many")
def upload_many(request, files: File[List[UploadedFile]]):
    return [f.name for f in files]

Uploading files with extra fields

Note: The HTTP protocol does not allow you to send files in application/json format by default (unless you encode it somehow to JSON on client side)

To send files along with some extra attributes, you need to send bodies with multipart/form-data encoding. You can do it by simply marking fields with Form:

python hl_lines="14"
from ninja import NinjaAPI, Schema, UploadedFile, Form, File
from datetime import date

api = NinjaAPI()


class UserDetails(Schema):
    first_name: str
    last_name: str
    birthdate: date


@api.post('/users')
def create_user(request, details: Form[UserDetails], file: File[UploadedFile]):
    return [details.dict(), file.name]

Note: in this case all fields should be send as form fields

You can as well send payload in single field as JSON - just remove the Form mark from:

python
@api.post('/users')
def create_user(request, details: UserDetails, file: File[UploadedFile]):
    return [details.dict(), file.name]

this will expect from the client side to send data as `multipart/form-data with 2 fields:

  • details: JSON as string
  • file: file

List of files with extra info

python
@api.post('/users')
def create_user(request, details: Form[UserDetails], files: File[list[UploadedFile]]):
    return [details.dict(), [f.name for f in files]]

Optional file input

If you would like the file input to be optional, all that you have to do is to pass None to the File type, like so:

python
@api.post('/users')
def create_user(request, details: Form[UserDetails], avatar: File[UploadedFile] = None):
    user = add_user_to_database(details)
    if avatar is not None:
        set_user_avatar(user)

Handling request.FILES in PUT/PATCH Requests

Problem

python
@api.put("/upload") # !!!!
def upload(request, file: File[UploadedFile]):
   ...

For some historical reasons Djangoโ€™s request.FILES is populated only for POST requests by default. When using HTTP PUT or PATCH methods with file uploads (e.g., multipart/form-data), request.FILES will not contain uploaded files. This is a known Django behavior, not specific to Django Ninja.

As a result, views expecting files in PUT or PATCH requests may not behave correctly, since request.FILES will be empty.

Solution

Django Ninja provides a built-in middleware to automatically fix this behavior:
ninja.compatibility.files.fix_request_files_middleware

This middleware will manually parse multipart/form-data for PUT and PATCH requests and populate request.FILES, making file uploads work as expected across all HTTP methods.

Usage

To enable the middleware, add the following to your Django settings:

python
MIDDLEWARE = [
    # ... your existing middleware ...
    "ninja.compatibility.files.fix_request_files_middleware",
]

Auto-detection

When Django Ninja detects a PUT or PATCH etc methods with multipart/form-data and expected FILES - it will throw an error message suggesting you install the compatibility middleware:

Note: This middleware does not interfere with normal POST behavior or any other methods.


File: docs/docs/guides/input/filtering.md

Filtering

If you want to allow the user to filter your querysets by a number of different attributes, it makes sense
to encapsulate your filters into a FilterSchema class. FilterSchema is a regular Schema, so it's using all the
necessary features of Pydantic, but it also adds some bells and whistles that ease the translation of the user-facing filtering
parameters into database queries.

Start off with defining a subclass of FilterSchema:

python hl_lines="6 7 8 9"
from ninja import FilterSchema
from typing import Optional
from datetime import datetime


class BookFilterSchema(FilterSchema):
    name: Optional[str] = None
    author: Optional[str] = None
    created_after: Optional[datetime] = None

Next, use this schema in conjunction with Query in your API handler:

python hl_lines="2"
@api.get("/books")
def list_books(request, filters: BookFilterSchema = Query(...)):
    books = Book.objects.all()
    books = filters.filter(books)
    return books

Just like described in defining query params using schema, Django Ninja converts the fields
defined in BookFilterSchema into query parameters.

You can use a shorthand one-liner .filter() to apply those filters to your queryset:

python hl_lines="4"
@api.get("/books")
def list_books(request, filters: Query[BookFilterSchema]):
    books = Book.objects.all()
    books = filters.filter(books)
    return books

Under the hood, FilterSchema converts its fields into Q expressions which it then combines and uses to filter your queryset.

Alternatively to using the .filter method, you can get the prepared Q-expression and perform the filtering yourself.
That can be useful, when you have some additional queryset filtering on top of what you expose to the user through the API:

python hl_lines="5 8"
@api.get("/books")
def list_books(request, filters: Query[BookFilterSchema]):

    # Never serve books from inactive publishers and authors
    q = Q(author__is_active=True) | Q(publisher__is_active=True)
    
    # But allow filtering the rest of the books
    q &= filters.get_filter_expression()
    return Book.objects.filter(q)

By default, the filters will behave the following way:

  • None values will be ignored and not filtered against;
  • Every non-None field will be converted into a Q-expression based on the Field definition of each field;
  • All Q-expressions will be merged into one using AND logical operator;
  • The resulting Q-expression is used to filter the queryset and return you a queryset with a .filter clause applied.

Customizing Fields

By default, FilterSet will use the field names to generate Q expressions:

python
class BookFilterSchema(FilterSchema):
    name: Optional[str] = None

The name field will be converted into Q(name=...) expression.

When your database lookups are more complicated than that, you can annotate your fields with an instance of FilterLookup where you specify how you wish your field to be looked up for filtering:

python hl_lines="5"
from ninja import FilterSchema, FilterLookup
from typing import Annotated

class BookFilterSchema(FilterSchema):
    name: Annotated[Optional[str], FilterLookup("name__icontains")] = None

You can even specify multiple lookups as a list:

python hl_lines="3 4 5"
class BookFilterSchema(FilterSchema):
    search: Annotated[Optional[str], FilterLookup(
        ["name__icontains",
         "author__name__icontains",
         "publisher__name__icontains"]
    )]

By default, field-level expressions are combined using "OR" connector, so with the above setup, a query parameter ?search=foobar will search for books that have "foobar" in either of their name, author or publisher.

And to make generic fields, you can make the field name implicit by skipping it:

python hl_lines="1 4"
IContainsField = Annotated[Optional[str], FilterLookup('__icontains')]

class BookFilterSchema(FilterSchema):
    name: IContainsField = None

??? note "Deprecated syntax"

text
In previous versions, database lookups were specified using `Field(q=...)` syntax:
```python
from ninja import FilterSchema, Field

class BookFilterSchema(FilterSchema):
    name: Optional[str] = Field(None, q="name__icontains")
```

This approach is still supported, but it is considered **deprecated** and **not recommended** for new code because:

- Poor IDE support (IDEs don't recognize custom `Field` arguments)
- Uses deprecated Pydantic features (`**extra`)
- Less type-safe and harder to maintain

The new `FilterLookup` annotation provides better developer experience with full IDE support and type safety. Prefer using `FilterLookup` for new projects.

Combining expressions

By default,

  • Field-level expressions are joined together using OR operator.
  • The fields themselves are joined together using AND operator.

So, with the following FilterSchema...

python
class BookFilterSchema(FilterSchema):
    search: Annotated[
        Optional[str],
        FilterLookup(["name__icontains", "author__name__icontains"])] = None
    popular: Optional[bool] = None

...and the following query parameters from the user

text
http://localhost:8000/api/books?search=harry&popular=true

the FilterSchema instance will look for popular books that have harry in the book's or author's name.

You can customize this behavior using an expression_connector argument in field-level and class-level definition:

python hl_lines="12"
from ninja import FilterConfigDict, FilterLookup, FilterSchema

class BookFilterSchema(FilterSchema):
    active: Annotated[
        Optional[bool],
        FilterLookup(
            ["is_active", "publisher__is_active"],
            expression_connector="AND"
        )] = None
    name: Annotated[Optional[str], FilterLookup("name__icontains")] = None
    
    model_config = FilterConfigDict(expression_connector="OR")

An expression connector can take the values of "OR", "AND" and "XOR", but the latter is only supported in Django starting with 4.1.

Now, a request with these query parameters

text
http://localhost:8000/api/books?name=harry&active=true

...shall search for books that have harry in their name or are active themselves and are published by active publishers.

Filtering by Nones

You can make the FilterSchema treat None as a valid value that should be filtered against.

This can be done on a field level with a ignore_none kwarg:

python hl_lines="3"
class BookFilterSchema(FilterSchema):
    name: Annotated[Optional[str], FilterLookup("name__icontains")] = None
    tag: Annotated[Optional[str], FilterLookup("tag", ignore_none=False)] = None

This way when no other value for "tag" is provided by the user, the filtering will always include a condition tag=None.

You can also specify this setting for all fields at the same time in model_config:

python hl_lines="5"
class BookFilterSchema(FilterSchema):
    name: Annotated[Optional[str], FilterLookup("name__icontains")] = None
    tag: Optional[str] = None
    
    model_config = FilterConfigDict(ignore_none=False)

Custom expressions

Sometimes you might want to have complex filtering scenarios that cannot be handled by individual Field annotations.
For such cases you can implement your field filtering logic as a custom method. Simply define a method called filter_<fieldname> which takes a filter value and returns a Q expression:

python hl_lines="5"
class BookFilterSchema(FilterSchema):
    tag: Optional[str] = None
    popular: Optional[bool] = None
    
    def filter_popular(self, value: bool) -> Q:
        return Q(view_count__gt=1000) | Q(download_count__gt=100) if value else Q()

Such field methods take precedence over what is specified in the Field() definition of the corresponding fields.

If that is not enough, you can implement your own custom filtering logic for the entire FilterSet class in a custom_expression method:

python hl_lines="5"
class BookFilterSchema(FilterSchema):
    name: Optional[str] = None
    popular: Optional[bool] = None

    def custom_expression(self) -> Q:
        q = Q()
        if self.name:
            q &= Q(name__icontains=self.name)
        if self.popular:
            q &= (
                Q(view_count__gt=1000) |
                Q(downloads__gt=100) |
                Q(tag='popular')
            )
        return q

The custom_expression method takes precedence over any other definitions described earlier, including filter_<fieldname> methods.


File: docs/docs/guides/input/form-params.md

Form data

Django Ninja also allows you to parse and validate request.POST data
(aka application/x-www-form-urlencoded or multipart/form-data).

Form Data as params

python hl_lines="1 4"
from ninja import NinjaAPI, Form

@api.post("/login")
def login(request, username: Form[str], password: Form[str]):
    return {'username': username, 'password': '*****'}

Note the following:

  1. You need to import the Form class from ninja
python
from ninja import Form
  1. Use Form as default value for your parameter:
python
username: Form[str]

Using a Schema

In a similar manner to Body, you can use
a Schema to organize your parameters.

python hl_lines="12"
{!./src/tutorial/form/code01.py!}

Request form + path + query parameters

In a similar manner to Body, you can use
Form data in combination with other parameter sources.

You can declare query and path and form field, and etc... parameters at the same time.

Django Ninja will recognize that the function parameters that match path
parameters should be taken from the path, and that function parameters that
are declared with Form(...) should be taken from the request form fields, etc.

python hl_lines="12"
{!./src/tutorial/form/code02.py!}

Mapping Empty Form Field to Default

Form fields that are optional, are often sent with an empty value. This value is
interpreted as an empty string, and thus may fail validation for fields such as int or bool.

This can be fixed, as described in the Pydantic docs, by using
Generic Classes as Types.

python hl_lines="15 16 23-25"
{!./src/tutorial/form/code03.py!}

File: docs/docs/guides/input/operations.md

HTTP Methods

Defining operations

An operation can be one of the following HTTP methods:

  • GET
  • POST
  • PUT
  • DELETE
  • PATCH

Django Ninja comes with a decorator for each operation:

python hl_lines="1 5 9 13 17"
@api.get("/path")
def get_operation(request):
    ...

@api.post("/path")
def post_operation(request):
    ...

@api.put("/path")
def put_operation(request):
    ...

@api.delete("/path")
def delete_operation(request):
    ...

@api.patch("/path")
def patch_operation(request):
    ...

See the operations parameters
reference docs for information on what you can pass to any of these decorators.

Handling multiple methods

If you need to handle multiple methods with a single function for a given path,
you can use the api_operation decorator:

python hl_lines="1"
@api.api_operation(["POST", "PATCH"], "/path")
def mixed_operation(request):
    ...

This feature can also be used to implement other HTTP methods that don't have
corresponding Django Ninja methods, such as HEAD or OPTIONS.

python hl_lines="1"
@api.api_operation(["HEAD", "OPTIONS"], "/path")
def mixed_operation(request):
    ...

File: docs/docs/guides/input/path-params.md

Path parameters

You can declare path "parameters" with the same syntax used by Python format-strings (which luckily also matches the OpenAPI path parameters):

python hl_lines="1 2"
{!./src/tutorial/path/code01.py!}

The value of the path parameter item_id will be passed to your function as the argument item_id.

So, if you run this example and go to http://localhost:8000/api/items/foo, you will see this response:

json
{"item_id":"foo"}

Path parameters with types

You can declare the type of path parameter in the function using standard Python type annotations:

python hl_lines="2"
{!./src/tutorial/path/code02.py!}

In this case,item_id is declared to be an int. This will give you editor and linter support for error checks, completion, etc.

If you run this in your browser with http://localhost:8000/api/items/3, you will see this response:

json
{"item_id":3}

!!! tip
Notice that the value your function received (and returned) is 3, as a Python int - not a string "3".
So, with just that type declaration, Django Ninja gives you automatic request "parsing" and validation.

Data validation

On the other hand, if you go to the browser at http://localhost:8000/api/items/foo ("foo" is not int), you will see an HTTP error like this:

json hl_lines="8"
{
    "detail": [
        {
            "loc": [
                "path",
                "item_id"
            ],
            "msg": "value is not a valid integer",
            "type": "type_error.integer"
        }
    ]
}

Django Path Converters

You can use Django Path Converters
to help parse the path:

python hl_lines="1"
@api.get("/items/{int:item_id}")
def read_item(request, item_id):
    return {"item_id": item_id}

In this case,item_id will be parsed as an int. If item_id is not a valid int, the url will not
match. (e.g. if no other path matches, a 404 Not Found will be returned)

!!! tip
Notice that, since Django Ninja uses a default type of str for unannotated parameters, the value the
function above received (and returned) is "3", as a Python str - not an integer 3. To receive
an int, simply declare item_id as an int type annotation in the function definition as normal:

text
```python hl_lines="2"
@api.get("/items/{int:item_id}")
def read_item(request, item_id:int):
    return {"item_id": item_id}
```

Path params with slashes

Django's path converter allows you to handle path-like parameters:

python hl_lines="1"
@api.get('/dir/{path:value}')
def someview(request, value: str):
    return value

you can query this operation with /dir/some/path/with-slashes and your value will be equal to some/path/with-slashes

Multiple parameters

You can pass as many variables as you want into path, just remember to have unique names and don't forget to use the same names in the function arguments.

python
@api.get("/events/{year}/{month}/{day}")
def events(request, year: int, month: int, day: int):
    return {"date": [year, month, day]}

Using Schema

You can also use Schema to encapsulate path parameters that depend on each other (and validate them as a group):

python hl_lines="1 2 5 6 7 8 9 10 11 15"
{!./src/tutorial/path/code010.py!}

!!! note
Notice that here we used a Path source hint to let Django Ninja know that this schema will be applied to path parameters.

Documentation

Now, when you open your browser at http://localhost:8000/api/docs, you will see the automatic, interactive, API documentation.


File: docs/docs/guides/input/query-params.md

Query parameters

When you declare other function parameters that are not part of the path parameters, they are automatically interpreted as "query" parameters.

python hl_lines="5"
{!./src/tutorial/query/code01.py!}

To query this operation, you use a URL like:

text
http://localhost:8000/api/weapons?offset=0&limit=10

By default, all GET parameters are strings, and when you annotate your function arguments with types, they are converted to that type and validated against it.

The same benefits that apply to path parameters also apply to query parameters:

  • Editor support (obviously)
  • Data "parsing"
  • Data validation
  • Automatic documentation

!!! Note
if you do not annotate your arguments, they will be treated as str types

python hl_lines="2"
@api.get("/weapons")
def list_weapons(request, limit, offset):
    # type(limit) == str
    # type(offset) == str

Defaults

As query parameters are not a fixed part of a path, they are optional and can have default values:

python hl_lines="2"
@api.get("/weapons")
def list_weapons(request, limit: int = 10, offset: int = 0):
    return weapons[offset : offset + limit]

In the example above we set default values of offset=0 and limit=10.

So, going to the URL:

text
http://localhost:8000/api/weapons

would be the same as going to:

text
http://localhost:8000/api/weapons?offset=0&limit=10

If you go to, for example:

text
http://localhost:8000/api/weapons?offset=20

the parameter values in your function will be:

  • offset=20 (because you set it in the URL)
  • limit=10 (because that was the default value)

Required and optional parameters

You can declare required or optional GET parameters in the same way as declaring Python function arguments:

python hl_lines="5"
{!./src/tutorial/query/code02.py!}

In this case, Django Ninja will always validate that you pass the q param in the GET, and the offset param is an optional integer.

GET parameters type conversion

Let's declare multiple type arguments:

python hl_lines="5"
{!./src/tutorial/query/code03.py!}

The str type is passed as is.

For the bool type, all the following:

text
http://localhost:8000/api/example?b=1
http://localhost:8000/api/example?b=True
http://localhost:8000/api/example?b=true
http://localhost:8000/api/example?b=on
http://localhost:8000/api/example?b=yes

or any other case variation (uppercase, first letter in uppercase, etc.), your function will see
the parameter b with a bool value of True, otherwise as False.

Date can be both date string and integer (unix timestamp):

text
http://localhost:8000/api/example?d=**1577836800**  # same as 2020-01-01
http://localhost:8000/api/example?d=**2020-01-01**

Using Schema

You can also use Schema to encapsulate GET parameters:

python hl_lines="1 2 5 6 7 8"
{!./src/tutorial/query/code010.py!}

For more complex filtering scenarios please refer to filtering.


File: docs/docs/guides/input/request-parsers.md

Request parsers

In most cases, the default content type for REST API's is JSON, but in case you need to work with
other content types (like YAML, XML, CSV) or use faster JSON parsers, Django Ninja provides a parser configuration.

python
api = NinjaAPI(parser=MyYamlParser())

To create your own parser, you need to extend the ninja.parser.Parser class, and override the parse_body method.

Example YAML Parser

Let's create our custom YAML parser:

python hl_lines="4 8 9"
import yaml
from typing import List
from ninja import NinjaAPI
from ninja.parser import Parser


class MyYamlParser(Parser):
    def parse_body(self, request):
        return yaml.safe_load(request.body)


api = NinjaAPI(parser=MyYamlParser())


class Payload(Schema):
    ints: List[int]
    string: str
    f: float


@api.post('/yaml')
def operation(request, payload: Payload):
    return payload.dict()

If you now send YAML like this as the request body:

yaml
ints:
 - 0
 - 1
string: hello
f: 3.14

it will be correctly parsed, and you should have JSON output like this:

json
{
  "ints": [
    0,
    1
  ],
  "string": "hello",
  "f": 3.14
}

Example ORJSON Parser

orjson is a fast, accurate JSON library for Python. It benchmarks as the fastest Python library for JSON and is more accurate than the standard json library or other third-party libraries.

text
pip install orjson

Parser code:

python hl_lines="1 8 9"
import orjson
from ninja import NinjaAPI
from ninja.parser import Parser


class ORJSONParser(Parser):
    def parse_body(self, request):
        return orjson.loads(request.body)


api = NinjaAPI(parser=ORJSONParser())

File: docs/docs/guides/response/config-pydantic.md

Overriding Pydantic Config

There are many customizations available for a Django Ninja Schema, via the schema's
Pydantic model_config.

!!! info
Under the hood Django Ninja uses Pydantic Models
with all their power and benefits. The alias Schema was chosen to avoid confusion in code
when using Django models, as Pydantic's model class is called Model by default, and conflicts with
Django's Model class.

Example Camel Case mode

One interesting config attribute is alias_generator.
Using Pydantic's example in Django Ninja can look something like:

python hl_lines="10"
from pydantic import ConfigDict
from ninja import Schema


def to_camel(string: str) -> str:
    words = string.split('_')
    return words[0].lower() + ''.join(word.capitalize() for word in words[1:])

class CamelModelSchema(Schema):
    model_config = ConfigDict(alias_generator=to_camel)
    str_field_name: str
    float_field_name: float

Keep in mind that when you want modify output for field names (like camel case) - you need to set as well populate_by_name and by_alias

python hl_lines="6 14"
from pydantic import ConfigDict

class UserSchema(ModelSchema):
    model_config = ConfigDict(
        alias_generator = to_camel
        populate_by_name = True,  # !!!!!! <--------
    )
    class Meta:
        model = User
        fields = ["id", "email", "is_staff"]



@api.get("/users", response=list[UserSchema], by_alias=True) # !!!!!! <-------- by_alias
def get_users(request):
    return User.objects.all()

results:

json
[
  {
    "id": 1,
    "email": "[email protected]",
    "isStaff": true
  },
  {
    "id": 2,
    "email": "[email protected]",
    "isStaff": false
  }
  ...
]