Skip to content
Guide

Type hints

The framework is typed, so your editor and type checkers like mypy and Pyright know which model you get back. This page shows what is typed and where you add the model type yourself.

Get typed results from a ModelSerializer

A ModelSerializer needs nothing extra. Its methods return the model class you call them on:

Python
article = Article.create({"title": "Hello", "body": "...", "category_id": 1})
article = Article.get(pk=1)
article = Article.update(article, {"title": "Hi"})
result = Article.bulk_create([{"title": "A", "body": "...", "category_id": 1}])
Python
article = await Article.acreate({"title": "Hello", "body": "...", "category_id": 1})
article = await Article.aget(pk=1)
article = await Article.aupdate(article, {"title": "Hi"})
Call Type
create(), acreate() Article
get(), aget() Article
update(), aupdate() Article
bulk_create(), abulk_create(), bulk_update(), abulk_update() BulkResult[Article], so result.succeeded is list[Article]
get_queryset(), aget_queryset() QuerySet[Article]
model_dump(), amodel_dump() dict[str, Any]
model_dumps(), amodel_dumps() list[dict[str, Any]]

The arguments are typed too: model_dump() takes an Article, and update() takes an Article or a primary key.

Type a Serializer

A Serializer describes a plain Django model. Put the model in brackets so the type checker knows it:

blog/serializers.py
from ninja_aio import Serializer, SchemaConfig

from .models import Article


class ArticleSerializer(Serializer[Article]):
    class Meta:
        model = Article

    class Schemas:
        create = SchemaConfig(fields=["title", "body", "category"])
        read = SchemaConfig(fields=["id", "title", "category"])

Now the same methods return Article:

Python
article = ArticleSerializer.create({"title": "Hello", "body": "...", "category_id": 1})
result = ArticleSerializer.bulk_create([...])  # BulkResult[Article]
queryset = ArticleSerializer.get_queryset()  # QuerySet[Article]

Without [Article], the type checker cannot tell which model you get back.

Type a viewset

Write APIViewSet[Article] to type the model in your viewset methods:

blog/api.py
from ninja_aio import APIViewSet
from ninja_aio.exceptions import ForbiddenError

from .models import Article


@api.viewset(model=Article)
class ArticleViewSet(APIViewSet[Article]):
    async def aon_before_object_operation(self, request, operation, obj):
        if operation == "delete" and obj.is_published:
            raise ForbiddenError()
Name Type
self.model type[Article]
obj in on_before_object_operation / aon_before_object_operation Article
obj in has_object_permission / ahas_object_permission Article

Mixins take the model the same way:

Python
from ninja_aio.views.mixins import PermissionViewSetMixin


@api.viewset(model=Article)
class ArticleViewSet(PermissionViewSetMixin[Article], APIViewSet[Article]):
    async def ahas_object_permission(self, request, operation, obj):
        return obj.is_published or operation != "retrieve"

@api.viewset keeps your class type. After the decorator, ArticleViewSet is the registered viewset, typed as ArticleViewSet.

Use schema attributes

read_schema, detail_schema, create_schema, update_schema and related_schema are typed type[Schema] | None. They are None when you do not declare that kind, so check before you use one:

Python
schema = Article.read_schema
assert schema is not None
print(schema.model_json_schema())

get_schema("read", depth=2) has the same type.

Type request.auth

Django's HttpRequest has no auth attribute, so type checkers do not know what your auth class stored there. Read it through a small typed helper:

blog/auth.py
from django.contrib.auth.models import User
from django.http import HttpRequest


def current_user(request: HttpRequest) -> User | None:
    return getattr(request, "auth", None)
Python
user = current_user(request)
if user is not None:
    print(user.username)

The helper returns whatever your auth_handler returned, or None on endpoints without authentication. Use your own user model in the annotation if you have one.

See also