diff --git a/django-stubs/conf/urls/__init__.pyi b/django-stubs/conf/urls/__init__.pyi index 6711367bd..611a96344 100644 --- a/django-stubs/conf/urls/__init__.pyi +++ b/django-stubs/conf/urls/__init__.pyi @@ -12,7 +12,7 @@ handler403: str | Callable[..., HttpResponse] handler404: str | Callable[..., HttpResponse] handler500: str | Callable[..., HttpResponse] -IncludedURLConf: TypeAlias = tuple[Sequence[URLResolver | URLPattern], str | None, str | None] +_IncludedURLConf: TypeAlias = tuple[Sequence[URLResolver | URLPattern], str | None, str | None] # Deprecated @overload @@ -21,7 +21,7 @@ def url( ) -> URLPattern: ... @overload def url( - regex: str, view: IncludedURLConf, kwargs: dict[str, Any] | None = ..., name: str | None = ... + regex: str, view: _IncludedURLConf, kwargs: dict[str, Any] | None = ..., name: str | None = ... ) -> URLResolver: ... @overload def url( diff --git a/django-stubs/urls/conf.pyi b/django-stubs/urls/conf.pyi index d73e4fb4a..e278805de 100644 --- a/django-stubs/urls/conf.pyi +++ b/django-stubs/urls/conf.pyi @@ -6,14 +6,12 @@ from django.urls import URLPattern, URLResolver, _AnyURL from django.utils.functional import _StrOrPromise from typing_extensions import TypeAlias -from ..conf.urls import IncludedURLConf +from ..conf.urls import _IncludedURLConf from ..http.response import HttpResponseBase _URLConf: TypeAlias = str | ModuleType | Sequence[_AnyURL] -def include( - arg: _URLConf | tuple[_URLConf, str], namespace: str | None = ... -) -> tuple[Sequence[URLResolver | URLPattern], str | None, str | None]: ... +def include(arg: _URLConf | tuple[_URLConf, str], namespace: str | None = ...) -> _IncludedURLConf: ... # path() @overload @@ -28,7 +26,9 @@ def path( name: str = ..., ) -> URLPattern: ... @overload -def path(route: _StrOrPromise, view: IncludedURLConf, kwargs: dict[str, Any] = ..., name: str = ...) -> URLResolver: ... +def path( + route: _StrOrPromise, view: _IncludedURLConf, kwargs: dict[str, Any] = ..., name: str = ... +) -> URLResolver: ... @overload def path( route: _StrOrPromise, view: Sequence[URLResolver | str], kwargs: dict[str, Any] = ..., name: str = ... @@ -48,7 +48,7 @@ def re_path( ) -> URLPattern: ... @overload def re_path( - route: _StrOrPromise, view: IncludedURLConf, kwargs: dict[str, Any] = ..., name: str = ... + route: _StrOrPromise, view: _IncludedURLConf, kwargs: dict[str, Any] = ..., name: str = ... ) -> URLResolver: ... @overload def re_path( diff --git a/pytest.ini b/pytest.ini index e30adfa9b..205000931 100644 --- a/pytest.ini +++ b/pytest.ini @@ -9,3 +9,4 @@ addopts = --cache-clear --mypy-ini-file=mypy.ini --mypy-extension-hook=scripts.tests_extension_hook.django_plugin_hook + --ignore=tests/assert_type diff --git a/scripts/stubtest/allowlist_todo.txt b/scripts/stubtest/allowlist_todo.txt index f5bd7c869..b7468dda0 100644 --- a/scripts/stubtest/allowlist_todo.txt +++ b/scripts/stubtest/allowlist_todo.txt @@ -7,7 +7,6 @@ django.conf.LazySettings.DEFAULT_FILE_STORAGE django.conf.LazySettings.STATICFILES_STORAGE django.conf.STATICFILES_STORAGE_DEPRECATED_MSG django.conf.global_settings.gettext_noop -django.conf.urls.IncludedURLConf django.conf.urls.url django.contrib.admin.FieldListFilter.title django.contrib.admin.ModelAdmin diff --git a/tests/assert_type/urls/test_conf.py b/tests/assert_type/urls/test_conf.py new file mode 100644 index 000000000..aa7273502 --- /dev/null +++ b/tests/assert_type/urls/test_conf.py @@ -0,0 +1,49 @@ +from typing import List, Tuple + +from django.conf.urls.i18n import urlpatterns as i18n_urlpatterns +from django.contrib import admin +from django.contrib.auth.views import LoginView +from django.contrib.flatpages import urls as flatpages_urls +from django.contrib.staticfiles.urls import staticfiles_urlpatterns +from django.http import HttpResponse +from django.urls import URLPattern, URLResolver, _AnyURL, include, path, re_path +from django.utils.translation import gettext_lazy as _ +from typing_extensions import assert_type + +# Test 'path' accepts mix of pattern and resolver object +include1: Tuple[List[_AnyURL], None, None] = ([], None, None) +assert_type(path("test/", include1), URLResolver) + +# Test 'path' accepts pattern resolver union subset +include2: Tuple[List[URLPattern], None, None] = ([], None, None) +assert_type(path("test/", include2), URLResolver) + +# Test 'path' +assert_type(path("admin/", admin.site.urls), URLResolver) +assert_type(path(_("admin/"), admin.site.urls, name="admin"), URLResolver) +assert_type(path("login/", LoginView.as_view(), name="login1"), URLPattern) +assert_type(path(_("login/"), LoginView.as_view(), name="login2"), URLPattern) + + +def v1() -> HttpResponse: ... +async def v2() -> HttpResponse: ... + + +assert_type(path("v1/", v1), URLPattern) +assert_type(path("v2/", v2), URLPattern) +assert_type(re_path("^v1/", v1), URLPattern) +assert_type(re_path("^v2/", v2), URLPattern) + +# Test 'include' +patterns1: List[_AnyURL] = [] +assert_type(re_path(_("^foo/"), include(patterns1)), URLResolver) +assert_type(re_path("^foo/", include(patterns1, namespace="foo")), URLResolver) +assert_type(re_path("^foo/", include((patterns1, "foo"), namespace="foo")), URLResolver) +assert_type(re_path("^foo/", include(patterns1, "foo")), URLResolver) +assert_type(path("flat/", include(flatpages_urls)), URLResolver) +assert_type(path("flat/", include((flatpages_urls, "static"))), URLResolver) +assert_type(path("i18n/", include(i18n_urlpatterns)), URLResolver) +assert_type(path("i18n/", include((i18n_urlpatterns, "i18n"))), URLResolver) +assert_type(path("admindocs/", include("django.contrib.admindocs.urls")), URLResolver) +assert_type(path("admindocs/", include(("django.contrib.admindocs.urls", "i18n"))), URLResolver) +assert_type(path("", include(staticfiles_urlpatterns(prefix="static/"))), URLResolver) diff --git a/tests/typecheck/urls/test_conf.yml b/tests/typecheck/urls/test_conf.yml deleted file mode 100644 index 464317d29..000000000 --- a/tests/typecheck/urls/test_conf.yml +++ /dev/null @@ -1,60 +0,0 @@ -- case: test_path_accepts_mix_of_pattern_and_resolver_output - main: | - from typing import List, Tuple, Union - from django.urls import path, _AnyURL - - def include() -> Tuple[List[_AnyURL], None, None]: ... - - path('test/', include()) - - -- case: test_path_accepts_pattern_resolver_union_subset - main: | - from typing import List, Tuple - from django.urls import path, URLPattern - - def include() -> Tuple[List[URLPattern], None, None]: ... - - path('test/', include()) - -- case: test_path_accepts_translated_patterns - main: | - from typing import List - from django.urls import path, _AnyURL, re_path, include - from django.utils.translation import gettext_lazy as _ - from django.contrib import admin - - foo_patterns: List[_AnyURL] = [] - - urlpatterns: List[_AnyURL] = [ - path(_("admin/"), admin.site.urls, name="admin"), - re_path(_("^foo/"), include(foo_patterns)), - ] - -- 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/'))) - ]