Monkey-patching and extensions for django-stubs providing runtime type support for Django generic 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.
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."""
...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]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."""
...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.
"""
passUsing 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 NoneUsing 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]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 Anyfrom 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."""
passInstall with Tessl CLI
npx tessl i tessl/pypi-django-stubs-ext