programing

장고 쿼리셋과 함께 파이썬 타입 힌트를 사용하는 방법?

iphone6s 2023. 10. 9. 22:22
반응형

장고 쿼리셋과 함께 파이썬 타입 힌트를 사용하는 방법?

파이썬 타입 힌트로 장고 쿼리셋에서 레코드 타입을 지정할 수 있나요?뭐 이런 거.QuerySet[SomeModel]?

예를 들어, 다음과 같은 모델이 있습니다.

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

그리고 이 모델의 QuerySet을 func의 param으로 전달하고자 합니다.

def somefunc(rows: QuerySet):
    pass

그러나 QuerySet에서 레코드 유형을 지정하는 방법(예:List[SomeModel]:

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

QuerySet으로?

하나의 해결책은 Union typing class를 사용하는 것일 수 있습니다.

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논쟁도.

라는 특별한 소포가 있습니다.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'

그리고 모델들과.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]

일반적인 유형 힌트를 얻기 위해 이 도우미 클래스를 만들었습니다.

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]).

업데이트 2023

django_hint 0.3.0에서는 모델과 공통 함수의 반환 유형을 확장할 수 있습니다.objects예를 들어filter,get, 등이 자동으로 감지됩니다.

from django_hint import StandardModelType

class SampleModel(models.Model, StandardModelType['SampleModel']):
    name: str = models.CharField(max_length=300)

원답

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로서QuerySetobject 그러면 다음과 같은 제안을 받을 것입니다.count()그리고 객체들을 순환하는 동안, 당신은 제안들을 얻을 것입니다.SampleClass

메모
이 형식의 유형 암시는 다음에서 사용할 수 있습니다.python3.6앞으로

또한 Django에 대한 힌트 클래스가 있는 django_hint를 사용할 수 있습니다.


면책 사항:저는 의 저자입니다.django_hint

QuerySet는 모든 모델의 쿼리 세트를 반환하는 함수/ method에 적합한 접근 방식입니다.장고 쿼리 집합은 반복 가능합니다.하지만 반품 유형이 한 모델에 매우 특정적인 경우 사용하는 것이 더 나을 수 있습니다.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"

은 IMHO, 입니다를 입니다.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!

주석 모듈을 가져오면 실제로 원하는 작업을 수행할 수 있습니다.

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는 아마 타이핑을 할 수 없을 것입니다. 하지만 반품 유형을 문서화하는 것만 원하신다면, 이 정도면 충분합니다.

from typing import Iterable

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

쿼리 세트와 모델 인스턴스 목록은 모두 반복 가능 개체입니다.

를 사용한다는 되었습니다.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()을 지원하는 모든 것입니다.

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

언급URL : https://stackoverflow.com/questions/42397502/how-to-use-python-type-hints-with-django-queryset

반응형