forked from orbit-oss/flask
Merge pull request #4487 from pallets/typing-errorhandler
relax `errorhandler` function arg type
This commit is contained in:
commit
cb7cd1e79b
6 changed files with 34 additions and 36 deletions
|
|
@ -47,6 +47,9 @@ Unreleased
|
||||||
loader thread. :issue:`4460`
|
loader thread. :issue:`4460`
|
||||||
- Deleting the session cookie uses the ``httponly`` flag.
|
- Deleting the session cookie uses the ``httponly`` flag.
|
||||||
:issue:`4485`
|
:issue:`4485`
|
||||||
|
- Relax typing for ``errorhandler`` to allow the user to use more
|
||||||
|
precise types and decorate the same function multiple times.
|
||||||
|
:issue:`4095, 4295, 4297`
|
||||||
|
|
||||||
|
|
||||||
Version 2.0.3
|
Version 2.0.3
|
||||||
|
|
|
||||||
|
|
@ -1265,9 +1265,7 @@ class Flask(Scaffold):
|
||||||
self.shell_context_processors.append(f)
|
self.shell_context_processors.append(f)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def _find_error_handler(
|
def _find_error_handler(self, e: Exception) -> t.Optional["ErrorHandlerCallable"]:
|
||||||
self, e: Exception
|
|
||||||
) -> t.Optional["ErrorHandlerCallable[Exception]"]:
|
|
||||||
"""Return a registered error handler for an exception in this order:
|
"""Return a registered error handler for an exception in this order:
|
||||||
blueprint handler for a specific code, app handler for a specific code,
|
blueprint handler for a specific code, app handler for a specific code,
|
||||||
blueprint handler for an exception class, app handler for an exception
|
blueprint handler for an exception class, app handler for an exception
|
||||||
|
|
@ -1674,13 +1672,13 @@ class Flask(Scaffold):
|
||||||
|
|
||||||
# a 3-tuple is unpacked directly
|
# a 3-tuple is unpacked directly
|
||||||
if len_rv == 3:
|
if len_rv == 3:
|
||||||
rv, status, headers = rv
|
rv, status, headers = rv # type: ignore[misc]
|
||||||
# decide if a 2-tuple has status or headers
|
# decide if a 2-tuple has status or headers
|
||||||
elif len_rv == 2:
|
elif len_rv == 2:
|
||||||
if isinstance(rv[1], (Headers, dict, tuple, list)):
|
if isinstance(rv[1], (Headers, dict, tuple, list)):
|
||||||
rv, headers = rv
|
rv, headers = rv
|
||||||
else:
|
else:
|
||||||
rv, status = rv
|
rv, status = rv # type: ignore[misc]
|
||||||
# other sized tuples are not allowed
|
# other sized tuples are not allowed
|
||||||
else:
|
else:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
|
|
@ -1703,7 +1701,11 @@ class Flask(Scaffold):
|
||||||
# let the response class set the status and headers instead of
|
# let the response class set the status and headers instead of
|
||||||
# waiting to do it manually, so that the class can handle any
|
# waiting to do it manually, so that the class can handle any
|
||||||
# special logic
|
# special logic
|
||||||
rv = self.response_class(rv, status=status, headers=headers)
|
rv = self.response_class(
|
||||||
|
rv,
|
||||||
|
status=status,
|
||||||
|
headers=headers, # type: ignore[arg-type]
|
||||||
|
)
|
||||||
status = headers = None
|
status = headers = None
|
||||||
elif isinstance(rv, dict):
|
elif isinstance(rv, dict):
|
||||||
rv = jsonify(rv)
|
rv = jsonify(rv)
|
||||||
|
|
@ -1731,13 +1733,13 @@ class Flask(Scaffold):
|
||||||
# prefer the status if it was provided
|
# prefer the status if it was provided
|
||||||
if status is not None:
|
if status is not None:
|
||||||
if isinstance(status, (str, bytes, bytearray)):
|
if isinstance(status, (str, bytes, bytearray)):
|
||||||
rv.status = status # type: ignore
|
rv.status = status
|
||||||
else:
|
else:
|
||||||
rv.status_code = status
|
rv.status_code = status
|
||||||
|
|
||||||
# extend existing headers with provided headers
|
# extend existing headers with provided headers
|
||||||
if headers:
|
if headers:
|
||||||
rv.headers.update(headers)
|
rv.headers.update(headers) # type: ignore[arg-type]
|
||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -574,9 +574,7 @@ class Blueprint(Scaffold):
|
||||||
handler is used for all requests, even if outside of the blueprint.
|
handler is used for all requests, even if outside of the blueprint.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(
|
def decorator(f: "ErrorHandlerCallable") -> "ErrorHandlerCallable":
|
||||||
f: "ErrorHandlerCallable[Exception]",
|
|
||||||
) -> "ErrorHandlerCallable[Exception]":
|
|
||||||
self.record_once(lambda s: s.app.errorhandler(code)(f))
|
self.record_once(lambda s: s.app.errorhandler(code)(f))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ def make_response(*args: t.Any) -> "Response":
|
||||||
return current_app.response_class()
|
return current_app.response_class()
|
||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
args = args[0]
|
args = args[0]
|
||||||
return current_app.make_response(args)
|
return current_app.make_response(args) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def url_for(endpoint: str, **values: t.Any) -> str:
|
def url_for(endpoint: str, **values: t.Any) -> str:
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ from .templating import _default_template_ctx_processor
|
||||||
from .typing import AfterRequestCallable
|
from .typing import AfterRequestCallable
|
||||||
from .typing import AppOrBlueprintKey
|
from .typing import AppOrBlueprintKey
|
||||||
from .typing import BeforeRequestCallable
|
from .typing import BeforeRequestCallable
|
||||||
from .typing import GenericException
|
|
||||||
from .typing import TeardownCallable
|
from .typing import TeardownCallable
|
||||||
from .typing import TemplateContextProcessorCallable
|
from .typing import TemplateContextProcessorCallable
|
||||||
from .typing import URLDefaultCallable
|
from .typing import URLDefaultCallable
|
||||||
|
|
@ -145,10 +144,7 @@ class Scaffold:
|
||||||
#: directly and its format may change at any time.
|
#: directly and its format may change at any time.
|
||||||
self.error_handler_spec: t.Dict[
|
self.error_handler_spec: t.Dict[
|
||||||
AppOrBlueprintKey,
|
AppOrBlueprintKey,
|
||||||
t.Dict[
|
t.Dict[t.Optional[int], t.Dict[t.Type[Exception], "ErrorHandlerCallable"]],
|
||||||
t.Optional[int],
|
|
||||||
t.Dict[t.Type[Exception], "ErrorHandlerCallable[Exception]"],
|
|
||||||
],
|
|
||||||
] = defaultdict(lambda: defaultdict(dict))
|
] = defaultdict(lambda: defaultdict(dict))
|
||||||
|
|
||||||
#: A data structure of functions to call at the beginning of
|
#: A data structure of functions to call at the beginning of
|
||||||
|
|
@ -652,11 +648,8 @@ class Scaffold:
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def errorhandler(
|
def errorhandler(
|
||||||
self, code_or_exception: t.Union[t.Type[GenericException], int]
|
self, code_or_exception: t.Union[t.Type[Exception], int]
|
||||||
) -> t.Callable[
|
) -> t.Callable[["ErrorHandlerCallable"], "ErrorHandlerCallable"]:
|
||||||
["ErrorHandlerCallable[GenericException]"],
|
|
||||||
"ErrorHandlerCallable[GenericException]",
|
|
||||||
]:
|
|
||||||
"""Register a function to handle errors by code or exception class.
|
"""Register a function to handle errors by code or exception class.
|
||||||
|
|
||||||
A decorator that is used to register a function given an
|
A decorator that is used to register a function given an
|
||||||
|
|
@ -686,9 +679,7 @@ class Scaffold:
|
||||||
an arbitrary exception
|
an arbitrary exception
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(
|
def decorator(f: "ErrorHandlerCallable") -> "ErrorHandlerCallable":
|
||||||
f: "ErrorHandlerCallable[GenericException]",
|
|
||||||
) -> "ErrorHandlerCallable[GenericException]":
|
|
||||||
self.register_error_handler(code_or_exception, f)
|
self.register_error_handler(code_or_exception, f)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
@ -697,8 +688,8 @@ class Scaffold:
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def register_error_handler(
|
def register_error_handler(
|
||||||
self,
|
self,
|
||||||
code_or_exception: t.Union[t.Type[GenericException], int],
|
code_or_exception: t.Union[t.Type[Exception], int],
|
||||||
f: "ErrorHandlerCallable[GenericException]",
|
f: "ErrorHandlerCallable",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Alternative error attach function to the :meth:`errorhandler`
|
"""Alternative error attach function to the :meth:`errorhandler`
|
||||||
decorator that is more straightforward to use for non decorator
|
decorator that is more straightforward to use for non decorator
|
||||||
|
|
@ -722,9 +713,7 @@ class Scaffold:
|
||||||
" instead."
|
" instead."
|
||||||
) from None
|
) from None
|
||||||
|
|
||||||
self.error_handler_spec[None][code][exc_class] = t.cast(
|
self.error_handler_spec[None][code][exc_class] = f
|
||||||
"ErrorHandlerCallable[Exception]", f
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_exc_class_and_code(
|
def _get_exc_class_and_code(
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,16 @@ import typing as t
|
||||||
if t.TYPE_CHECKING:
|
if t.TYPE_CHECKING:
|
||||||
from _typeshed.wsgi import WSGIApplication # noqa: F401
|
from _typeshed.wsgi import WSGIApplication # noqa: F401
|
||||||
from werkzeug.datastructures import Headers # noqa: F401
|
from werkzeug.datastructures import Headers # noqa: F401
|
||||||
from .wrappers import Response # noqa: F401
|
from werkzeug.wrappers.response import Response # noqa: F401
|
||||||
|
|
||||||
# The possible types that are directly convertible or are a Response object.
|
# The possible types that are directly convertible or are a Response object.
|
||||||
ResponseValue = t.Union[
|
ResponseValue = t.Union[
|
||||||
"Response",
|
"Response",
|
||||||
t.AnyStr,
|
str,
|
||||||
|
bytes,
|
||||||
t.Dict[str, t.Any], # any jsonify-able dict
|
t.Dict[str, t.Any], # any jsonify-able dict
|
||||||
t.Generator[t.AnyStr, None, None],
|
t.Iterator[str],
|
||||||
|
t.Iterator[bytes],
|
||||||
]
|
]
|
||||||
StatusCode = int
|
StatusCode = int
|
||||||
|
|
||||||
|
|
@ -33,8 +35,6 @@ ResponseReturnValue = t.Union[
|
||||||
"WSGIApplication",
|
"WSGIApplication",
|
||||||
]
|
]
|
||||||
|
|
||||||
GenericException = t.TypeVar("GenericException", bound=Exception, contravariant=True)
|
|
||||||
|
|
||||||
AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named
|
AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named
|
||||||
AfterRequestCallable = t.Callable[["Response"], "Response"]
|
AfterRequestCallable = t.Callable[["Response"], "Response"]
|
||||||
BeforeFirstRequestCallable = t.Callable[[], None]
|
BeforeFirstRequestCallable = t.Callable[[], None]
|
||||||
|
|
@ -46,4 +46,10 @@ TemplateGlobalCallable = t.Callable[..., t.Any]
|
||||||
TemplateTestCallable = t.Callable[..., bool]
|
TemplateTestCallable = t.Callable[..., bool]
|
||||||
URLDefaultCallable = t.Callable[[str, dict], None]
|
URLDefaultCallable = t.Callable[[str, dict], None]
|
||||||
URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], None]
|
URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], None]
|
||||||
ErrorHandlerCallable = t.Callable[[GenericException], ResponseReturnValue]
|
# This should take Exception, but that either breaks typing the argument
|
||||||
|
# with a specific exception, or decorating multiple times with different
|
||||||
|
# exceptions (and using a union type on the argument).
|
||||||
|
# https://github.com/pallets/flask/issues/4095
|
||||||
|
# https://github.com/pallets/flask/issues/4295
|
||||||
|
# https://github.com/pallets/flask/issues/4297
|
||||||
|
ErrorHandlerCallable = t.Callable[[t.Any], ResponseReturnValue]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue