Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support returning the correct values for the different QuerySet methods when using .values() and .values_list(). #33

Merged
merged 11 commits into from
Mar 10, 2019
Merged
Prev Previous commit
Next Next commit
- Fix return type for QuerySet.select_for_update().
- Use correct return type for QuerySet.dates() / QuerySet.datetimes().
- Use correct type params in return type for QuerySet.__and__ / QuerySet.__or__
- Re-add Sized as base class for QuerySet.
- Add test of .all() for all _Row types.
- Add test of .get() for all _Row types.
- Remove some redundant QuerySet method tests.
syastrov committed Mar 6, 2019
commit d286a91dda983a7d07e62c2e0b83cee1f4c3580b
17 changes: 11 additions & 6 deletions django-stubs/db/models/query.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime
from typing import (
Any,
Dict,
@@ -49,7 +50,7 @@ class FlatValuesListIterable(BaseIterable):

_T = TypeVar("_T", bound=models.Model, covariant=True)

class QuerySet(Generic[_T, _Row], Collection[_Row]):
class QuerySet(Generic[_T, _Row], Collection[_Row], Sized):
query: Query
def __init__(
self,
@@ -71,8 +72,10 @@ class QuerySet(Generic[_T, _Row], Collection[_Row]):
def __class_getitem__(cls, item: Type[_T]):
pass
def __getstate__(self) -> Dict[str, Any]: ...
def __and__(self, other: QuerySet) -> QuerySet: ...
def __or__(self, other: QuerySet) -> QuerySet: ...
# __and__ and __or__ ignore the other QuerySet's _Row type parameter because they use the same row type as the self QuerySet.
# Technically, the other QuerySet must be of the same type _T, but _T is covariant
def __and__(self, other: QuerySet[_T, Any]) -> QuerySet[_T, _Row]: ...
def __or__(self, other: QuerySet[_T, Any]) -> QuerySet[_T, _Row]: ...
def iterator(self, chunk_size: int = ...) -> Iterator[_Row]: ...
def aggregate(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: ...
def get(self, *args: Any, **kwargs: Any) -> _Row: ...
@@ -108,8 +111,10 @@ class QuerySet(Generic[_T, _Row], Collection[_Row]):
def values_list(
self, *fields: Union[str, Combinable], flat: Literal[False] = ..., named: Literal[False] = ...
) -> QuerySet[_T, Tuple]: ...
def dates(self, field_name: str, kind: str, order: str = ...) -> QuerySet: ...
def datetimes(self, field_name: str, kind: str, order: str = ..., tzinfo: None = ...) -> QuerySet: ...
def dates(self, field_name: str, kind: str, order: str = ...) -> QuerySet[_T, datetime.date]: ...
def datetimes(
self, field_name: str, kind: str, order: str = ..., tzinfo: None = ...
) -> QuerySet[_T, datetime.datetime]: ...
def none(self) -> QuerySet[_T, _Row]: ...
def all(self) -> QuerySet[_T, _Row]: ...
def filter(self, *args: Any, **kwargs: Any) -> QuerySet[_T, _Row]: ...
@@ -119,7 +124,7 @@ class QuerySet(Generic[_T, _Row], Collection[_Row]):
def union(self, *other_qs: Any, all: bool = ...) -> QuerySet[_T, _Row]: ...
def intersection(self, *other_qs: Any) -> QuerySet[_T, _Row]: ...
def difference(self, *other_qs: Any) -> QuerySet[_T, _Row]: ...
def select_for_update(self, nowait: bool = ..., skip_locked: bool = ..., of: Tuple = ...) -> QuerySet: ...
def select_for_update(self, nowait: bool = ..., skip_locked: bool = ..., of: Tuple = ...) -> QuerySet[_T, _Row]: ...
def select_related(self, *fields: Any) -> QuerySet[_T, _Row]: ...
def prefetch_related(self, *lookups: Any) -> QuerySet[_T, _Row]: ...
def annotate(self, *args: Any, **kwargs: Any) -> QuerySet[_T, _Row]: ...
48 changes: 25 additions & 23 deletions test-data/typecheck/queryset.test
Original file line number Diff line number Diff line change
@@ -2,84 +2,86 @@
from django.db import models

class Blog(models.Model):
slug = models.CharField(max_length=100)
name = models.CharField(max_length=100)
created_at = models.DateTimeField()


reveal_type(Blog.objects.in_bulk([1])) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
reveal_type(Blog.objects.in_bulk()) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
reveal_type(Blog.objects.in_bulk(['beatles_blog'], field_name='slug')) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
reveal_type(Blog.objects.in_bulk(['beatles_blog'], field_name='name')) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'

# When ANDing QuerySets, the left-side's _Row parameter is used
reveal_type(Blog.objects.all() & Blog.objects.values()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, main.Blog*]'
reveal_type(Blog.objects.values() & Blog.objects.values()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict*[builtins.str, Any]]'
reveal_type(Blog.objects.values_list('id', 'name') & Blog.objects.values()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.tuple*[Any]]'
reveal_type(Blog.objects.values_list('id', 'name', named=True) & Blog.objects.values()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, typing.NamedTuple*]'
reveal_type(Blog.objects.values_list('id', flat=True) & Blog.objects.values()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Any]'

# .dates / .datetimes
reveal_type(Blog.objects.dates("created_at", "day")) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, datetime.date]'
reveal_type(Blog.objects.datetimes("created_at", "day")) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, datetime.datetime]'

qs = Blog.objects.all()
reveal_type(qs) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, main.Blog*]'
reveal_type(qs.get(id=1)) # E: Revealed type is 'main.Blog*'
reveal_type(iter(qs)) # E: Revealed type is 'typing.Iterator[main.Blog*]'
reveal_type(qs.iterator()) # E: Revealed type is 'typing.Iterator[main.Blog*]'
reveal_type(qs.first()) # E: Revealed type is 'Union[main.Blog*, None]'
reveal_type(qs.last()) # E: Revealed type is 'Union[main.Blog*, None]'
reveal_type(qs.earliest()) # E: Revealed type is 'main.Blog*'
reveal_type(qs.latest()) # E: Revealed type is 'main.Blog*'
reveal_type(qs[0]) # E: Revealed type is 'main.Blog*'
reveal_type(qs[:9]) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, main.Blog*]'
reveal_type(qs.in_bulk([1])) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
reveal_type(qs.in_bulk()) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
reveal_type(qs.in_bulk(['beatles_blog'], field_name='slug')) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'


