add aborter object to app

This commit is contained in:
dzcode 2022-05-02 10:16:12 -06:00 committed by David Lord
parent a25d234cdd
commit eb5dd9f5ef
No known key found for this signature in database
GPG key ID: 7A1C87E3F5BC42A8
5 changed files with 92 additions and 1 deletions

View file

@ -5,6 +5,10 @@ Version 2.2.0
Unreleased Unreleased
- Add ``aborter_class`` and ``aborter`` attributes to the Flask app
object. ``flask.abort`` will call ``app.aborter``. This makes it
possible for an app to override how aborts work, including custom
status codes. :issue:`4567`
- Add an ``app.redirect`` method, which ``flask.redirect`` will call. - Add an ``app.redirect`` method, which ``flask.redirect`` will call.
This makes it possible for an app to override how redirects work. This makes it possible for an app to override how redirects work.
:issue:`4569` :issue:`4569`

View file

@ -1,6 +1,5 @@
from markupsafe import escape from markupsafe import escape
from markupsafe import Markup from markupsafe import Markup
from werkzeug.exceptions import abort as abort
from . import json as json from . import json as json
from .app import Flask as Flask from .app import Flask as Flask
@ -18,6 +17,7 @@ from .globals import current_app as current_app
from .globals import g as g from .globals import g as g
from .globals import request as request from .globals import request as request
from .globals import session as session from .globals import session as session
from .helpers import abort as abort
from .helpers import flash as flash from .helpers import flash as flash
from .helpers import get_flashed_messages as get_flashed_messages from .helpers import get_flashed_messages as get_flashed_messages
from .helpers import get_template_attribute as get_template_attribute from .helpers import get_template_attribute as get_template_attribute

View file

@ -12,6 +12,7 @@ from types import TracebackType
from werkzeug.datastructures import Headers from werkzeug.datastructures import Headers
from werkzeug.datastructures import ImmutableDict from werkzeug.datastructures import ImmutableDict
from werkzeug.exceptions import Aborter
from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequest
from werkzeug.exceptions import BadRequestKeyError from werkzeug.exceptions import BadRequestKeyError
from werkzeug.exceptions import HTTPException from werkzeug.exceptions import HTTPException
@ -201,6 +202,16 @@ class Flask(Scaffold):
#: :class:`~flask.Response` for more information. #: :class:`~flask.Response` for more information.
response_class = Response response_class = Response
#: The class of the object assigned to :attr:`aborter`, created by
#: :meth:`create_aborter`. That object is called by
#: :func:`flask.abort` to raise HTTP errors, and can be
#: called directly as well.
#:
#: Defaults to :class:`werkzeug.exceptions.Aborter`.
#:
#: .. versionadded:: 2.2
aborter_class = Aborter
#: The class that is used for the Jinja environment. #: The class that is used for the Jinja environment.
#: #:
#: .. versionadded:: 0.11 #: .. versionadded:: 0.11
@ -421,6 +432,13 @@ class Flask(Scaffold):
#: to load a config from files. #: to load a config from files.
self.config = self.make_config(instance_relative_config) self.config = self.make_config(instance_relative_config)
#: An instance of :attr:`aborter_class` created by
#: :meth:`make_aborter`. This is called by :func:`flask.abort`
#: to raise HTTP errors, and can be called directly as well.
#:
#: .. versionadded:: 2.2
self.aborter = self.make_aborter()
#: A list of functions that are called when :meth:`url_for` raises a #: A list of functions that are called when :meth:`url_for` raises a
#: :exc:`~werkzeug.routing.BuildError`. Each function registered here #: :exc:`~werkzeug.routing.BuildError`. Each function registered here
#: is called with `error`, `endpoint` and `values`. If a function #: is called with `error`, `endpoint` and `values`. If a function
@ -628,6 +646,18 @@ class Flask(Scaffold):
defaults["DEBUG"] = get_debug_flag() defaults["DEBUG"] = get_debug_flag()
return self.config_class(root_path, defaults) return self.config_class(root_path, defaults)
def make_aborter(self) -> Aborter:
"""Create the object to assign to :attr:`aborter`. That object
is called by :func:`flask.abort` to raise HTTP errors, and can
be called directly as well.
By default, this creates an instance of :attr:`aborter_class`,
which defaults to :class:`werkzeug.exceptions.Aborter`.
.. versionadded:: 2.2
"""
return self.aborter_class()
def auto_find_instance_path(self) -> str: def auto_find_instance_path(self) -> str:
"""Tries to locate the instance path if it was not provided to the """Tries to locate the instance path if it was not provided to the
constructor of the application class. It will basically calculate constructor of the application class. It will basically calculate

