Merge pull request #4576 from dzcode/dzcode/4567/abort-implementation

Add 'aborter' and 'aborter_class' attrs to 'Flask' object
This commit is contained in:
David Lord 2022-05-12 13:45:37 -07:00 committed by GitHub
commit 5512a66881
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 92 additions and 1 deletions

View file

@ -5,6 +5,10 @@ Version 2.2.0
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.
This makes it possible for an app to override how redirects work.
:issue:`4569`

View file

@ -1,6 +1,5 @@
from markupsafe import escape
from markupsafe import Markup
from werkzeug.exceptions import abort as abort
from . import json as json
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 request as request
from .globals import session as session
from .helpers import abort as abort
from .helpers import flash as flash
from .helpers import get_flashed_messages as get_flashed_messages
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 ImmutableDict
from werkzeug.exceptions import Aborter
from werkzeug.exceptions import BadRequest
from werkzeug.exceptions import BadRequestKeyError
from werkzeug.exceptions import HTTPException
@ -201,6 +202,16 @@ class Flask(Scaffold):
#: :class:`~flask.Response` for more information.
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.
#:
#: .. versionadded:: 0.11
@ -421,6 +432,13 @@ class Flask(Scaffold):
#: to load a config from files.
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
#: :exc:`~werkzeug.routing.BuildError`. Each function registered here
#: is called with `error`, `endpoint` and `values`. If a function
@ -628,6 +646,18 @@ class Flask(Scaffold):
defaults["DEBUG"] = get_debug_flag()
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:
"""Tries to locate the instance path if it was not provided to the
constructor of the application class. It will basically calculate

View file

@ -10,6 +10,7 @@ from functools import update_wrapper
from threading import RLock
import werkzeug.utils
from werkzeug.exceptions import abort as _wz_abort
from werkzeug.routing import BuildError
from werkzeug.urls import url_quote
from werkzeug.utils import redirect as _wz_redirect
@ -24,6 +25,7 @@ from .signals import message_flashed
if t.TYPE_CHECKING: # pragma: no cover
from werkzeug.wrappers import Response as BaseResponse
from .wrappers import Response
import typing_extensions as te
def get_env() -> str:
@ -364,6 +366,31 @@ def redirect(
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:
"""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

View file

@ -2,6 +2,7 @@ import io
import os
import pytest
import werkzeug.exceptions
import flask
from flask.helpers import get_debug_flag
@ -174,6 +175,35 @@ def test_redirect_with_app(app):
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:
"""Test Flasks are created without import.