From 1a36c6c379693ed4a472058748e00473cd229eac Mon Sep 17 00:00:00 2001 From: Marti Raudsepp Date: Fri, 6 May 2022 09:00:21 +0300 Subject: [PATCH] Improve type hints of URL conf & include() (#949) * Improve type hints of URL conf & include() The type of `urlpatterns` list is `List[Union[URLPattern, URLResolver]]`. * https://docs.djangoproject.com/en/dev/ref/urls/#django.urls.include * https://docs.djangoproject.com/en/4.0/ref/urls/ * Alias _AnyURL = Union[URLPattern, URLResolver] * Fix extract_views_from_urlpatterns --- django-stubs/conf/urls/i18n.pyi | 10 +++---- django-stubs/contrib/admindocs/urls.pyi | 6 +++-- django-stubs/contrib/admindocs/views.pyi | 7 +++-- django-stubs/contrib/auth/urls.pyi | 6 +++-- django-stubs/contrib/flatpages/urls.pyi | 6 +++-- django-stubs/contrib/staticfiles/urls.pyi | 6 ++--- django-stubs/core/checks/urls.pyi | 6 ++--- django-stubs/urls/__init__.pyi | 4 +++ django-stubs/urls/conf.pyi | 8 ++++-- django-stubs/urls/resolvers.pyi | 9 ++++--- tests/typecheck/urls/test_conf.yml | 32 +++++++++++++++++++++-- 11 files changed, 72 insertions(+), 28 deletions(-) diff --git a/django-stubs/conf/urls/i18n.pyi b/django-stubs/conf/urls/i18n.pyi index ee4b533..9e7681e 100644 --- a/django-stubs/conf/urls/i18n.pyi +++ b/django-stubs/conf/urls/i18n.pyi @@ -1,10 +1,8 @@ -from typing import Callable, List, Tuple, Union +from typing import List, Tuple -from django.urls.resolvers import URLPattern, URLResolver +from django.urls import _AnyURL -def i18n_patterns( - *urls: Union[URLPattern, URLResolver], prefix_default_language: bool = ... -) -> List[Union[URLPattern, URLResolver]]: ... +def i18n_patterns(*urls: _AnyURL, prefix_default_language: bool = ...) -> List[_AnyURL]: ... def is_language_prefix_patterns_used(urlconf: str) -> Tuple[bool, bool]: ... -urlpatterns: List[Callable] +urlpatterns: List[_AnyURL] diff --git a/django-stubs/contrib/admindocs/urls.pyi b/django-stubs/contrib/admindocs/urls.pyi index 99cbb7c..3df94ca 100644 --- a/django-stubs/contrib/admindocs/urls.pyi +++ b/django-stubs/contrib/admindocs/urls.pyi @@ -1,3 +1,5 @@ -from typing import Any, List +from typing import List -urlpatterns: List[Any] = ... +from django.urls import _AnyURL + +urlpatterns: List[_AnyURL] = ... diff --git a/django-stubs/contrib/admindocs/views.pyi b/django-stubs/contrib/admindocs/views.pyi index 48ef49b..60fa6cc 100644 --- a/django-stubs/contrib/admindocs/views.pyi +++ b/django-stubs/contrib/admindocs/views.pyi @@ -1,6 +1,7 @@ -from typing import Any, Optional, Union +from typing import Any, Callable, Iterable, List, Optional, Pattern, Tuple, Union from django.db.models.fields import Field +from django.urls import _AnyURL from django.views.generic import TemplateView MODEL_METHODS_EXCLUDE: Any @@ -17,5 +18,7 @@ class TemplateDetailView(BaseAdminDocsView): ... def get_return_data_type(func_name: Any): ... def get_readable_field_data_type(field: Union[Field, str]) -> str: ... -def extract_views_from_urlpatterns(urlpatterns: Any, base: str = ..., namespace: Optional[Any] = ...): ... +def extract_views_from_urlpatterns( + urlpatterns: Iterable[_AnyURL], base: str = ..., namespace: Optional[str] = ... +) -> List[Tuple[Callable, Pattern[str], Optional[str], Optional[str]]]: ... def simplify_regex(pattern: str) -> str: ... diff --git a/django-stubs/contrib/auth/urls.pyi b/django-stubs/contrib/auth/urls.pyi index 99cbb7c..3df94ca 100644 --- a/django-stubs/contrib/auth/urls.pyi +++ b/django-stubs/contrib/auth/urls.pyi @@ -1,3 +1,5 @@ -from typing import Any, List +from typing import List -urlpatterns: List[Any] = ... +from django.urls import _AnyURL + +urlpatterns: List[_AnyURL] = ... diff --git a/django-stubs/contrib/flatpages/urls.pyi b/django-stubs/contrib/flatpages/urls.pyi index 99cbb7c..3df94ca 100644 --- a/django-stubs/contrib/flatpages/urls.pyi +++ b/django-stubs/contrib/flatpages/urls.pyi @@ -1,3 +1,5 @@ -from typing import Any, List +from typing import List -urlpatterns: List[Any] = ... +from django.urls import _AnyURL + +urlpatterns: List[_AnyURL] = ... diff --git a/django-stubs/contrib/staticfiles/urls.pyi b/django-stubs/contrib/staticfiles/urls.pyi index 3070976..dec7e6c 100644 --- a/django-stubs/contrib/staticfiles/urls.pyi +++ b/django-stubs/contrib/staticfiles/urls.pyi @@ -1,7 +1,7 @@ -from typing import Any, List, Optional +from typing import List, Optional -from django.urls.resolvers import URLPattern +from django.urls import URLPattern, _AnyURL -urlpatterns: List[Any] = ... +urlpatterns: List[_AnyURL] = ... def staticfiles_urlpatterns(prefix: Optional[str] = ...) -> List[URLPattern]: ... diff --git a/django-stubs/core/checks/urls.pyi b/django-stubs/core/checks/urls.pyi index 8565eab..cf65172 100644 --- a/django-stubs/core/checks/urls.pyi +++ b/django-stubs/core/checks/urls.pyi @@ -1,11 +1,11 @@ -from typing import Any, Optional, Sequence, Union +from typing import Any, Optional, Sequence from django.apps.config import AppConfig from django.core.checks.messages import CheckMessage, Error, Warning -from django.urls.resolvers import URLPattern, URLResolver +from django.urls import _AnyURL def check_url_config(app_configs: Optional[Sequence[AppConfig]], **kwargs: Any) -> Sequence[CheckMessage]: ... -def check_resolver(resolver: Union[URLPattern, URLResolver]) -> Sequence[CheckMessage]: ... +def check_resolver(resolver: _AnyURL) -> Sequence[CheckMessage]: ... def check_url_namespaces_unique(app_configs: Optional[Sequence[AppConfig]], **kwargs: Any) -> Sequence[Warning]: ... def get_warning_for_invalid_pattern(pattern: Any) -> Sequence[Error]: ... def check_url_settings(app_configs: Optional[Sequence[AppConfig]], **kwargs: Any) -> Sequence[Error]: ... diff --git a/django-stubs/urls/__init__.pyi b/django-stubs/urls/__init__.pyi index 0144ac2..f594236 100644 --- a/django-stubs/urls/__init__.pyi +++ b/django-stubs/urls/__init__.pyi @@ -1,3 +1,5 @@ +from typing import Union + # noinspection PyUnresolvedReferences from .base import clear_script_prefix as clear_script_prefix from .base import clear_url_caches as clear_url_caches @@ -34,3 +36,5 @@ from .resolvers import get_resolver as get_resolver # noinspection PyUnresolvedReferences from .utils import get_callable as get_callable from .utils import get_mod_func as get_mod_func + +_AnyURL = Union[URLPattern, URLResolver] diff --git a/django-stubs/urls/conf.pyi b/django-stubs/urls/conf.pyi index 4a79578..47cd6b4 100644 --- a/django-stubs/urls/conf.pyi +++ b/django-stubs/urls/conf.pyi @@ -1,11 +1,15 @@ +from types import ModuleType from typing import Any, Callable, Dict, Optional, Sequence, Tuple, Union, overload +from django.urls import URLPattern, URLResolver, _AnyURL + from ..conf.urls import IncludedURLConf from ..http.response import HttpResponseBase -from .resolvers import URLPattern, URLResolver + +_URLConf = Union[str, ModuleType, Sequence[_AnyURL]] def include( - arg: Any, namespace: Optional[str] = ... + arg: Union[_URLConf, tuple[_URLConf, str]], namespace: Optional[str] = ... ) -> Tuple[Sequence[Union[URLResolver, URLPattern]], Optional[str], Optional[str]]: ... # path() diff --git a/django-stubs/urls/resolvers.pyi b/django-stubs/urls/resolvers.pyi index 9c5cf10..1993fe3 100644 --- a/django-stubs/urls/resolvers.pyi +++ b/django-stubs/urls/resolvers.pyi @@ -2,6 +2,7 @@ from types import ModuleType from typing import Any, Callable, Dict, List, Optional, Pattern, Sequence, Tuple, Type, Union, overload from django.core.checks.messages import CheckMessage +from django.urls import _AnyURL from django.urls.converters import UUIDConverter from django.utils.datastructures import MultiValueDict @@ -96,7 +97,7 @@ class URLPattern: class URLResolver: pattern: _Pattern = ... - urlconf_name: Union[str, None, Sequence[Union[URLPattern, URLResolver]]] = ... + urlconf_name: Union[str, None, Sequence[_AnyURL]] = ... callback: None = ... default_kwargs: Dict[str, Any] = ... namespace: Optional[str] = ... @@ -106,7 +107,7 @@ class URLResolver: def __init__( self, pattern: _Pattern, - urlconf_name: Union[str, None, Sequence[Union[URLPattern, URLResolver]]], + urlconf_name: Union[str, None, Sequence[_AnyURL]], default_kwargs: Optional[Dict[str, Any]] = ..., app_name: Optional[str] = ..., namespace: Optional[str] = ..., @@ -118,9 +119,9 @@ class URLResolver: @property def app_dict(self) -> Dict[str, List[str]]: ... @property - def urlconf_module(self) -> Union[ModuleType, None, Sequence[Union[URLPattern, URLResolver]]]: ... + def urlconf_module(self) -> Union[ModuleType, None, Sequence[_AnyURL]]: ... @property - def url_patterns(self) -> List[Union[URLPattern, URLResolver]]: ... + def url_patterns(self) -> List[_AnyURL]: ... def resolve(self, path: str) -> ResolverMatch: ... def resolve_error_handler(self, view_type: int) -> Callable: ... def reverse(self, lookup_view: str, *args: Any, **kwargs: Any) -> str: ... diff --git a/tests/typecheck/urls/test_conf.yml b/tests/typecheck/urls/test_conf.yml index 047264d..5039df1 100644 --- a/tests/typecheck/urls/test_conf.yml +++ b/tests/typecheck/urls/test_conf.yml @@ -1,9 +1,9 @@ - case: test_path_accepts_mix_of_pattern_and_resolver_output main: | from typing import List, Tuple, Union - from django.urls import path, URLPattern, URLResolver + from django.urls import path, _AnyURL - def include() -> Tuple[List[Union[URLPattern, URLResolver]], None, None]: ... + def include() -> Tuple[List[_AnyURL], None, None]: ... path('test/', include()) @@ -16,3 +16,31 @@ def include() -> Tuple[List[URLPattern], None, None]: ... path('test/', include()) + +- case: test_urlconf_include + main: | + from typing import List + + from django.conf.urls.i18n import urlpatterns as i18n_urlpatterns + from django.contrib.auth.views import LoginView + from django.contrib.staticfiles.urls import staticfiles_urlpatterns + from django.contrib import admin + from django.contrib.flatpages import urls as flatpages_urls + from django.urls import _AnyURL, re_path, include, path + + foo_patterns: List[_AnyURL] = [] + + urlpatterns: List[_AnyURL] = [ + path('login/', LoginView.as_view(), name='login'), + path('admin/', admin.site.urls), + re_path('^foo/', include(foo_patterns, namespace='foo')), + re_path('^foo/', include((foo_patterns, 'foo'), namespace='foo')), + re_path('^foo/', include(foo_patterns, 'foo')), + path('flat/', include(flatpages_urls)), + path('flat/', include((flatpages_urls, 'static'))), + path('i18n/', include(i18n_urlpatterns)), + path('i18n/', include((i18n_urlpatterns, 'i18n'))), + path('admindocs/', include('django.contrib.admindocs.urls')), + path('admindocs/', include(('django.contrib.admindocs.urls', 'i18n'))), + path('', include(staticfiles_urlpatterns(prefix='static/'))) + ]