mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-06 20:24:31 +08:00
884 lines
32 KiB
Python
884 lines
32 KiB
Python
import itertools
|
|
import os
|
|
import re
|
|
import shutil
|
|
import sys
|
|
from contextlib import contextmanager
|
|
from pathlib import Path
|
|
from typing import Pattern
|
|
|
|
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.2.x'
|
|
|
|
# Specific commit in the Django repository to check against
|
|
DJANGO_COMMIT_SHA = '395cf7c37514b642c4bcf30e01fc1a2c4f82b2fe'
|
|
|
|
# Some errors occur for the test suite itself, and cannot be addressed via django-stubs. They should be ignored
|
|
# using this constant.
|
|
MOCK_OBJECTS = ['MockRequest', 'MockCompiler', 'modelz', 'call_count', 'call_args_list', 'call_args', 'MockUser']
|
|
IGNORED_ERRORS = {
|
|
'__common__': [
|
|
*MOCK_OBJECTS,
|
|
'LazySettings',
|
|
'NullTranslations',
|
|
'Need type annotation for',
|
|
'Invalid value for a to= parameter',
|
|
'already defined (possibly by an import)',
|
|
'already defined on line',
|
|
'gets multiple values for keyword argument',
|
|
'Cannot assign to a type',
|
|
re.compile(r'Cannot assign to class variable "[a-z_]+" via instance'),
|
|
# 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(, )?)*((, )?VarArg\(Any\))?((, )?KwArg\(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\['),
|
|
# cookies private attribute
|
|
'full_clean" of "Model" does not return a value',
|
|
# private members
|
|
re.compile(r'has no attribute ("|\')_[a-zA-Z_]+("|\')'),
|
|
'Invalid base class',
|
|
'ValuesIterable',
|
|
'Value of type "Optional[Dict[str, Any]]" is not indexable',
|
|
'Argument 1 to "len" has incompatible type "Optional[List[_Record]]"; expected "Sized"',
|
|
'Argument 1 to "loads" has incompatible type "Union[bytes, str, None]"; '
|
|
+ 'expected "Union[str, bytes, bytearray]"',
|
|
'Incompatible types in assignment (expression has type "None", variable has type Module)',
|
|
'note:',
|
|
'\'Settings\' object has no attribute',
|
|
re.compile(r'"Type\[Model\]" has no attribute "[a-zA-Z_]+"'),
|
|
re.compile(r'Item "None" of "[a-zA-Z_ ,\[\]]+" has no attribute'),
|
|
'Xtemplate',
|
|
re.compile(r'has no attribute "get_[a-z_]+_display"'),
|
|
re.compile(r'has no attribute "get_next_by_[a-z_]+"'),
|
|
re.compile(r'has no attribute "get_previous_by_[a-z_]+"'),
|
|
re.compile(r'has no attribute "set_[a-z_]+_order"'),
|
|
'psycopg2',
|
|
'PIL',
|
|
'has no attribute "getvalue"',
|
|
'MySQLdb',
|
|
'sqlparse',
|
|
'selenium',
|
|
'oracle',
|
|
'mysql',
|
|
'sqlite3',
|
|
'LogEntry',
|
|
'"HttpResponse" has no attribute',
|
|
'"HttpResponseBase" has no attribute',
|
|
'"object" has no attribute',
|
|
'"HttpRequest" has no attribute',
|
|
'xml.dom',
|
|
'numpy',
|
|
'tblib',
|
|
'bcrypt',
|
|
'argon2',
|
|
# TODO: values().annotate()
|
|
'TypedDict',
|
|
'namedtuple',
|
|
'has no attribute "deconstruct"',
|
|
'**Dict',
|
|
'undefined in superclass',
|
|
'SimpleLazyObject'
|
|
],
|
|
'admin_scripts': [
|
|
'Incompatible types in assignment (expression has type "Callable['
|
|
],
|
|
'admin_utils': [
|
|
re.compile(r'Argument [0-9] to "lookup_field" has incompatible type'),
|
|
'MockModelAdmin',
|
|
'Incompatible types in assignment (expression has type "str", variable has type "Callable[..., Any]")',
|
|
'"Article" has no attribute "non_field"'
|
|
],
|
|
'admin_views': [
|
|
'Argument 1 to "FileWrapper" has incompatible type "StringIO"; expected "IO[bytes]"',
|
|
'Incompatible types in assignment',
|
|
'"object" not callable',
|
|
'"Type[SubscriberAdmin]" has no attribute "overridden"'
|
|
],
|
|
'admin_ordering': [
|
|
'"Band" has no attribute "field"'
|
|
],
|
|
'aggregation': [
|
|
'Incompatible type for "contact" of "Book" (got "Optional[Author]", expected "Union[Author, Combinable]")',
|
|
'Incompatible type for "publisher" of "Book" (got "Optional[Publisher]", '
|
|
+ 'expected "Union[Publisher, Combinable]")',
|
|
'has no attribute'
|
|
],
|
|
'aggregation_regress': [
|
|
'has no attribute'
|
|
],
|
|
'annotations': [
|
|
'Incompatible type for "store" of "Employee" (got "Optional[Store]", expected "Union[Store, Combinable]")',
|
|
'"Book" has no attribute',
|
|
'"Employee" has no attribute',
|
|
'Item "Book" of',
|
|
'Item "Ticket" of'
|
|
],
|
|
'apps': [
|
|
'Incompatible types in assignment (expression has type "str", target has type "type")',
|
|
'"Callable[[bool, bool], List[Type[Model]]]" has no attribute "cache_clear"'
|
|
],
|
|
'auth_tests': [
|
|
'"PasswordValidator" has no attribute "min_length"',
|
|
'"validate_password" does not return a value',
|
|
'"password_changed" does not return a value',
|
|
re.compile(r'"validate" of "([A-Za-z]+)" does not return a value'),
|
|
'Module has no attribute "SessionStore"',
|
|
'AbstractBaseUser',
|
|
'Argument "user" to "password_changed" has incompatible type "object"',
|
|
'Argument "password_validators" to "password_changed" has incompatible type "Tuple[Validator]"; '
|
|
+ 'expected "Optional[Sequence[PasswordValidator]]"',
|
|
'Value of type "Optional[List[_Record]]" is not indexable',
|
|
'"Model" has no attribute',
|
|
'Incompatible type for "id" of "User"',
|
|
re.compile(r'Module has no attribute "(update|revert)_proxy_model_permissions"'),
|
|
'Unsupported right operand type for in ("object")',
|
|
'mock_getpass',
|
|
'Unsupported left operand type for + ("Sequence[str]")',
|
|
'AuthenticationFormWithInactiveUsersOkay',
|
|
'has no attribute "alternatives"',
|
|
'Incompatible types in assignment (expression has type "Dict[str, Any]", variable has type "QueryDict")'
|
|
],
|
|
'basic': [
|
|
'Unexpected keyword argument "unknown_kwarg" for "refresh_from_db" of "Model"',
|
|
'Unexpected attribute "foo" for model "Article"',
|
|
'has no attribute'
|
|
],
|
|
'backends': [
|
|
'"DatabaseError" has no attribute "pgcode"'
|
|
],
|
|
'check_framework': [
|
|
'base class "Model" defined the type as "Callable',
|
|
'Cannot determine type of \'check\''
|
|
],
|
|
'constraints': [
|
|
'Argument "condition" to "UniqueConstraint" has incompatible type "str"; expected "Optional[Q]"'
|
|
],
|
|
'contenttypes_tests': [
|
|
'Item "Model" of "Union[GenericForeignKey, Model, None]" has no attribute'
|
|
],
|
|
'custom_lookups': [
|
|
'in base class "SQLFuncMixin"'
|
|
],
|
|
'custom_pk': [
|
|
'"Employee" has no attribute "id"'
|
|
],
|
|
'custom_managers': [
|
|
'"Type[Book]" has no attribute "objects"'
|
|
],
|
|
'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"'
|
|
],
|
|
'dispatch': [
|
|
'Argument 1 to "connect" of "Signal" has incompatible type "object"; expected "Callable[..., Any]"',
|
|
'Item "str" of "Union[ValueError, str]" has no attribute "args"'
|
|
],
|
|
'deprecation': [
|
|
'"Manager" has no attribute "old"',
|
|
'"Manager" has no attribute "new"'
|
|
],
|
|
'db_functions': [
|
|
'for **',
|
|
'expected "float"',
|
|
'Incompatible types in assignment (expression has type "Optional[FloatModel]", variable has type "FloatModel")',
|
|
re.compile(r'Item .* has no attribute'),
|
|
'Module has no attribute',
|
|
'Module \'datetime\' has no attribute',
|
|
'"DTModel" has no attribute',
|
|
'"Author" has no attribute',
|
|
'"FloatModel" has no attribute',
|
|
'"Article" has no attribute'
|
|
],
|
|
'decorators': [
|
|
'DummyRequest',
|
|
'DummyUser',
|
|
'"Type[object]" has no attribute "method"'
|
|
],
|
|
'defer_regress': [
|
|
'"Base" has no attribute "derived"'
|
|
],
|
|
'delete': [
|
|
'"RChild" has no attribute',
|
|
'"Child" has no attribute "parent_ptr"'
|
|
],
|
|
'expressions': [
|
|
'Item "Experiment" of',
|
|
'Item "Time" of',
|
|
'"Experiment" has no attribute',
|
|
'"Time" has no attribute',
|
|
],
|
|
'expressions_case': [
|
|
'"CaseTestModel" has no attribute "selected"'
|
|
],
|
|
'expressions_window': [
|
|
'has incompatible type "str"'
|
|
],
|
|
'extra_regress': [
|
|
'"User" has no attribute "alpha"'
|
|
],
|
|
'file_uploads': [
|
|
'"Iterable[Any]" has no attribute',
|
|
'"IO[Any]" has no attribute'
|
|
],
|
|
'file_storage': [
|
|
'Incompatible types in assignment (expression has type "Callable[[], Any]"'
|
|
],
|
|
'files': [
|
|
'"file_move_safe" does not return a value',
|
|
'Incompatible types in assignment (expression has type "IOBase", variable has type "File")',
|
|
'Module has no attribute "open"'
|
|
],
|
|
'fixtures': [
|
|
'Incompatible types in assignment (expression has type "int", target has type "Iterable[str]")'
|
|
],
|
|
'flatpages_tests': [
|
|
'"Site" has no attribute "add"',
|
|
],
|
|
'forms_tests': [
|
|
'List item 0 has incompatible type "Jinja2"; expected "DjangoTemplates"',
|
|
'Not enough arguments for format string',
|
|
'Argument after ** must be a mapping, not "object"',
|
|
'expression has type "None", base class "TestFormParent"',
|
|
'variable has type "SongForm"',
|
|
'"full_clean" of "BaseForm" does not return a value',
|
|
'No overload variant of "zip" matches argument types "Tuple[str, str, str]", "object"',
|
|
'Incompatible types in assignment (expression has type "GetDateShowHiddenInitial", '
|
|
+ 'variable has type "GetDate")',
|
|
re.compile(r'Incompatible types in assignment \(expression has type "[a-zA-Z]+Field", '
|
|
r'base class "BaseForm" defined the type as "Dict\[str, Any\]"\)'),
|
|
'List or tuple expected as variable arguments',
|
|
'Argument 1 to "__init__" of "MultiWidget" has incompatible type "List[object]"; '
|
|
+ 'expected "Sequence[Union[Widget, Type[Widget]]]"',
|
|
'Argument 1 to "issubclass" has incompatible type "ModelFormMetaclass"; expected "type"',
|
|
'Incompatible types in assignment (expression has type "List[str]", target has type "str")',
|
|
'Incompatible types in assignment (expression has type "TestForm", variable has type "Person")',
|
|
'Incompatible types in assignment (expression has type "Type[Textarea]", '
|
|
+ 'base class "Field" defined the type as "Widget")',
|
|
'Incompatible types in assignment (expression has type "SimpleUploadedFile", variable has type "BinaryIO")',
|
|
'has no attribute',
|
|
'Name \'forms.Field\' is not defined'
|
|
],
|
|
'foreign_object': [
|
|
'"Person" has no attribute',
|
|
'"SlugPage" has no attribute'
|
|
],
|
|
'from_db_value': [
|
|
'"Cash" has no attribute'
|
|
],
|
|
'get_object_or_404': [
|
|
'Argument 1 to "get_object_or_404" has incompatible type "str"; '
|
|
+ 'expected "Union[Type[<nothing>], QuerySet[<nothing>, <nothing>]]"',
|
|
'Argument 1 to "get_list_or_404" has incompatible type "List[Type[Article]]"; '
|
|
+ 'expected "Union[Type[<nothing>], QuerySet[<nothing>, <nothing>]]"',
|
|
'CustomClass'
|
|
],
|
|
'generic_relations': [
|
|
'has no attribute "id"',
|
|
'has no attribute "pk"'
|
|
],
|
|
'generic_relations_regress': [
|
|
'"HasLinkThing" has no attribute',
|
|
'"Link" has no attribute'
|
|
],
|
|
'httpwrappers': [
|
|
'Argument 2 to "appendlist" of "QueryDict"',
|
|
'Incompatible types in assignment (expression has type "int", target has type "Union[str, List[str]]")',
|
|
'Argument 1 to "fromkeys" of "QueryDict" has incompatible type "int"'
|
|
],
|
|
'humanize_tests': [
|
|
'Argument 1 to "append" of "list" has incompatible type "None"; expected "str"'
|
|
],
|
|
'inline_formsets': [
|
|
'has no attribute "form"'
|
|
],
|
|
'i18n': [
|
|
'Incompatible types in assignment (expression has type "I18nForm", variable has type "SelectDateForm")',
|
|
'has incompatible type "object"',
|
|
'Value of type "Optional[List[_Record]]" is not indexable',
|
|
'"Command" has no attribute'
|
|
],
|
|
'logging_tests': [
|
|
'"Handler" has no attribute "stream"'
|
|
],
|
|
'lookup': [
|
|
'Unexpected keyword argument "headline__startswith" for "in_bulk" of "QuerySet"',
|
|
'\'flat\' is not valid when values_list is called with more than one field.'
|
|
],
|
|
'm2o_recursive': [
|
|
'Incompatible type for "id" of "Category" (got "None", expected "int")'
|
|
],
|
|
'many_to_one': [
|
|
'Incompatible type for "parent" of "Child" (got "None", expected "Union[Parent, Combinable]")',
|
|
'Incompatible type for "parent" of "Child" (got "Child", expected "Union[Parent, Combinable]")'
|
|
],
|
|
'managers_regress': [
|
|
'"Type[AbstractBase3]" has no attribute "objects"'
|
|
],
|
|
'middleware_exceptions': [
|
|
'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any]"; expected "str"'
|
|
],
|
|
'model_fields': [
|
|
'Incompatible types in assignment (expression has type "Type[Person]", variable has type',
|
|
'Unexpected keyword argument "name" for "Person"',
|
|
'Cannot assign multiple types to name "PersonTwoImages" without an explicit "Type[...]" annotation',
|
|
re.compile(
|
|
r'Incompatible types in assignment \(expression has type "Type\[.+?\]", base class "IntegerFieldTests"'
|
|
r' defined the type as "Type\[IntegerModel\]"\)'),
|
|
re.compile(r'Incompatible types in assignment \(expression has type "Type\[.+?\]", base class'
|
|
r' "ImageFieldTestMixin" defined the type as "Type\[PersonWithHeightAndWidth\]"\)'),
|
|
'Incompatible import of "Person"',
|
|
'Incompatible types in assignment (expression has type "FloatModel", variable has type '
|
|
'"Union[float, int, str, Combinable]")',
|
|
'"UUIDGrandchild" has no attribute "uuidchild_ptr_id"',
|
|
'"Person" has no attribute',
|
|
'"Foo" has no attribute',
|
|
],
|
|
'model_formsets': [
|
|
'has no attribute'
|
|
],
|
|
'model_indexes': [
|
|
'Argument "condition" to "Index" has incompatible type "str"; expected "Optional[Q]"'
|
|
],
|
|
'model_inheritance_regress': [
|
|
'"Restaurant" has no attribute',
|
|
'"ArticleWithAuthor" has no attribute',
|
|
'"Person" has no attribute',
|
|
'"MessyBachelorParty" has no attribute',
|
|
'"Place" has no attribute',
|
|
'"ItalianRestaurant" has no attribute'
|
|
],
|
|
'model_meta': [
|
|
'"Field[Any, Any]" has no attribute "many_to_many"'
|
|
],
|
|
'model_regress': [
|
|
re.compile(r'Incompatible type for "[a-z]+" of "Worker" \(got "int", expected'),
|
|
'"PickledModel" has no attribute',
|
|
'"Department" has no attribute'
|
|
],
|
|
'modeladmin': [
|
|
'BandAdmin',
|
|
'base class "ModelAdmin" defined the type a',
|
|
'base class "InlineModelAdmin" defined the type a',
|
|
'List item 0 has incompatible type',
|
|
'Incompatible types in assignment (expression has type "None", base class "AdminBase" '
|
|
+ 'defined the type as "List[str]")'
|
|
],
|
|
'migrate_signals': [
|
|
'Value of type "None" is not indexable',
|
|
'Argument 1 to "set" has incompatible type "None"; expected "Iterable[<nothing>]"'
|
|
],
|
|
'migrations': [
|
|
'FakeMigration',
|
|
'FakeLoader',
|
|
'Dict entry 0 has incompatible type "Any": "Set[Tuple[Any, ...]]"; expected "Any": "str"',
|
|
'Argument 1 to "RunPython" has incompatible type "str"; expected "Callable[..., Any]"',
|
|
'Argument 1 to "append" of "list" has incompatible type "AddIndex"; expected "CreateModel"',
|
|
'Argument 2 to "register_serializer" of "MigrationWriter" has incompatible type '
|
|
+ '"Type[TestModel1]"; expected "Type[BaseSerializer]"',
|
|
'Argument 1 to "append" of "list" has incompatible type "AddConstraint"; expected "CreateModel"'
|
|
],
|
|
'multiple_database': [
|
|
'Too many arguments for "create" of "QuerySet"',
|
|
'"User" has no attribute "userprofile"',
|
|
'Item "GenericForeignKey" of',
|
|
'Item "Model" of'
|
|
],
|
|
'known_related_objects': [
|
|
'"Pool" has no attribute'
|
|
],
|
|
'one_to_one': [
|
|
'"Place" has no attribute',
|
|
'"Type[Place]" has no attribute',
|
|
'"ManualPrimaryKey" has no attribute'
|
|
],
|
|
'postgres_tests': [
|
|
'DummyArrayField',
|
|
'DummyJSONField',
|
|
'Cannot assign multiple types to name',
|
|
'Incompatible types in assignment (expression has type "Type[Field[Any, Any]]',
|
|
'Argument "encoder" to "JSONField" has incompatible type "DjangoJSONEncoder"; '
|
|
+ 'expected "Optional[Type[JSONEncoder]]"',
|
|
'Incompatible type for "field" of "IntegerArrayModel" (got "None", '
|
|
+ 'expected "Union[Sequence[int], Combinable]")',
|
|
re.compile(r'Incompatible types in assignment \(expression has type "Type\[.+?\]", '
|
|
r'base class "(UnaccentTest|TrigramTest)" defined the type as "Type\[CharFieldModel\]"\)'),
|
|
'"Type[PostgreSQLModel]" has no attribute "objects"',
|
|
'("None" and "SearchQuery")',
|
|
# TODO:
|
|
'django.contrib.postgres.forms',
|
|
'django.contrib.postgres.aggregates',
|
|
],
|
|
'properties': [
|
|
re.compile('Unexpected attribute "(full_name|full_name_2)" for model "Person"')
|
|
],
|
|
'prefetch_related': [
|
|
'Incompatible types in assignment (expression has type "List[Room]", variable has type "QuerySet[Room, Room]")',
|
|
'"Person" has no attribute',
|
|
'"Author" has no attribute',
|
|
'"Book" has no attribute',
|
|
'Item "Room" of',
|
|
'"AuthorWithAge" has no attribute',
|
|
'has no attribute "read_by"'
|
|
],
|
|
'proxy_model_inheritance': [
|
|
'Incompatible import of "ProxyModel"'
|
|
],
|
|
'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"',
|
|
'No overload variant of "values_list" of "QuerySet" matches argument types "str", "bool", "bool"',
|
|
'Unsupported operand types for & ("QuerySet[Author, Author]" and "QuerySet[Tag, Tag]")',
|
|
'Unsupported operand types for | ("QuerySet[Author, Author]" and "QuerySet[Tag, Tag]")',
|
|
'Incompatible types in assignment (expression has type "ObjectB", variable has type "ObjectA")',
|
|
'Incompatible types in assignment (expression has type "ObjectC", variable has type "ObjectA")',
|
|
'Incompatible type for "objectb" of "ObjectC" (got "ObjectA", expected'
|
|
' "Union[ObjectB, Combinable, None, None]")',
|
|
'"Note" has no attribute',
|
|
'"Ranking" has no attribute',
|
|
'"BaseUser" has no attribute',
|
|
'"Item" has no attribute',
|
|
"'flat' and 'named' can't be used together.",
|
|
],
|
|
'queryset_pickle': [
|
|
'RelatedObjectDoesNotExist',
|
|
'"Type[Event]" has no attribute "happening"'
|
|
],
|
|
'requests': [
|
|
'Incompatible types in assignment (expression has type "Dict[str, str]", variable has type "QueryDict")'
|
|
],
|
|
'responses': [
|
|
'Argument 1 to "TextIOWrapper" has incompatible type "HttpResponse"; expected "IO[bytes]"'
|
|
],
|
|
'schema': [
|
|
'Incompatible type for "info" of "Note" (got "None", expected "Union[str, Combinable]")',
|
|
'Incompatible type for "detail_info" of "NoteRename" (got "None", expected "Union[str, Combinable]")',
|
|
'Incompatible type for "year" of "UniqueTest" (got "None", expected "Union[float, int, str, Combinable]")'
|
|
],
|
|
'settings_tests': [
|
|
'Argument 1 to "Settings" has incompatible type "Optional[str]"; expected "str"'
|
|
],
|
|
'signals': [
|
|
'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any, Optional[Any], Any]"; '
|
|
+ 'expected "Tuple[Any, Any, Any]"'
|
|
],
|
|
'sites_tests': [
|
|
'Item "RequestSite" of "Union[Site, RequestSite]" has no attribute "id"'
|
|
],
|
|
'syndication_tests': [
|
|
'List or tuple expected as variable arguments'
|
|
],
|
|
'sessions_tests': [
|
|
'base class "SessionTestsMixin" defined the type as "None")',
|
|
'Incompatible types in assignment (expression has type "None", variable has type "int")',
|
|
'"AbstractBaseSession" has no attribute'
|
|
],
|
|
'select_related': [
|
|
'"Species" has no attribute',
|
|
'Item "object" of "Union[object, Any]" has no attribute "first"'
|
|
],
|
|
'select_related_onetoone': [
|
|
'Incompatible types in assignment (expression has type "Parent2", variable has type "Parent1")',
|
|
'has no attribute'
|
|
],
|
|
'select_for_update': [
|
|
'has incompatible type "object"'
|
|
],
|
|
'servers': [
|
|
re.compile('Argument [0-9] to "WSGIRequestHandler"'),
|
|
'"HTTPResponse" has no attribute',
|
|
'"type" has no attribute',
|
|
'"WSGIRequest" has no attribute "makefile"'
|
|
],
|
|
'template_tests': [
|
|
re.compile(r'Argument 1 to "[a-zA-Z_]+" has incompatible type "int"; expected "str"'),
|
|
'TestObject',
|
|
'variable has type "Callable[[Any], Any]',
|
|
'Value of type variable "AnyStr" of "urljoin" cannot be "Optional[str]"',
|
|
'has no attribute "template_debug"',
|
|
"Module 'django.template.base' has no attribute 'TemplateSyntaxError'"
|
|
],
|
|
'template_backends': [
|
|
'Incompatible import of "Jinja2" (imported name has type "Type[Jinja2]", local name has type "object")',
|
|
'TemplateStringsTests',
|
|
"Cannot find module named 'DoesNotExist'"
|
|
],
|
|
'test_client': [
|
|
'Incompatible types in assignment (expression has type "HttpResponse", '
|
|
+ 'variable has type "StreamingHttpResponse")'
|
|
],
|
|
'test_client_regress': [
|
|
'Incompatible types in assignment (expression has type "Dict[<nothing>, <nothing>]", '
|
|
+ 'variable has type "SessionBase")',
|
|
'Unsupported left operand type for + ("None")',
|
|
],
|
|
'test_runner': [
|
|
'Argument "result" to "run" of "TestCase" has incompatible type "RemoteTestResult"; '
|
|
+ 'expected "Optional[TestResult]"',
|
|
'has no attribute "id"',
|
|
'MockTestRunner'
|
|
],
|
|
'transactions': [
|
|
'Incompatible types in assignment (expression has type "Thread", variable has type "Callable[[], Any]")'
|
|
],
|
|
'urlpatterns': [
|
|
'"object" not callable'
|
|
],
|
|
'user_commands': [
|
|
'Incompatible types in assignment (expression has type "Callable[[Any, KwArg(Any)], Any]", variable has type',
|
|
'Cannot find module named \'user_commands.management.commands\''
|
|
],
|
|
'utils_tests': [
|
|
'a_package_name_that_does_not_exist',
|
|
'Too few arguments for "__init__"',
|
|
'Argument 1 to "activate" has incompatible type "None"; expected "Union[tzinfo, str]"',
|
|
'Module has no attribute "content"',
|
|
'Argument 1 to "int_to_base36" has incompatible type "object"; expected "int"',
|
|
'Argument 1 to "base36_to_int" has incompatible type "object"; expected "str"',
|
|
'Incompatible types in assignment (expression has type "None", base class "object" defined the type as',
|
|
'Invalid type "Class"',
|
|
'has no attribute "cp"',
|
|
'Argument "name" to "cached_property" has incompatible type "int"; expected "Optional[str]"',
|
|
'foo',
|
|
'"ImmutableList[int]" has no attribute "sort"',
|
|
'has no attribute "cache_clear"',
|
|
'has no attribute "cache_info"',
|
|
'Argument 1 to "Path" has incompatible type "Optional[str]"; expected "Union[str, _PathLike[str]]"',
|
|
'defined the type as "None"'
|
|
],
|
|
'view_tests': [
|
|
'"EmailMessage" has no attribute "alternatives"',
|
|
'"Handler" has no attribute "include_html"',
|
|
"Module 'django.views.debug' has no attribute 'Path'"
|
|
]
|
|
}
|
|
# Test folders to typecheck
|
|
TESTS_DIRS = [
|
|
'absolute_url_overrides',
|
|
'admin_autodiscover',
|
|
'admin_changelist',
|
|
'admin_checks',
|
|
'admin_custom_urls',
|
|
'admin_default_site',
|
|
'admin_docs',
|
|
# TODO: 'admin_filters',
|
|
'admin_inlines',
|
|
'admin_ordering',
|
|
'admin_registration',
|
|
'admin_scripts',
|
|
'admin_utils',
|
|
'admin_views',
|
|
'admin_widgets',
|
|
'aggregation',
|
|
'aggregation_regress',
|
|
'annotations',
|
|
'app_loading',
|
|
'apps',
|
|
'auth_tests',
|
|
'backends',
|
|
'base',
|
|
'bash_completion',
|
|
'basic',
|
|
'builtin_server',
|
|
'bulk_create',
|
|
# TODO: 'cache',
|
|
'check_framework',
|
|
'choices',
|
|
'conditional_processing',
|
|
'constraints',
|
|
'contenttypes_tests',
|
|
'context_processors',
|
|
'csrf_tests',
|
|
'custom_columns',
|
|
'custom_lookups',
|
|
'custom_managers',
|
|
'custom_methods',
|
|
'custom_migration_operations',
|
|
'custom_pk',
|
|
'datatypes',
|
|
'dates',
|
|
'datetimes',
|
|
'db_functions',
|
|
'db_typecasts',
|
|
'db_utils',
|
|
'dbshell',
|
|
'decorators',
|
|
'defer',
|
|
'defer_regress',
|
|
'delete',
|
|
'delete_regress',
|
|
'deprecation',
|
|
'dispatch',
|
|
'distinct_on_fields',
|
|
'empty',
|
|
'expressions',
|
|
'expressions_case',
|
|
'expressions_window',
|
|
'extra_regress',
|
|
'field_deconstruction',
|
|
'field_defaults',
|
|
'field_subclassing',
|
|
'file_storage',
|
|
'file_uploads',
|
|
'files',
|
|
'filtered_relation',
|
|
'fixtures',
|
|
'fixtures_model_package',
|
|
'fixtures_regress',
|
|
'flatpages_tests',
|
|
'force_insert_update',
|
|
'foreign_object',
|
|
'forms_tests',
|
|
'from_db_value',
|
|
'generic_inline_admin',
|
|
'generic_relations',
|
|
'generic_relations_regress',
|
|
# TODO: 'generic_views',
|
|
'get_earliest_or_latest',
|
|
'get_object_or_404',
|
|
'get_or_create',
|
|
# TODO: 'gis_tests',
|
|
'handlers',
|
|
'httpwrappers',
|
|
'humanize_tests',
|
|
'i18n',
|
|
'import_error_package',
|
|
'indexes',
|
|
'inline_formsets',
|
|
'inspectdb',
|
|
'introspection',
|
|
# not practical
|
|
# 'invalid_models_tests',
|
|
'known_related_objects',
|
|
'logging_tests',
|
|
'lookup',
|
|
'm2m_and_m2o',
|
|
'm2m_intermediary',
|
|
'm2m_multiple',
|
|
'm2m_recursive',
|
|
'm2m_regress',
|
|
'm2m_signals',
|
|
'm2m_through',
|
|
'm2m_through_regress',
|
|
'm2o_recursive',
|
|
# TODO: 'mail',
|
|
'managers_regress',
|
|
'many_to_many',
|
|
'many_to_one',
|
|
'many_to_one_null',
|
|
'max_lengths',
|
|
# TODO: 'messages_tests',
|
|
'middleware',
|
|
'middleware_exceptions',
|
|
'migrate_signals',
|
|
'migration_test_data_persistence',
|
|
'migrations',
|
|
'migrations2',
|
|
'model_fields',
|
|
# TODO: 'model_forms',
|
|
'model_formsets',
|
|
'model_formsets_regress',
|
|
'model_indexes',
|
|
# TODO: 'model_inheritance',
|
|
'model_inheritance_regress',
|
|
'model_meta',
|
|
'model_options',
|
|
'model_package',
|
|
'model_regress',
|
|
'modeladmin',
|
|
'multiple_database',
|
|
'mutually_referential',
|
|
'nested_foreign_keys',
|
|
'no_models',
|
|
'null_fk',
|
|
'null_fk_ordering',
|
|
'null_queries',
|
|
'one_to_one',
|
|
'or_lookups',
|
|
'order_with_respect_to',
|
|
'ordering',
|
|
'pagination',
|
|
'postgres_tests',
|
|
'prefetch_related',
|
|
'project_template',
|
|
'properties',
|
|
'proxy_model_inheritance',
|
|
# TODO: 'proxy_models',
|
|
'queries',
|
|
'queryset_pickle',
|
|
'raw_query',
|
|
'redirects_tests',
|
|
'requests',
|
|
'reserved_names',
|
|
'resolve_url',
|
|
'responses',
|
|
'reverse_lookup',
|
|
'save_delete_hooks',
|
|
'schema',
|
|
'select_for_update',
|
|
'select_related',
|
|
'select_related_onetoone',
|
|
'select_related_regress',
|
|
# TODO: 'serializers',
|
|
'servers',
|
|
'sessions_tests',
|
|
'settings_tests',
|
|
'shell',
|
|
'shortcuts',
|
|
'signals',
|
|
'signed_cookies_tests',
|
|
'signing',
|
|
# TODO: 'sitemaps_tests',
|
|
'sites_framework',
|
|
'sites_tests',
|
|
# TODO: 'staticfiles_tests',
|
|
'str',
|
|
'string_lookup',
|
|
'swappable_models',
|
|
'syndication_tests',
|
|
'template_backends',
|
|
'template_loader',
|
|
'template_tests',
|
|
'test_client',
|
|
'test_client_regress',
|
|
'test_exceptions',
|
|
'test_runner',
|
|
'test_runner_apps',
|
|
'test_utils',
|
|
'timezones',
|
|
'transaction_hooks',
|
|
'transactions',
|
|
'unmanaged_models',
|
|
'update',
|
|
'update_only_fields',
|
|
'urlpatterns',
|
|
# not annotatable without annotation in test
|
|
# 'urlpatterns_reverse',
|
|
'user_commands',
|
|
'utils_tests',
|
|
'validation',
|
|
'validators',
|
|
'version',
|
|
'view_tests',
|
|
'wsgi',
|
|
]
|
|
|
|
|
|
@contextmanager
|
|
def cd(path):
|
|
"""Context manager to temporarily change working directories"""
|
|
if not path:
|
|
return
|
|
prev_cwd = Path.cwd().as_posix()
|
|
if isinstance(path, Path):
|
|
path = path.as_posix()
|
|
os.chdir(str(path))
|
|
try:
|
|
yield
|
|
finally:
|
|
os.chdir(prev_cwd)
|
|
|
|
|
|
def is_ignored(line: str, test_folder_name: str) -> bool:
|
|
for pattern in itertools.chain(IGNORED_ERRORS['__common__'],
|
|
IGNORED_ERRORS.get(test_folder_name, [])):
|
|
if isinstance(pattern, Pattern):
|
|
if pattern.search(line):
|
|
return True
|
|
else:
|
|
if pattern in line:
|
|
return True
|
|
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.partition(':')
|
|
|
|
try:
|
|
path = abs_test_folder.joinpath(fname).relative_to(PROJECT_DIRECTORY)
|
|
except ValueError:
|
|
# fail on travis, just show an error
|
|
return error
|
|
|
|
clickable_location = f'./{path}:{line_number or 1}'
|
|
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(['--cache-dir', str(config_file_path.parent / '.mypy_cache'),
|
|
'--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, abs_path.name):
|
|
error_happened = True
|
|
print(replace_with_clickable_location(error_line, abs_test_folder=abs_path))
|
|
return int(error_happened)
|
|
|
|
|
|
def get_absolute_path_for_test(test_dirname: str):
|
|
return (PROJECT_DIRECTORY / tests_root / test_dirname).absolute()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
mypy_config_file = (PROJECT_DIRECTORY / 'scripts' / 'mypy.ini').absolute()
|
|
repo_directory = PROJECT_DIRECTORY / 'django-sources'
|
|
mypy_cache_dir = Path(__file__).parent / '.mypy_cache'
|
|
tests_root = repo_directory / 'tests'
|
|
global_rc = 0
|
|
|
|
# clone Django repository, if it does not exist
|
|
if not repo_directory.exists():
|
|
repo = Repo.clone_from('https://github.com/django/django.git', repo_directory)
|
|
else:
|
|
repo = Repo(repo_directory)
|
|
repo.remotes['origin'].pull(DJANGO_BRANCH)
|
|
|
|
repo.git.checkout(DJANGO_COMMIT_SHA)
|
|
|
|
if len(sys.argv) > 1:
|
|
tests_to_run = sys.argv[1:]
|
|
else:
|
|
tests_to_run = TESTS_DIRS
|
|
|
|
try:
|
|
for dirname in tests_to_run:
|
|
abs_path = get_absolute_path_for_test(dirname)
|
|
print(f'Checking {abs_path}')
|
|
|
|
rc = check_with_mypy(abs_path, mypy_config_file)
|
|
if rc != 0:
|
|
global_rc = 1
|
|
|
|
sys.exit(global_rc)
|
|
|
|
except BaseException as exc:
|
|
shutil.rmtree(mypy_cache_dir, ignore_errors=True)
|
|
raise exc
|