7

What type hint to use for a function which returns a queryset like the one below?

def _get_cars_for_validation(filter_: dict) -> QuerySet:
    return (
        Car.objects.filter(**filter_)
        .values("id", "brand", "engine")
        .order_by("id")
    )

mypy returns an error

Incompatible return value type (got "ValuesQuerySet[Car, TypedDict({'id': int, 'brand': str, 'engine': str})]", expected "QuerySet[Any]")

I would use ValuesQuerySet but it was removed in Django 1.9. ValuesQuerySet reported by mypy comes from 3rd party lib django-stubs (and I am unable to import it; is it actually possible?).

Dušan Maďar
  • 9,269
  • 5
  • 49
  • 64

2 Answers2

4

I had exactly the same problem, and I found a solution in a GitHub issue:

import typing

if typing.TYPE_CHECKING:
    from django.db.models.query import ValuesQuerySet

def _get_cars_for_validation(filter_: dict) -> 'ValuesQuerySet[Car, int]':
    return (
        Car.objects.filter(**filter_)
        .values("id", "brand", "engine")
        .order_by("id")
    )

The if typing.TYPE_CHECKING prevents the ImportError when you run the program, because the nonexistent ValuesQuerySet is only imported during the mypy check. Also note that the annotation has to be a string: 'ValuesQuerySet[Car, int]'.

The second argument to 'ValuesQuerySet[Car, int]' is a mystery to me; the OP of the issue used int "and it worked", in my case I tried a few other types, and all of them worked too; you may just as well use Any, I suppose.

You can use reveal_type() to inspect the type of the variable yourself.

natka_m
  • 1,297
  • 17
  • 22
  • 1
    I'm having the same issue and plan to annotate with # type: ignore considering how hacky this ends up being. – Casey Jan 11 '21 at 20:28
1

You can also cast to QuerySet:

from typing import cast

from django.db.models import QuerySet

return (
        cast(QuerySet, Car.objects.filter(**filter_))
        .values("id", "brand", "engine")
        .order_by("id")
    )
Stefan_EOX
  • 1,279
  • 1
  • 16
  • 35