From 656105bab2d1da30b5273ab0e6272b74d4ed6236 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Fri, 23 Aug 2019 03:31:07 +0300 Subject: [PATCH] make first() an Optional, allow to specify QuerySet with one parameter (#136) --- django-stubs/db/models/manager.pyi | 4 +- django-stubs/db/models/query.pyi | 227 +++++++++++------- django-stubs/shortcuts.pyi | 4 +- mypy_django_plugin/main.py | 11 +- mypy_django_plugin/transformers/querysets.py | 28 +-- scripts/enabled_test_modules.py | 33 ++- .../managers/querysets/test_basic_methods.yml | 21 +- .../managers/querysets/test_values_list.yml | 8 +- .../typecheck/managers/test_managers.yml | 18 +- 9 files changed, 190 insertions(+), 164 deletions(-) diff --git a/django-stubs/db/models/manager.pyi b/django-stubs/db/models/manager.pyi index 0dfc2a1..ac6fa6b 100644 --- a/django-stubs/db/models/manager.pyi +++ b/django-stubs/db/models/manager.pyi @@ -5,7 +5,7 @@ from django.db.models.query import QuerySet _T = TypeVar("_T", bound=Model, covariant=True) -class BaseManager(QuerySet[_T, _T]): +class BaseManager(QuerySet[_T]): creation_counter: int = ... auto_created: bool = ... use_in_migrations: bool = ... @@ -21,7 +21,7 @@ class BaseManager(QuerySet[_T, _T]): def _get_queryset_methods(cls, queryset_class: type) -> Dict[str, Any]: ... def contribute_to_class(self, model: Type[Model], name: str) -> None: ... def db_manager(self, using: Optional[str] = ..., hints: Optional[Dict[str, Model]] = ...) -> Manager: ... - def get_queryset(self) -> QuerySet[_T, _T]: ... + def get_queryset(self) -> QuerySet[_T]: ... class Manager(BaseManager[_T]): ... diff --git a/django-stubs/db/models/query.pyi b/django-stubs/db/models/query.pyi index 578faba..31caebf 100644 --- a/django-stubs/db/models/query.pyi +++ b/django-stubs/db/models/query.pyi @@ -27,6 +27,111 @@ from django.db import models from django.db.models import Manager from django.db.models.query_utils import Q as Q +_T = TypeVar("_T", bound=models.Model, covariant=True) +_QS = TypeVar("_QS", bound="QuerySet") + +class QuerySet(Generic[_T], Collection[_T], Sized): + query: Query + def __init__( + self, + model: Optional[Type[models.Model]] = ..., + query: Optional[Query] = ..., + using: Optional[str] = ..., + hints: Optional[Dict[str, models.Model]] = ..., + ) -> None: ... + @classmethod + def as_manager(cls) -> Manager[Any]: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[_T]: ... + def __contains__(self, x: object) -> bool: ... + @overload + def __getitem__(self, i: int) -> _T: ... + @overload + def __getitem__(self, s: slice) -> QuerySet[_T]: ... + def __bool__(self) -> bool: ... + def __class_getitem__(cls, item: Type[_T]): + pass + def __getstate__(self) -> Dict[str, Any]: ... + # __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]) -> QuerySet[_T]: ... + def __or__(self, other: QuerySet[_T]) -> QuerySet[_T]: ... + def iterator(self, chunk_size: int = ...) -> Iterator[_T]: ... + def aggregate(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: ... + def get(self, *args: Any, **kwargs: Any) -> _T: ... + def create(self, *args: Any, **kwargs: Any) -> _T: ... + def bulk_create( + self, objs: Iterable[Model], batch_size: Optional[int] = ..., ignore_conflicts: bool = ... + ) -> List[_T]: ... + def get_or_create(self, defaults: Optional[MutableMapping[str, Any]] = ..., **kwargs: Any) -> Tuple[_T, bool]: ... + def update_or_create( + self, defaults: Optional[MutableMapping[str, Any]] = ..., **kwargs: Any + ) -> Tuple[_T, bool]: ... + def earliest(self, *fields: Any, field_name: Optional[Any] = ...) -> _T: ... + def latest(self, *fields: Any, field_name: Optional[Any] = ...) -> _T: ... + def first(self) -> Optional[_T]: ... + def last(self) -> Optional[_T]: ... + def in_bulk(self, id_list: Iterable[Any] = ..., *, field_name: str = ...) -> Dict[Any, _T]: ... + def delete(self) -> Tuple[int, Dict[str, int]]: ... + def update(self, **kwargs: Any) -> int: ... + def exists(self) -> bool: ... + def explain(self, *, format: Optional[Any] = ..., **options: Any) -> str: ... + def raw( + self, + raw_query: str, + params: Any = ..., + translations: Optional[Dict[str, str]] = ..., + using: Optional[str] = ..., + ) -> RawQuerySet: ... + # The type of values may be overridden to be more specific in the mypy plugin, depending on the fields param + def values(self, *fields: Union[str, Combinable], **expressions: Any) -> ValuesQuerySet[_T, Dict[str, Any]]: ... + # The type of values_list may be overridden to be more specific in the mypy plugin, depending on the fields param + def values_list( + self, *fields: Union[str, Combinable], flat: bool = ..., named: bool = ... + ) -> ValuesQuerySet[_T, Any]: ... + def dates(self, field_name: str, kind: str, order: str = ...) -> ValuesQuerySet[_T, datetime.date]: ... + def datetimes( + self, field_name: str, kind: str, order: str = ..., tzinfo: None = ... + ) -> ValuesQuerySet[_T, datetime.datetime]: ... + def none(self) -> QuerySet[_T]: ... + def all(self) -> QuerySet[_T]: ... + def filter(self, *args: Any, **kwargs: Any) -> QuerySet[_T]: ... + def exclude(self, *args: Any, **kwargs: Any) -> QuerySet[_T]: ... + def complex_filter(self, filter_obj: Any) -> QuerySet[_T]: ... + def count(self) -> int: ... + def union(self, *other_qs: Any, all: bool = ...) -> QuerySet[_T]: ... + def intersection(self, *other_qs: Any) -> QuerySet[_T]: ... + def difference(self, *other_qs: Any) -> QuerySet[_T]: ... + def select_for_update(self, nowait: bool = ..., skip_locked: bool = ..., of: Tuple = ...) -> QuerySet[_T]: ... + def select_related(self, *fields: Any) -> QuerySet[_T]: ... + def prefetch_related(self, *lookups: Any) -> QuerySet[_T]: ... + # TODO: return type + def annotate(self, *args: Any, **kwargs: Any) -> QuerySet[Any]: ... + def order_by(self, *field_names: Any) -> QuerySet[_T]: ... + def distinct(self, *field_names: Any) -> QuerySet[_T]: ... + # extra() return type won't be supported any time soon + def extra( + self, + select: Optional[Dict[str, Any]] = ..., + where: Optional[List[str]] = ..., + params: Optional[List[Any]] = ..., + tables: Optional[List[str]] = ..., + order_by: Optional[Sequence[str]] = ..., + select_params: Optional[Sequence[Any]] = ..., + ) -> QuerySet[Any]: ... + def reverse(self) -> QuerySet[_T]: ... + def defer(self, *fields: Any) -> QuerySet[_T]: ... + def only(self, *fields: Any) -> QuerySet[_T]: ... + def using(self, alias: Optional[str]) -> QuerySet[_T]: ... + @property + def ordered(self) -> bool: ... + @property + def db(self) -> str: ... + def resolve_expression(self, *args: Any, **kwargs: Any) -> Any: ... + # TODO: remove when django adds __class_getitem__ methods + def __getattr__(self, item: str) -> Any: ... + _Row = TypeVar("_Row", covariant=True) class BaseIterable(Sequence[_Row]): @@ -47,88 +152,39 @@ class NamedValuesListIterable(ValuesListIterable): ... class FlatValuesListIterable(BaseIterable): def __iter__(self) -> Iterator[Any]: ... -_T = TypeVar("_T", bound=models.Model, covariant=True) - -class QuerySet(Generic[_T, _Row], Collection[_Row], Sized): - query: Query - def __init__( - self, - model: Optional[Type[models.Model]] = ..., - query: Optional[Query] = ..., - using: Optional[str] = ..., - hints: Optional[Dict[str, models.Model]] = ..., - ) -> None: ... - @classmethod - def as_manager(cls) -> Manager[Any]: ... - def __len__(self) -> int: ... - def __iter__(self) -> Iterator[_Row]: ... - def __contains__(self, x: object) -> bool: ... - @overload +class ValuesQuerySet(Generic[_T, _Row], QuerySet[_T], Collection[_Row], Sized): + def __iter__(self) -> Iterator[_Row]: ... # type: ignore + @overload # type: ignore def __getitem__(self, i: int) -> _Row: ... @overload - def __getitem__(self, s: slice) -> QuerySet[_T, _Row]: ... - def __bool__(self) -> bool: ... - def __class_getitem__(cls, item: Type[_T]): - pass - def __getstate__(self) -> Dict[str, Any]: ... - # __and__ and __or__ ignore the other QuerySet's _Row type parameter because they use the same row type as the self QuerySet. + def __getitem__(self, s: slice) -> ValuesQuerySet[_T, _Row]: ... # 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: ... - def create(self, *args: Any, **kwargs: Any) -> _T: ... - def bulk_create( - self, objs: Iterable[Model], batch_size: Optional[int] = ..., ignore_conflicts: bool = ... - ) -> List[_T]: ... - def get_or_create(self, defaults: Optional[MutableMapping[str, Any]] = ..., **kwargs: Any) -> Tuple[_T, bool]: ... - def update_or_create( - self, defaults: Optional[MutableMapping[str, Any]] = ..., **kwargs: Any - ) -> Tuple[_T, bool]: ... - def earliest(self, *fields: Any, field_name: Optional[Any] = ...) -> _Row: ... - def latest(self, *fields: Any, field_name: Optional[Any] = ...) -> _Row: ... - # technically it's Optional[_Row], but it creates a lot of false-positives (same for last()) - def first(self) -> _Row: ... - def last(self) -> _Row: ... - def in_bulk(self, id_list: Iterable[Any] = ..., *, field_name: str = ...) -> Dict[Any, _T]: ... - def delete(self) -> Tuple[int, Dict[str, int]]: ... - def update(self, **kwargs: Any) -> int: ... - def exists(self) -> bool: ... - def explain(self, *, format: Optional[Any] = ..., **options: Any) -> str: ... - def raw( - self, - raw_query: str, - params: Any = ..., - translations: Optional[Dict[str, str]] = ..., - using: Optional[str] = ..., - ) -> RawQuerySet: ... - # The type of values may be overridden to be more specific in the mypy plugin, depending on the fields param - def values(self, *fields: Union[str, Combinable], **expressions: Any) -> QuerySet[_T, Dict[str, Any]]: ... - # The type of values_list may be overridden to be more specific in the mypy plugin, depending on the fields param - def values_list( - self, *fields: Union[str, Combinable], flat: bool = ..., named: bool = ... - ) -> QuerySet[_T, Any]: ... - 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]: ... - def exclude(self, *args: Any, **kwargs: Any) -> QuerySet[_T, _Row]: ... - def complex_filter(self, filter_obj: Any) -> QuerySet[_T, _Row]: ... + def __and__(self, other: ValuesQuerySet[_T, _Row]) -> ValuesQuerySet[_T, _Row]: ... # type: ignore + def __or__(self, other: ValuesQuerySet[_T, _Row]) -> ValuesQuerySet[_T, _Row]: ... # type: ignore + def iterator(self, chunk_size: int = ...) -> Iterator[_Row]: ... # type: ignore + def get(self, *args: Any, **kwargs: Any) -> _Row: ... # type: ignore + def earliest(self, *fields: Any, field_name: Optional[Any] = ...) -> _Row: ... # type: ignore + def latest(self, *fields: Any, field_name: Optional[Any] = ...) -> _Row: ... # type: ignore + def first(self) -> Optional[_Row]: ... # type: ignore + def last(self) -> Optional[_Row]: ... # type: ignore + def none(self) -> ValuesQuerySet[_T, _Row]: ... + def all(self) -> ValuesQuerySet[_T, _Row]: ... + def filter(self, *args: Any, **kwargs: Any) -> ValuesQuerySet[_T, _Row]: ... + def exclude(self, *args: Any, **kwargs: Any) -> ValuesQuerySet[_T, _Row]: ... + def complex_filter(self, filter_obj: Any) -> ValuesQuerySet[_T, _Row]: ... def count(self) -> int: ... - 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[_T, _Row]: ... - def select_related(self, *fields: Any) -> QuerySet[_T, _Row]: ... - def prefetch_related(self, *lookups: Any) -> QuerySet[_T, _Row]: ... + def union(self, *other_qs: Any, all: bool = ...) -> ValuesQuerySet[_T, _Row]: ... + def intersection(self, *other_qs: Any) -> ValuesQuerySet[_T, _Row]: ... + def difference(self, *other_qs: Any) -> ValuesQuerySet[_T, _Row]: ... + def select_for_update( + self, nowait: bool = ..., skip_locked: bool = ..., of: Tuple = ... + ) -> ValuesQuerySet[_T, _Row]: ... + def select_related(self, *fields: Any) -> ValuesQuerySet[_T, _Row]: ... + def prefetch_related(self, *lookups: Any) -> ValuesQuerySet[_T, _Row]: ... # TODO: return type - def annotate(self, *args: Any, **kwargs: Any) -> QuerySet[Any, Any]: ... - def order_by(self, *field_names: Any) -> QuerySet[_T, _Row]: ... - def distinct(self, *field_names: Any) -> QuerySet[_T, _Row]: ... + def annotate(self, *args: Any, **kwargs: Any) -> QuerySet[Any]: ... + def order_by(self, *field_names: Any) -> ValuesQuerySet[_T, _Row]: ... + def distinct(self, *field_names: Any) -> ValuesQuerySet[_T, _Row]: ... # extra() return type won't be supported any time soon def extra( self, @@ -138,18 +194,11 @@ class QuerySet(Generic[_T, _Row], Collection[_Row], Sized): tables: Optional[List[str]] = ..., order_by: Optional[Sequence[str]] = ..., select_params: Optional[Sequence[Any]] = ..., - ) -> QuerySet[Any, Any]: ... - def reverse(self) -> QuerySet[_T, _Row]: ... - def defer(self, *fields: Any) -> QuerySet[_T, _Row]: ... - def only(self, *fields: Any) -> QuerySet[_T, _Row]: ... - def using(self, alias: Optional[str]) -> QuerySet[_T, _Row]: ... - @property - def ordered(self) -> bool: ... - @property - def db(self) -> str: ... - def resolve_expression(self, *args: Any, **kwargs: Any) -> Any: ... - # TODO: remove when django adds __class_getitem__ methods - def __getattr__(self, item: str) -> Any: ... + ) -> QuerySet[Any]: ... + def reverse(self) -> ValuesQuerySet[_T, _Row]: ... + def defer(self, *fields: Any) -> ValuesQuerySet[_T, _Row]: ... + def only(self, *fields: Any) -> ValuesQuerySet[_T, _Row]: ... + def using(self, alias: Optional[str]) -> ValuesQuerySet[_T, _Row]: ... class RawQuerySet(Iterable[_T], Sized): query: RawQuery diff --git a/django-stubs/shortcuts.pyi b/django-stubs/shortcuts.pyi index 04b19b0..7cd19a9 100644 --- a/django-stubs/shortcuts.pyi +++ b/django-stubs/shortcuts.pyi @@ -31,6 +31,6 @@ def redirect( _T = TypeVar("_T", bound=Model) -def get_object_or_404(klass: Union[Type[_T], Manager[_T], QuerySet[_T, _T]], *args: Any, **kwargs: Any) -> _T: ... -def get_list_or_404(klass: Union[Type[_T], Manager[_T], QuerySet[_T, _T]], *args: Any, **kwargs: Any) -> List[_T]: ... +def get_object_or_404(klass: Union[Type[_T], Manager[_T], QuerySet[_T]], *args: Any, **kwargs: Any) -> _T: ... +def get_list_or_404(klass: Union[Type[_T], Manager[_T], QuerySet[_T]], *args: Any, **kwargs: Any) -> List[_T]: ... def resolve_url(to: Union[Callable, Model, str], *args: Any, **kwargs: Any) -> str: ... diff --git a/mypy_django_plugin/main.py b/mypy_django_plugin/main.py index dbd8342..2c76bf1 100644 --- a/mypy_django_plugin/main.py +++ b/mypy_django_plugin/main.py @@ -7,7 +7,7 @@ from mypy.errors import Errors from mypy.nodes import MypyFile, TypeInfo from mypy.options import Options from mypy.plugin import ( - AnalyzeTypeContext, AttributeContext, ClassDefContext, FunctionContext, MethodContext, Plugin, + AttributeContext, ClassDefContext, FunctionContext, MethodContext, Plugin, ) from mypy.types import Type as MypyType @@ -233,15 +233,6 @@ class NewSemanalDjangoPlugin(Plugin): return partial(request.set_auth_user_model_as_type_for_request_user, django_context=self.django_context) return None - def get_type_analyze_hook(self, fullname: str - ) -> Optional[Callable[[AnalyzeTypeContext], MypyType]]: - info = self._get_typeinfo_or_none(fullname) - if (info - and info.has_base(fullnames.QUERYSET_CLASS_FULLNAME) - and not info.has_base(fullnames.BASE_MANAGER_CLASS_FULLNAME)): - return partial(querysets.set_first_generic_param_as_default_for_second, fullname=fullname) - return None - def plugin(version): return NewSemanalDjangoPlugin diff --git a/mypy_django_plugin/transformers/querysets.py b/mypy_django_plugin/transformers/querysets.py index a524aab..7a8a7b4 100644 --- a/mypy_django_plugin/transformers/querysets.py +++ b/mypy_django_plugin/transformers/querysets.py @@ -1,15 +1,12 @@ from collections import OrderedDict -from typing import List, Optional, Sequence, Type, Union, cast +from typing import List, Optional, Sequence, Type, Union from django.core.exceptions import FieldError from django.db.models.base import Model from django.db.models.fields.related import RelatedField -from mypy.newsemanal.typeanal import TypeAnalyser -from mypy.nodes import Expression, NameExpr, TypeInfo -from mypy.plugin import AnalyzeTypeContext, FunctionContext, MethodContext -from mypy.types import AnyType, Instance -from mypy.types import Type as MypyType -from mypy.types import TypeOfAny +from mypy.nodes import Expression, NameExpr +from mypy.plugin import FunctionContext, MethodContext +from mypy.types import AnyType, Instance, Type as MypyType, TypeOfAny from mypy_django_plugin.django.context import DjangoContext from mypy_django_plugin.lib import fullnames, helpers @@ -183,20 +180,3 @@ def extract_proper_type_queryset_values(ctx: MethodContext, django_context: Djan row_type = helpers.make_typeddict(ctx.api, column_types, set(column_types.keys())) return helpers.reparametrize_instance(ctx.default_return_type, [model_type, row_type]) - - -def set_first_generic_param_as_default_for_second(ctx: AnalyzeTypeContext, fullname: str) -> MypyType: - type_analyser_api = cast(TypeAnalyser, ctx.api) - - info = helpers.lookup_fully_qualified_typeinfo(type_analyser_api.api, fullname) # type: ignore - assert isinstance(info, TypeInfo) - - if not ctx.type.args: - return Instance(info, [AnyType(TypeOfAny.explicit), AnyType(TypeOfAny.explicit)]) - - args = ctx.type.args - if len(args) == 1: - args = [args[0], args[0]] - - analyzed_args = [type_analyser_api.analyze_type(arg) for arg in args] - return Instance(info, analyzed_args) diff --git a/scripts/enabled_test_modules.py b/scripts/enabled_test_modules.py index 69ad718..9438a7f 100644 --- a/scripts/enabled_test_modules.py +++ b/scripts/enabled_test_modules.py @@ -28,7 +28,6 @@ IGNORED_ERRORS = { 'Cannot assign to a type', '"HttpResponse" has no attribute', '"HttpResponseBase" has no attribute', - # '"HttpRequest" has no attribute', '"object" has no attribute', 'defined in the current module', re.compile(r'"Callable\[(\[(Any(, )?)*((, )?VarArg\(Any\))?((, )?KwArg\(Any\))?\]|\.\.\.), Any\]" ' @@ -73,6 +72,12 @@ IGNORED_ERRORS = { 'Incompatible types in assignment (expression has type "Callable[', 'SimpleLazyObject' ], + 'aggregation': [ + re.compile(r'got "Optional\[(Author|Publisher)\]", expected "Union\[(Author|Publisher), Combinable\]"') + ], + 'annotations': [ + 'Incompatible type for "store" of "Employee" (got "Optional[Store]", expected "Union[Store, Combinable]")' + ], 'apps': [ 'Incompatible types in assignment (expression has type "str", target has type "type")', ], @@ -114,7 +119,8 @@ IGNORED_ERRORS = { 'custom_managers': [ 'Unsupported dynamic base class', '"Book" has no attribute "favorite_avg"', - 'Incompatible types in assignment (expression has type "CharField' + 'Incompatible types in assignment (expression has type "CharField', + 'Item "Book" of "Optional[Book]" has no attribute "favorite_avg"' ], 'csrf_tests': [ 'Incompatible types in assignment (expression has type "property", ' + @@ -135,6 +141,7 @@ IGNORED_ERRORS = { ], 'db_functions': [ '"FloatModel" has no attribute', + 'Incompatible types in assignment (expression has type "Optional[Any]", variable has type "FloatModel")' ], 'decorators': [ '"Type[object]" has no attribute "method"' @@ -164,9 +171,9 @@ IGNORED_ERRORS = { ], 'get_object_or_404': [ 'Argument 1 to "get_object_or_404" has incompatible type "str"; ' - + 'expected "Union[Type[], QuerySet[, ]]"', + + 'expected "Union[Type[], QuerySet[]]"', 'Argument 1 to "get_list_or_404" has incompatible type "List[Type[Article]]"; ' - + 'expected "Union[Type[], QuerySet[, ]]"', + + 'expected "Union[Type[], QuerySet[]]"', 'CustomClass' ], 'generic_relations_regress': [ @@ -234,7 +241,9 @@ IGNORED_ERRORS = { '"Dimension" has no attribute "set_component_order"', ], 'one_to_one': [ - 'expression has type "None", variable has type "UndergroundBar"' + 'expression has type "None", variable has type "UndergroundBar"', + 'Item "OneToOneField[Union[Place, Combinable], Place]" ' + + 'of "Union[OneToOneField[Union[Place, Combinable], Place], Any]"', ], 'postgres_tests': [ 'DummyArrayField', @@ -255,9 +264,9 @@ IGNORED_ERRORS = { '"Person" has no attribute "houses_lst"', '"Book" has no attribute "first_authors"', '"Book" has no attribute "the_authors"', - 'Incompatible types in assignment (expression has type "List[Room]", variable has type "QuerySet[Room, Room]")', - '"Room" has no attribute "main_room_of_attr"', - '"Room" has no attribute "house_attr"' + 'Incompatible types in assignment (expression has type "List[Room]", variable has type "QuerySet[Room]")', + 'Item "Room" of "Optional[Room]" has no attribute "house_attr"', + 'Item "Room" of "Optional[Room]" has no attribute "main_room_of_attr"' ], 'proxy_models': [ 'Incompatible types in assignment', @@ -266,8 +275,8 @@ IGNORED_ERRORS = { 'queries': [ 'Incompatible types in assignment (expression has type "None", variable has type "str")', 'Invalid index type "Optional[str]" for "Dict[str, int]"; expected type "str"', - 'Unsupported operand types for & ("QuerySet[Author, Author]" and "QuerySet[Tag, Tag]")', - 'Unsupported operand types for | ("QuerySet[Author, Author]" and "QuerySet[Tag, Tag]")', + 'Unsupported operand types for & ("QuerySet[Author]" and "QuerySet[Tag]")', + 'Unsupported operand types for | ("QuerySet[Author]" and "QuerySet[Tag]")', 'ObjectA', 'ObjectB', 'ObjectC', @@ -293,6 +302,10 @@ IGNORED_ERRORS = { 'Incompatible types in assignment (expression has type "None", variable has type "int")', '"AbstractBaseSession" has no attribute' ], + 'select_related': [ + 'Item "ForeignKey[Union[Genus, Combinable], Genus]" ' + + 'of "Union[ForeignKey[Union[Genus, Combinable], Genus], Any]"' + ], 'select_related_onetoone': [ 'Incompatible types in assignment (expression has type "Parent2", variable has type "Parent1")', '"Parent1" has no attribute' diff --git a/test-data/typecheck/managers/querysets/test_basic_methods.yml b/test-data/typecheck/managers/querysets/test_basic_methods.yml index 64b4b49..70ec822 100644 --- a/test-data/typecheck/managers/querysets/test_basic_methods.yml +++ b/test-data/typecheck/managers/querysets/test_basic_methods.yml @@ -3,22 +3,22 @@ from myapp.models import Blog qs = Blog.objects.all() - reveal_type(qs) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*, myapp.models.Blog*]' + reveal_type(qs) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*]' reveal_type(qs.get(id=1)) # N: Revealed type is 'myapp.models.Blog*' reveal_type(iter(qs)) # N: Revealed type is 'typing.Iterator[myapp.models.Blog*]' reveal_type(qs.iterator()) # N: Revealed type is 'typing.Iterator[myapp.models.Blog*]' - reveal_type(qs.first()) # N: Revealed type is 'myapp.models.Blog*' + reveal_type(qs.first()) # N: Revealed type is 'Union[myapp.models.Blog*, None]' reveal_type(qs.earliest()) # N: Revealed type is 'myapp.models.Blog*' reveal_type(qs[0]) # N: Revealed type is 'myapp.models.Blog*' - reveal_type(qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*, myapp.models.Blog*]' + reveal_type(qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*]' reveal_type(qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, myapp.models.Blog*]' # .dates / .datetimes - reveal_type(Blog.objects.dates("created_at", "day")) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*, datetime.date]' - reveal_type(Blog.objects.datetimes("created_at", "day")) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*, datetime.datetime]' + reveal_type(Blog.objects.dates("created_at", "day")) # N: Revealed type is 'django.db.models.query.ValuesQuerySet[myapp.models.Blog*, datetime.date]' + reveal_type(Blog.objects.datetimes("created_at", "day")) # N: Revealed type is 'django.db.models.query.ValuesQuerySet[myapp.models.Blog*, datetime.datetime]' # AND-ing QuerySets - reveal_type(Blog.objects.all() & Blog.objects.all()) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*, myapp.models.Blog*]' + reveal_type(Blog.objects.all() & Blog.objects.all()) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*]' installed_apps: - myapp files: @@ -28,11 +28,4 @@ from django.db import models class Blog(models.Model): - created_at = models.DateTimeField() - -- case: queryset_could_be_specified_with_one_type - main: | - from typing import Optional - from django.db import models - queryset: models.QuerySet[models.Model] = models.QuerySet() - reveal_type(queryset) # N: Revealed type is 'django.db.models.query.QuerySet[django.db.models.base.Model, django.db.models.base.Model]' + created_at = models.DateTimeField() \ No newline at end of file diff --git a/test-data/typecheck/managers/querysets/test_values_list.yml b/test-data/typecheck/managers/querysets/test_values_list.yml index 94a1243..5f1f5cf 100644 --- a/test-data/typecheck/managers/querysets/test_values_list.yml +++ b/test-data/typecheck/managers/querysets/test_values_list.yml @@ -192,8 +192,8 @@ - case: values_list_flat_true_with_ids main: | from myapp.models import Blog, Publisher - reveal_type(Blog.objects.values_list('id', flat=True)) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog, builtins.int]' - reveal_type(Blog.objects.values_list('publisher_id', flat=True)) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog, builtins.int]' + reveal_type(Blog.objects.values_list('id', flat=True)) # N: Revealed type is 'django.db.models.query.ValuesQuerySet[myapp.models.Blog, builtins.int]' + reveal_type(Blog.objects.values_list('publisher_id', flat=True)) # N: Revealed type is 'django.db.models.query.ValuesQuerySet[myapp.models.Blog, builtins.int]' installed_apps: - myapp files: @@ -210,8 +210,8 @@ main: | from myapp.models import TransactionQuerySet reveal_type(TransactionQuerySet()) # N: Revealed type is 'myapp.models.TransactionQuerySet' - reveal_type(TransactionQuerySet().values()) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Transaction, TypedDict({'id': builtins.int, 'total': builtins.int})]' - reveal_type(TransactionQuerySet().values_list()) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Transaction, Tuple[builtins.int, builtins.int]]' + reveal_type(TransactionQuerySet().values()) # N: Revealed type is 'django.db.models.query.ValuesQuerySet[myapp.models.Transaction, TypedDict({'id': builtins.int, 'total': builtins.int})]' + reveal_type(TransactionQuerySet().values_list()) # N: Revealed type is 'django.db.models.query.ValuesQuerySet[myapp.models.Transaction, Tuple[builtins.int, builtins.int]]' installed_apps: - myapp files: diff --git a/test-data/typecheck/managers/test_managers.yml b/test-data/typecheck/managers/test_managers.yml index 0e0e299..12cc217 100644 --- a/test-data/typecheck/managers/test_managers.yml +++ b/test-data/typecheck/managers/test_managers.yml @@ -197,10 +197,10 @@ main: | from myapp.models import UnrelatedModel, MyModel reveal_type(UnrelatedModel.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.UnrelatedModel]' - reveal_type(UnrelatedModel.objects.first()) # N: Revealed type is 'myapp.models.UnrelatedModel*' + reveal_type(UnrelatedModel.objects.first()) # N: Revealed type is 'Union[myapp.models.UnrelatedModel*, None]' reveal_type(MyModel.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel]' - reveal_type(MyModel.objects.first()) # N: Revealed type is 'myapp.models.MyModel*' + reveal_type(MyModel.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel*, None]' installed_apps: - myapp files: @@ -218,10 +218,10 @@ main: | from myapp.models import UnrelatedModel2, MyModel2 reveal_type(UnrelatedModel2.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.UnrelatedModel2]' - reveal_type(UnrelatedModel2.objects.first()) # N: Revealed type is 'myapp.models.UnrelatedModel2*' + reveal_type(UnrelatedModel2.objects.first()) # N: Revealed type is 'Union[myapp.models.UnrelatedModel2*, None]' reveal_type(MyModel2.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel2]' - reveal_type(MyModel2.objects.first()) # N: Revealed type is 'myapp.models.MyModel2*' + reveal_type(MyModel2.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel2*, None]' installed_apps: - myapp files: @@ -239,10 +239,10 @@ main: | from myapp.models import ParentOfMyModel3, MyModel3 reveal_type(ParentOfMyModel3.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.ParentOfMyModel3]' - reveal_type(ParentOfMyModel3.objects.first()) # N: Revealed type is 'myapp.models.ParentOfMyModel3*' + reveal_type(ParentOfMyModel3.objects.first()) # N: Revealed type is 'Union[myapp.models.ParentOfMyModel3*, None]' reveal_type(MyModel3.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel3]' - reveal_type(MyModel3.objects.first()) # N: Revealed type is 'myapp.models.MyModel3*' + reveal_type(MyModel3.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel3*, None]' installed_apps: - myapp files: @@ -260,10 +260,10 @@ main: | from myapp.models import ParentOfMyModel4, MyModel4 reveal_type(ParentOfMyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.ParentOfMyModel4]' - reveal_type(ParentOfMyModel4.objects.first()) # N: Revealed type is 'myapp.models.ParentOfMyModel4*' + reveal_type(ParentOfMyModel4.objects.first()) # N: Revealed type is 'Union[myapp.models.ParentOfMyModel4*, None]' reveal_type(MyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel4]' - reveal_type(MyModel4.objects.first()) # N: Revealed type is 'myapp.models.MyModel4*' + reveal_type(MyModel4.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel4*, None]' installed_apps: - myapp files: @@ -308,7 +308,7 @@ main: | from myapp.models import User reveal_type(User.objects.get()) # N: Revealed type is 'myapp.models.User*' - reveal_type(User.objects.select_related()) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.User*, myapp.models.User*]' + reveal_type(User.objects.select_related()) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.User*]' installed_apps: - myapp files: