Skip to content

Commit

Permalink
Add hook on global and view exception catch
Browse files Browse the repository at this point in the history
  • Loading branch information
buxx committed Jul 25, 2019
1 parent 302e876 commit e2a9d93
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 0 deletions.
28 changes: 28 additions & 0 deletions hapic/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,19 @@ def is_debug(self) -> bool:
"""
raise NotImplementedError()

def global_exception_caught(self, caught_exception: Exception) -> None:
"""
This method must be called by context when an exception is caught
at global level (when use .handle_exception and .handle_exceptions)
"""
raise NotImplementedError()

def local_exception_caught(self, caught_exception: Exception) -> None:
"""
This method is called when hapic caught exception at view level
"""
raise NotImplementedError()


class HandledException(object):
"""
Expand Down Expand Up @@ -271,6 +284,7 @@ def decorator(*args, **kwargs):
# TODO BS 2018-05-04: How to be attentive to hierarchy ?
for handled_exception in handled_exceptions:
if isinstance(exc, handled_exception.exception_class):
self.global_exception_caught(exc)
dumped_error = self._get_dumped_error_from_exception_error(exc)
return self.get_response(
json.dumps(dumped_error), handled_exception.http_code
Expand Down Expand Up @@ -298,3 +312,17 @@ def _add_exception_class_to_catch(
:return:
"""
raise NotImplementedError()

def global_exception_caught(self, caught_exception: Exception) -> None:
"""
See parent docstring. Override it to perform action when exception is
caught at global level.
"""
pass

def local_exception_caught(self, exc: Exception) -> None:
"""
TSee parent docstring. Override it to perform action when exception is
caught at view level.
"""
pass
2 changes: 2 additions & 0 deletions hapic/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,7 @@ def _execute_wrapped_function(self, func, func_args, func_kwargs) -> typing.Any:
try:
return super()._execute_wrapped_function(func, func_args, func_kwargs)
except self.handled_exception_class as exc:
self.context.local_exception_caught(exc)
return self._build_error_response(exc)

def _build_error_response(self, exc: Exception) -> typing.Any:
Expand Down Expand Up @@ -709,4 +710,5 @@ async def _execute_wrapped_function(self, func, func_args, func_kwargs) -> typin
try:
return await func(*func_args, **func_kwargs)
except self.handled_exception_class as exc:
self.context.local_exception_caught(exc)
return self._build_error_response(exc)
1 change: 1 addition & 0 deletions hapic/ext/aiohttp/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ async def error_middleware(
# Parse each managed exceptions to manage it if must be
for handled_exception in self._handled_exceptions:
if isinstance(exc, handled_exception.exception_class):
self.global_exception_caught(exc)
err = self._get_dumped_error_from_exception_error(exc)
return self.get_response(json.dumps(err), handled_exception.http_code)
raise exc
Expand Down
1 change: 1 addition & 0 deletions hapic/ext/flask/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ def _add_exception_class_to_catch(
self, exception_class: typing.Type[Exception], http_code: int
) -> None:
def return_response_error(exc):
self.global_exception_caught(exc)
dumped_error = self._get_dumped_error_from_exception_error(exc)
return self.get_response(json.dumps(dumped_error), http_code)

Expand Down
1 change: 1 addition & 0 deletions hapic/ext/pyramid/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ def view_func(exc, request):
)
)
logger.debug(traceback.format_exc())
self.global_exception_caught(exc)
# TODO BS 2018-05-04: How to be attentive to hierarchy ?
error_builder = self.default_error_builder
error_body = error_builder.build_from_exception(
Expand Down
52 changes: 52 additions & 0 deletions tests/ext/unit/test_aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,32 @@ def divide_by_zero(request):

assert 400 == response.status

async def test_unit__handle_exception__ok__hook_called(self, aiohttp_client):
hapic = Hapic(async_=True, processor_class=MarshmallowProcessor)

class MyContext(AiohttpContext):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.hook_called = False

def local_exception_caught(self, exc: Exception) -> None:
self.hook_called = True

@hapic.with_api_doc()
@hapic.handle_exception(ZeroDivisionError, http_code=HTTPStatus.BAD_REQUEST)
def divide_by_zero(request):
raise ZeroDivisionError()

app = web.Application(debug=True)
context = MyContext(app)
hapic.set_context(context)
app.router.add_get("/", divide_by_zero)
client = await aiohttp_client(app)

assert not context.hook_called
await client.get("/")
assert context.hook_called

async def test_unit__global_exception__ok__nominal_case(self, aiohttp_client):
hapic = Hapic(async_=True, processor_class=MarshmallowProcessor)

Expand All @@ -654,3 +680,29 @@ def divide_by_zero(request):
response = await client.get("/")

assert 400 == response.status

async def test_unit__global_exception__ok__hook_called(self, aiohttp_client):
hapic = Hapic(async_=True, processor_class=MarshmallowProcessor)

class MyContext(AiohttpContext):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.hook_called = False

def global_exception_caught(self, exc: Exception) -> None:
self.hook_called = True

@hapic.with_api_doc()
def divide_by_zero(request):
raise ZeroDivisionError()

app = web.Application(debug=True)
context = MyContext(app)
hapic.set_context(context)
context.handle_exception(ZeroDivisionError, http_code=HTTPStatus.BAD_REQUEST)
app.router.add_get("/", divide_by_zero)
client = await aiohttp_client(app)

assert not context.hook_called
await client.get("/")
assert context.hook_called

0 comments on commit e2a9d93

Please sign in to comment.