78π
One solution may be using Union typing class.
from typing import Union, List
from django.db.models import QuerySet
from my_app.models import MyModel
def somefunc(row: Union[QuerySet, List[MyModel]]):
pass
Now when you slice the row
argument it will know that the returned type is either another list of MyModel or an instance of MyModel, whilst also hinting that the methods of the QuerySet
class are available on the row
argument too.
45π
Thereβs a special package called django-stubs
(the name follows PEP561
) to type your django
code.
Thatβs how it works:
# server/apps/main/views.py
from django.http import HttpRequest, HttpResponse
from django.shortcuts import render
def index(request: HttpRequest) -> HttpResponse:
reveal_type(request.is_ajax)
reveal_type(request.user)
return render(request, 'main/index.html')
Output:
Β» PYTHONPATH="$PYTHONPATH:$PWD" mypy server
server/apps/main/views.py:14: note: Revealed type is 'def () -> builtins.bool'
server/apps/main/views.py:15: note: Revealed type is 'django.contrib.auth.models.User'
And with models and QuerySet
s:
# server/apps/main/logic/repo.py
from django.db.models.query import QuerySet
from server.apps.main.models import BlogPost
def published_posts() -> 'QuerySet[BlogPost]': # works fine!
return BlogPost.objects.filter(
is_published=True,
)
Output:
reveal_type(published_posts().first())
# => Union[server.apps.main.models.BlogPost*, None]
- Complete tutorial: https://sobolevn.me/2019/08/typechecking-django-and-drf
- Types for
django
: https://github.com/typeddjango/django-stubs - Types for
drf
: https://github.com/typeddjango/djangorestframework-stubs
- [Django]-What are the limitations of Django's ORM?
- [Django]-Running Django with FastCGI or with mod_python
- [Django]-Error: No module named staticfiles
26π
I made this helper class to get a generic type hint:
from django.db.models import QuerySet
from typing import Iterator, Union, TypeVar, Generic
T = TypeVar("T")
class ModelType(Generic[T]):
def __iter__(self) -> Iterator[Union[T, QuerySet]]:
pass
Then use it like this:
def somefunc(row: ModelType[SomeModel]):
pass
This reduces the noise everytime I use this type and it make it usable between models (like ModelType[DifferentModel]
).
- [Django]-Django β "Incorrect type. Expected pk value, received str" error
- [Django]-Differences between STATICFILES_DIR, STATIC_ROOT and MEDIA_ROOT
- [Django]-Set up a scheduled job?
17π
Update 2023
In django_hint 0.3.0 you can extend your model and the return type of the common functions of objects
such as filter
, get
, etc. will be automatically detected.
from django_hint import StandardModelType
class SampleModel(models.Model, StandardModelType['SampleModel']):
name: str = models.CharField(max_length=300)
Original Answer
This is an improved helper class of Or Duan.
from django.db.models import QuerySet
from typing import Iterator, TypeVar, Generic
_Z = TypeVar("_Z")
class QueryType(Generic[_Z], QuerySet):
def __iter__(self) -> Iterator[_Z]: ...
This class is used specifically for QuerySet
object such as when you use filter
in a query.
Sample:
from some_file import QueryType
sample_query: QueryType[SampleClass] = SampleClass.objects.filter(name=name)
Now the interpreter recognizes the sample_query
as a QuerySet
object and you will get suggestions such as count()
and while looping through the objects, you will get suggestions for the SampleClass
Note
This format of type hinting is available from python3.6
onwards.
You can also use django_hint which has hinting classes specifically for Django.
Disclaimer: I am the author of django_hint
- [Django]-Make the first letter uppercase inside a django template
- [Django]-How do you use the django-filter package with a list of parameters?
- [Django]-Django error: got multiple values for keyword argument
13π
QuerySet
is a good approach for function/method returning any queryset of any models. The Django queryset is iterable. But when the return type is very specific to one model, it may be better to use QuerySet[Model]
over QuerySet
.
Example: Filter all active users of a company
import datetime
from django.utils import timezone
from myapp.models import User
from collections.abc import Iterable
def get_active_users(company_id: int) -> QuerySet[User]:
one_month_ago = (timezone.now() - datetime.timedelta(days=30)).timestamp()
return User.objects.filter(company_id=company_id, is_active=True,
last_seen__gte=one_month_ago)
The above function signature is more readable than def get_active_users(company_id: int) -> QuerySet:
Iterable[User]
will also work the type checker will complain when the returned queryset is invoked on other methods.
def func() -> Iterable[User]:
return User.objects.all()
users = func()
users.filter(email__startswith='support')
MyPy output
"Iterable[User]" has no attribute "filter"
- [Django]-Django Multiple Authentication Backend for one project
- [Django]-Create Django model or update if exists
- [Django]-Trying to migrate in Django 1.9 β strange SQL error "django.db.utils.OperationalError: near ")": syntax error"
8π
IMHO, the proper way to do it is to define a type that inherits QuerySet
and specify a generic return type for the iterator.
from django.db.models import QuerySet
from typing import Iterator, TypeVar, Generic, Optional
T = TypeVar("T")
class QuerySetType(Generic[T], QuerySet): # QuerySet + Iterator
def __iter__(self) -> Iterator[T]:
pass
def first(self) -> Optional[T]:
pass
# ... add more refinements
Then you can use it like this:
users: QuerySetType[User] = User.objects.all()
for user in users:
print(user.email) # typing OK!
user = users.first() # typing OK!
- [Django]-Django filter JSONField list of dicts
- [Django]-Heroku, postgreSQL, django, comments, tastypie: No operator matches the given name and argument type(s). You might need to add explicit type casts
- [Django]-How do I add a placeholder on a CharField in Django?
7π
You actually can do what you want if you import the annotations module:
from __future__ import annotations
from django.db import models
from django.db.models.query import QuerySet
class MyModel(models.Model):
pass
def my_function() -> QuerySet[MyModel]:
return MyModel.objects.all()
Neither MyPy nor the Python interpreter will complain or raise exceptions on this (tested on python 3.7). MyPy will probably be unable to type-check it, but if all you want is to document your return type, this should be good enough.
- [Django]-Django REST Framework : "This field is required." with required=False and unique_together
- [Django]-What is the purpose of adding to INSTALLED_APPS in Django?
- [Django]-How to delete a record in Django models?
3π
from typing import Iterable
def func(queryset_or_list: Iterable[MyModel]):
pass
Both of queryset and list of model instance is iterable object.
- [Django]-Annotate a queryset with the average date difference? (django)
- [Django]-OneToOneField() vs ForeignKey() in Django
- [Django]-Django values_list vs values
1π
Iβve found myself that using typing.Sequence
to solve a similar issue:
from typing import Sequence
def print_emails(users: Sequence[User]):
for user in users:
print(user.email)
users = User.objects.all()
print_emails(users=users)
As far as I know from docs:
A Sequence is anything that supports len() and .getitem(), independent of its actual type.
- [Django]-Logging in Django and gunicorn
- [Django]-How do you log server errors on django sites
- [Django]-STATIC_ROOT vs STATIC_URL in Django
1π
from typing import (TypeVar, Generic, Iterable, Optional)
from django.db.models import Model
from django.db.models import QuerySet
_T = TypeVar("_T", bound=Model)
class QuerySetType(Generic[_T], QuerySet):
def __iter__(self) -> Iterable[_T]:
pass
def first(self) -> Optional[_T]:
pass
- [Django]-Getting the SQL from a Django QuerySet
- [Django]-Why won't Django use IPython?
- [Django]-How to override and extend basic Django admin templates?