From 81f4b07fc07167798bcb408f597d18ad56c923b8 Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Mon, 13 Jun 2022 16:18:17 -0400 Subject: [PATCH 1/5] Add a missing attribute to Jinja2. https://github.com/django/django/blob/main/django/template/backends/jinja2.py#L35 Signed-off-by: Zixuan James Li --- django-stubs/template/backends/jinja2.pyi | 1 + 1 file changed, 1 insertion(+) diff --git a/django-stubs/template/backends/jinja2.pyi b/django-stubs/template/backends/jinja2.pyi index 623bd409a..32b52a27a 100644 --- a/django-stubs/template/backends/jinja2.pyi +++ b/django-stubs/template/backends/jinja2.pyi @@ -5,6 +5,7 @@ from django.template.exceptions import TemplateSyntaxError from .base import BaseEngine class Jinja2(BaseEngine): + env: Any = ... context_processors: List[str] = ... def __init__(self, params: Dict[str, Any]) -> None: ... @property From 7291344269a0d4cb3972e0769e30350424e24224 Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Thu, 16 Jun 2022 18:59:20 -0400 Subject: [PATCH 2/5] Make _QuerySet.extra's signature more generic. This makes sure that we don't reject tuples, which is also valid according to the implementation. Relevant source code: https://github.com/django/django/blob/03eec9ff6cc78e7c1bcf88bb76ecd11f0d433c72/django/db/models/sql/where.py#L271-L281 https://github.com/django/django/blob/03eec9ff6cc78e7c1bcf88bb76ecd11f0d433c72/django/db/models/sql/query.py#L2307-L2308 Signed-off-by: Zixuan James Li --- django-stubs/db/models/query.pyi | 6 +++--- tests/typecheck/managers/querysets/test_from_queryset.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/django-stubs/db/models/query.pyi b/django-stubs/db/models/query.pyi index c596e0680..2ad08b1b5 100644 --- a/django-stubs/db/models/query.pyi +++ b/django-stubs/db/models/query.pyi @@ -115,9 +115,9 @@ class _QuerySet(Generic[_T, _Row], Collection[_Row], Reversible[_Row], Sized): def extra( self, select: Optional[Dict[str, Any]] = ..., - where: Optional[List[str]] = ..., - params: Optional[List[Any]] = ..., - tables: Optional[List[str]] = ..., + where: Optional[Sequence[str]] = ..., + params: Optional[Sequence[Any]] = ..., + tables: Optional[Sequence[str]] = ..., order_by: Optional[Sequence[str]] = ..., select_params: Optional[Sequence[Any]] = ..., ) -> _QuerySet[Any, Any]: ... diff --git a/tests/typecheck/managers/querysets/test_from_queryset.yml b/tests/typecheck/managers/querysets/test_from_queryset.yml index 2a2ae5db4..aadd114e7 100644 --- a/tests/typecheck/managers/querysets/test_from_queryset.yml +++ b/tests/typecheck/managers/querysets/test_from_queryset.yml @@ -407,7 +407,7 @@ reveal_type(MyModel.objects.difference) # N: Revealed type is "def (*other_qs: Any) -> myapp.models.MyQuerySet[myapp.models.MyModel]" reveal_type(MyModel.objects.distinct) # N: Revealed type is "def (*field_names: Any) -> myapp.models.MyQuerySet[myapp.models.MyModel]" reveal_type(MyModel.objects.exclude) # N: Revealed type is "def (*args: Any, **kwargs: Any) -> myapp.models.MyQuerySet[myapp.models.MyModel]" - reveal_type(MyModel.objects.extra) # N: Revealed type is "def (select: Union[builtins.dict[builtins.str, Any], None] =, where: Union[builtins.list[builtins.str], None] =, params: Union[builtins.list[Any], None] =, tables: Union[builtins.list[builtins.str], None] =, order_by: Union[typing.Sequence[builtins.str], None] =, select_params: Union[typing.Sequence[Any], None] =) -> myapp.models.MyQuerySet[myapp.models.MyModel]" + reveal_type(MyModel.objects.extra) # N: Revealed type is "def (select: Union[builtins.dict[builtins.str, Any], None] =, where: Union[typing.Sequence[builtins.str], None] =, params: Union[typing.Sequence[Any], None] =, tables: Union[typing.Sequence[builtins.str], None] =, order_by: Union[typing.Sequence[builtins.str], None] =, select_params: Union[typing.Sequence[Any], None] =) -> myapp.models.MyQuerySet[myapp.models.MyModel]" reveal_type(MyModel.objects.filter) # N: Revealed type is "def (*args: Any, **kwargs: Any) -> myapp.models.MyQuerySet[myapp.models.MyModel]" reveal_type(MyModel.objects.intersection) # N: Revealed type is "def (*other_qs: Any) -> myapp.models.MyQuerySet[myapp.models.MyModel]" reveal_type(MyModel.objects.none) # N: Revealed type is "def () -> myapp.models.MyQuerySet[myapp.models.MyModel]" From b5cfdb1ac3b6df7e02c052fc8be4e52f5a179a5b Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Fri, 1 Jul 2022 10:44:13 -0400 Subject: [PATCH 3/5] Fix user_passes_test to use AUTH_USER_MODEL. According to the documentation, `test_func` is a callable that takes a `User` (possibly anonymous). Relevant documentation: https://docs.djangoproject.com/en/4.0/topics/auth/default/#django.contrib.auth.decorators.user_passes_test Signed-off-by: Zixuan James Li --- django-stubs/contrib/auth/decorators.pyi | 6 ++++-- tests/typecheck/contrib/auth/test_decorators.yml | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/django-stubs/contrib/auth/decorators.pyi b/django-stubs/contrib/auth/decorators.pyi index ce8b161ab..fd5b0320b 100644 --- a/django-stubs/contrib/auth/decorators.pyi +++ b/django-stubs/contrib/auth/decorators.pyi @@ -1,13 +1,15 @@ from typing import Callable, Iterable, Optional, TypeVar, Union, overload from django.contrib.auth import REDIRECT_FIELD_NAME as REDIRECT_FIELD_NAME # noqa: F401 -from django.contrib.auth.models import AbstractUser +from django.contrib.auth.models import AbstractBaseUser, AnonymousUser from django.http.response import HttpResponseBase _VIEW = TypeVar("_VIEW", bound=Callable[..., HttpResponseBase]) def user_passes_test( - test_func: Callable[[AbstractUser], bool], login_url: Optional[str] = ..., redirect_field_name: str = ... + test_func: Callable[[Union[AbstractBaseUser, AnonymousUser]], bool], + login_url: Optional[str] = ..., + redirect_field_name: str = ..., ) -> Callable[[_VIEW], _VIEW]: ... # There are two ways of calling @login_required: @with(arguments) and @bare diff --git a/tests/typecheck/contrib/auth/test_decorators.yml b/tests/typecheck/contrib/auth/test_decorators.yml index 18ac1d3a1..a43504bbe 100644 --- a/tests/typecheck/contrib/auth/test_decorators.yml +++ b/tests/typecheck/contrib/auth/test_decorators.yml @@ -27,14 +27,14 @@ - case: user_passes_test main: | from django.contrib.auth.decorators import user_passes_test - @user_passes_test(lambda u: u.username.startswith('super')) + @user_passes_test(lambda u: u.get_username().startswith('super')) def view_func(request): ... reveal_type(view_func) # N: Revealed type is "def (request: Any) -> Any" - case: user_passes_test_bare_is_error main: | from django.http.response import HttpResponse from django.contrib.auth.decorators import user_passes_test - @user_passes_test # E: Argument 1 to "user_passes_test" has incompatible type "Callable[[Any], HttpResponse]"; expected "Callable[[AbstractUser], bool]" + @user_passes_test # E: Argument 1 to "user_passes_test" has incompatible type "Callable[[Any], HttpResponse]"; expected "Callable[[Union[AbstractBaseUser, AnonymousUser]], bool]" def view_func(request) -> HttpResponse: ... - case: permission_required main: | From 71bb3f3a1ab40a12d4e845f7240895fe430a62cd Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Fri, 1 Jul 2022 10:51:16 -0400 Subject: [PATCH 4/5] Add more accurate type annotations for dirs. Though not documented, it's possible for `dirs` to contain `pathlib.Path`. `django.template.loaders.app_directories.Loader` is an example for this: https://github.com/django/django/blob/03eec9ff6cc78e7c1bcf88bb76ecd11f0d433c72/django/template/loaders/app_directories.py https://github.com/django/django/blob/03eec9ff6cc78e7c1bcf88bb76ecd11f0d433c72/django/template/utils.py#L97-L111 Signed-off-by: Zixuan James Li --- django-stubs/template/loaders/filesystem.pyi | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/django-stubs/template/loaders/filesystem.pyi b/django-stubs/template/loaders/filesystem.pyi index bed0faac9..8931c630d 100644 --- a/django-stubs/template/loaders/filesystem.pyi +++ b/django-stubs/template/loaders/filesystem.pyi @@ -1,4 +1,5 @@ -from typing import Iterator, List, Optional +from pathlib import Path +from typing import Iterator, List, Optional, Union from django.template.base import Origin from django.template.engine import Engine @@ -6,8 +7,8 @@ from django.template.engine import Engine from .base import Loader as BaseLoader class Loader(BaseLoader): - dirs: Optional[List[str]] = ... - def __init__(self, engine: Engine, dirs: Optional[List[str]] = ...) -> None: ... - def get_dirs(self) -> List[str]: ... + dirs: Optional[List[Union[str, Path]]] = ... + def __init__(self, engine: Engine, dirs: Optional[List[Union[str, Path]]] = ...) -> None: ... + def get_dirs(self) -> List[Union[str, Path]]: ... def get_contents(self, origin: Origin) -> str: ... def get_template_sources(self, template_name: str) -> Iterator[Origin]: ... From 561ee3f55fdb8c531f713c9b32b4e48e2ca5b15d Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Fri, 1 Jul 2022 14:35:14 -0400 Subject: [PATCH 5/5] serve should return FileResponse. There are several serve functions that should return a `FileResponse`. Source code: https://github.com/django/django/blob/863aa7541d30247e7eb7a973ff68a7d36f16dc02/django/views/static.py#L17-L53 https://github.com/django/django/blob/863aa7541d30247e7eb7a973ff68a7d36f16dc02/django/contrib/staticfiles/views.py#L15-L39 https://github.com/django/django/blob/863aa7541d30247e7eb7a973ff68a7d36f16dc02/django/contrib/staticfiles/handlers.py#L48-L50 https://github.com/django/django/blob/863aa7541d30247e7eb7a973ff68a7d36f16dc02/django/test/testcases.py#L1680-L1687 Signed-off-by: Zixuan James Li --- django-stubs/contrib/staticfiles/handlers.pyi | 8 ++++---- django-stubs/contrib/staticfiles/views.pyi | 4 ++-- django-stubs/test/testcases.pyi | 4 ++-- django-stubs/views/static.pyi | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/django-stubs/contrib/staticfiles/handlers.pyi b/django-stubs/contrib/staticfiles/handlers.pyi index b6402c38b..370098132 100644 --- a/django-stubs/contrib/staticfiles/handlers.pyi +++ b/django-stubs/contrib/staticfiles/handlers.pyi @@ -1,11 +1,11 @@ from typing import Any, Awaitable, Callable, Dict, Mapping, Sequence, Tuple from urllib.parse import ParseResult -from django.core.handlers.asgi import ASGIHandler, ASGIRequest +from django.core.handlers.asgi import ASGIHandler from django.core.handlers.base import BaseHandler -from django.core.handlers.wsgi import WSGIHandler, WSGIRequest +from django.core.handlers.wsgi import WSGIHandler from django.http import HttpRequest -from django.http.response import HttpResponseBase +from django.http.response import FileResponse, HttpResponseBase class StaticFilesHandlerMixin: handles_files: bool = ... @@ -15,7 +15,7 @@ class StaticFilesHandlerMixin: def get_base_url(self) -> str: ... def _should_handle(self, path: str) -> bool: ... def file_path(self, url: str) -> str: ... - def serve(self, request: HttpRequest) -> HttpResponseBase: ... + def serve(self, request: HttpRequest) -> FileResponse: ... def get_response(self, request: HttpRequest) -> HttpResponseBase: ... async def get_response_async(self, request: HttpRequest) -> HttpResponseBase: ... diff --git a/django-stubs/contrib/staticfiles/views.pyi b/django-stubs/contrib/staticfiles/views.pyi index 59767a556..0f5fbb02a 100644 --- a/django-stubs/contrib/staticfiles/views.pyi +++ b/django-stubs/contrib/staticfiles/views.pyi @@ -1,6 +1,6 @@ from typing import Any from django.http.request import HttpRequest -from django.http.response import HttpResponseBase +from django.http.response import FileResponse -def serve(request: HttpRequest, path: str, insecure: bool = ..., **kwargs: Any) -> HttpResponseBase: ... +def serve(request: HttpRequest, path: str, insecure: bool = ..., **kwargs: Any) -> FileResponse: ... diff --git a/django-stubs/test/testcases.pyi b/django-stubs/test/testcases.pyi index 5b8c4479b..40b0199eb 100644 --- a/django-stubs/test/testcases.pyi +++ b/django-stubs/test/testcases.pyi @@ -26,7 +26,7 @@ from django.db.backends.base.base import BaseDatabaseWrapper from django.db.models.base import Model from django.db.models.query import QuerySet, RawQuerySet from django.forms.fields import EmailField -from django.http.response import HttpResponse, HttpResponseBase +from django.http.response import FileResponse, HttpResponseBase from django.template.base import Template from django.test.client import AsyncClient, Client from django.test.html import Element @@ -202,7 +202,7 @@ class FSFilesHandler(WSGIHandler): base_url: Any = ... def __init__(self, application: Any) -> None: ... def file_path(self, url: Any): ... - def serve(self, request: Any): ... + def serve(self, request: Any) -> FileResponse: ... class _StaticFilesHandler(FSFilesHandler): def get_base_dir(self): ... diff --git a/django-stubs/views/static.pyi b/django-stubs/views/static.pyi index a1b9c896c..80d194394 100644 --- a/django-stubs/views/static.pyi +++ b/django-stubs/views/static.pyi @@ -1,11 +1,11 @@ from typing import Any, Optional +from django.http import FileResponse from django.http.request import HttpRequest -from django.http.response import HttpResponseBase def serve( request: HttpRequest, path: str, document_root: Optional[str] = ..., show_indexes: bool = ... -) -> HttpResponseBase: ... +) -> FileResponse: ... DEFAULT_DIRECTORY_INDEX_TEMPLATE: str template_translatable: Any