CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-django-stubs-ext

Monkey-patching and extensions for django-stubs providing runtime type support for Django generic classes

Pending
Overview
Eval results
Files

protocols.mddocs/

Protocols and Base Classes

Protocol definitions for flexible attribute access and typed base classes for Django components. These provide structural typing support and typed interfaces for Django's dynamic components like Model.Meta classes and database routers.

Capabilities

Flexible Attribute Access Protocol

A protocol defining arbitrary attribute access patterns, used internally by the mypy Django plugin for dynamic attribute handling.

class AnyAttrAllowed(Protocol):
    """
    Protocol for classes allowing arbitrary attribute access.
    
    Used internally by mypy_django_plugin to handle dynamic attributes
    on Django models and other classes that support flexible attribute access.
    """
    
    def __getattr__(self, item: str) -> Any:
        """Allow getting any attribute by name."""
        ...
    
    def __setattr__(self, item: str, value: Any) -> None:
        """Allow setting any attribute by name."""
        ...

Typed Model Meta Base Class

A typed base class for Django Model Meta inner classes, providing comprehensive type safety for model metadata configuration.

class TypedModelMeta:
    """
    Typed base class for Django Model `class Meta:` inner class.
    At runtime this is just an alias to `object`.
    
    Most attributes are the same as `django.db.models.options.Options`.
    Options has some additional attributes and some values are normalized by Django.
    """
    
    abstract: ClassVar[bool]  # default: False
    app_label: ClassVar[str]
    base_manager_name: ClassVar[str]
    db_table: ClassVar[str]
    db_table_comment: ClassVar[str]
    db_tablespace: ClassVar[str]
    default_manager_name: ClassVar[str]
    default_related_name: ClassVar[str]
    get_latest_by: ClassVar[Union[str, Sequence[str]]]
    managed: ClassVar[bool]  # default: True
    order_with_respect_to: ClassVar[str]
    ordering: ClassVar[Sequence[Union[str, OrderBy]]]
    permissions: ClassVar[List[Tuple[str, str]]]
    default_permissions: ClassVar[Sequence[str]]  # default: ("add", "change", "delete", "view")
    proxy: ClassVar[bool]  # default: False
    required_db_features: ClassVar[List[str]]
    required_db_vendor: ClassVar[Literal["sqlite", "postgresql", "mysql", "oracle"]]
    select_on_save: ClassVar[bool]  # default: False
    indexes: ClassVar[List[Index]]
    unique_together: ClassVar[Union[Sequence[Sequence[str]], Sequence[str]]]
    index_together: ClassVar[Union[Sequence[Sequence[str]], Sequence[str]]]  # Deprecated in Django 4.2
    constraints: ClassVar[List[BaseConstraint]]
    verbose_name: ClassVar[StrOrPromise]
    verbose_name_plural: ClassVar[StrOrPromise]

Typed Database Router Base Class

A typed base class for Django's DATABASE_ROUTERS setting, providing comprehensive type safety for database routing logic.

class TypedDatabaseRouter:
    """
    Typed base class for Django's DATABASE_ROUTERS setting.
    At runtime this is just an alias to `object`.
    
    All methods are optional.
    
    Django documentation: https://docs.djangoproject.com/en/stable/topics/db/multi-db/#automatic-database-routing
    """
    
    def db_for_read(self, model: Type[Model], **hints: Any) -> Optional[str]:
        """Suggest the database to read from for objects of type model."""
        ...
    
    def db_for_write(self, model: Type[Model], **hints: Any) -> Optional[str]:
        """Suggest the database to write to for objects of type model."""
        ...
    
    def allow_relation(self, obj1: Type[Model], obj2: Type[Model], **hints: Any) -> Optional[bool]:
        """Return True if a relation between obj1 and obj2 should be allowed."""
        ...
    
    def allow_migrate(
        self, db: str, app_label: str, model_name: Optional[str] = None, **hints: Any
    ) -> Optional[bool]:
        """Return True if the migration operation is allowed on the database with alias db."""
        ...

Related Manager Protocols

Protocol definitions for Django's dynamically-created related managers, providing type safety for relationship management. These are re-exported from django-stubs for convenience.

# During TYPE_CHECKING: imported from django-stubs
# At runtime: Protocol definitions to prevent isinstance() usage

class RelatedManager(Protocol[_T]):
    """
    Protocol for Django related managers.
    
    These are fake classes - Django defines these inside function bodies.
    Defined as Protocol to prevent use with isinstance().
    These actually inherit from BaseManager.
    """
    pass

class ManyRelatedManager(Protocol[_T]):
    """
    Protocol for Django many-to-many related managers.
    
    These are fake classes - Django defines these inside function bodies.
    Defined as Protocol to prevent use with isinstance().
    These actually inherit from BaseManager.
    """
    pass

