123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- # -*- coding: utf-8 -*-
- import json
- import typing
-
- from hapic.error import ErrorBuilderInterface
-
- try: # Python 3.5+
- from http import HTTPStatus
- except ImportError:
- from http import client as HTTPStatus
-
- from hapic.processor import RequestParameters
- from hapic.processor import ProcessValidationError
-
- if typing.TYPE_CHECKING:
- from hapic.decorator import DecoratedController
-
-
- class RouteRepresentation(object):
- def __init__(
- self,
- rule: str,
- method: str,
- original_route_object: typing.Any=None,
- ) -> None:
- self.rule = rule
- self.method = method
- self.original_route_object = original_route_object
-
-
- class ContextInterface(object):
- def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
- raise NotImplementedError()
-
- def get_response(
- self,
- # TODO BS 20171228: rename into response_content
- response: str,
- http_code: int,
- mimetype: str='application/json',
- ) -> typing.Any:
- raise NotImplementedError()
-
- def get_validation_error_response(
- self,
- error: ProcessValidationError,
- http_code: HTTPStatus=HTTPStatus.BAD_REQUEST,
- ) -> typing.Any:
- raise NotImplementedError()
-
- def find_route(
- self,
- decorated_controller: 'DecoratedController',
- ) -> RouteRepresentation:
- raise NotImplementedError()
-
- # TODO BS 20171228: rename into openapi !
- def get_swagger_path(self, contextualised_rule: str) -> str:
- """
- Return OpenAPI path with context path
- TODO BS 20171228: Give example
- :param contextualised_rule: path of original context
- :return: OpenAPI path
- """
- raise NotImplementedError()
-
- # TODO BS 20171228: rename into "bypass"
- def by_pass_output_wrapping(self, response: typing.Any) -> bool:
- """
- Return True if the controller response is the final response object:
- we do not have to apply any processing on it.
- :param response: the original response of controller
- :return:
- """
- raise NotImplementedError()
-
- def get_default_error_builder(self) -> ErrorBuilderInterface:
- """
- Return a ErrorBuilder who will be used to build default errors
- :return: ErrorBuilderInterface instance
- """
- raise NotImplementedError()
-
- def add_view(
- self,
- route: str,
- http_method: str,
- view_func: typing.Callable[..., typing.Any],
- ) -> None:
- """
- This method must permit to add a view in current context
- :param route: The route depending of framework format, ex "/foo"
- :param http_method: HTTP method like GET, POST, etc ...
- :param view_func: The view callable
- """
- raise NotImplementedError()
-
- def serve_directory(
- self,
- route_prefix: str,
- directory_path: str,
- ) -> None:
- """
- Configure a path to serve a directory content
- :param route_prefix: The base url for serve the directory, eg /static
- :param directory_path: The file system path
- """
- raise NotImplementedError()
-
- def handle_exception(
- self,
- exception_class: typing.Type[Exception],
- http_code: int,
- ) -> None:
- """
- Enable management of this exception during execution of views. If this
- exception caught, an http response will be returned with this http
- code.
- :param exception_class: Exception class to catch
- :param http_code: HTTP code to use in response if exception caught
- """
- raise NotImplementedError()
-
- def handle_exceptions(
- self,
- exception_classes: typing.List[typing.Type[Exception]],
- http_code: int,
- ) -> None:
- """
- Enable management of these exceptions during execution of views. If
- this exception caught, an http response will be returned with this http
- code.
- :param exception_classes: Exception classes to catch
- :param http_code: HTTP code to use in response if exception caught
- """
- raise NotImplementedError()
-
- def is_debug(self) -> bool:
- """
- Method called to know if Hapic has been called in debug mode.
- Debug mode provide some informations like debug trace and error
- message in body when internal error happen.
- :return: True if in debug mode
- """
- raise NotImplementedError()
-
-
- class HandledException(object):
- """
- Representation of an handled exception with it's http code
- """
- def __init__(
- self,
- exception_class: typing.Type[Exception],
- http_code: int = 500,
- ):
- self.exception_class = exception_class
- self.http_code = http_code
-
-
- class BaseContext(ContextInterface):
- def get_default_error_builder(self) -> ErrorBuilderInterface:
- """ see hapic.context.ContextInterface#get_default_error_builder"""
- return self.default_error_builder
-
- def handle_exception(
- self,
- exception_class: typing.Type[Exception],
- http_code: int,
- ) -> None:
- self._add_exception_class_to_catch(exception_class, http_code)
-
- def handle_exceptions(
- self,
- exception_classes: typing.List[typing.Type[Exception]],
- http_code: int,
- ) -> None:
- for exception_class in exception_classes:
- self._add_exception_class_to_catch(exception_class, http_code)
-
- def handle_exceptions_decorator_builder(
- self,
- func: typing.Callable[..., typing.Any],
- ) -> typing.Callable[..., typing.Any]:
- """
- Return a decorator who catch exceptions raised during given function
- execution and return a response built by the default error builder.
-
- :param func: decorated function
- :return: the decorator
- """
- def decorator(*args, **kwargs):
- try:
- return func(*args, **kwargs)
- except Exception as exc:
- # Reverse list to read first user given exception before
- # the hapic default Exception catch
- handled_exceptions = reversed(
- self._get_handled_exception_class_and_http_codes(),
- )
- for handled_exception in handled_exceptions:
- # TODO BS 2018-05-04: How to be attentive to hierarchy ?
- if isinstance(exc, handled_exception.exception_class):
- error_builder = self.get_default_error_builder()
- error_body = error_builder.build_from_exception(exc)
- return self.get_response(
- json.dumps(error_body),
- handled_exception.http_code,
- )
- raise exc
-
- return decorator
-
- def _get_handled_exception_class_and_http_codes(
- self,
- ) -> typing.List[HandledException]:
- """
- :return: A list of tuple where: thirst item of tuple is a exception
- class and second tuple item is a http code. This list will be used by
- `handle_exceptions_decorator_builder` decorator to catch exceptions.
- """
- raise NotImplementedError()
-
- def _add_exception_class_to_catch(
- self,
- exception_class: typing.Type[Exception],
- http_code: int,
- ) -> None:
- """
- Add an exception class to catch and matching http code. Will be used by
- `handle_exceptions_decorator_builder` decorator to catch exceptions.
- :param exception_class: exception class to catch
- :param http_code: http code to use if this exception catched
- :return:
- """
- raise NotImplementedError()
|