Adds Blueprint Group exception decorator (#2238)

* Add exception decorator

* Added tests

* Fix line too long
This commit is contained in:
Néstor Pérez 2021-09-12 21:02:59 +02:00 committed by GitHub
parent a937e08ef0
commit 404c5f9f9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 53 additions and 2 deletions

View File

@ -197,6 +197,27 @@ class BlueprintGroup(MutableSequence):
""" """
self._blueprints.append(value) self._blueprints.append(value)
def exception(self, *exceptions, **kwargs):
"""
A decorator that can be used to implement a global exception handler
for all the Blueprints that belong to this Blueprint Group.
In case of nested Blueprint Groups, the same handler is applied
across each of the Blueprints recursively.
:param args: List of Python exceptions to be caught by the handler
:param kwargs: Additional optional arguments to be passed to the
exception handler
:return a decorated method to handle global exceptions for any
blueprint registered under this group.
"""
def register_exception_handler_for_blueprints(fn):
for blueprint in self.blueprints:
blueprint.exception(*exceptions, **kwargs)(fn)
return register_exception_handler_for_blueprints
def insert(self, index: int, item: Blueprint) -> None: def insert(self, index: int, item: Blueprint) -> None:
""" """
The Abstract class `MutableSequence` leverages this insert method to The Abstract class `MutableSequence` leverages this insert method to

View File

@ -3,6 +3,7 @@ from pytest import raises
from sanic.app import Sanic from sanic.app import Sanic
from sanic.blueprint_group import BlueprintGroup from sanic.blueprint_group import BlueprintGroup
from sanic.blueprints import Blueprint from sanic.blueprints import Blueprint
from sanic.exceptions import Forbidden, InvalidUsage, SanicException, ServerError
from sanic.request import Request from sanic.request import Request
from sanic.response import HTTPResponse, text from sanic.response import HTTPResponse, text
@ -96,16 +97,28 @@ def test_bp_group(app: Sanic):
def blueprint_1_default_route(request): def blueprint_1_default_route(request):
return text("BP1_OK") return text("BP1_OK")
@blueprint_1.route("/invalid")
def blueprint_1_error(request: Request):
raise InvalidUsage("Invalid")
@blueprint_2.route("/") @blueprint_2.route("/")
def blueprint_2_default_route(request): def blueprint_2_default_route(request):
return text("BP2_OK") return text("BP2_OK")
@blueprint_2.route("/error")
def blueprint_2_error(request: Request):
raise ServerError("Error")
blueprint_group_1 = Blueprint.group( blueprint_group_1 = Blueprint.group(
blueprint_1, blueprint_2, url_prefix="/bp" blueprint_1, blueprint_2, url_prefix="/bp"
) )
blueprint_3 = Blueprint("blueprint_3", url_prefix="/bp3") blueprint_3 = Blueprint("blueprint_3", url_prefix="/bp3")
@blueprint_group_1.exception(InvalidUsage)
def handle_group_exception(request, exception):
return text("BP1_ERR_OK")
@blueprint_group_1.middleware("request") @blueprint_group_1.middleware("request")
def blueprint_group_1_middleware(request): def blueprint_group_1_middleware(request):
global MIDDLEWARE_INVOKE_COUNTER global MIDDLEWARE_INVOKE_COUNTER
@ -130,10 +143,18 @@ def test_bp_group(app: Sanic):
def blueprint_3_default_route(request): def blueprint_3_default_route(request):
return text("BP3_OK") return text("BP3_OK")
@blueprint_3.route("/forbidden")
def blueprint_3_forbidden(request: Request):
raise Forbidden("Forbidden")
blueprint_group_2 = Blueprint.group( blueprint_group_2 = Blueprint.group(
blueprint_group_1, blueprint_3, url_prefix="/api" blueprint_group_1, blueprint_3, url_prefix="/api"
) )
@blueprint_group_2.exception(SanicException)
def handle_non_handled_exception(request, exception):
return text("BP2_ERR_OK")
@blueprint_group_2.middleware("response") @blueprint_group_2.middleware("response")
def blueprint_group_2_middleware(request, response): def blueprint_group_2_middleware(request, response):
global MIDDLEWARE_INVOKE_COUNTER global MIDDLEWARE_INVOKE_COUNTER
@ -161,14 +182,23 @@ def test_bp_group(app: Sanic):
_, response = app.test_client.get("/api/bp/bp1") _, response = app.test_client.get("/api/bp/bp1")
assert response.text == "BP1_OK" assert response.text == "BP1_OK"
_, response = app.test_client.get("/api/bp/bp1/invalid")
assert response.text == "BP1_ERR_OK"
_, response = app.test_client.get("/api/bp/bp2") _, response = app.test_client.get("/api/bp/bp2")
assert response.text == "BP2_OK" assert response.text == "BP2_OK"
_, response = app.test_client.get("/api/bp/bp2/error")
assert response.text == "BP2_ERR_OK"
_, response = app.test_client.get("/api/bp3") _, response = app.test_client.get("/api/bp3")
assert response.text == "BP3_OK" assert response.text == "BP3_OK"
assert MIDDLEWARE_INVOKE_COUNTER["response"] == 9 _, response = app.test_client.get("/api/bp3/forbidden")
assert MIDDLEWARE_INVOKE_COUNTER["request"] == 8 assert response.text == "BP2_ERR_OK"
assert MIDDLEWARE_INVOKE_COUNTER["response"] == 18
assert MIDDLEWARE_INVOKE_COUNTER["request"] == 16
def test_bp_group_list_operations(app: Sanic): def test_bp_group_list_operations(app: Sanic):