website/docs/django.mdx
{/*
Pyrefly includes built-in support for Django, a popular Python web framework. This feature provides static type checking for Django's Object-Relational Mapping (ORM) system, which is used to interact with databases.
Note: This support covers a subset of Django's ORM features. The implementation continues to evolve based on feedback and development. We currently support Django models, fields, and basic relationships.
We welcome your feedback and suggestions. Please share your thoughts and ideas by opening an issue on GitHub.
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.
To use Pyrefly with Django, follow these steps:
# Install django-stubs
pip install django-stubs
# Create and activate a virtual environment
python3 -m venv .venv
source .venv/bin/activate
pip install pyrefly
That's it! Pyrefly will automatically recognize Django constructs and provide appropriate type checking.
Pyrefly provides type inference for Django's ORM without requiring any plugins or manual configuration. It:
models.ModelCharField, IntegerField, ForeignKey, etc.)id and pk)ForeignKey, ManyToManyField)choices fields, including the Choices/IntegerChoices/TextChoices enum classes and Django's auto-generated get_<fieldname>_display() methodThe 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.
Django automatically adds certain fields to every model, even if you don't define them explicitly.
id FieldBy 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()
assert_type(reporter.id, int)
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()
assert_type(reporter.uuid, UUID)
assert_type(reporter.pk, UUID) # pk aliases the custom primary key
_id Suffix FieldsFor 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()
assert_type(article.reporter_id, int)
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)
Accessing a ForeignKey field returns an instance of the related model:
article = Article()
assert_type(article.reporter, Reporter)
You can access fields on the related model:
assert_type(article.reporter.full_name, str)
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()
assert_type(article.reporter, Reporter | None)
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')
Accessing a ManyToManyField returns a manager object that provides methods to interact with the related objects:
book = Book()
assert_type(book.authors, ManyRelatedManager[Author, models.Model])
assert_type(book.authors.all(), QuerySet[Author, Author])
The manager provides methods like .add(), .remove(), .clear(), and .all() to manage the relationship.
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
assert_type(Vehicle.CAR.value, int)
assert_type(Vehicle.CAR.label, str)
assert_type(Vehicle.values, list[int])
assert_type(Vehicle.choices, list[tuple[int, str]])
Pyrefly also supports TextChoices and the base Choices class with various value types including enum.auto() for automatic value generation.
Pyrefly recognizes factory_boy factories that subclass DjangoModelFactory. For each factory, it figures out which Django model is produced and infers the correct return types for create, build, create_batch, and build_batch. No plugin or extra configuration is required.
The examples below all build on this User model and UserFactory:
# myapp/models.py
from django.db import models
class User(models.Model):
username = models.CharField(max_length=150)
# myapp/factories.py
from factory.django import DjangoModelFactory
from myapp.models import User
class UserFactory(DjangoModelFactory):
class Meta:
model = User
create() and build()UserFactory.create() and UserFactory.build() each return a User instance:
user = UserFactory.create()
assert_type(user, User)
user = UserFactory.build()
assert_type(user, User)
Field access on the returned instance is type-checked against the model:
assert_type(user.username, str)
create_batch() and build_batch()UserFactory.create_batch(n) and UserFactory.build_batch(n) return list[User]:
users = UserFactory.create_batch(3)
assert_type(users, list[User])
users = UserFactory.build_batch(3)
assert_type(users, list[User])
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.
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.authors has type: Author_ManyRelatedManager[Book_authors]article.authors has type: Author_ManyRelatedManager[Article_authors]Pyrefly (following stubs):
book.authors has type: ManyRelatedManager[Author, Model]article.authors has type: ManyRelatedManager[Author, Model]These are some of the Django features that are not yet supported:
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]
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.