Usage Examples

Using TypedModelMeta for model configuration:

from django_stubs_ext.db.models import TypedModelMeta
from django.db import models
from django.utils.translation import gettext_lazy as _

class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta(TypedModelMeta):
        verbose_name = _('Article')
        verbose_name_plural = _('Articles')
        ordering = ['-created_at']
        indexes = [
            models.Index(fields=['created_at']),
            models.Index(fields=['title'])
        ]
        permissions = [
            ('can_publish', 'Can publish articles'),
            ('can_feature', 'Can feature articles')
        ]

Implementing a typed database router:

from django_stubs_ext.db.router import TypedDatabaseRouter
from django.db.models import Model
from typing import Optional, Type, Any

class DatabaseRouter(TypedDatabaseRouter):
    """Route reads and writes based on model type."""
    
    def db_for_read(self, model: Type[Model], **hints: Any) -> Optional[str]:
        """Route read operations."""
        if model._meta.app_label == 'analytics':
            return 'analytics_db'
        return None
    
    def db_for_write(self, model: Type[Model], **hints: Any) -> Optional[str]:
        """Route write operations."""
        if model._meta.app_label == 'analytics':
            return 'analytics_db'
        return None
    
    def allow_relation(self, obj1: Type[Model], obj2: Type[Model], **hints: Any) -> Optional[bool]:
        """Allow relations within the same app."""
        db_set = {'default', 'analytics_db'}
        if hints.get('instance1')._meta.app_label in db_set:
            return hints.get('instance2')._meta.app_label in db_set
        return None
    
    def allow_migrate(
        self, db: str, app_label: str, model_name: Optional[str] = None, **hints: Any
    ) -> Optional[bool]:
        """Ensure analytics models only appear in analytics_db."""
        if app_label == 'analytics':
            return db == 'analytics_db'
        elif db == 'analytics_db':
            return False
        return None

Using related manager protocols:

from django_stubs_ext.db.models.manager import RelatedManager, ManyRelatedManager
from django.db import models
from django.db.models import QuerySet
from typing import TYPE_CHECKING

class Author(models.Model):
    name = models.CharField(max_length=100)

class Publisher(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=200)
    authors = models.ManyToManyField(Author, related_name='books')
    publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE, related_name='books')

if TYPE_CHECKING:
    # Type annotations for related managers
    Author.books: ManyRelatedManager[Book]
    Publisher.books: RelatedManager[Book]

# Type-safe usage
def get_author_books(author: Author) -> QuerySet[Book]:
    """Get all books by an author."""
    return author.books.all()  # Type checker knows this returns QuerySet[Book]

AnyAttrAllowed Protocol Usage

The AnyAttrAllowed protocol is primarily used internally by the mypy Django plugin:

from django_stubs_ext import AnyAttrAllowed
from typing import Any

class DynamicModel(AnyAttrAllowed):
    """Example class with flexible attribute access."""
    
    def __init__(self):
        self._data = {}
    
    def __getattr__(self, item: str) -> Any:
        return self._data.get(item)
    
    def __setattr__(self, item: str, value: Any) -> None:
        if item.startswith('_'):
            super().__setattr__(item, value)
        else:
            self._data[item] = value

# Type-safe dynamic attribute access
model = DynamicModel()
model.any_attribute = "value"  # Type checker allows this
retrieved = model.any_attribute  # Type checker knows this is Any

Types

from typing import TYPE_CHECKING, Any, ClassVar, List, Literal, Optional, Protocol, Sequence, Tuple, Type, TypeVar, Union
from django.db.models import BaseConstraint, Index, Model, OrderBy
from django_stubs_ext import StrOrPromise

_T = TypeVar("_T")

if TYPE_CHECKING:
    class TypedModelMeta:
        """Typed base class for Django Model `class Meta:` inner class."""
        # ... (all the attributes as shown above)
    
    class TypedDatabaseRouter:
        """Typed base class for Django's DATABASE_ROUTERS setting."""
        # ... (all the methods as shown above)
else:
    TypedModelMeta = object
    TypedDatabaseRouter = object

class AnyAttrAllowed(Protocol):
    """Protocol for classes allowing arbitrary attribute access."""
    def __getattr__(self, item: str) -> Any: ...
    def __setattr__(self, item: str, value: Any) -> None: ...

class RelatedManager(Protocol[_T]):
    """Protocol for Django related managers."""
    pass

class ManyRelatedManager(Protocol[_T]):
    """Protocol for Django many-to-many related managers."""
    pass

Install with Tessl CLI

npx tessl i tessl/pypi-django-stubs-ext

docs

index.md

model-annotations.md

monkey-patching.md

protocols.md

queryset-types.md

string-types.md

tile.json