View file

@ -10,6 +10,7 @@ from functools import update_wrapper
from threading import RLock from threading import RLock
import werkzeug.utils import werkzeug.utils
from werkzeug.exceptions import abort as _wz_abort
from werkzeug.routing import BuildError from werkzeug.routing import BuildError
from werkzeug.urls import url_quote from werkzeug.urls import url_quote
from werkzeug.utils import redirect as _wz_redirect from werkzeug.utils import redirect as _wz_redirect
@ -24,6 +25,7 @@ from .signals import message_flashed
if t.TYPE_CHECKING: # pragma: no cover if t.TYPE_CHECKING: # pragma: no cover
from werkzeug.wrappers import Response as BaseResponse from werkzeug.wrappers import Response as BaseResponse
from .wrappers import Response from .wrappers import Response
import typing_extensions as te
def get_env() -> str: def get_env() -> str:
@ -364,6 +366,31 @@ def redirect(
return _wz_redirect(location, code=code, Response=Response) return _wz_redirect(location, code=code, Response=Response)
def abort( # type: ignore[misc]
code: t.Union[int, "BaseResponse"], *args: t.Any, **kwargs: t.Any
) -> "te.NoReturn":
"""Raise an :exc:`~werkzeug.exceptions.HTTPException` for the given
status code.
If :data:`~flask.current_app` is available, it will call its
:attr:`~flask.Flask.aborter` object, otherwise it will use
:func:`werkzeug.exceptions.abort`.
:param code: The status code for the exception, which must be
registered in ``app.aborter``.
:param args: Passed to the exception.
:param kwargs: Passed to the exception.
.. versionadded:: 2.2
Calls ``current_app.aborter`` if available instead of always
using Werkzeug's default ``abort``.
"""
if current_app:
current_app.aborter(code, *args, **kwargs)
_wz_abort(code, *args, **kwargs)
def get_template_attribute(template_name: str, attribute: str) -> t.Any: def get_template_attribute(template_name: str, attribute: str) -> t.Any:
"""Loads a macro (or variable) a template exports. This can be used to """Loads a macro (or variable) a template exports. This can be used to
invoke a macro from within Python code. If you for example have a invoke a macro from within Python code. If you for example have a

View file

@ -2,6 +2,7 @@ import io
import os import os
import pytest import pytest
import werkzeug.exceptions
import flask import flask
from flask.helpers import get_debug_flag from flask.helpers import get_debug_flag
@ -174,6 +175,35 @@ def test_redirect_with_app(app):
flask.redirect("other") flask.redirect("other")
def test_abort_no_app():
with pytest.raises(werkzeug.exceptions.Unauthorized):
flask.abort(401)
with pytest.raises(LookupError):
flask.abort(900)
def test_app_aborter_class():
class MyAborter(werkzeug.exceptions.Aborter):
pass
class MyFlask(flask.Flask):
aborter_class = MyAborter
app = MyFlask(__name__)
assert isinstance(app.aborter, MyAborter)
def test_abort_with_app(app):
class My900Error(werkzeug.exceptions.HTTPException):
code = 900
app.aborter.mapping[900] = My900Error
with app.app_context(), pytest.raises(My900Error):
flask.abort(900)
class TestNoImports: class TestNoImports:
"""Test Flasks are created without import. """Test Flasks are created without import.