From 1afa079b0b9000b62ebf5db2ab0169133c0997a1 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Sat, 26 Jan 2019 17:17:35 +0300 Subject: [PATCH] add tests for the django test suite --- .travis.yml | 26 ++-- django-stubs/__init__.pyi | 1 + django-stubs/conf/urls/__init__.pyi | 6 +- django-stubs/core/checks/__init__.pyi | 1 + django-stubs/core/checks/messages.pyi | 2 +- django-stubs/core/checks/model_checks.pyi | 2 +- django-stubs/core/files/storage.pyi | 5 + django-stubs/core/management/__init__.pyi | 2 +- django-stubs/core/management/base.pyi | 2 +- django-stubs/db/__init__.pyi | 1 + django-stubs/db/models/fields/__init__.pyi | 157 ++++++++++++++++++--- django-stubs/forms/__init__.pyi | 2 + django-stubs/template/backends/django.pyi | 2 +- django-stubs/template/backends/jinja2.pyi | 2 +- django-stubs/template/engine.pyi | 2 +- django-stubs/template/loader.pyi | 15 +- django-stubs/test/__init__.pyi | 2 +- scripts/typecheck_django_tests.xsh | 59 +++++++- 18 files changed, 232 insertions(+), 57 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9218bfc..7d380ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,21 +7,21 @@ sudo: required jobs: include: - - name: "Run plugin test suite with python 3.7" - python: 3.7 - script: | - set -e - pytest + - name: "Typecheck Django test suite" + python: 3.7 + script: | + xonsh ./scripts/typecheck_django_tests.xsh - - name: "Lint with black" - python: 3.7 - script: | - black --check --line-length=120 django-stubs/ + - name: "Run plugin test suite with python 3.7" + python: 3.7 + script: | + set -e + pytest -# - name: "Typecheck Django test suite" -# python: 3.7 -# script: | -# xonsh ./scripts/typecheck_django_tests.xsh + - name: "Lint with black" + python: 3.7 + script: | + black --check --line-length=120 django-stubs/ before_install: | # Upgrade pip, setuptools, and wheel diff --git a/django-stubs/__init__.pyi b/django-stubs/__init__.pyi index b0aa111..1453961 100644 --- a/django-stubs/__init__.pyi +++ b/django-stubs/__init__.pyi @@ -1,4 +1,5 @@ from typing import Any +from django.utils.version import get_version as get_version VERSION: Any diff --git a/django-stubs/conf/urls/__init__.pyi b/django-stubs/conf/urls/__init__.pyi index b59ac58..e2850fb 100644 --- a/django-stubs/conf/urls/__init__.pyi +++ b/django-stubs/conf/urls/__init__.pyi @@ -1,7 +1,7 @@ # Stubs for django.conf.urls (Python 3.5) from typing import Any, Callable, Dict, List, Optional, overload, Tuple, Union -from django.http.response import HttpResponse +from django.http.response import HttpResponse, HttpResponseBase from django.urls import URLResolver, URLPattern @@ -14,7 +14,9 @@ IncludedURLConf = Tuple[List[URLResolver], Optional[str], Optional[str]] def include(arg: Any, namespace: str = ..., app_name: str = ...) -> IncludedURLConf: ... @overload -def url(regex: str, view: Callable[..., HttpResponse], kwargs: Dict[str, Any] = ..., name: str = ...) -> URLPattern: ... +def url( + regex: str, view: Callable[..., HttpResponseBase], kwargs: Dict[str, Any] = ..., name: str = ... +) -> URLPattern: ... @overload def url(regex: str, view: IncludedURLConf, kwargs: Dict[str, Any] = ..., name: str = ...) -> URLResolver: ... @overload diff --git a/django-stubs/core/checks/__init__.pyi b/django-stubs/core/checks/__init__.pyi index e69de29..33a4a0d 100644 --- a/django-stubs/core/checks/__init__.pyi +++ b/django-stubs/core/checks/__init__.pyi @@ -0,0 +1 @@ +from .messages import Warning as Warning, Info as Info, Debug as Debug, Error as Error, Critical as Critical diff --git a/django-stubs/core/checks/messages.pyi b/django-stubs/core/checks/messages.pyi index 3405e5f..5a19783 100644 --- a/django-stubs/core/checks/messages.pyi +++ b/django-stubs/core/checks/messages.pyi @@ -1,4 +1,4 @@ -from typing import Any, Optional, Union +from typing import Any, Optional DEBUG: int INFO: int diff --git a/django-stubs/core/checks/model_checks.pyi b/django-stubs/core/checks/model_checks.pyi index 23fd841..a424b11 100644 --- a/django-stubs/core/checks/model_checks.pyi +++ b/django-stubs/core/checks/model_checks.pyi @@ -1,4 +1,4 @@ -from typing import Any, List, Optional +from typing import Any, List from django.core.checks.messages import Warning diff --git a/django-stubs/core/files/storage.pyi b/django-stubs/core/files/storage.pyi index fb7582d..0f27f0a 100644 --- a/django-stubs/core/files/storage.pyi +++ b/django-stubs/core/files/storage.pyi @@ -31,10 +31,15 @@ class FileSystemStorage(Storage): file_permissions_mode: Optional[int] = ..., directory_permissions_mode: Optional[int] = ..., ) -> None: ... + @property def base_location(self) -> str: ... + @property def location(self) -> str: ... + @property def base_url(self) -> str: ... + @property def file_permissions_mode(self) -> Optional[int]: ... + @property def directory_permissions_mode(self) -> Optional[int]: ... class DefaultStorage(LazyObject): ... diff --git a/django-stubs/core/management/__init__.pyi b/django-stubs/core/management/__init__.pyi index 4ff3c2d..92d994e 100644 --- a/django-stubs/core/management/__init__.pyi +++ b/django-stubs/core/management/__init__.pyi @@ -1,6 +1,6 @@ from typing import Any, Dict, List, Optional, Tuple, Union -from django.core.management.base import BaseCommand as BaseCommand +from django.core.management.base import BaseCommand as BaseCommand, CommandError as CommandError def find_commands(management_dir: str) -> List[str]: ... def load_command_class(app_name: str, name: str) -> BaseCommand: ... diff --git a/django-stubs/core/management/base.pyi b/django-stubs/core/management/base.pyi index 4a0c9ac..866f895 100644 --- a/django-stubs/core/management/base.pyi +++ b/django-stubs/core/management/base.pyi @@ -60,7 +60,7 @@ class BaseCommand: def add_arguments(self, parser: CommandParser) -> None: ... def print_help(self, prog_name: str, subcommand: str) -> None: ... def run_from_argv(self, argv: List[str]) -> None: ... - def execute(self, *args: Any, **options: Any) -> Optional[Union[Tuple, str]]: ... + def execute(self, *args: Any, **options: Any) -> Any: ... def check( self, app_configs: Optional[List[AppConfig]] = ..., diff --git a/django-stubs/db/__init__.pyi b/django-stubs/db/__init__.pyi index 7447a3a..5115442 100644 --- a/django-stubs/db/__init__.pyi +++ b/django-stubs/db/__init__.pyi @@ -11,6 +11,7 @@ from .utils import ( NotSupportedError as NotSupportedError, InternalError as InternalError, InterfaceError as InterfaceError, + ConnectionHandler as ConnectionHandler, ) from . import migrations diff --git a/django-stubs/db/models/fields/__init__.pyi b/django-stubs/db/models/fields/__init__.pyi index 41f4eae..555f93f 100644 --- a/django-stubs/db/models/fields/__init__.pyi +++ b/django-stubs/db/models/fields/__init__.pyi @@ -1,9 +1,35 @@ -from typing import Any, Optional +from typing import Any, Optional, Tuple, Iterable, Callable, Dict, Union from django.db.models.query_utils import RegisterLookupMixin +_Choice = Tuple[Any, str] +_ChoiceNamedGroup = Tuple[str, Iterable[_Choice]] +_FieldChoices = Iterable[Union[_Choice, _ChoiceNamedGroup]] + +_ValidatorCallable = Callable[[...], None] +_ErrorMessagesToOverride = Dict[str, Any] + class Field(RegisterLookupMixin): - def __init__(self, **kwargs: Any): ... + def __init__( + self, + verbose_name: Optional[str] = ..., + name: Optional[str] = ..., + primary_key: bool = ..., + unique: bool = ..., + blank: bool = ..., + null: bool = ..., + db_index: bool = ..., + default: Any = ..., + editable: bool = ..., + auto_created: bool = ..., + serialize: bool = ..., + choices: Optional[_FieldChoices] = ..., + help_text: str = ..., + db_column: Optional[str] = ..., + db_tablespace: Optional[str] = ..., + validators: Iterable[_ValidatorCallable] = ..., + error_messages: Optional[_ErrorMessagesToOverride] = ..., + ): ... def __get__(self, instance, owner) -> Any: ... class IntegerField(Field): @@ -19,24 +45,83 @@ class BigIntegerField(IntegerField): ... class FloatField(Field): ... class DecimalField(IntegerField): - def __init__(self, *, max_digits: Optional[int] = ..., decimal_places: Optional[int] = ..., **kwargs): ... + def __init__( + self, + verbose_name: Optional[str] = ..., + name: Optional[str] = ..., + max_digits: Optional[int] = ..., + decimal_places: Optional[int] = ..., + primary_key: bool = ..., + unique: bool = ..., + blank: bool = ..., + null: bool = ..., + db_index: bool = ..., + default: Any = ..., + editable: bool = ..., + auto_created: bool = ..., + serialize: bool = ..., + choices: Optional[_FieldChoices] = ..., + help_text: str = ..., + db_column: Optional[str] = ..., + db_tablespace: Optional[str] = ..., + validators: Iterable[_ValidatorCallable] = ..., + error_messages: Optional[_ErrorMessagesToOverride] = ..., + ): ... class AutoField(Field): def __get__(self, instance, owner) -> int: ... class CharField(Field): - def __init__(self, max_length: int = ..., **kwargs: Any): ... + def __init__( + self, + verbose_name: Optional[str] = ..., + name: Optional[str] = ..., + primary_key: bool = ..., + max_length: Optional[int] = ..., + unique: bool = ..., + blank: bool = ..., + null: bool = ..., + db_index: bool = ..., + default: Any = ..., + editable: bool = ..., + auto_created: bool = ..., + serialize: bool = ..., + choices: Optional[_FieldChoices] = ..., + help_text: str = ..., + db_column: Optional[str] = ..., + db_tablespace: Optional[str] = ..., + validators: Iterable[_ValidatorCallable] = ..., + error_messages: Optional[_ErrorMessagesToOverride] = ..., + ): ... def __set__(self, instance, value: str) -> None: ... def __get__(self, instance, owner) -> str: ... class SlugField(CharField): - def __init__(self, max_length: int = ..., **kwargs: Any): ... + def __init__( + self, + verbose_name: Optional[str] = ..., + name: Optional[str] = ..., + primary_key: bool = ..., + max_length: Optional[int] = ..., + allow_unicode: bool = ..., + unique: bool = ..., + blank: bool = ..., + null: bool = ..., + db_index: bool = ..., + default: Any = ..., + editable: bool = ..., + auto_created: bool = ..., + serialize: bool = ..., + choices: Optional[_FieldChoices] = ..., + help_text: str = ..., + db_column: Optional[str] = ..., + db_tablespace: Optional[str] = ..., + validators: Iterable[_ValidatorCallable] = ..., + error_messages: Optional[_ErrorMessagesToOverride] = ..., + ): ... -class EmailField(CharField): - def __init__(self, max_length: int = ..., **kwargs: Any): ... - -class URLField(CharField): - def __init__(self, max_length: int = ..., **kwargs: Any): ... +class EmailField(CharField): ... +class URLField(CharField): ... class TextField(Field): def __set__(self, instance, value: str) -> None: ... @@ -63,7 +148,21 @@ class GenericIPAddressField(Field): name: Optional[Any] = ..., protocol: str = ..., unpack_ipv4: bool = ..., - **kwargs: Any + primary_key: bool = ..., + unique: bool = ..., + blank: bool = ..., + null: bool = ..., + db_index: bool = ..., + default: Any = ..., + editable: bool = ..., + auto_created: bool = ..., + serialize: bool = ..., + choices: Optional[_FieldChoices] = ..., + help_text: str = ..., + db_column: Optional[str] = ..., + db_tablespace: Optional[str] = ..., + validators: Iterable[_ValidatorCallable] = ..., + error_messages: Optional[_ErrorMessagesToOverride] = ..., ) -> None: ... class DateTimeCheckMixin: ... @@ -75,7 +174,21 @@ class DateField(DateTimeCheckMixin, Field): name: Optional[str] = ..., auto_now: bool = ..., auto_now_add: bool = ..., - **kwargs + primary_key: bool = ..., + unique: bool = ..., + blank: bool = ..., + null: bool = ..., + db_index: bool = ..., + default: Any = ..., + editable: bool = ..., + auto_created: bool = ..., + serialize: bool = ..., + choices: Optional[_FieldChoices] = ..., + help_text: str = ..., + db_column: Optional[str] = ..., + db_tablespace: Optional[str] = ..., + validators: Iterable[_ValidatorCallable] = ..., + error_messages: Optional[_ErrorMessagesToOverride] = ..., ): ... class TimeField(DateTimeCheckMixin, Field): ... @@ -92,10 +205,22 @@ class FilePathField(Field): recursive: bool = ..., allow_files: bool = ..., allow_folders: bool = ..., - **kwargs + primary_key: bool = ..., + unique: bool = ..., + blank: bool = ..., + null: bool = ..., + db_index: bool = ..., + default: Any = ..., + editable: bool = ..., + auto_created: bool = ..., + serialize: bool = ..., + choices: Optional[_FieldChoices] = ..., + help_text: str = ..., + db_column: Optional[str] = ..., + db_tablespace: Optional[str] = ..., + validators: Iterable[_ValidatorCallable] = ..., + error_messages: Optional[_ErrorMessagesToOverride] = ..., ): ... -class BinaryField(Field): - def __init__(self, editable: bool = ..., **kwargs: Any): ... - +class BinaryField(Field): ... class DurationField(Field): ... diff --git a/django-stubs/forms/__init__.pyi b/django-stubs/forms/__init__.pyi index 9dde990..5c361a5 100644 --- a/django-stubs/forms/__init__.pyi +++ b/django-stubs/forms/__init__.pyi @@ -1,3 +1,5 @@ +from django.core.exceptions import ValidationError as ValidationError + from .forms import Form as Form, BaseForm as BaseForm from .models import ModelForm as ModelForm diff --git a/django-stubs/template/backends/django.pyi b/django-stubs/template/backends/django.pyi index c69aed1..c4ed389 100644 --- a/django-stubs/template/backends/django.pyi +++ b/django-stubs/template/backends/django.pyi @@ -1,6 +1,6 @@ from typing import Any, Dict, Iterator, Optional, List -from django.template.base import Template +from django.template.base import Template as Template from django.template.exceptions import TemplateDoesNotExist from django.template.engine import Engine diff --git a/django-stubs/template/backends/jinja2.pyi b/django-stubs/template/backends/jinja2.pyi index c4f0367..122b5b0 100644 --- a/django-stubs/template/backends/jinja2.pyi +++ b/django-stubs/template/backends/jinja2.pyi @@ -1,6 +1,6 @@ from typing import Callable, Dict, List, Optional, Tuple, Any -from django.template.base import Template +from django.template.base import Template as Template from django.template.exceptions import TemplateSyntaxError from .base import BaseEngine diff --git a/django-stubs/template/engine.pyi b/django-stubs/template/engine.pyi index cd07f30..14ff00a 100644 --- a/django-stubs/template/engine.pyi +++ b/django-stubs/template/engine.pyi @@ -51,5 +51,5 @@ class Engine: ) -> Tuple[Template, Origin]: ... def from_string(self, template_code: str) -> Template: ... def get_template(self, template_name: str) -> Template: ... - def render_to_string(self, template_name: str, context: Any = ...) -> SafeText: ... + def render_to_string(self, template_name: str, context: Optional[Dict[str, Any]] = ...) -> SafeText: ... def select_template(self, template_name_list: List[str]) -> Template: ... diff --git a/django-stubs/template/loader.pyi b/django-stubs/template/loader.pyi index a36bae4..9f51ce4 100644 --- a/django-stubs/template/loader.pyi +++ b/django-stubs/template/loader.pyi @@ -1,19 +1,12 @@ -from typing import Dict, List, Optional, Union +from typing import Dict, List, Optional, Union, Any from django.core.handlers.wsgi import WSGIRequest -from django.template.backends.django import Template as DjangoTemplate -from django.template.backends.dummy import Template as DummyTemplate -from django.template.backends.jinja2 import Template as Jinja2Template -def get_template( - template_name: str, using: Optional[str] = ... -) -> Union[DjangoTemplate, DummyTemplate, Jinja2Template]: ... -def select_template( - template_name_list: Union[List[str], str], using: Optional[str] = ... -) -> Union[DjangoTemplate, DummyTemplate, Jinja2Template]: ... +def get_template(template_name: str, using: Optional[str] = ...) -> Any: ... +def select_template(template_name_list: Union[List[str], str], using: Optional[str] = ...) -> Any: ... def render_to_string( template_name: Union[List[str], str], - context: Optional[Union[Dict[str, bool], Dict[str, str]]] = ..., + context: Optional[Dict[str, Any]] = ..., request: Optional[WSGIRequest] = ..., using: Optional[str] = ..., ) -> str: ... diff --git a/django-stubs/test/__init__.pyi b/django-stubs/test/__init__.pyi index 4999e6c..a7ef21f 100644 --- a/django-stubs/test/__init__.pyi +++ b/django-stubs/test/__init__.pyi @@ -5,6 +5,6 @@ from .testcases import ( LiveServerTestCase as LiveServerTestCase, ) -from .utils import override_settings as override_settings +from .utils import override_settings as override_settings, modify_settings as modify_settings from .client import Client as Client diff --git a/scripts/typecheck_django_tests.xsh b/scripts/typecheck_django_tests.xsh index fbcbfe8..8881195 100644 --- a/scripts/typecheck_django_tests.xsh +++ b/scripts/typecheck_django_tests.xsh @@ -2,10 +2,55 @@ import os if not os.path.exists('./django-sources'): git clone -b stable/2.1.x https://github.com/django/django.git django-sources -ignored_error_patterns = ["Need type annotation for", "already defined on", "Cannot assign to a"] -for line in $(mypy --config-file ./scripts/mypy.ini ./django-sources/tests).split('\n'): - for pattern in ignored_error_patterns: - if pattern in line: - break - else: - print(line) \ No newline at end of file +IGNORED_ERROR_PATTERNS = [ + 'Need type annotation for', + 'already defined on', + 'Cannot assign to a', + 'cannot perform relative import', + 'broken_app', + 'LazySettings', + 'Cannot infer type of lambda', + 'Incompatible types in assignment (expression has type "Callable[', + '"Callable[[Any], Any]" has no attribute', + 'Invalid value for a to= parameter' +] +TESTS_DIRS = [ + 'absolute_url_overrides', + 'admin_*', + 'aggregation*', + 'annotations' +] + +def check_file_in_the_current_directory(directory, fname): + cd @(directory) + with ${...}.swap(FNAME=fname): + for line in $(mypy --config-file ../../../scripts/mypy.ini $FNAME).split('\n'): + for pattern in IGNORED_ERROR_PATTERNS: + if pattern in line: + break + else: + if line: + print(line) + cd - + +def parse_ls_output_into_fnames(output): + fnames = [] + for line in output.splitlines()[1:]: + fnames.append(line.split()[-1]) + return fnames + +all_tests_dirs = [] +for test_dir in TESTS_DIRS: + with ${...}.swap(TEST_DIR=test_dir): + dirs = g`django-sources/tests/$TEST_DIR` + all_tests_dirs.extend(dirs) + +for tests_dir in all_tests_dirs: + print('Checking ' + tests_dir) + abs_dir = os.path.join(os.getcwd(), tests_dir) + + with ${...}.swap(ABS_DIR=abs_dir): + ls_output = $(ls $ABS_DIR) + for fname in parse_ls_output_into_fnames(ls_output): + path_to_check = os.path.join(abs_dir, fname) + check_file_in_the_current_directory(abs_dir, fname) \ No newline at end of file