Experimental Django Support
Pyrefly includes experimental support for Django, a popular Python web framework. This feature aims to provide improved static type checking for Django's Object-Relational Mapping (ORM) system, which is used to interact with databases.
Note: This support is experimental and currently covers a subset of Django's ORM features. The implementation is actively evolving based on feedback and development. We currently support Django models, fields, and basic relationships.
Feedback
We welcome your feedback and suggestions. Please share your thoughts and ideas by opening an issue on GitHub.
What is Django?
Django is a high-level Python web framework. One of its core components is the Object-Relational Mapping (ORM) system, which allows developers to interact with databases using Python classes (models) instead of writing SQL queries directly.
Django models define the structure of your database tables, and Django automatically handles the creation and management of the underlying database schema.
Getting Started with Django and Pyrefly
To use Pyrefly with Django, follow these steps:
1. Install django-stubs and set up a virtual environment
# Install django-stubs
pip install django-stubs
# Create and activate a virtual environment
python3 -m venv .venv
source .venv/bin/activate
2. Install pyrefly (version 0.42.0 or later)
pip install pyrefly
That's it! Pyrefly will automatically recognize Django constructs and provide appropriate type checking.
How Pyrefly Supports Django
Pyrefly provides type inference for Django's ORM without requiring any plugins or manual configuration. It:
- Recognizes Django model classes that inherit from
models.Model - Understands Django field types (
CharField,IntegerField,ForeignKey, etc.) - Infers types for auto-generated fields (like
idandpk) - Handles relationships between models (
ForeignKey,ManyToManyField) - Provides special enum support
- Provides immediate feedback when the code has type errors
- Does not require a plugin or manual config — support is built-in and automatic
Supported Features with Examples
The following examples showcase which Django features are currently supported by Pyrefly. This is a subset of Django's full feature set, but covers the most common use cases.
Auto-Generated Fields
Django automatically adds certain fields to every model, even if you don't define them explicitly.
Primary Key: id Field
By default, Django automatically adds an id field to serve as the primary key (unless you define a custom primary key):
from django.db import models
class Reporter(models.Model):
full_name = models.CharField(max_length=70)
# Django auto-adds: id = models.AutoField(primary_key=True)
reporter = Reporter()
reveal_type(reporter.id) # Pyrefly infers: int
Custom Primary Keys
If you define a field with primary_key=True, Django will not add the id field. Pyrefly correctly infers the type of custom primary keys:
from django.db import models
class Reporter(models.Model):
uuid = models.UUIDField(primary_key=True)
full_name = models.CharField(max_length=70)
reporter = Reporter()
reveal_type(reporter.uuid) # Pyrefly infers: UUID
reveal_type(reporter.pk) # Pyrefly infers: UUID (pk aliases the custom primary key)
ForeignKey _id Suffix Fields
For every ForeignKey field named X, Django automatically creates a field named X_id that stores the ID of the related object:
class Article(models.Model):
reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)
# Django auto-adds: reporter_id: int
article = Article()
reveal_type(article.reporter_id) # Pyrefly infers: int
ForeignKey Relationships
A ForeignKey creates a many-to-one relationship where each instance of one model relates to an instance of another model.
from django.db import models
class Reporter(models.Model):
full_name = models.CharField(max_length=70)
class Article(models.Model):
reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)
Basic Forward Access
Accessing a ForeignKey field returns an instance of the related model:
article = Article()
reveal_type(article.reporter) # Pyrefly infers: Reporter
Chained Access
You can access fields on the related model:
reveal_type(article.reporter.full_name) # Pyrefly infers: str
Nullable ForeignKeys
If a ForeignKey has null=True, Pyrefly reflects this in the inferred type:
class Article(models.Model):
reporter = models.ForeignKey(Reporter, null=True, on_delete=models.CASCADE)
article = Article()
reveal_type(article.reporter) # Pyrefly infers: Reporter | None
ManyToManyField Relationships
A ManyToManyField creates a many-to-many relationship where instances of one model can be related to multiple instances of another model.
class Author(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')
Forward Managers
Accessing a ManyToManyField returns a manager object that provides methods to interact with the related objects:
book = Book()
reveal_type(book.authors) # Pyrefly infers: ManyRelatedManager[Author, Model]
reveal_type(book.authors.all()) # Pyrefly infers: QuerySet[Author, Author]
The manager provides methods like .add(), .remove(), .clear(), and .all() to manage the relationship.
Django Model Enums
Pyrefly supports Django's model choices using Choices, IntegerChoices, and TextChoices. These provide type-safe enumerations for model fields.
from django.db import models
class Vehicle(models.IntegerChoices):
CAR = 1, "Car"
TRUCK = 2, "Truck"
MOTORCYCLE = 3, "Motorcycle"
class Product(models.Model):
vehicle_type = models.IntegerField(choices=Vehicle.choices)
# Pyrefly correctly infers enum types
reveal_type(Vehicle.CAR.value) # Pyrefly infers: int
reveal_type(Vehicle.CAR.label) # Pyrefly infers: str
reveal_type(Vehicle.values) # Pyrefly infers: list[int]
reveal_type(Vehicle.choices) # Pyrefly infers: list[tuple[int, str]]
Pyrefly also supports TextChoices and the base Choices class with various value types including enum.auto() for automatic value generation.
Differences from Mypy
Mypy uses a plugin (mypy-django-plugin) that provides very detailed type information by accessing runtime Django internals and performing multiple passes over the code. Pyrefly takes a different approach by following the type stubs directly without runtime introspection.
Type Representation Differences
In some cases, such as ManyToManyField relationships, Mypy and Pyrefly infer different types:
Example:
from django.db import models
class Author(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")
class Article(models.Model):
headline = models.CharField(max_length=200)
authors = models.ManyToManyField(Author, related_name="articles")
# What types do the managers have?
book = Book()
article = Article()
Mypy (with django plugin):
book.authorshas type:Author_ManyRelatedManager[Book_authors]article.authorshas type:Author_ManyRelatedManager[Article_authors]- These are different types (different class name, different type parameter)
- Mypy will reject assigning one to the other
Pyrefly (following stubs):
book.authorshas type:ManyRelatedManager[Author, Model]article.authorshas type:ManyRelatedManager[Author, Model]- These are the same type
- Pyrefly will accept assigning one to the other
Features Not Yet Supported
These are some of the Django features are not yet supported in this experimental release:
Reverse Relationships
Django automatically creates reverse relationships for ForeignKey and ManyToManyField. For example:
class Reporter(models.Model):
full_name = models.CharField(max_length=70)
class Article(models.Model):
reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)
# Not yet supported:
reporter = Reporter()
reveal_type(reporter.article_set) # Expected: RelatedManager[Article]
Advanced QuerySet Operations
While basic .all() operations are supported, more complex QuerySet operations may not have complete type inference.
Those are not the only unsupported features, so if there are specific features you would like to see, please request them by opening a github issue and adding the Django label to it.