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

Але*_*дин 43 python django type-hinting

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

例如,我们有模型:

class SomeModel(models.Model):
    smth = models.IntegerField()
Run Code Online (Sandbox Code Playgroud)

我们想在func中将该模型的QuerySet作为param传递:

def somefunc(rows: QuerySet):
    pass
Run Code Online (Sandbox Code Playgroud)

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

def somefunc(rows: List[SomeModel]):
    pass
Run Code Online (Sandbox Code Playgroud)

但是使用QuerySet?

A. *_*arr 31

一种解决方案可能是使用Union输入类.

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
Run Code Online (Sandbox Code Playgroud)

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

  • django 有带有类型化“QuerySet”和模型的“存根”:https://github.com/typeddjango/django-stubs 教程:https://sobolevn.me/2019/08/typechecking-django-and-drf (4认同)

sob*_*evn 24

有一个称为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')
Run Code Online (Sandbox Code Playgroud)

输出:

» 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'
Run Code Online (Sandbox Code Playgroud)

并使用模型和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,
    )
Run Code Online (Sandbox Code Playgroud)

输出:

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


Or *_*uan 17

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

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
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它:

def somefunc(row: ModelType[SomeModel]):
    pass
Run Code Online (Sandbox Code Playgroud)

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


Kra*_*mar 14

QuerySet是返回任何模型的任何查询集的函数/方法的好方法。Django 查询集是可迭代的。但是,当返回类型非常特定于一种模型时,使用QuerySet[Model]over可能会更好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)
Run Code Online (Sandbox Code Playgroud)

上面的函数签名比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')
Run Code Online (Sandbox Code Playgroud)

MyPy 输出

"Iterable[User]" has no attribute "filter"
Run Code Online (Sandbox Code Playgroud)


Ram*_*tin 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]: ...
Run Code Online (Sandbox Code Playgroud)

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

from some_file import QueryType

sample_query: QueryType[SampleClass] = SampleClass.objects.filter(name=name)
Run Code Online (Sandbox Code Playgroud)

现在解释器将 识别sample_queryQuerySet对象,您将获得建议,例如count(),在遍历对象时,您将获得有关对象的建议SampleClass

注意
这种类型提示的格式从python3.6以后可用。


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


dra*_*ion 8

如果导入注释模块,您实际上可以做您想做的事情:

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()
Run Code Online (Sandbox Code Playgroud)

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


Dom*_*TTI 5

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


Run Code Online (Sandbox Code Playgroud)

然后你可以像这样使用它:

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

Run Code Online (Sandbox Code Playgroud)