Make decorator functions transparent to Mypy (#306)

By declaring return type as -> Callable[[_C], _C], Mypy can infer that
the decorated function has also the same arguments and return type as
the original.

View functions are constrained to return HttpResponseBase (or any
subclass of it).

Also added typecheck test coverage to most of the cases.
This commit is contained in:
Marti Raudsepp
2020-03-11 23:32:30 +02:00
committed by GitHub
parent f77073157b
commit afa16bfb74
8 changed files with 126 additions and 14 deletions
+15 -7
View File
@@ -1,13 +1,21 @@
from typing import Callable, List, Optional, Set, Union
from typing import Callable, List, Optional, Set, Union, TypeVar, overload
from django.contrib.auth import REDIRECT_FIELD_NAME as REDIRECT_FIELD_NAME # noqa: F401
from django.http.response import HttpResponseBase
from django.contrib.auth.models import AbstractUser
_VIEW = TypeVar("_VIEW", bound=Callable[..., HttpResponseBase])
def user_passes_test(
test_func: Callable, login_url: Optional[str] = ..., redirect_field_name: str = ...
) -> Callable: ...
def login_required(
function: Optional[Callable] = ..., redirect_field_name: str = ..., login_url: Optional[str] = ...
) -> Callable: ...
test_func: Callable[[AbstractUser], bool], login_url: Optional[str] = ..., redirect_field_name: str = ...
) -> Callable[[_VIEW], _VIEW]: ...
# There are two ways of calling @login_required: @with(arguments) and @bare
@overload
def login_required(redirect_field_name: str = ..., login_url: Optional[str] = ...) -> Callable[[_VIEW], _VIEW]: ...
@overload
def login_required(function: _VIEW, redirect_field_name: str = ..., login_url: Optional[str] = ...) -> _VIEW: ...
def permission_required(
perm: Union[List[str], Set[str], str], login_url: None = ..., raise_exception: bool = ...
) -> Callable: ...
) -> Callable[[_VIEW], _VIEW]: ...
+8 -1
View File
@@ -39,4 +39,11 @@ def atomic(using: _C) -> _C: ...
# Decorator or context-manager with parameters
@overload
def atomic(using: Optional[str] = ..., savepoint: bool = ...) -> Atomic: ...
def non_atomic_requests(using: Callable = ...) -> Callable: ...
# Bare decorator
@overload
def non_atomic_requests(using: _C) -> _C: ...
# Decorator with arguments
@overload
def non_atomic_requests(using: Optional[str] = ...) -> Callable[[_C], _C]: ...
+4 -2
View File
@@ -16,6 +16,7 @@ from typing import (
Type,
Union,
ContextManager,
TypeVar,
)
from django.apps.registry import Apps
@@ -29,6 +30,7 @@ from django.conf import LazySettings, Settings
_TestClass = Type[SimpleTestCase]
_DecoratedTest = Union[Callable, _TestClass]
_C = TypeVar("_C", bound=Callable) # Any callable
TZ_SUPPORT: bool = ...
@@ -56,7 +58,7 @@ class TestContextDecorator:
def __enter__(self) -> Optional[Apps]: ...
def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None: ...
def decorate_class(self, cls: _TestClass) -> _TestClass: ...
def decorate_callable(self, func: Callable) -> Callable: ...
def decorate_callable(self, func: _C) -> _C: ...
def __call__(self, decorated: _DecoratedTest) -> Any: ...
class override_settings(TestContextDecorator):
@@ -146,7 +148,7 @@ def get_unique_databases_and_mirrors() -> Tuple[Dict[_Signature, _TestDatabase],
def teardown_databases(
old_config: Iterable[Tuple[Any, str, bool]], verbosity: int, parallel: int = ..., keepdb: bool = ...
) -> None: ...
def require_jinja2(test_func: Callable) -> Callable: ...
def require_jinja2(test_func: _C) -> _C: ...
@contextmanager
def register_lookup(
field: Type[RegisterLookupMixin], *lookups: Type[Union[Lookup, Transform]], lookup_name: Optional[str] = ...
+6 -3
View File
@@ -1,13 +1,16 @@
from typing import Any, Callable, Iterable, Optional, Type, Union
from typing import Any, Callable, Iterable, Optional, Type, Union, TypeVar
from django.utils.deprecation import MiddlewareMixin
from django.views.generic.base import View
_T = TypeVar("_T", bound=Union[View, Callable]) # Any callable
class classonlymethod(classmethod): ...
def method_decorator(decorator: Union[Callable, Iterable[Callable]], name: str = ...) -> Callable: ...
def method_decorator(decorator: Union[Callable, Iterable[Callable]], name: str = ...) -> Callable[[_T], _T]: ...
def decorator_from_middleware_with_args(middleware_class: type) -> Callable: ...
def decorator_from_middleware(middleware_class: type) -> Callable: ...
def available_attrs(fn: Any): ...
def available_attrs(fn: Callable): ...
def make_middleware_decorator(middleware_class: Type[MiddlewareMixin]) -> Callable: ...
class classproperty: