Wie verwende ich Hinweise vom Typ Python mit Django QuerySet?

73

Ist es möglich, den Datensatztyp in Django QuerySet mit Python-Typhinweisen anzugeben? So etwas wie QuerySet[SomeModel]?

Zum Beispiel haben wir Modell:

class SomeModel(models.Model):
    smth = models.IntegerField()

Und wir möchten QuerySet dieses Modells als Parameter in func übergeben:

def somefunc(rows: QuerySet):
    pass

Aber wie man den Datensatztyp in QuerySet angibt, wie bei List[SomeModel]:

def somefunc(rows: List[SomeModel]):
    pass

aber mit QuerySet?

Алексей Голобурдин
quelle
Sie suchen möglicherweise nach SomeModelQuerySet und importieren es aus demselben Pfad, aus dem Sie SomeModel importieren
JayTurnr

Antworten:

49

Eine Lösung könnte die Verwendung der Union-Typisierungsklasse sein.

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

Wenn Sie nun das rowArgument in Scheiben schneiden , wissen Sie, dass der zurückgegebene Typ entweder eine andere Liste von MyModel oder eine Instanz von MyModel ist, und weisen gleichzeitig darauf hin, dass die Methoden der QuerySetKlasse auch für das rowArgument verfügbar sind .

AJ Parr
quelle
2
Es gibt stubsfür Django mit getippten QuerySetund Modellen: github.com/typeddjango/django-stubs Tutorial: sobolevn.me/2019/08/typechecking-django-and-drf
sobolevn
21

Ich habe diese Hilfsklasse erstellt, um einen allgemeinen Typ-Hinweis zu erhalten:

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

Dann benutze es so:

def somefunc(row: ModelType[SomeModel]):
    pass

Dies reduziert das Rauschen jedes Mal, wenn ich diesen Typ verwende, und macht ihn zwischen Modellen (wie ModelType[DifferentModel]) verwendbar .

Oder Duan
quelle
15

Es gibt ein spezielles Paket namens django-stubs(der Name folgtPEP561 ), um Ihren djangoCode einzugeben.

So funktioniert das:

# 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')

Ausgabe:

» 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'

Und mit Modellen und QuerySets:

# 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,
    )

Ausgabe:

reveal_type(published_posts().first())
# => Union[server.apps.main.models.BlogPost*, None]
sobolevn
quelle
10

Dies ist eine verbesserte Helferklasse von 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]: ...

Diese Klasse wird speziell für QuerySetObjekte verwendet, z. B. wenn Sie sie filterin einer Abfrage verwenden.
Stichprobe:

from some_file import QueryType

sample_query: QueryType[SampleClass] = SampleClass.objects.filter(name=name)

Jetzt erkennt der Interpreter das sample_queryals QuerySetObjekt und Sie erhalten Vorschläge wie count()und während Sie die Objekte durchlaufen, erhalten Sie Vorschläge für dasSampleClass

Hinweis
Dieses Format für Typhinweise ist ab sofort python3.6verfügbar.


Sie können auch django_hint verwenden, das Hinweisklassen speziell für Django enthält.

Ramtin
quelle
Kurznotiz, wenn jemand VSCode verwendet; Diese Lösung funktioniert einwandfrei, wenn der Antworttyp durch einfache Anführungszeichen ersetzt wird (der Grund ist nicht bekannt). def my_method(self) -> 'QueryType[MyModel]': ...
Manu
2

Meiner Meinung nach besteht der richtige Weg darin, einen Typ zu definieren QuerySet, der einen generischen Rückgabetyp für den Iterator erbt, und diesen anzugeben.

    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


Dann können Sie es so verwenden:

users: QuerySetType[User] = User.objects.all()
for user in users:
   print(user.email)  # typing OK!
user = users.first()  # typing OK!

Dominique PERETTI
quelle
1
from typing import Iterable

def func(queryset_or_list: Iterable[MyModel]): 
    pass

Sowohl der Abfragesatz als auch die Liste der Modellinstanzen sind iterierbare Objekte.

Shaffron
quelle
0

Ich habe festgestellt, dass mit typing.Sequenceein ähnliches Problem zu lösen:

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)

Soweit ich aus den Dokumenten weiß:

Eine Sequenz ist alles, was len () und unterstützt. getitem (), unabhängig von seinem tatsächlichen Typ.

dharmagetisch
quelle
-1

Sie können tatsächlich tun, was Sie wollen, wenn Sie das Anmerkungsmodul importieren:

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()

Weder MyPy noch der Python-Interpreter werden sich darüber beschweren oder Ausnahmen auslösen (getestet unter Python 3.7). MyPy kann es wahrscheinlich nicht tippen, aber wenn Sie nur Ihren Rückgabetyp dokumentieren möchten, sollte dies gut genug sein.

Drakenation
quelle