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?
python
django
type-hinting
Алексей Голобурдин
quelle
quelle
Antworten:
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
row
Argument 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 derQuerySet
Klasse auch für dasrow
Argument verfügbar sind .quelle
stubs
für Django mit getipptenQuerySet
und Modellen: github.com/typeddjango/django-stubs Tutorial: sobolevn.me/2019/08/typechecking-django-and-drfIch 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 .quelle
Es gibt ein spezielles Paket namens
django-stubs
(der Name folgtPEP561
), um Ihrendjango
Code 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
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, )
Ausgabe:
reveal_type(published_posts().first()) # => Union[server.apps.main.models.BlogPost*, None]
django
: https://github.com/typeddjango/django-stubsdrf
: https://github.com/typeddjango/djangorestframework-stubsquelle
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
QuerySet
Objekte verwendet, z. B. wenn Sie siefilter
in einer Abfrage verwenden.Stichprobe:
from some_file import QueryType sample_query: QueryType[SampleClass] = SampleClass.objects.filter(name=name)
Jetzt erkennt der Interpreter das
sample_query
alsQuerySet
Objekt und Sie erhalten Vorschläge wiecount()
und während Sie die Objekte durchlaufen, erhalten Sie Vorschläge für dasSampleClass
Hinweis
Dieses Format für Typhinweise ist ab sofort
python3.6
verfügbar.Sie können auch django_hint verwenden, das Hinweisklassen speziell für Django enthält.
quelle
def my_method(self) -> 'QueryType[MyModel]': ...
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!
quelle
from typing import Iterable def func(queryset_or_list: Iterable[MyModel]): pass
Sowohl der Abfragesatz als auch die Liste der Modellinstanzen sind iterierbare Objekte.
quelle
Ich habe festgestellt, dass mit
typing.Sequence
ein ä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ß:
quelle
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.
quelle