PEP 484 type stubs for Django REST Framework enabling static type checking with comprehensive type definitions for all major DRF components
—
Django REST Framework serializers provide a powerful system for converting complex data types to native Python datatypes and vice versa. The type stubs provide comprehensive type safety for all serializer operations, field definitions, and validation patterns.
class BaseSerializer(Field[Any, Any, Any, _IN]):
"""Base class for all serializers with generic instance type support."""
partial: bool
many: bool
instance: _IN | None
initial_data: Any
def __init__(
self,
instance: _IN | None = None,
data: Any = empty,
**kwargs: Any
) -> None: ...
def is_valid(self, *, raise_exception: bool = False) -> bool: ...
def save(self, **kwargs: Any) -> _IN: ...
def create(self, validated_data: Any) -> _IN: ...
def update(self, instance: _IN, validated_data: Any) -> _IN: ...
def to_representation(self, instance: _IN) -> Any: ...Parameters:
instance: _IN | None - Object instance to serialize or updatedata: Any - Input data for deserializationpartial: bool - Allow partial updates when Truemany: bool - Serialize multiple objects when True**kwargs: Any - Additional serializer optionsclass Serializer(BaseSerializer[_IN]):
"""Main serializer class for custom serializations."""
def __init__(
self,
instance: _IN | None = None,
data: Any = empty,
many: bool = False,
partial: bool = False,
context: dict[str, Any] | None = None,
allow_empty: bool = False,
**kwargs: Any
) -> None: ...
def get_fields(self) -> dict[str, Field]: ...
def validate(self, attrs: Any) -> Any: ...
def to_representation(self, instance: _IN) -> dict[str, Any]: ...Parameters:
context: dict[str, Any] | None - Additional context for serializationallow_empty: bool - Allow empty values when Trueclass ModelSerializer(Serializer[_MT]):
"""Serializer that automatically generates fields from Django models."""
serializer_field_mapping: dict[type[models.Field], type[Field]]
class Meta:
model: type[_MT]
fields: Sequence[str] | Literal["__all__"]
exclude: Sequence[str] | None
read_only_fields: Sequence[str] | None
extra_kwargs: dict[str, dict[str, Any]]
depth: int | None
def get_field_names(self, declared_fields: dict[str, Field], info: Any) -> list[str]: ...
def build_field(self, field_name: str, info: Any, model_class: type[Model], nested_depth: int) -> tuple[type[Field], dict[str, Any]]: ...
def build_standard_field(self, field_name: str, model_field: models.Field) -> tuple[type[Field], dict[str, Any]]: ...
def build_relational_field(self, field_name: str, relation_info: Any) -> tuple[type[Field], dict[str, Any]]: ...
def build_nested_field(self, field_name: str, relation_info: Any, nested_depth: int) -> tuple[type[Field], dict[str, Any]]: ...
def create(self, validated_data: dict[str, Any]) -> _MT: ...
def update(self, instance: _MT, validated_data: dict[str, Any]) -> _MT: ...Meta Class Parameters:
model: type[_MT] - Django model class to serializefields: Sequence[str] | Literal["__all__"] - Fields to include ('all' or field list)exclude: Sequence[str] | None - Fields to exclude from serializationread_only_fields: Sequence[str] | None - Fields that cannot be modifiedextra_kwargs: dict[str, dict[str, Any]] - Additional field argumentsdepth: int | None - Depth of nested serialization (default: None)class HyperlinkedModelSerializer(ModelSerializer[_MT]):
"""Model serializer that uses hyperlinked relationships."""
serializer_related_field: type[HyperlinkedRelatedField]
serializer_url_field: type[HyperlinkedIdentityField]
url_field_name: str
class Meta(ModelSerializer.Meta):
passclass ListSerializer(BaseSerializer[_IN]):
"""Serializer for handling multiple object instances."""
child: Field | BaseSerializer | None
many: bool
allow_empty: bool | None
max_length: int | None
min_length: int | None
def __init__(
self,
*args: Any,
child: BaseSerializer | None = None,
allow_empty: bool = True,
max_length: int | None = None,
min_length: int | None = None,
**kwargs: Any
) -> None: ...
def create(self, validated_data: list[dict[str, Any]]) -> list[_IN]: ...
def update(self, instance: list[_IN], validated_data: list[dict[str, Any]]) -> list[_IN]: ...Parameters:
child: BaseSerializer | None - Serializer for individual itemsallow_empty: bool - Allow empty lists when Truemax_length: int | None - Maximum number of itemsmin_length: int | None - Minimum number of itemsfrom rest_framework import serializers
class BookSerializer(serializers.ModelSerializer[Book]):
# Explicit field declarations
isbn = serializers.CharField(max_length=13, required=True)
rating = serializers.FloatField(min_value=0.0, max_value=5.0)
tags = serializers.ListField(child=serializers.CharField())
# Method fields
author_name = serializers.SerializerMethodField()
class Meta:
model = Book
fields = ['id', 'title', 'isbn', 'rating', 'tags', 'author_name']
read_only_fields = ['id']
def get_author_name(self, obj: Book) -> str:
return f"{obj.author.first_name} {obj.author.last_name}"class AuthorSerializer(serializers.ModelSerializer[Author]):
class Meta:
model = Author
fields = ['id', 'first_name', 'last_name', 'bio']
class BookSerializer(serializers.ModelSerializer[Book]):
author = AuthorSerializer(read_only=True)
author_id = serializers.IntegerField(write_only=True)
class Meta:
model = Book
fields = ['id', 'title', 'author', 'author_id', 'published_date']
def create(self, validated_data: dict[str, Any]) -> Book:
author_id = validated_data.pop('author_id')
author = Author.objects.get(id=author_id)
return Book.objects.create(author=author, **validated_data)class BookSearchSerializer(serializers.Serializer):
"""Custom serializer for search functionality."""
query = serializers.CharField(max_length=100)
author = serializers.CharField(max_length=50, required=False)
genre = serializers.ChoiceField(choices=['fiction', 'non-fiction', 'mystery'], required=False)
published_after = serializers.DateField(required=False)
min_rating = serializers.FloatField(min_value=0.0, max_value=5.0, required=False)
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
"""Cross-field validation."""
if attrs.get('min_rating', 0) > 4.5 and not attrs.get('author'):
raise serializers.ValidationError(
"High rating searches require an author filter"
)
return attrs
def validate_query(self, value: str) -> str:
"""Field-level validation."""
if len(value.strip()) < 2:
raise serializers.ValidationError("Query must be at least 2 characters")
return value.strip()class UserSerializer(serializers.ModelSerializer[User]):
class Meta:
model = User
fields = ['username', 'email', 'password']
extra_kwargs = {
'password': {'write_only': True, 'min_length': 8}
}
def validate_username(self, value: str) -> str:
"""Validate username field."""
if User.objects.filter(username__iexact=value).exists():
raise serializers.ValidationError("Username already exists")
if not value.replace('_', '').replace('-', '').isalnum():
raise serializers.ValidationError("Username can only contain letters, numbers, - and _")
return value
def validate_email(self, value: str) -> str:
"""Validate email field."""
if User.objects.filter(email__iexact=value).exists():
raise serializers.ValidationError("Email already registered")
return value.lower()class EventSerializer(serializers.ModelSerializer[Event]):
class Meta:
model = Event
fields = ['name', 'start_date', 'end_date', 'max_attendees', 'current_attendees']
read_only_fields = ['current_attendees']
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
"""Cross-field validation."""
start_date = attrs.get('start_date')
end_date = attrs.get('end_date')
if start_date and end_date:
if start_date >= end_date:
raise serializers.ValidationError(
"End date must be after start date"
)
if start_date < timezone.now().date():
raise serializers.ValidationError(
"Start date cannot be in the past"
)
max_attendees = attrs.get('max_attendees')
if max_attendees and max_attendees < 1:
raise serializers.ValidationError(
"Maximum attendees must be at least 1"
)
return attrsfrom rest_framework import validators
def validate_positive(value: int) -> int:
"""Ensure value is positive."""
if value <= 0:
raise serializers.ValidationError("Value must be positive")
return value
class ProductSerializer(serializers.ModelSerializer[Product]):
price = serializers.DecimalField(
max_digits=10,
decimal_places=2,
validators=[validate_positive]
)
class Meta:
model = Product
fields = ['name', 'price', 'category', 'stock_quantity']
validators = [
validators.UniqueTogetherValidator(
queryset=Product.objects.all(),
fields=['name', 'category']
)
]# Serializing single object
book = Book.objects.get(id=1)
serializer = BookSerializer(book)
data = serializer.data # dict[str, Any]
# Serializing multiple objects
books = Book.objects.all()
serializer = BookSerializer(books, many=True)
data = serializer.data # list[dict[str, Any]]
# Including context
serializer = BookSerializer(book, context={'request': request})
data = serializer.data# Creating new object
data = {'title': 'New Book', 'author_id': 1}
serializer = BookSerializer(data=data)
if serializer.is_valid():
book = serializer.save() # Returns Book instance
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# Updating existing object
book = Book.objects.get(id=1)
data = {'title': 'Updated Title'}
serializer = BookSerializer(book, data=data, partial=True)
if serializer.is_valid():
updated_book = serializer.save()
return Response(serializer.data)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)class BookListSerializer(serializers.ModelSerializer[Book]):
"""Minimal serializer for list views."""
author_name = serializers.CharField(source='author.full_name', read_only=True)
class Meta:
model = Book
fields = ['id', 'title', 'author_name', 'rating']
class BookDetailSerializer(serializers.ModelSerializer[Book]):
"""Full serializer for detail views."""
author = AuthorSerializer(read_only=True)
reviews = ReviewSerializer(many=True, read_only=True)
class Meta:
model = Book
fields = ['id', 'title', 'description', 'author', 'reviews', 'published_date']
# Dynamic serializer selection
def get_serializer_class(self) -> type[BaseSerializer]:
if self.action == 'list':
return BookListSerializer
return BookDetailSerializerdef as_serializer_error(exc: Exception) -> dict[str, list[ErrorDetail]]:
"""Convert exception to serializer error format."""
...
def raise_errors_on_nested_writes(
method_name: str,
serializer: BaseSerializer,
validated_data: Any
) -> None:
"""Raise error if nested writes are attempted."""
...LIST_SERIALIZER_KWARGS: Sequence[str]
LIST_SERIALIZER_KWARGS_REMOVE: Sequence[str]
ALL_FIELDS: str # Value: '__all__'from rest_framework.exceptions import ValidationError
# Field-level validation error
def validate_age(self, value: int) -> int:
if value < 0 or value > 150:
raise ValidationError("Age must be between 0 and 150")
return value
# Object-level validation error
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
if attrs['password'] != attrs['confirm_password']:
raise ValidationError("Passwords don't match")
return attrs# Single field error
{
"username": ["This field is required."]
}
# Multiple field errors
{
"username": ["This field is required."],
"email": ["Enter a valid email address."]
}
# Non-field errors
{
"non_field_errors": ["Passwords don't match."]
}class AuthorSerializer(serializers.ModelSerializer[Author]):
book_count = serializers.SerializerMethodField()
class Meta:
model = Author
fields = ['id', 'name', 'book_count']
def get_book_count(self, obj: Author) -> int:
# Use prefetch_related to avoid N+1 queries
return obj.books.count()
# In view:
queryset = Author.objects.prefetch_related('books')class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""Serializer that allows dynamic field selection."""
def __init__(self, *args: Any, **kwargs: Any) -> None:
fields = kwargs.pop('fields', None)
super().__init__(*args, **kwargs)
if fields is not None:
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)This comprehensive serializer system provides type-safe data conversion, validation, and serialization for Django REST Framework applications, with full mypy support for catching type errors during development.
Install with Tessl CLI
npx tessl i tessl/pypi-djangorestframework-stubs