From cda703a94bc36fe3c5d1c620c692ed6b314a061e Mon Sep 17 00:00:00 2001 From: Seth Yastrov Date: Wed, 20 Feb 2019 20:51:43 +0100 Subject: [PATCH 1/4] QuerySet.in_bulk returns Dict with values of correct model type. - The keys are still Union[int, str] which doesn't cover all possibilities, but it's not possible to cover without handling this in the plugin, because the type of the Dict keys are dynamic depending on which value for field_name you pass in. --- django-stubs/db/models/query.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django-stubs/db/models/query.pyi b/django-stubs/db/models/query.pyi index 7f0f44c..670927f 100644 --- a/django-stubs/db/models/query.pyi +++ b/django-stubs/db/models/query.pyi @@ -86,7 +86,7 @@ class QuerySet(Iterable[_T], Sized): def last(self) -> Optional[_T]: ... def in_bulk( self, id_list: Any = ..., *, field_name: str = ..., **kwargs: Any - ) -> Dict[Union[int, str], models.Model]: ... + ) -> Dict[Union[int, str], _T]: ... def delete(self) -> Tuple[int, Dict[str, int]]: ... def update(self, **kwargs: Any) -> int: ... def _update(self, values: Any) -> Optional[Any]: ... From 3b8c5d08e8d83069ddedb11f6af93a3d3bd084bd Mon Sep 17 00:00:00 2001 From: Seth Yastrov Date: Fri, 22 Feb 2019 08:10:53 +0100 Subject: [PATCH 2/4] QuerySet.in_bulk fixes. - Made id_list of type Sequence[Any], rather than Any, according to mkurnikov's review. - Removed **kwargs. - Made returned Dict keys of type Any rather than int or str as it depends on the provided field's type. - Added test with examples from Django docs. --- django-stubs/db/models/query.pyi | 4 +--- test-data/typecheck/queryset.test | 10 ++++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 test-data/typecheck/queryset.test diff --git a/django-stubs/db/models/query.pyi b/django-stubs/db/models/query.pyi index 670927f..de51553 100644 --- a/django-stubs/db/models/query.pyi +++ b/django-stubs/db/models/query.pyi @@ -84,9 +84,7 @@ class QuerySet(Iterable[_T], Sized): def latest(self, *fields: Any, field_name: Optional[Any] = ...) -> _T: ... def first(self) -> Optional[_T]: ... def last(self) -> Optional[_T]: ... - def in_bulk( - self, id_list: Any = ..., *, field_name: str = ..., **kwargs: Any - ) -> Dict[Union[int, str], _T]: ... + def in_bulk(self, id_list: Sequence[Any] = ..., *, field_name: str = ...) -> Dict[Any, _T]: ... def delete(self) -> Tuple[int, Dict[str, int]]: ... def update(self, **kwargs: Any) -> int: ... def _update(self, values: Any) -> Optional[Any]: ... diff --git a/test-data/typecheck/queryset.test b/test-data/typecheck/queryset.test new file mode 100644 index 0000000..25882af --- /dev/null +++ b/test-data/typecheck/queryset.test @@ -0,0 +1,10 @@ +[CASE test_queryset] +from django.db import models + +class Blog(models.Model): + slug = models.CharField(max_length=100) +reveal_type(Blog.objects.in_bulk([1])) # E: Revealed type is 'builtins.dict[Any, main.Blog*]' +reveal_type(Blog.objects.in_bulk()) # E: Revealed type is 'builtins.dict[Any, main.Blog*]' +reveal_type(Blog.objects.in_bulk(['beatles_blog'], field_name='slug')) # E: Revealed type is 'builtins.dict[Any, main.Blog*]' + +[out] From 2dadd681ffeda03ee252ff0cb105f22b0a2cce55 Mon Sep 17 00:00:00 2001 From: Seth Yastrov Date: Fri, 22 Feb 2019 20:02:33 +0100 Subject: [PATCH 3/4] Change in_bulk id_list param to Iterable rather than Sequence. --- django-stubs/db/models/query.pyi | 2 +- scripts/typecheck_tests.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/django-stubs/db/models/query.pyi b/django-stubs/db/models/query.pyi index de51553..34e82cb 100644 --- a/django-stubs/db/models/query.pyi +++ b/django-stubs/db/models/query.pyi @@ -84,7 +84,7 @@ class QuerySet(Iterable[_T], Sized): def latest(self, *fields: Any, field_name: Optional[Any] = ...) -> _T: ... def first(self) -> Optional[_T]: ... def last(self) -> Optional[_T]: ... - def in_bulk(self, id_list: Sequence[Any] = ..., *, field_name: str = ...) -> Dict[Any, _T]: ... + def in_bulk(self, id_list: Iterable[Any] = ..., *, field_name: str = ...) -> Dict[Any, _T]: ... def delete(self) -> Tuple[int, Dict[str, int]]: ... def update(self, **kwargs: Any) -> int: ... def _update(self, values: Any) -> Optional[Any]: ... diff --git a/scripts/typecheck_tests.py b/scripts/typecheck_tests.py index fa68a6a..3d822f9 100644 --- a/scripts/typecheck_tests.py +++ b/scripts/typecheck_tests.py @@ -214,6 +214,10 @@ IGNORED_ERRORS = { 'logging_tests': [ re.compile('"(setUpClass|tearDownClass)" undefined in superclass') ], + 'lookup': [ + 'Unexpected keyword argument "headline__startswith" for "in_bulk" of "QuerySet"', + 'note: ' + ], 'many_to_one': [ 'Incompatible type for "parent" of "Child" (got "None", expected "Union[Parent, Combinable]")' ], From 0e30821ad36e06bd4064b19b34f94e3ad38096b5 Mon Sep 17 00:00:00 2001 From: Seth Yastrov Date: Fri, 22 Feb 2019 20:03:30 +0100 Subject: [PATCH 4/4] Add possibility to pass list of test names as command-line arguments to typecheck_tests.py script. --- scripts/typecheck_tests.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/typecheck_tests.py b/scripts/typecheck_tests.py index 3d822f9..112a450 100644 --- a/scripts/typecheck_tests.py +++ b/scripts/typecheck_tests.py @@ -665,6 +665,10 @@ def check_with_mypy(abs_path: Path, config_file_path: Path) -> int: 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' @@ -679,8 +683,14 @@ if __name__ == '__main__': repo.remotes['origin'].pull(DJANGO_BRANCH) repo.git.checkout(DJANGO_COMMIT_SHA) - for dirname in TESTS_DIRS: - abs_path = (PROJECT_DIRECTORY / tests_root / dirname).absolute() + + if len(sys.argv) > 1: + tests_to_run = sys.argv[1:] + else: + tests_to_run = TESTS_DIRS + + 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)