Improve decorator typing (#4676)

* Add a missing setupmethod decorator

* Improve the decorator typing

This will allow type checkers to understand that the decorators return
the same function signature as passed as an argument. This follows the
guidelines from
https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators.

I've chosen to keep a TypeVar per module and usage as I think
encouraged by PEP 695, which I hope is accepted as the syntax is much
nicer.
This commit is contained in:
Phil Jones 2022-07-06 22:05:20 +01:00 committed by GitHub
parent d7482cd765
commit 9b44bf2818
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 123 additions and 76 deletions

View file

@ -13,6 +13,23 @@ if t.TYPE_CHECKING: # pragma: no cover
from .app import Flask
DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable]
T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable)
T_before_first_request = t.TypeVar(
"T_before_first_request", bound=ft.BeforeFirstRequestCallable
)
T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable)
T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable)
T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
T_template_context_processor = t.TypeVar(
"T_template_context_processor", bound=ft.TemplateContextProcessorCallable
)
T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable)
T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable)
T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable)
T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable)
T_url_value_preprocessor = t.TypeVar(
"T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable
)
class BlueprintSetupState:
@ -236,7 +253,7 @@ class Blueprint(Scaffold):
if state.first_registration:
func(state)
return self.record(update_wrapper(wrapper, func))
self.record(update_wrapper(wrapper, func))
def make_setup_state(
self, app: "Flask", options: dict, first_registration: bool = False
@ -392,7 +409,7 @@ class Blueprint(Scaffold):
self,
rule: str,
endpoint: t.Optional[str] = None,
view_func: t.Optional[ft.ViewCallable] = None,
view_func: t.Optional[ft.RouteCallable] = None,
provide_automatic_options: t.Optional[bool] = None,
**options: t.Any,
) -> None:
@ -418,7 +435,7 @@ class Blueprint(Scaffold):
@setupmethod
def app_template_filter(
self, name: t.Optional[str] = None
) -> t.Callable[[ft.TemplateFilterCallable], ft.TemplateFilterCallable]:
) -> t.Callable[[T_template_filter], T_template_filter]:
"""Register a custom template filter, available application wide. Like
:meth:`Flask.template_filter` but for a blueprint.
@ -426,7 +443,7 @@ class Blueprint(Scaffold):
function name will be used.
"""
def decorator(f: ft.TemplateFilterCallable) -> ft.TemplateFilterCallable:
def decorator(f: T_template_filter) -> T_template_filter:
self.add_app_template_filter(f, name=name)
return f
@ -452,7 +469,7 @@ class Blueprint(Scaffold):
@setupmethod
def app_template_test(
self, name: t.Optional[str] = None
) -> t.Callable[[ft.TemplateTestCallable], ft.TemplateTestCallable]:
) -> t.Callable[[T_template_test], T_template_test]:
"""Register a custom template test, available application wide. Like
:meth:`Flask.template_test` but for a blueprint.
@ -462,7 +479,7 @@ class Blueprint(Scaffold):
function name will be used.
"""
def decorator(f: ft.TemplateTestCallable) -> ft.TemplateTestCallable:
def decorator(f: T_template_test) -> T_template_test:
self.add_app_template_test(f, name=name)
return f
@ -490,7 +507,7 @@ class Blueprint(Scaffold):
@setupmethod
def app_template_global(
self, name: t.Optional[str] = None
) -> t.Callable[[ft.TemplateGlobalCallable], ft.TemplateGlobalCallable]:
) -> t.Callable[[T_template_global], T_template_global]:
"""Register a custom template global, available application wide. Like
:meth:`Flask.template_global` but for a blueprint.
@ -500,7 +517,7 @@ class Blueprint(Scaffold):
function name will be used.
"""
def decorator(f: ft.TemplateGlobalCallable) -> ft.TemplateGlobalCallable:
def decorator(f: T_template_global) -> T_template_global:
self.add_app_template_global(f, name=name)
return f
@ -526,9 +543,7 @@ class Blueprint(Scaffold):
self.record_once(register_template)
@setupmethod
def before_app_request(
self, f: ft.BeforeRequestCallable
) -> ft.BeforeRequestCallable:
def before_app_request(self, f: T_before_request) -> T_before_request:
"""Like :meth:`Flask.before_request`. Such a function is executed
before each request, even if outside of a blueprint.
"""
@ -539,8 +554,8 @@ class Blueprint(Scaffold):
@setupmethod
def before_app_first_request(
self, f: ft.BeforeFirstRequestCallable
) -> ft.BeforeFirstRequestCallable:
self, f: T_before_first_request
) -> T_before_first_request:
"""Like :meth:`Flask.before_first_request`. Such a function is
executed before the first request to the application.
@ -560,7 +575,8 @@ class Blueprint(Scaffold):
self.record_once(lambda s: s.app.before_first_request_funcs.append(f))
return f
def after_app_request(self, f: ft.AfterRequestCallable) -> ft.AfterRequestCallable:
@setupmethod
def after_app_request(self, f: T_after_request) -> T_after_request:
"""Like :meth:`Flask.after_request` but for a blueprint. Such a function
is executed after each request, even if outside of the blueprint.
"""
@ -570,7 +586,7 @@ class Blueprint(Scaffold):
return f
@setupmethod
def teardown_app_request(self, f: ft.TeardownCallable) -> ft.TeardownCallable:
def teardown_app_request(self, f: T_teardown) -> T_teardown:
"""Like :meth:`Flask.teardown_request` but for a blueprint. Such a
function is executed when tearing down each request, even if outside of
the blueprint.
@ -582,8 +598,8 @@ class Blueprint(Scaffold):
@setupmethod
def app_context_processor(
self, f: ft.TemplateContextProcessorCallable
) -> ft.TemplateContextProcessorCallable:
self, f: T_template_context_processor
) -> T_template_context_processor:
"""Like :meth:`Flask.context_processor` but for a blueprint. Such a
function is executed each request, even if outside of the blueprint.
"""
@ -595,12 +611,12 @@ class Blueprint(Scaffold):
@setupmethod
def app_errorhandler(
self, code: t.Union[t.Type[Exception], int]
) -> t.Callable[[ft.ErrorHandlerDecorator], ft.ErrorHandlerDecorator]:
) -> t.Callable[[T_error_handler], T_error_handler]:
"""Like :meth:`Flask.errorhandler` but for a blueprint. This
handler is used for all requests, even if outside of the blueprint.
"""
def decorator(f: ft.ErrorHandlerDecorator) -> ft.ErrorHandlerDecorator:
def decorator(f: T_error_handler) -> T_error_handler:
self.record_once(lambda s: s.app.errorhandler(code)(f))
return f
@ -608,8 +624,8 @@ class Blueprint(Scaffold):
@setupmethod
def app_url_value_preprocessor(
self, f: ft.URLValuePreprocessorCallable
) -> ft.URLValuePreprocessorCallable:
self, f: T_url_value_preprocessor
) -> T_url_value_preprocessor:
"""Same as :meth:`url_value_preprocessor` but application wide."""
self.record_once(
lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f)
@ -617,7 +633,7 @@ class Blueprint(Scaffold):
return f
@setupmethod
def app_url_defaults(self, f: ft.URLDefaultCallable) -> ft.URLDefaultCallable:
def app_url_defaults(self, f: T_url_defaults) -> T_url_defaults:
"""Same as :meth:`url_defaults` but application wide."""
self.record_once(
lambda s: s.app.url_default_functions.setdefault(None, []).append(f)