Monkey-patching and extensions for django-stubs providing runtime type support for Django generic classes
—
Specialized type aliases for Django QuerySets that provide better type safety and IDE support when working with query results and values() calls. These aliases adapt their behavior based on whether type checking is active.
A flexible QuerySet type alias that works with any model type, providing proper generic support for type checkers while maintaining runtime compatibility.
QuerySetAny = QuerySet
"""
Type alias for generic QuerySet with any model.
In TYPE_CHECKING mode: Resolves to _QuerySetAny from django-stubs
At runtime: Standard Django QuerySet class
"""Specialized QuerySet type for handling values() query results with proper row typing support.
ValuesQuerySet = QuerySet
"""
Type alias for QuerySet with values() calls.
In TYPE_CHECKING mode: Resolves to _QuerySet[_T, _Row] from django-stubs
At runtime: Standard Django QuerySet class
"""Basic QuerySet typing:
from django_stubs_ext import QuerySetAny
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
# Type-safe QuerySet operations
def get_users() -> QuerySetAny:
return User.objects.all()
users: QuerySetAny = get_users()
active_users: QuerySetAny = users.filter(is_active=True)Values QuerySet typing:
from django_stubs_ext import ValuesQuerySet
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=10, decimal_places=2)
# Type-safe values() operations
def get_product_summaries() -> ValuesQuerySet:
return Product.objects.values('name', 'price')
summaries: ValuesQuerySet = get_product_summaries()
for summary in summaries:
# Type checker understands this is a dict-like row
print(f"{summary['name']}: ${summary['price']}")The aliases provide different behavior in static analysis vs runtime:
import typing
from django.db.models.query import QuerySet
if typing.TYPE_CHECKING:
# Static type checking mode
from django.db.models.query import _QuerySetAny, _QuerySet, _Row, _T
QuerySetAny = _QuerySetAny
ValuesQuerySet = _QuerySet[_T, _Row]
else:
# Runtime mode
QuerySetAny = QuerySet
ValuesQuerySet = QuerySetThis dual behavior ensures compatibility with both type checkers (which use the django-stubs type definitions) and runtime execution (which uses the actual Django QuerySet class).
These aliases work seamlessly with Django's ORM methods:
from django_stubs_ext import QuerySetAny, ValuesQuerySet
from django.db import models
from typing import Optional
class BlogPost(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
published = models.BooleanField(default=False)
def search_posts(query: str) -> QuerySetAny:
"""Search for published blog posts."""
return BlogPost.objects.filter(
title__icontains=query,
published=True
)
def get_post_titles() -> ValuesQuerySet:
"""Get just the titles of published posts."""
return BlogPost.objects.filter(published=True).values('title')
# Type-safe usage
results: QuerySetAny = search_posts("django")
first_post: Optional[BlogPost] = results.first()
titles: ValuesQuerySet = get_post_titles()
title_list: list = list(titles)import typing
from django.db.models.query import QuerySet
if typing.TYPE_CHECKING:
from django.db.models.query import _QuerySetAny, _QuerySet, _Row, _T
QuerySetAny = _QuerySetAny
ValuesQuerySet = _QuerySet[_T, _Row]
else:
QuerySetAny = QuerySet
ValuesQuerySet = QuerySetInstall with Tessl CLI
npx tessl i tessl/pypi-django-stubs-ext