values_qs = Blog.objects.values()
reveal_type(values_qs) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict[builtins.str, Any]]'
reveal_type(values_qs.all()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict*[builtins.str, Any]]'
reveal_type(values_qs.get(id=1)) # E: Revealed type is 'builtins.dict*[builtins.str, Any]'
reveal_type(iter(values_qs)) # E: Revealed type is 'typing.Iterator[builtins.dict*[builtins.str, Any]]'
reveal_type(values_qs.iterator()) # E: Revealed type is 'typing.Iterator[builtins.dict*[builtins.str, Any]]'
reveal_type(values_qs.first()) # E: Revealed type is 'Union[builtins.dict*[builtins.str, Any], None]'
reveal_type(values_qs.last()) # E: Revealed type is 'Union[builtins.dict*[builtins.str, Any], None]'
reveal_type(values_qs.earliest()) # E: Revealed type is 'builtins.dict*[builtins.str, Any]'
reveal_type(values_qs.latest()) # E: Revealed type is 'builtins.dict*[builtins.str, Any]'
reveal_type(values_qs[0]) # E: Revealed type is 'builtins.dict*[builtins.str, Any]'
reveal_type(values_qs[:9]) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict*[builtins.str, Any]]'
reveal_type(values_qs.in_bulk([1])) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
reveal_type(values_qs.in_bulk()) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
reveal_type(values_qs.in_bulk(['beatles_blog'], field_name='slug')) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'


