split error suppression for tests typechecking, fix ci, bunch of fixes

This commit is contained in:
Maxim Kurnikov
2019-02-03 19:33:51 +03:00
parent e409dbdb82
commit d03fddd96d
19 changed files with 191 additions and 105 deletions

View File

@@ -1,5 +1,5 @@
from django.contrib.admin.decorators import register as register from .decorators import register as register
from django.contrib.admin.filters import ( from .filters import (
AllValuesFieldListFilter as AllValuesFieldListFilter, AllValuesFieldListFilter as AllValuesFieldListFilter,
BooleanFieldListFilter as BooleanFieldListFilter, BooleanFieldListFilter as BooleanFieldListFilter,
ChoicesFieldListFilter as ChoicesFieldListFilter, ChoicesFieldListFilter as ChoicesFieldListFilter,
@@ -10,14 +10,15 @@ from django.contrib.admin.filters import (
RelatedOnlyFieldListFilter as RelatedOnlyFieldListFilter, RelatedOnlyFieldListFilter as RelatedOnlyFieldListFilter,
SimpleListFilter as SimpleListFilter, SimpleListFilter as SimpleListFilter,
) )
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME as ACTION_CHECKBOX_NAME from .helpers import ACTION_CHECKBOX_NAME as ACTION_CHECKBOX_NAME
from django.contrib.admin.options import ( from .options import (
HORIZONTAL as HORIZONTAL, HORIZONTAL as HORIZONTAL,
VERTICAL as VERTICAL, VERTICAL as VERTICAL,
ModelAdmin as ModelAdmin, ModelAdmin as ModelAdmin,
StackedInline as StackedInline, StackedInline as StackedInline,
TabularInline as TabularInline, 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: ... def autodiscover() -> None: ...

View File

@@ -11,11 +11,8 @@ def check_dependencies(**kwargs: Any) -> List[_CheckError]: ...
class BaseModelAdminChecks: class BaseModelAdminChecks:
def check(self, admin_obj: BaseModelAdmin, **kwargs: Any) -> List[_CheckError]: ... def check(self, admin_obj: BaseModelAdmin, **kwargs: Any) -> List[_CheckError]: ...
class ModelAdminChecks(BaseModelAdminChecks): class ModelAdminChecks(BaseModelAdminChecks): ...
def check(self, admin_obj: ModelAdmin, **kwargs: Any) -> List[_CheckError]: ... class InlineModelAdminChecks(BaseModelAdminChecks): ...
class InlineModelAdminChecks(BaseModelAdminChecks):
def check(self, inline_obj: InlineModelAdmin, **kwargs: Any) -> List[_CheckError]: ...
def must_be(type: Any, option: Any, obj: Any, id: Any): ... def must_be(type: Any, option: Any, obj: Any, id: Any): ...
def must_inherit_from(parent: Any, option: Any, obj: Any, id: Any): ... def must_inherit_from(parent: Any, option: Any, obj: Any, id: Any): ...

View File

@@ -21,6 +21,7 @@ from django.urls.resolvers import URLPattern
from django.utils.safestring import SafeText from django.utils.safestring import SafeText
from django.db.models.fields import Field from django.db.models.fields import Field
from django.template.response import TemplateResponse
IS_POPUP_VAR: str IS_POPUP_VAR: str
TO_FIELD_VAR: str TO_FIELD_VAR: str
@@ -229,7 +230,7 @@ class ModelAdmin(BaseModelAdmin):
) -> HttpResponse: ... ) -> HttpResponse: ...
def changelist_view( def changelist_view(
self, request: WSGIRequest, extra_context: Optional[Dict[str, str]] = ... self, request: WSGIRequest, extra_context: Optional[Dict[str, str]] = ...
) -> HttpResponseBase: ... ) -> TemplateResponse: ...
def get_deleted_objects( def get_deleted_objects(
self, objs: QuerySet, request: WSGIRequest self, objs: QuerySet, request: WSGIRequest
) -> Tuple[List[Any], Dict[Any, Any], Set[Any], List[Any]]: ... ) -> Tuple[List[Any], Dict[Any, Any], Set[Any], List[Any]]: ...

View File

@@ -3,12 +3,14 @@ from datetime import datetime
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
from uuid import UUID from uuid import UUID
from django.forms.models import ModelChoiceIterator
from django import forms from django import forms
from django.contrib.admin.sites import AdminSite 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.db.models.query_utils import Q
from django.forms.fields import Field 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.http.request import QueryDict
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
@@ -92,7 +94,7 @@ def url_params_from_lookup_dict(
class ForeignKeyRawIdWidget(forms.TextInput): class ForeignKeyRawIdWidget(forms.TextInput):
attrs: Dict[Any, Any] attrs: Dict[Any, Any]
template_name: str = ... template_name: str = ...
rel: django.db.models.fields.reverse_related.ManyToOneRel = ... rel: ManyToOneRel = ...
admin_site: AdminSite = ... admin_site: AdminSite = ...
db: None = ... db: None = ...
def __init__(self, rel: ForeignObjectRel, admin_site: AdminSite, attrs: None = ..., using: None = ...) -> None: ... def __init__(self, rel: ForeignObjectRel, admin_site: AdminSite, attrs: None = ..., using: None = ...) -> None: ...
@@ -107,7 +109,7 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
admin_site: AdminSite admin_site: AdminSite
attrs: Dict[Any, Any] attrs: Dict[Any, Any]
db: None db: None
rel: django.db.models.fields.reverse_related.ManyToManyRel rel: ManyToManyRel
template_name: str = ... template_name: str = ...
def get_context( def get_context(
self, name: str, value: Optional[List[int]], attrs: Optional[Dict[str, str]] self, name: str, value: Optional[List[int]], attrs: Optional[Dict[str, str]]
@@ -122,8 +124,8 @@ class RelatedFieldWidgetWrapper(forms.Widget):
needs_multipart_form: bool = ... needs_multipart_form: bool = ...
attrs: Dict[Any, Any] = ... attrs: Dict[Any, Any] = ...
choices: ModelChoiceIterator = ... choices: ModelChoiceIterator = ...
widget: django.contrib.admin.widgets.AutocompleteSelect = ... widget: AutocompleteSelect = ...
rel: django.db.models.fields.reverse_related.ManyToOneRel = ... rel: ManyToOneRel = ...
can_add_related: bool = ... can_add_related: bool = ...
can_change_related: bool = ... can_change_related: bool = ...
can_delete_related: bool = ... can_delete_related: bool = ...

View File

@@ -14,6 +14,9 @@ class SupportsLen(Protocol):
class SupportsCount(Protocol): class SupportsCount(Protocol):
def count(self) -> int: ... def count(self) -> int: ...
class SupportsOrdered(Protocol):
ordered: bool = ...
class Paginator: class Paginator:
object_list: QuerySet = ... object_list: QuerySet = ...
per_page: int = ... per_page: int = ...
@@ -21,7 +24,7 @@ class Paginator:
allow_empty_first_page: bool = ... allow_empty_first_page: bool = ...
def __init__( def __init__(
self, self,
object_list: Union[SupportsLen, SupportsCount], object_list: Union[SupportsLen, SupportsCount, SupportsOrdered],
per_page: Union[int, str], per_page: Union[int, str],
orphans: int = ..., orphans: int = ...,
allow_empty_first_page: bool = ..., allow_empty_first_page: bool = ...,

View File

@@ -0,0 +1,6 @@
from django.dispatch import Signal
request_started: Signal = ...
request_finished: Signal = ...
got_request_exception: Signal = ...
setting_changed: Signal = ...

View File

@@ -47,6 +47,7 @@ from .fields.related import (
ForeignObject as ForeignObject, ForeignObject as ForeignObject,
) )
from .fields.files import ImageField as ImageField, FileField as FileField from .fields.files import ImageField as ImageField, FileField as FileField
from .fields.proxy import OrderWrt as OrderWrt
from .deletion import ( from .deletion import (
CASCADE as CASCADE, CASCADE as CASCADE,
@@ -54,6 +55,7 @@ from .deletion import (
SET_NULL as SET_NULL, SET_NULL as SET_NULL,
DO_NOTHING as DO_NOTHING, DO_NOTHING as DO_NOTHING,
PROTECT as PROTECT, PROTECT as PROTECT,
SET as SET,
) )
from .query import ( from .query import (
@@ -97,3 +99,5 @@ from .aggregates import (
) )
from .indexes import Index as Index from .indexes import Index as Index
from . import signals as signals

View File

@@ -1,3 +1,5 @@
from typing import Any, Callable
from django.db import IntegrityError from django.db import IntegrityError
def CASCADE(collector, field, sub_objs, using): ... 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 SET_DEFAULT(collector, field, sub_objs, using): ...
def DO_NOTHING(collector, field, sub_objs, using): ... def DO_NOTHING(collector, field, sub_objs, using): ...
def PROTECT(collector, field, sub_objs, using): ... def PROTECT(collector, field, sub_objs, using): ...
def SET(value: Any) -> Callable: ...
class ProtectedError(IntegrityError): ... class ProtectedError(IntegrityError): ...

View File

@@ -1,4 +1,4 @@
from typing import Any, Optional from typing import Any
from django.db.models import fields from django.db.models import fields

View File

@@ -152,8 +152,10 @@ class Prefetch(object):
def get_current_queryset(self, level) -> Optional[QuerySet]: ... def get_current_queryset(self, level) -> Optional[QuerySet]: ...
def prefetch_related_objects(model_instances: Iterable[_T], *related_lookups: Union[str, Prefetch]) -> None: ... 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 InstanceCheckMeta(type): ...
class EmptyQuerySet(metaclass=InstanceCheckMeta): ... class EmptyQuerySet(metaclass=InstanceCheckMeta): ...

View File

@@ -7,7 +7,7 @@ from django.dispatch import Signal
class_prepared: Any class_prepared: Any
class ModelSignal(Signal): class ModelSignal(Signal):
def connect( def connect( # type: ignore
self, self,
receiver: Callable, receiver: Callable,
sender: Optional[Union[Type[Model], str]] = ..., sender: Optional[Union[Type[Model], str]] = ...,
@@ -15,7 +15,7 @@ class ModelSignal(Signal):
dispatch_uid: None = ..., dispatch_uid: None = ...,
apps: Optional[Apps] = ..., apps: Optional[Apps] = ...,
) -> None: ... ) -> None: ...
def disconnect( def disconnect( # type: ignore
self, self,
receiver: Callable = ..., receiver: Callable = ...,
sender: Optional[Union[Type[Model], str]] = ..., sender: Optional[Union[Type[Model], str]] = ...,

View File

@@ -15,15 +15,15 @@ class Signal:
def __init__(self, providing_args: List[str] = ..., use_caching: bool = ...) -> None: ... def __init__(self, providing_args: List[str] = ..., use_caching: bool = ...) -> None: ...
def connect( def connect(
self, self,
receiver: Any, receiver: Callable,
sender: Optional[Union[Type[Model], AppConfig]] = ..., sender: Optional[Union[Type[Model], AppConfig, str]] = ...,
weak: bool = ..., weak: bool = ...,
dispatch_uid: Optional[str] = ..., dispatch_uid: Optional[str] = ...,
) -> None: ... ) -> None: ...
def disconnect( def disconnect(
self, self,
receiver: Optional[Callable] = ..., receiver: Optional[Callable] = ...,
sender: Optional[Union[Type[Model], AppConfig]] = ..., sender: Optional[Union[Type[Model], AppConfig, str]] = ...,
dispatch_uid: Optional[str] = ..., dispatch_uid: Optional[str] = ...,
) -> bool: ... ) -> bool: ...
def has_listeners(self, sender: Any = ...) -> bool: ... def has_listeners(self, sender: Any = ...) -> bool: ...

View File

@@ -34,6 +34,9 @@ from .widgets import (
SelectMultiple as SelectMultiple, SelectMultiple as SelectMultiple,
TimeInput as TimeInput, TimeInput as TimeInput,
URLInput as URLInput, URLInput as URLInput,
SelectDateWidget as SelectDateWidget,
SplitHiddenDateTimeWidget as SplitHiddenDateTimeWidget,
SplitDateTimeWidget as SplitDateTimeWidget,
) )
from .fields import ( from .fields import (

View File

@@ -159,6 +159,7 @@ class SelectMultiple(Select):
allow_multiple_selected: bool = ... allow_multiple_selected: bool = ...
class RadioSelect(ChoiceWidget): class RadioSelect(ChoiceWidget):
can_add_related: bool
option_template_name: str = ... option_template_name: str = ...
class CheckboxSelectMultiple(ChoiceWidget): class CheckboxSelectMultiple(ChoiceWidget):

View File

@@ -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 .context import Context as Context, RequestContext as RequestContext
from .library import Library as Library from .library import Library as Library
from . import defaultfilters as defaultfilters

View File

@@ -1,4 +1,4 @@
from datetime import date, datetime, timedelta from datetime import _date, datetime, timedelta
from decimal import Decimal from decimal import Decimal
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union 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 = ... value: Union[Iterator[Any], List[Union[List[Union[List[Union[List[str], str]], str]], str]]], autoescape: bool = ...
) -> SafeText: ... ) -> SafeText: ...
def add( 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], 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 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 time(value: Optional[Union[datetime, str]], arg: Optional[str] = ...) -> str: ...
def timesince_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 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(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 default_if_none(value: Optional[str], arg: Union[int, str]) -> Union[int, str]: ...
def divisibleby(value: int, arg: int) -> bool: ... def divisibleby(value: int, arg: int) -> bool: ...

View File

@@ -1,4 +1,5 @@
from typing import Any from typing import Any
from django.core.signals import setting_changed as setting_changed
template_rendered: Any template_rendered: Any
COMPLEX_OVERRIDE_SETTINGS: Any COMPLEX_OVERRIDE_SETTINGS: Any

View File

@@ -1,5 +1,6 @@
import decimal import decimal
import warnings import warnings
from io import StringIO
from contextlib import contextmanager from contextlib import contextmanager
from decimal import Decimal from decimal import Decimal
from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple, Type, Union, IO from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple, Type, Union, IO
@@ -107,13 +108,13 @@ class isolate_apps(TestContextDecorator):
@contextmanager @contextmanager
def extend_sys_path(*paths: str) -> Iterator[None]: ... def extend_sys_path(*paths: str) -> Iterator[None]: ...
@contextmanager @contextmanager
def captured_output(stream_name) -> Iterator[IO[str]]: ... def captured_output(stream_name) -> Iterator[StringIO]: ...
@contextmanager @contextmanager
def captured_stdin() -> Iterator[IO[str]]: ... def captured_stdin() -> Iterator[StringIO]: ...
@contextmanager @contextmanager
def captured_stdout() -> Iterator[IO[str]]: ... def captured_stdout() -> Iterator[StringIO]: ...
@contextmanager @contextmanager
def captured_stderr() -> Iterator[IO[str]]: ... def captured_stderr() -> Iterator[StringIO]: ...
@contextmanager @contextmanager
def freeze_time(t: float) -> Iterator[None]: ... def freeze_time(t: float) -> Iterator[None]: ...
def tag(*tags: str): ... def tag(*tags: str): ...

View File

@@ -9,6 +9,8 @@ from git import Repo
from mypy import build from mypy import build
from mypy.main import process_options from mypy.main import process_options
PROJECT_DIRECTORY = Path(__file__).parent.parent
# Django branch to typecheck against # Django branch to typecheck against
DJANGO_BRANCH = 'stable/2.1.x' 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 # Some errors occur for the test suite itself, and cannot be addressed via django-stubs. They should be ignored
# using this constant. # using this constant.
IGNORED_ERROR_PATTERNS = [ MOCK_OBJECTS = ['MockRequest', 'MockCompiler', 'modelz']
'Need type annotation for', IGNORED_ERRORS = {
'already defined on', '__common__': [
'Cannot assign to a', *MOCK_OBJECTS,
'cannot perform relative import', 'LazySettings',
'broken_app', 'NullTranslations',
'cache_clear', 'Need type annotation for',
'call_count', 'Invalid value for a to= parameter',
'call_args_list', 'already defined (possibly by an import)',
'call_args', 'Cannot assign to a type',
'"password_changed" does not return a value', # forms <-> models plugin support
'"validate_password" does not return a value', '"Model" has no attribute',
'LazySettings', re.compile(r'Cannot determine type of \'(objects|stuff)\''),
'Cannot infer type of lambda', # settings
'"refresh_from_db" of "Model"', re.compile(r'Module has no attribute "[A-Z_]+"'),
'"as_sql" undefined in superclass', # attributes assigned to test functions
'Incompatible types in assignment (expression has type "str", target has type "type")', re.compile(r'"Callable\[\[(Any(, )?)+\], Any\]" has no attribute'),
'Incompatible types in assignment (expression has type "Callable[', # assign empty tuple
'Invalid value for a to= parameter', re.compile(r'Incompatible types in assignment \(expression has type "Tuple\[\]", '
'Incompatible types in assignment (expression has type "FilteredChildAdmin", variable has type "ChildAdmin")', r'variable has type "Tuple\[[A-Za-z, ]+\]"'),
'Incompatible types in assignment (expression has type "RelatedFieldWidgetWrapper", variable has type "AdminRadioSelect")', # assign method to a method
'has incompatible type "MockRequest"; expected "WSGIRequest"', 'Cannot assign to a method',
'"NullTranslations" has no attribute "_catalog"', 'Cannot infer type of lambda',
'Definition of "as_sql" in base class', re.compile(r'Incompatible types in assignment \(expression has type "Callable\[\[(Any(, )?)+\], Any\]", '
'expression has type "property"', r'variable has type "Callable\['),
'"object" has no attribute "__iter__"', ],
'Too few arguments for "dates" of "QuerySet"', 'admin_changelist': [
'has no attribute "vendor"', 'Incompatible types in assignment (expression has type "FilteredChildAdmin", variable has type "ChildAdmin")'
'Argument 1 to "get_list_or_404" has incompatible type "List', ],
'error: "AdminRadioSelect" has no attribute "can_add_related"', 'admin_scripts': [
'MockCompiler', 'Incompatible types in assignment (expression has type "Callable['
'SessionTestsMixin', ],
'Argument 1 to "Paginator" has incompatible type "ObjectList"', 'admin_widgets': [
'"Type[Morsel[Any]]" has no attribute "_reserved"', 'Incompatible types in assignment (expression has type "RelatedFieldWidgetWrapper", '
'Argument 1 to "append" of "list"', 'variable has type "AdminRadioSelect")',
'Argument 1 to "bytes"', 'Incompatible types in assignment (expression has type "Widget", variable has type "AutocompleteSelect")'
'"full_clean" of "Model" does not return a value', ],
'"object" not callable', 'aggregation': [
'Item "GenericForeignKey" of "Union[GenericForeignKey, Model, None]" has no attribute "read_by"', 'Incompatible types in assignment (expression has type "QuerySet[Any]", variable has type "List[Any]")',
'Item "Model" of "Union[GenericForeignKey, Model, None]" has no attribute "read_by"', '"as_sql" undefined in superclass'
re.compile('Cannot determine type of \'(objects|stuff|specimens|normal_manager)\''), ],
re.compile(r'"Callable\[\[(Any(, )?)+\], Any\]" has no attribute'), 'aggregation_regress': [
re.compile(r'"HttpResponseBase" has no attribute "[A-Za-z_]+"'), 'Incompatible types in assignment (expression has type "List[str]", variable has type "QuerySet[Author]")'
re.compile(r'Incompatible types in assignment \(expression has type "Tuple\[\]", ' ],
r'variable has type "Tuple\[[A-Za-z, ]+\]"'), 'basic': [
re.compile(r'"validate" of "[A-Za-z]+" does not return a value'), 'Unexpected keyword argument "unknown_kwarg" for "refresh_from_db" of "Model"',
re.compile(r'Module has no attribute "[A-Za-z_]+"'), '"refresh_from_db" of "Model" defined here'
re.compile(r'"[A-Za-z\[\]]+" has no attribute "getvalue"'), ],
# TODO: remove when reassignment will be possible (in 0.670? ) 'builtin_server': [
re.compile(r'Incompatible types in assignment \(expression has type "(QuerySet|List)\[[A-Za-z, ]+\]", ' 'has no attribute "getvalue"'
r'variable has type "(QuerySet|List)\[[A-Za-z, ]+\]"\)'), ],
re.compile(r'"(MockRequest|DummyRequest|DummyUser)" has no attribute "[a-zA-Z_]+"'), 'csrf_tests': [
# TODO: remove when form <-> model plugin support is added 'Incompatible types in assignment (expression has type "property", ' +
re.compile(r'"Model" has no attribute "[A-Za-z_]+"'), 'base class "HttpRequest" defined the type as "QueryDict")'
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]+"'), '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 # Test folders to typecheck
TESTS_DIRS = [ TESTS_DIRS = [
'absolute_url_overrides', 'absolute_url_overrides',
@@ -190,7 +242,7 @@ TESTS_DIRS = [
# TODO: 'messages_tests', # TODO: 'messages_tests',
# TODO: 'middleware', # TODO: 'middleware',
# TODO: 'middleware_exceptions', # TODO: 'middleware_exceptions',
# SKIPPED (all errors are false positives) 'migrate_signals', 'migrate_signals',
'migration_test_data_persistence', 'migration_test_data_persistence',
# TODO: 'migrations', # TODO: 'migrations',
'migrations2', 'migrations2',
@@ -201,7 +253,7 @@ TESTS_DIRS = [
'model_indexes', 'model_indexes',
# TODO: 'model_inheritance', # TODO: 'model_inheritance',
'model_inheritance_regress', 'model_inheritance_regress',
# SKIPPED (all errors are false positives) 'model_meta', 'model_meta',
'model_options', 'model_options',
'model_package', 'model_package',
'model_regress', 'model_regress',
@@ -298,8 +350,8 @@ def cd(path):
os.chdir(prev_cwd) os.chdir(prev_cwd)
def is_ignored(line: str) -> bool: def is_ignored(line: str, test_folder_name: str) -> bool:
for pattern in IGNORED_ERROR_PATTERNS: for pattern in IGNORED_ERRORS['__common__'] + IGNORED_ERRORS.get(test_folder_name, []):
if isinstance(pattern, Pattern): if isinstance(pattern, Pattern):
if pattern.search(line): if pattern.search(line):
return True return True
@@ -309,22 +361,29 @@ def is_ignored(line: str) -> bool:
return False 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: def check_with_mypy(abs_path: Path, config_file_path: Path) -> int:
error_happened = False error_happened = False
with cd(abs_path): with cd(abs_path):
sources, options = process_options(['--config-file', str(config_file_path), str(abs_path)]) sources, options = process_options(['--config-file', str(config_file_path), str(abs_path)])
res = build.build(sources, options) res = build.build(sources, options)
for error_line in res.errors: for error_line in res.errors:
if not is_ignored(error_line): if not is_ignored(error_line, abs_path.name):
error_happened = True error_happened = True
print(error_line) print(replace_with_clickable_location(error_line, abs_test_folder=abs_path))
return int(error_happened) return int(error_happened)
if __name__ == '__main__': if __name__ == '__main__':
project_directory = Path(__file__).parent.parent mypy_config_file = (PROJECT_DIRECTORY / 'scripts' / 'mypy.ini').absolute()
mypy_config_file = (project_directory / 'scripts' / 'mypy.ini').absolute() repo_directory = PROJECT_DIRECTORY / 'django-sources'
repo_directory = project_directory / 'django-sources'
tests_root = repo_directory / 'tests' tests_root = repo_directory / 'tests'
global_rc = 0 global_rc = 0
@@ -337,8 +396,8 @@ if __name__ == '__main__':
repo.git.checkout(DJANGO_COMMIT_SHA) repo.git.checkout(DJANGO_COMMIT_SHA)
for dirname in TESTS_DIRS: for dirname in TESTS_DIRS:
abs_path = (project_directory / tests_root / dirname).absolute() abs_path = (PROJECT_DIRECTORY / tests_root / dirname).absolute()
print(f'Checking {abs_path.as_uri()}') print(f'Checking {abs_path}')
rc = check_with_mypy(abs_path, mypy_config_file) rc = check_with_mypy(abs_path, mypy_config_file)
if rc != 0: if rc != 0: