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

时间:2017-02-22 16:48:13

标签: python django type-hinting

是否可以使用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?

12 个答案:

答案 0 :(得分:28)

一种解决方案可能是使用联盟打字类。

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参数也是。{/ p>

答案 1 :(得分:4)

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

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

然后像这样使用它:

ModelType[DifferentModel]

这会减少我每次使用此类型时的噪音,并使其在模型之间可用(例如def middle(word): return word[1:-1] def is_palindrome(word): if word[0] != word[-1]: return False elif len(word)<=1: return True else: return is_palindrome(middle(word)) )。

答案 2 :(得分:4)

有一个名为routes()(名称为follows PEP561)的特殊程序包,用于键入您的django-stubs代码。

它是这样工作的:

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'

QuerySet

输出:

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

答案 3 :(得分:2)

我也正在寻找解决方案。 Django开发人员列表中有thread,他们正在讨论如何实现这样的功能。

他们目前正在开发Django extension to mypy,但看起来我们可能因为我们的具体要求而运气不好。在他们的路线图中的标题是&#34;可能从不&#34;:

  

Queryset可能有一些部分支持,但复杂的参数(如   过滤和获取查询的那些)或Q和F对象超出了   mypy现在的表达可能性。

说明,在条件改善之前,我们必须使用普通的QuerySet

答案 4 :(得分:1)

QuerySet 是返回任何模型的任何查询集的函数/方法的好方法。 Django 查询集是可迭代的。但是当返回类型非常特定于一种模型时,最好使用 QuerySet[Model] 而不是 QuerySet

示例:过滤公司的所有活跃用户

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)

上面的函数签名比def get_active_users(company_id: int) -> QuerySet:更具可读性

Iterable[User] 也可以工作,类型检查器会在其他方法上调用返回的查询集时抱怨。

def func() -> Iterable[User]:
    return User.objects.all()

users = func()
users.filter(email__startswith='support')

MyPy 输出

"Iterable[User]" has no attribute "filter"

答案 5 :(得分:0)

这是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_query识别为QuerySet对象,您将获得诸如count()之类的建议,并且在遍历这些对象时,您将获得针对{{1} }

注意
SampleClass起可以使用这种类型的提示格式。

答案 6 :(得分: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 ()的任何东西,而与它的实际类型无关。

答案 7 :(得分:0)

from typing import Iterable

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

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

答案 8 :(得分:0)

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

答案 9 :(得分:-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可能无法对其进行类型检查,但是如果您只想记录您的返回类型,这应该就足够了。

答案 10 :(得分:-1)

恕我直言,正确的方法是定义一个继承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!

答案 11 :(得分:-14)

如果你想通过一个参数,就这样做:

def somefunc(smth):
    pass

如果您想返回列表类型,请使用函数list()