如何在Django QuerySet中使用Python类型提示?


73

是否可以使用Python类型提示在Django QuerySet中指定记录类型?像QuerySet[SomeModel]什么?

例如,我们有模型:

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

而且我们想将该模型的QuerySet作为参数传递给func:

def somefunc(rows: QuerySet):
    pass

但是如何在QuerySet中指定记录类型,例如List[SomeModel]

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

但是使用QuerySet?



您可能正在寻找SomeModelQuerySet,并从与导入SomeModel相同的路径中导入
JayTurnr

Answers:


49

一种解决方案可能是使用联合键入类。

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

现在,在对row参数进行切片时,它将知道返回的类型是MyModel的另一个列表还是MyModel的实例,同时还暗示了QuerySet该类的方法也可以在该row参数上使用。


2
stubs与输入的DjangoQuerySet和模型:github.com/typeddjango/django-stubs教程:sobolevn.me/2019/08/typechecking-django-and-drf
sobolevn

21

我制作了此帮助器类以获取泛型类型提示:

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

然后像这样使用它:

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

每次使用此类型时,这都会减少噪音,并使它在模型之间(例如ModelType[DifferentModel])可用。


15

有一个特殊的程序包django-stubs(名称如下PEP561)来键入您的django代码。

这就是它的工作方式:

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

输出:

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

并使用model和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,
    )

输出:

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

10

这是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]: ...

此类专门用于QuerySet对象,例如filter在查询中使用时。
样品:

from some_file import QueryType

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

现在,解释器将识别sample_queryQuerySet对象,并且您将获得诸如的建议,count()并在遍历对象时,您将获得针对对象的建议。SampleClass

注意
这种类型的提示类型从python3.6开始就可用。


您还可以使用django_hint,它具有专门用于Django的提示类。


如果有人使用VSCode,请快速注意;如果响应类型由单引号代替(不知道原因),则此解决方案效果很好;def my_method(self) -> 'QueryType[MyModel]': ...
马努

2

恕我直言,正确的方法是定义一个继承的类型QuerySet并为迭代器指定一个通用的返回类型。

    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


然后,您可以像这样使用它:

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


1
from typing import Iterable

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

queryset和模型实例列表都是可迭代的对象。


0

我发现自己曾经typing.Sequence用来解决类似的问题:

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)

据我从文档了解:

序列是支持len()和的任何东西。getitem(),独立于其实际类型。


-1

如果导入注释模块,您实际上可以执行所需的操作:

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

MyPy和Python解释器都不会对此进行抱怨或引发异常(在python 3.7上进行了测试)。MyPy可能无法对其进行类型检查,但是如果您只想记录您的返回类型,这应该足够好。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.