From d03fddd96d556f95694b5e9b9678161b9c8881a2 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Sun, 3 Feb 2019 19:33:51 +0300 Subject: [PATCH] split error suppression for tests typechecking, fix ci, bunch of fixes --- django-stubs/contrib/admin/__init__.pyi | 11 +- django-stubs/contrib/admin/checks.pyi | 7 +- django-stubs/contrib/admin/options.pyi | 3 +- django-stubs/contrib/admin/widgets.pyi | 14 +- django-stubs/core/paginator.pyi | 5 +- django-stubs/core/signals.pyi | 6 + django-stubs/db/models/__init__.pyi | 4 + django-stubs/db/models/deletion.pyi | 3 + django-stubs/db/models/fields/proxy.pyi | 2 +- django-stubs/db/models/query.pyi | 6 +- django-stubs/db/models/signals.pyi | 4 +- django-stubs/dispatch/dispatcher.pyi | 6 +- django-stubs/forms/__init__.pyi | 3 + django-stubs/forms/widgets.pyi | 1 + django-stubs/template/__init__.pyi | 2 + django-stubs/template/defaultfilters.pyi | 12 +- django-stubs/test/signals.pyi | 1 + django-stubs/test/utils.pyi | 9 +- scripts/typecheck_tests.py | 197 +++++++++++++++-------- 19 files changed, 191 insertions(+), 105 deletions(-) create mode 100644 django-stubs/core/signals.pyi diff --git a/django-stubs/contrib/admin/__init__.pyi b/django-stubs/contrib/admin/__init__.pyi index a8d01d0..5737e42 100644 --- a/django-stubs/contrib/admin/__init__.pyi +++ b/django-stubs/contrib/admin/__init__.pyi @@ -1,5 +1,5 @@ -from django.contrib.admin.decorators import register as register -from django.contrib.admin.filters import ( +from .decorators import register as register +from .filters import ( AllValuesFieldListFilter as AllValuesFieldListFilter, BooleanFieldListFilter as BooleanFieldListFilter, ChoicesFieldListFilter as ChoicesFieldListFilter, @@ -10,14 +10,15 @@ from django.contrib.admin.filters import ( RelatedOnlyFieldListFilter as RelatedOnlyFieldListFilter, SimpleListFilter as SimpleListFilter, ) -from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME as ACTION_CHECKBOX_NAME -from django.contrib.admin.options import ( +from .helpers import ACTION_CHECKBOX_NAME as ACTION_CHECKBOX_NAME +from .options import ( HORIZONTAL as HORIZONTAL, VERTICAL as VERTICAL, ModelAdmin as ModelAdmin, StackedInline as StackedInline, TabularInline as TabularInline, ) -from django.contrib.admin.sites import AdminSite as AdminSite, site as site +from .sites import AdminSite as AdminSite, site as site +from . import checks as checks def autodiscover() -> None: ... diff --git a/django-stubs/contrib/admin/checks.pyi b/django-stubs/contrib/admin/checks.pyi index 9143c36..c43e226 100644 --- a/django-stubs/contrib/admin/checks.pyi +++ b/django-stubs/contrib/admin/checks.pyi @@ -11,11 +11,8 @@ def check_dependencies(**kwargs: Any) -> List[_CheckError]: ... class BaseModelAdminChecks: def check(self, admin_obj: BaseModelAdmin, **kwargs: Any) -> List[_CheckError]: ... -class ModelAdminChecks(BaseModelAdminChecks): - def check(self, admin_obj: ModelAdmin, **kwargs: Any) -> List[_CheckError]: ... - -class InlineModelAdminChecks(BaseModelAdminChecks): - def check(self, inline_obj: InlineModelAdmin, **kwargs: Any) -> List[_CheckError]: ... +class ModelAdminChecks(BaseModelAdminChecks): ... +class InlineModelAdminChecks(BaseModelAdminChecks): ... def must_be(type: Any, option: Any, obj: Any, id: Any): ... def must_inherit_from(parent: Any, option: Any, obj: Any, id: Any): ... diff --git a/django-stubs/contrib/admin/options.pyi b/django-stubs/contrib/admin/options.pyi index e012b05..4b34ade 100644 --- a/django-stubs/contrib/admin/options.pyi +++ b/django-stubs/contrib/admin/options.pyi @@ -21,6 +21,7 @@ from django.urls.resolvers import URLPattern from django.utils.safestring import SafeText from django.db.models.fields import Field +from django.template.response import TemplateResponse IS_POPUP_VAR: str TO_FIELD_VAR: str @@ -229,7 +230,7 @@ class ModelAdmin(BaseModelAdmin): ) -> HttpResponse: ... def changelist_view( self, request: WSGIRequest, extra_context: Optional[Dict[str, str]] = ... - ) -> HttpResponseBase: ... + ) -> TemplateResponse: ... def get_deleted_objects( self, objs: QuerySet, request: WSGIRequest ) -> Tuple[List[Any], Dict[Any, Any], Set[Any], List[Any]]: ... diff --git a/django-stubs/contrib/admin/widgets.pyi b/django-stubs/contrib/admin/widgets.pyi index 7099caa..562a2eb 100644 --- a/django-stubs/contrib/admin/widgets.pyi +++ b/django-stubs/contrib/admin/widgets.pyi @@ -3,12 +3,14 @@ from datetime import datetime from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union from uuid import UUID +from django.forms.models import ModelChoiceIterator + from django import forms from django.contrib.admin.sites import AdminSite -from django.db.models.fields.reverse_related import ForeignObjectRel +from django.db.models.fields.reverse_related import ForeignObjectRel, ManyToOneRel, ManyToManyRel from django.db.models.query_utils import Q from django.forms.fields import Field -from django.forms.widgets import ChoiceWidget, Media, Widget +from django.forms.widgets import ChoiceWidget, Media, Widget, DateTimeBaseInput from django.http.request import QueryDict from django.utils.datastructures import MultiValueDict @@ -92,7 +94,7 @@ def url_params_from_lookup_dict( class ForeignKeyRawIdWidget(forms.TextInput): attrs: Dict[Any, Any] template_name: str = ... - rel: django.db.models.fields.reverse_related.ManyToOneRel = ... + rel: ManyToOneRel = ... admin_site: AdminSite = ... db: None = ... def __init__(self, rel: ForeignObjectRel, admin_site: AdminSite, attrs: None = ..., using: None = ...) -> None: ... @@ -107,7 +109,7 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): admin_site: AdminSite attrs: Dict[Any, Any] db: None - rel: django.db.models.fields.reverse_related.ManyToManyRel + rel: ManyToManyRel template_name: str = ... def get_context( self, name: str, value: Optional[List[int]], attrs: Optional[Dict[str, str]] @@ -122,8 +124,8 @@ class RelatedFieldWidgetWrapper(forms.Widget): needs_multipart_form: bool = ... attrs: Dict[Any, Any] = ... choices: ModelChoiceIterator = ... - widget: django.contrib.admin.widgets.AutocompleteSelect = ... - rel: django.db.models.fields.reverse_related.ManyToOneRel = ... + widget: AutocompleteSelect = ... + rel: ManyToOneRel = ... can_add_related: bool = ... can_change_related: bool = ... can_delete_related: bool = ... diff --git a/django-stubs/core/paginator.pyi b/django-stubs/core/paginator.pyi index e047122..3238255 100644 --- a/django-stubs/core/paginator.pyi +++ b/django-stubs/core/paginator.pyi @@ -14,6 +14,9 @@ class SupportsLen(Protocol): class SupportsCount(Protocol): def count(self) -> int: ... +class SupportsOrdered(Protocol): + ordered: bool = ... + class Paginator: object_list: QuerySet = ... per_page: int = ... @@ -21,7 +24,7 @@ class Paginator: allow_empty_first_page: bool = ... def __init__( self, - object_list: Union[SupportsLen, SupportsCount], + object_list: Union[SupportsLen, SupportsCount, SupportsOrdered], per_page: Union[int, str], orphans: int = ..., allow_empty_first_page: bool = ..., diff --git a/django-stubs/core/signals.pyi b/django-stubs/core/signals.pyi new file mode 100644 index 0000000..edd2b30 --- /dev/null +++ b/django-stubs/core/signals.pyi @@ -0,0 +1,6 @@ +from django.dispatch import Signal + +request_started: Signal = ... +request_finished: Signal = ... +got_request_exception: Signal = ... +setting_changed: Signal = ... diff --git a/django-stubs/db/models/__init__.pyi b/django-stubs/db/models/__init__.pyi index d922fb6..50a0838 100644 --- a/django-stubs/db/models/__init__.pyi +++ b/django-stubs/db/models/__init__.pyi @@ -47,6 +47,7 @@ from .fields.related import ( ForeignObject as ForeignObject, ) from .fields.files import ImageField as ImageField, FileField as FileField +from .fields.proxy import OrderWrt as OrderWrt from .deletion import ( CASCADE as CASCADE, @@ -54,6 +55,7 @@ from .deletion import ( SET_NULL as SET_NULL, DO_NOTHING as DO_NOTHING, PROTECT as PROTECT, + SET as SET, ) from .query import ( @@ -97,3 +99,5 @@ from .aggregates import ( ) from .indexes import Index as Index + +from . import signals as signals diff --git a/django-stubs/db/models/deletion.pyi b/django-stubs/db/models/deletion.pyi index 10f78ef..bc1d241 100644 --- a/django-stubs/db/models/deletion.pyi +++ b/django-stubs/db/models/deletion.pyi @@ -1,3 +1,5 @@ +from typing import Any, Callable + from django.db import IntegrityError def CASCADE(collector, field, sub_objs, using): ... @@ -5,5 +7,6 @@ def SET_NULL(collector, field, sub_objs, using): ... def SET_DEFAULT(collector, field, sub_objs, using): ... def DO_NOTHING(collector, field, sub_objs, using): ... def PROTECT(collector, field, sub_objs, using): ... +def SET(value: Any) -> Callable: ... class ProtectedError(IntegrityError): ... diff --git a/django-stubs/db/models/fields/proxy.pyi b/django-stubs/db/models/fields/proxy.pyi index a5595c3..ef64753 100644 --- a/django-stubs/db/models/fields/proxy.pyi +++ b/django-stubs/db/models/fields/proxy.pyi @@ -1,4 +1,4 @@ -from typing import Any, Optional +from typing import Any from django.db.models import fields diff --git a/django-stubs/db/models/query.pyi b/django-stubs/db/models/query.pyi index 23ab5cf..eb2fbe8 100644 --- a/django-stubs/db/models/query.pyi +++ b/django-stubs/db/models/query.pyi @@ -152,8 +152,10 @@ class Prefetch(object): def get_current_queryset(self, level) -> Optional[QuerySet]: ... def prefetch_related_objects(model_instances: Iterable[_T], *related_lookups: Union[str, Prefetch]) -> None: ... -def get_prefetcher(instance: _T, through_attr: str, to_attr: str) -> Tuple[Any, Any, bool, bool]: ... +def get_prefetcher(instance: Model, through_attr: str, to_attr: str) -> Tuple[Any, Any, bool, bool]: ... + +class ModelIterable(Iterable[_T]): + def __iter__(self) -> Iterator[_T]: ... -class ModelIterable(Iterable[_T]): ... class InstanceCheckMeta(type): ... class EmptyQuerySet(metaclass=InstanceCheckMeta): ... diff --git a/django-stubs/db/models/signals.pyi b/django-stubs/db/models/signals.pyi index 04af046..bd8da95 100644 --- a/django-stubs/db/models/signals.pyi +++ b/django-stubs/db/models/signals.pyi @@ -7,7 +7,7 @@ from django.dispatch import Signal class_prepared: Any class ModelSignal(Signal): - def connect( + def connect( # type: ignore self, receiver: Callable, sender: Optional[Union[Type[Model], str]] = ..., @@ -15,7 +15,7 @@ class ModelSignal(Signal): dispatch_uid: None = ..., apps: Optional[Apps] = ..., ) -> None: ... - def disconnect( + def disconnect( # type: ignore self, receiver: Callable = ..., sender: Optional[Union[Type[Model], str]] = ..., diff --git a/django-stubs/dispatch/dispatcher.pyi b/django-stubs/dispatch/dispatcher.pyi index 4b5c88f..2767a1a 100644 --- a/django-stubs/dispatch/dispatcher.pyi +++ b/django-stubs/dispatch/dispatcher.pyi @@ -15,15 +15,15 @@ class Signal: def __init__(self, providing_args: List[str] = ..., use_caching: bool = ...) -> None: ... def connect( self, - receiver: Any, - sender: Optional[Union[Type[Model], AppConfig]] = ..., + receiver: Callable, + sender: Optional[Union[Type[Model], AppConfig, str]] = ..., weak: bool = ..., dispatch_uid: Optional[str] = ..., ) -> None: ... def disconnect( self, receiver: Optional[Callable] = ..., - sender: Optional[Union[Type[Model], AppConfig]] = ..., + sender: Optional[Union[Type[Model], AppConfig, str]] = ..., dispatch_uid: Optional[str] = ..., ) -> bool: ... def has_listeners(self, sender: Any = ...) -> bool: ... diff --git a/django-stubs/forms/__init__.pyi b/django-stubs/forms/__init__.pyi index a476594..bcb247d 100644 --- a/django-stubs/forms/__init__.pyi +++ b/django-stubs/forms/__init__.pyi @@ -34,6 +34,9 @@ from .widgets import ( SelectMultiple as SelectMultiple, TimeInput as TimeInput, URLInput as URLInput, + SelectDateWidget as SelectDateWidget, + SplitHiddenDateTimeWidget as SplitHiddenDateTimeWidget, + SplitDateTimeWidget as SplitDateTimeWidget, ) from .fields import ( diff --git a/django-stubs/forms/widgets.pyi b/django-stubs/forms/widgets.pyi index 1724a52..e92c22b 100644 --- a/django-stubs/forms/widgets.pyi +++ b/django-stubs/forms/widgets.pyi @@ -159,6 +159,7 @@ class SelectMultiple(Select): allow_multiple_selected: bool = ... class RadioSelect(ChoiceWidget): + can_add_related: bool option_template_name: str = ... class CheckboxSelectMultiple(ChoiceWidget): diff --git a/django-stubs/template/__init__.pyi b/django-stubs/template/__init__.pyi index 48fdf2c..02a0ada 100644 --- a/django-stubs/template/__init__.pyi +++ b/django-stubs/template/__init__.pyi @@ -12,3 +12,5 @@ from .base import Node as Node, NodeList as NodeList, Origin as Origin, Template from .context import Context as Context, RequestContext as RequestContext from .library import Library as Library + +from . import defaultfilters as defaultfilters diff --git a/django-stubs/template/defaultfilters.pyi b/django-stubs/template/defaultfilters.pyi index ab2c69b..7f9f00a 100644 --- a/django-stubs/template/defaultfilters.pyi +++ b/django-stubs/template/defaultfilters.pyi @@ -1,4 +1,4 @@ -from datetime import date, datetime, timedelta +from datetime import _date, datetime, timedelta from decimal import Decimal from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union @@ -72,14 +72,14 @@ def unordered_list( value: Union[Iterator[Any], List[Union[List[Union[List[Union[List[str], str]], str]], str]]], autoescape: bool = ... ) -> SafeText: ... def add( - value: Union[List[int], Tuple[int, int], date, int, str], + value: Union[List[int], Tuple[int, int], _date, int, str], arg: Union[List[int], Tuple[int, int], timedelta, int, str], -) -> Union[List[int], Tuple[int, int, int, int], date, int, str]: ... +) -> Union[List[int], Tuple[int, int, int, int], _date, int, str]: ... def get_digit(value: Union[int, str], arg: int) -> Union[int, str]: ... -def date(value: Optional[Union[datetime, str]], arg: Optional[str] = ...) -> str: ... +def date(value: Optional[Union[_date, datetime, str]], arg: Optional[str] = ...) -> str: ... def time(value: Optional[Union[datetime, str]], arg: Optional[str] = ...) -> str: ... -def timesince_filter(value: Optional[date], arg: Optional[date] = ...) -> str: ... -def timeuntil_filter(value: Optional[date], arg: Optional[date] = ...) -> str: ... +def timesince_filter(value: Optional[_date], arg: Optional[_date] = ...) -> str: ... +def timeuntil_filter(value: Optional[_date], arg: Optional[_date] = ...) -> str: ... def default(value: Optional[Union[int, str]], arg: Union[int, str]) -> Union[int, str]: ... def default_if_none(value: Optional[str], arg: Union[int, str]) -> Union[int, str]: ... def divisibleby(value: int, arg: int) -> bool: ... diff --git a/django-stubs/test/signals.pyi b/django-stubs/test/signals.pyi index 68162d4..885f318 100644 --- a/django-stubs/test/signals.pyi +++ b/django-stubs/test/signals.pyi @@ -1,4 +1,5 @@ from typing import Any +from django.core.signals import setting_changed as setting_changed template_rendered: Any COMPLEX_OVERRIDE_SETTINGS: Any diff --git a/django-stubs/test/utils.pyi b/django-stubs/test/utils.pyi index 4cd9921..ca6130d 100644 --- a/django-stubs/test/utils.pyi +++ b/django-stubs/test/utils.pyi @@ -1,5 +1,6 @@ import decimal import warnings +from io import StringIO from contextlib import contextmanager from decimal import Decimal from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple, Type, Union, IO @@ -107,13 +108,13 @@ class isolate_apps(TestContextDecorator): @contextmanager def extend_sys_path(*paths: str) -> Iterator[None]: ... @contextmanager -def captured_output(stream_name) -> Iterator[IO[str]]: ... +def captured_output(stream_name) -> Iterator[StringIO]: ... @contextmanager -def captured_stdin() -> Iterator[IO[str]]: ... +def captured_stdin() -> Iterator[StringIO]: ... @contextmanager -def captured_stdout() -> Iterator[IO[str]]: ... +def captured_stdout() -> Iterator[StringIO]: ... @contextmanager -def captured_stderr() -> Iterator[IO[str]]: ... +def captured_stderr() -> Iterator[StringIO]: ... @contextmanager def freeze_time(t: float) -> Iterator[None]: ... def tag(*tags: str): ... diff --git a/scripts/typecheck_tests.py b/scripts/typecheck_tests.py index 7f27204..ffad2a5 100644 --- a/scripts/typecheck_tests.py +++ b/scripts/typecheck_tests.py @@ -9,6 +9,8 @@ from git import Repo from mypy import build from mypy.main import process_options +PROJECT_DIRECTORY = Path(__file__).parent.parent + # Django branch to typecheck against DJANGO_BRANCH = 'stable/2.1.x' @@ -17,64 +19,114 @@ DJANGO_COMMIT_SHA = '03219b5f709dcd5b0bfacd963508625557ec1ef0' # Some errors occur for the test suite itself, and cannot be addressed via django-stubs. They should be ignored # using this constant. -IGNORED_ERROR_PATTERNS = [ - 'Need type annotation for', - 'already defined on', - 'Cannot assign to a', - 'cannot perform relative import', - 'broken_app', - 'cache_clear', - 'call_count', - 'call_args_list', - 'call_args', - '"password_changed" does not return a value', - '"validate_password" does not return a value', - 'LazySettings', - 'Cannot infer type of lambda', - '"refresh_from_db" of "Model"', - '"as_sql" undefined in superclass', - 'Incompatible types in assignment (expression has type "str", target has type "type")', - 'Incompatible types in assignment (expression has type "Callable[', - 'Invalid value for a to= parameter', - 'Incompatible types in assignment (expression has type "FilteredChildAdmin", variable has type "ChildAdmin")', - 'Incompatible types in assignment (expression has type "RelatedFieldWidgetWrapper", variable has type "AdminRadioSelect")', - 'has incompatible type "MockRequest"; expected "WSGIRequest"', - '"NullTranslations" has no attribute "_catalog"', - 'Definition of "as_sql" in base class', - 'expression has type "property"', - '"object" has no attribute "__iter__"', - 'Too few arguments for "dates" of "QuerySet"', - 'has no attribute "vendor"', - 'Argument 1 to "get_list_or_404" has incompatible type "List', - 'error: "AdminRadioSelect" has no attribute "can_add_related"', - 'MockCompiler', - 'SessionTestsMixin', - 'Argument 1 to "Paginator" has incompatible type "ObjectList"', - '"Type[Morsel[Any]]" has no attribute "_reserved"', - 'Argument 1 to "append" of "list"', - 'Argument 1 to "bytes"', - '"full_clean" of "Model" does not return a value', - '"object" not callable', - 'Item "GenericForeignKey" of "Union[GenericForeignKey, Model, None]" has no attribute "read_by"', - 'Item "Model" of "Union[GenericForeignKey, Model, None]" has no attribute "read_by"', - re.compile('Cannot determine type of \'(objects|stuff|specimens|normal_manager)\''), - re.compile(r'"Callable\[\[(Any(, )?)+\], Any\]" has no attribute'), - re.compile(r'"HttpResponseBase" has no attribute "[A-Za-z_]+"'), - re.compile(r'Incompatible types in assignment \(expression has type "Tuple\[\]", ' - r'variable has type "Tuple\[[A-Za-z, ]+\]"'), - re.compile(r'"validate" of "[A-Za-z]+" does not return a value'), - re.compile(r'Module has no attribute "[A-Za-z_]+"'), - re.compile(r'"[A-Za-z\[\]]+" has no attribute "getvalue"'), - # TODO: remove when reassignment will be possible (in 0.670? ) - re.compile(r'Incompatible types in assignment \(expression has type "(QuerySet|List)\[[A-Za-z, ]+\]", ' - r'variable has type "(QuerySet|List)\[[A-Za-z, ]+\]"\)'), - re.compile(r'"(MockRequest|DummyRequest|DummyUser)" has no attribute "[a-zA-Z_]+"'), - # TODO: remove when form <-> model plugin support is added - re.compile(r'"Model" has no attribute "[A-Za-z_]+"'), - re.compile(r'Argument 1 to "get_object_or_404" has incompatible type "(str|Type\[CustomClass\])"'), - re.compile(r'"None" has no attribute "[a-zA-Z_0-9]+"'), -] - +MOCK_OBJECTS = ['MockRequest', 'MockCompiler', 'modelz'] +IGNORED_ERRORS = { + '__common__': [ + *MOCK_OBJECTS, + 'LazySettings', + 'NullTranslations', + 'Need type annotation for', + 'Invalid value for a to= parameter', + 'already defined (possibly by an import)', + 'Cannot assign to a type', + # forms <-> models plugin support + '"Model" has no attribute', + re.compile(r'Cannot determine type of \'(objects|stuff)\''), + # settings + re.compile(r'Module has no attribute "[A-Z_]+"'), + # attributes assigned to test functions + re.compile(r'"Callable\[\[(Any(, )?)+\], Any\]" has no attribute'), + # assign empty tuple + re.compile(r'Incompatible types in assignment \(expression has type "Tuple\[\]", ' + r'variable has type "Tuple\[[A-Za-z, ]+\]"'), + # assign method to a method + 'Cannot assign to a method', + 'Cannot infer type of lambda', + re.compile(r'Incompatible types in assignment \(expression has type "Callable\[\[(Any(, )?)+\], Any\]", ' + r'variable has type "Callable\['), + ], + 'admin_changelist': [ + 'Incompatible types in assignment (expression has type "FilteredChildAdmin", variable has type "ChildAdmin")' + ], + 'admin_scripts': [ + 'Incompatible types in assignment (expression has type "Callable[' + ], + 'admin_widgets': [ + 'Incompatible types in assignment (expression has type "RelatedFieldWidgetWrapper", ' + 'variable has type "AdminRadioSelect")', + 'Incompatible types in assignment (expression has type "Widget", variable has type "AutocompleteSelect")' + ], + 'aggregation': [ + 'Incompatible types in assignment (expression has type "QuerySet[Any]", variable has type "List[Any]")', + '"as_sql" undefined in superclass' + ], + 'aggregation_regress': [ + 'Incompatible types in assignment (expression has type "List[str]", variable has type "QuerySet[Author]")' + ], + 'basic': [ + 'Unexpected keyword argument "unknown_kwarg" for "refresh_from_db" of "Model"', + '"refresh_from_db" of "Model" defined here' + ], + 'builtin_server': [ + 'has no attribute "getvalue"' + ], + 'csrf_tests': [ + 'Incompatible types in assignment (expression has type "property", ' + + 'base class "HttpRequest" defined the type as "QueryDict")' + ], + 'dates': [ + 'Too few arguments for "dates" of "QuerySet"', + ], + 'defer': [ + 'Too many arguments for "refresh_from_db" of "Model"' + ], + 'db_typecasts': [ + '"object" has no attribute "__iter__"; maybe "__str__" or "__dir__"? (not iterable)' + ], + 'from_db_value': [ + 'has no attribute "vendor"' + ], + 'get_object_or_404': [ + 'Argument 1 to "get_object_or_404" has incompatible type "str"; ' + + 'expected "Union[Type[Model], Manager[Any], QuerySet[Any]]"', + 'Argument 1 to "get_object_or_404" has incompatible type "Type[CustomClass]"; ' + + 'expected "Union[Type[Model], Manager[Any], QuerySet[Any]]"', + 'Argument 1 to "get_list_or_404" has incompatible type "List[Type[Article]]"; ' + + 'expected "Union[Type[Model], Manager[Any], QuerySet[Any]]"' + ], + 'model_inheritance_regress': [ + 'Incompatible types in assignment (expression has type "List[Supplier]", variable has type "QuerySet[Supplier]")' + ], + 'model_meta': [ + '"object" has no attribute "items"', + '"Field" has no attribute "many_to_many"' + ], + 'migrate_signals': [ + 'Value of type "None" is not indexable', + ], + 'queryset_pickle': [ + '"None" has no attribute "somefield"' + ], + 'prefetch_related': [ + 'Incompatible types in assignment (expression has type "List[Room]", variable has type "QuerySet[Room]")', + '"None" has no attribute "__iter__"', + 'has no attribute "read_by"' + ], + 'urlpatterns': [ + '"object" has no attribute "__iter__"; maybe "__str__" or "__dir__"? (not iterable)', + '"object" not callable' + ], + 'user_commands': [ + 'Incompatible types in assignment (expression has type "Callable[[Any, KwArg(Any)], Any]", variable has type' + ], + 'sessions_tests': [ + 'base class "SessionTestsMixin" defined the type as "None")', + 'has no attribute "_reserved"' + ], + 'select_related_onetoone': [ + '"None" has no attribute' + ] +} # Test folders to typecheck TESTS_DIRS = [ 'absolute_url_overrides', @@ -190,7 +242,7 @@ TESTS_DIRS = [ # TODO: 'messages_tests', # TODO: 'middleware', # TODO: 'middleware_exceptions', - # SKIPPED (all errors are false positives) 'migrate_signals', + 'migrate_signals', 'migration_test_data_persistence', # TODO: 'migrations', 'migrations2', @@ -201,7 +253,7 @@ TESTS_DIRS = [ 'model_indexes', # TODO: 'model_inheritance', 'model_inheritance_regress', - # SKIPPED (all errors are false positives) 'model_meta', + 'model_meta', 'model_options', 'model_package', 'model_regress', @@ -298,8 +350,8 @@ def cd(path): os.chdir(prev_cwd) -def is_ignored(line: str) -> bool: - for pattern in IGNORED_ERROR_PATTERNS: +def is_ignored(line: str, test_folder_name: str) -> bool: + for pattern in IGNORED_ERRORS['__common__'] + IGNORED_ERRORS.get(test_folder_name, []): if isinstance(pattern, Pattern): if pattern.search(line): return True @@ -309,22 +361,29 @@ def is_ignored(line: str) -> bool: return False +def replace_with_clickable_location(error: str, abs_test_folder: Path) -> str: + raw_path, _, error_line = error.partition(': ') + fname, line_number = raw_path.split(':') + path = abs_test_folder.joinpath(fname).relative_to(PROJECT_DIRECTORY) + clickable_location = f'./{path}:{line_number}' + return error.replace(raw_path, clickable_location) + + def check_with_mypy(abs_path: Path, config_file_path: Path) -> int: error_happened = False with cd(abs_path): sources, options = process_options(['--config-file', str(config_file_path), str(abs_path)]) res = build.build(sources, options) for error_line in res.errors: - if not is_ignored(error_line): + if not is_ignored(error_line, abs_path.name): error_happened = True - print(error_line) + print(replace_with_clickable_location(error_line, abs_test_folder=abs_path)) return int(error_happened) if __name__ == '__main__': - project_directory = Path(__file__).parent.parent - mypy_config_file = (project_directory / 'scripts' / 'mypy.ini').absolute() - repo_directory = project_directory / 'django-sources' + mypy_config_file = (PROJECT_DIRECTORY / 'scripts' / 'mypy.ini').absolute() + repo_directory = PROJECT_DIRECTORY / 'django-sources' tests_root = repo_directory / 'tests' global_rc = 0 @@ -337,8 +396,8 @@ if __name__ == '__main__': repo.git.checkout(DJANGO_COMMIT_SHA) for dirname in TESTS_DIRS: - abs_path = (project_directory / tests_root / dirname).absolute() - print(f'Checking {abs_path.as_uri()}') + abs_path = (PROJECT_DIRECTORY / tests_root / dirname).absolute() + print(f'Checking {abs_path}') rc = check_with_mypy(abs_path, mypy_config_file) if rc != 0: