CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-djangorestframework-stubs

PEP 484 type stubs for Django REST Framework enabling static type checking with comprehensive type definitions for all major DRF components

Pending
Overview
Eval results
Files

serializers.mddocs/

Serializers

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.

Core Serializer Classes

BaseSerializer

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 update
  • data: Any - Input data for deserialization
  • partial: bool - Allow partial updates when True
  • many: bool - Serialize multiple objects when True
  • **kwargs: Any - Additional serializer options

Serializer

class 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 serialization
  • allow_empty: bool - Allow empty values when True

ModelSerializer

class 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 serialize
  • fields: Sequence[str] | Literal["__all__"] - Fields to include ('all' or field list)
  • exclude: Sequence[str] | None - Fields to exclude from serialization
  • read_only_fields: Sequence[str] | None - Fields that cannot be modified
  • extra_kwargs: dict[str, dict[str, Any]] - Additional field arguments
  • depth: int | None - Depth of nested serialization (default: None)

HyperlinkedModelSerializer

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):
        pass

ListSerializer

class 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 items
  • allow_empty: bool - Allow empty lists when True
  • max_length: int | None - Maximum number of items
  • min_length: int | None - Minimum number of items

Serializer Configuration

Field Declaration

from 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}"

Nested Serialization

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)

Custom Serializer

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()

Validation Capabilities

Field-Level Validation

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()

Object-Level Validation

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 attrs

Custom Validators

from 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']
            )
        ]

Serialization Operations

Basic Serialization

# 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

Deserialization and Validation

# 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)

Advanced Serialization Patterns

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 BookDetailSerializer

Utility Functions

Serializer Helpers

def 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."""
    ...

Constants

LIST_SERIALIZER_KWARGS: Sequence[str]
LIST_SERIALIZER_KWARGS_REMOVE: Sequence[str]  
ALL_FIELDS: str  # Value: '__all__'

Error Handling

Validation Errors

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

Error Response Format

# 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."]
}

Performance Considerations

Optimizing Queries

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')

Field Selection

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

docs

authentication-permissions.md

exceptions-status.md

fields-relations.md

index.md

pagination-filtering.md

requests-responses.md

routers-urls.md

serializers.md

views-viewsets.md

tile.json