from django.db import models
class ItemQuerySet(models.QuerySet):
def public(self):
return self.filter(is_public=True)
class Item(models.Model):
is_public = models.BooleanField()
description = models.CharField()
objects = ItemQuerySet.as_manager()
django-stubs를 적용하지 않았기 때문에, 타입 검사기로 Pylance를 사용한다.
쿼리셋 객체는 기본적으로 Django가 제공하는 QuerySet에 해당하므로 QuerySet을 타입 힌트에 적을 수 있다. 다만, 커스텀 쿼리셋에서 정의한 메서드를 호출하면 타입 에러가 발생한다.
from django.db.models import QuerySet
items: QuerySet = Item.objects.all()
items.count()
items.public() # Cannot access member "public" for type "BaseManager[Unknown]"
# Member "public" is unknownPylancereportAttributeAccessIssue Pylance(reportAttributeAccessIssue)
for item in items:
print(item.description)
커스텀 쿼리셋 클래스를 ORM 결과의 타입으로 지정한다. 커스텀 쿼리셋에 정의한 메서드 호출은 허용되지만, ORM 결과를 지역변수에 할당할 때 타입 에러가 발생한다.
Django QuerySet에는
__contains__()
매직 메서드가 정의되어 있진 않지만, in 연산자가 정상적으로 작동하는 이유는 뭘까?
items: ItemQuerySet = Item.objects.all() # Expression of type "BaseManager[Item]" cannot be assigned to declared type "ItemQuerySet"
# "BaseManager[Item]" is incompatible with "ItemQuerySet" Pylance(reportAssignmentType)
items.count()
items.public()
for item in items:
print(item.description)
ORM에서 반환한 쿼리셋의 타입을 Sequence로 지정한다. Sequence 원소의 속성과 메서드에 접근할 때 자동완성 기능을 이용할 수 있다.
items: Sequence[Item] = Item.objects.all() # Expression of type "BaseManager[Item]" cannot be assigned to declared type "Sequence[Item]"
# "BaseManager[Item]" is incompatible with "Sequence[Item]"PylancereportAssignmentType
items.count() # Argument missing for parameter "value" Pylance(reportCallIssue)
items.filter(is_public=True) # "Sequence[Item]" has no attribute "filter" Mypy(attr-defined)
# Cannot access member "filter" for type "Sequence[Item]"
# Member "filter" is unknown Pylance(reportAttributeAccessIssue)
for item in items:
print(item.description)
Python docs에 따르면 Sequence는 믹스인 메서드로 __contains__()
, count()
등을 가진다. 반면, QuerySet에는 __contains__()
메서드가 정의되어 있지 않을 뿐만 아니라, count()
는 Sequence.count()
와 매개변수 형식이 다르다. 따라서 엄밀히 Sequence는 QuerySet의 부분집합이 아니다.
참고로, django-stubs에서는 QuerySet 타입을 Sequence가 아닌, Collection, Reversible, Sized를 직접 상속하여 정의한다.
https://github.com/typeddjango/django-stubs
실제 실행해보진 않음
from django.db.models import QuerySet
items: QuerySet[Item] = Item.objects.all()
items.count()
items.public()
for item in items:
print(item.description)