values_list_qs = Blog.objects.values_list('id', 'slug')
values_list_qs = Blog.objects.values_list('id', 'name')
reveal_type(values_list_qs) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.tuple[Any]]'
reveal_type(values_list_qs.all()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.tuple*[Any]]'
reveal_type(values_list_qs.get(id=1)) # E: Revealed type is 'builtins.tuple*[Any]'
reveal_type(iter(values_list_qs)) # E: Revealed type is 'typing.Iterator[builtins.tuple*[Any]]'
reveal_type(values_list_qs.iterator()) # E: Revealed type is 'typing.Iterator[builtins.tuple*[Any]]'
reveal_type(values_list_qs.first()) # E: Revealed type is 'Union[builtins.tuple*[Any], None]'
reveal_type(values_list_qs.last()) # E: Revealed type is 'Union[builtins.tuple*[Any], None]'
reveal_type(values_list_qs.earliest()) # E: Revealed type is 'builtins.tuple*[Any]'
reveal_type(values_list_qs.latest()) # E: Revealed type is 'builtins.tuple*[Any]'
reveal_type(values_list_qs[0]) # E: Revealed type is 'builtins.tuple*[Any]'
reveal_type(values_list_qs[:9]) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.tuple*[Any]]'
reveal_type(values_list_qs.in_bulk([1])) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
reveal_type(values_list_qs.in_bulk()) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
reveal_type(values_list_qs.in_bulk(['beatles_blog'], field_name='slug')) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'


flat_values_list_qs = Blog.objects.values_list('id', flat=True)
reveal_type(flat_values_list_qs) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Any]'
reveal_type(flat_values_list_qs.all()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Any]'
reveal_type(flat_values_list_qs.get(id=1)) # E: Revealed type is 'Any'
reveal_type(iter(flat_values_list_qs)) # E: Revealed type is 'typing.Iterator[Any]'
reveal_type(flat_values_list_qs.iterator()) # E: Revealed type is 'typing.Iterator[Any]'
reveal_type(flat_values_list_qs.first()) # E: Revealed type is 'Union[Any, None]'
reveal_type(flat_values_list_qs.last()) # E: Revealed type is 'Union[Any, None]'
reveal_type(flat_values_list_qs.earliest()) # E: Revealed type is 'Any'
reveal_type(flat_values_list_qs.latest()) # E: Revealed type is 'Any'
reveal_type(flat_values_list_qs[0]) # E: Revealed type is 'Any'
reveal_type(flat_values_list_qs[:9]) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Any]'
reveal_type(flat_values_list_qs.in_bulk([1])) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
reveal_type(flat_values_list_qs.in_bulk()) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
reveal_type(flat_values_list_qs.in_bulk(['beatles_blog'], field_name='slug')) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'


named_values_list_qs = Blog.objects.values_list('id', named=True)
reveal_type(named_values_list_qs) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, typing.NamedTuple]'
reveal_type(named_values_list_qs.all()) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, typing.NamedTuple*]'
reveal_type(named_values_list_qs.get(id=1)) # E: Revealed type is 'typing.NamedTuple*'
reveal_type(iter(named_values_list_qs)) # E: Revealed type is 'typing.Iterator[typing.NamedTuple*]'
reveal_type(named_values_list_qs.iterator()) # E: Revealed type is 'typing.Iterator[typing.NamedTuple*]'
reveal_type(named_values_list_qs.first()) # E: Revealed type is 'Union[typing.NamedTuple*, None]'
reveal_type(named_values_list_qs.last()) # E: Revealed type is 'Union[typing.NamedTuple*, None]'
reveal_type(named_values_list_qs.earliest()) # E: Revealed type is 'typing.NamedTuple*'
reveal_type(named_values_list_qs.latest()) # E: Revealed type is 'typing.NamedTuple*'
reveal_type(named_values_list_qs[0]) # E: Revealed type is 'typing.NamedTuple*'
reveal_type(named_values_list_qs[:9]) # E: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, typing.NamedTuple*]'
reveal_type(named_values_list_qs.in_bulk([1])) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
reveal_type(named_values_list_qs.in_bulk()) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'
reveal_type(named_values_list_qs.in_bulk(['beatles_blog'], field_name='slug')) # E: Revealed type is 'builtins.dict[Any, main.Blog*]'

[out]