context.py 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. # -*- coding: utf-8 -*-
  2. import json
  3. import typing
  4. from hapic.error import ErrorBuilderInterface
  5. try: # Python 3.5+
  6. from http import HTTPStatus
  7. except ImportError:
  8. from http import client as HTTPStatus
  9. from hapic.processor import RequestParameters
  10. from hapic.processor import ProcessValidationError
  11. if typing.TYPE_CHECKING:
  12. from hapic.decorator import DecoratedController
  13. class RouteRepresentation(object):
  14. def __init__(
  15. self,
  16. rule: str,
  17. method: str,
  18. original_route_object: typing.Any=None,
  19. ) -> None:
  20. self.rule = rule
  21. self.method = method
  22. self.original_route_object = original_route_object
  23. class ContextInterface(object):
  24. def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
  25. raise NotImplementedError()
  26. def get_response(
  27. self,
  28. # TODO BS 20171228: rename into response_content
  29. response: str,
  30. http_code: int,
  31. mimetype: str='application/json',
  32. ) -> typing.Any:
  33. raise NotImplementedError()
  34. def get_validation_error_response(
  35. self,
  36. error: ProcessValidationError,
  37. http_code: HTTPStatus=HTTPStatus.BAD_REQUEST,
  38. ) -> typing.Any:
  39. raise NotImplementedError()
  40. def find_route(
  41. self,
  42. decorated_controller: 'DecoratedController',
  43. ) -> RouteRepresentation:
  44. raise NotImplementedError()
  45. # TODO BS 20171228: rename into openapi !
  46. def get_swagger_path(self, contextualised_rule: str) -> str:
  47. """
  48. Return OpenAPI path with context path
  49. TODO BS 20171228: Give example
  50. :param contextualised_rule: path of original context
  51. :return: OpenAPI path
  52. """
  53. raise NotImplementedError()
  54. # TODO BS 20171228: rename into "bypass"
  55. def by_pass_output_wrapping(self, response: typing.Any) -> bool:
  56. """
  57. Return True if the controller response is the final response object:
  58. we do not have to apply any processing on it.
  59. :param response: the original response of controller
  60. :return:
  61. """
  62. raise NotImplementedError()
  63. def get_default_error_builder(self) -> ErrorBuilderInterface:
  64. """
  65. Return a ErrorBuilder who will be used to build default errors
  66. :return: ErrorBuilderInterface instance
  67. """
  68. raise NotImplementedError()
  69. def add_view(
  70. self,
  71. route: str,
  72. http_method: str,
  73. view_func: typing.Callable[..., typing.Any],
  74. ) -> None:
  75. """
  76. This method must permit to add a view in current context
  77. :param route: The route depending of framework format, ex "/foo"
  78. :param http_method: HTTP method like GET, POST, etc ...
  79. :param view_func: The view callable
  80. """
  81. raise NotImplementedError()
  82. def serve_directory(
  83. self,
  84. route_prefix: str,
  85. directory_path: str,
  86. ) -> None:
  87. """
  88. Configure a path to serve a directory content
  89. :param route_prefix: The base url for serve the directory, eg /static
  90. :param directory_path: The file system path
  91. """
  92. raise NotImplementedError()
  93. def handle_exception(
  94. self,
  95. exception_class: typing.Type[Exception],
  96. http_code: int,
  97. ) -> None:
  98. raise NotImplementedError()
  99. def handle_exceptions(
  100. self,
  101. exception_classes: typing.List[typing.Type[Exception]],
  102. http_code: int,
  103. ) -> None:
  104. raise NotImplementedError()
  105. def _add_exception_class_to_catch(
  106. self,
  107. exception_class: typing.List[typing.Type[Exception]],
  108. http_code: int,
  109. ) -> None:
  110. raise NotImplementedError()
  111. class BaseContext(ContextInterface):
  112. def get_default_error_builder(self) -> ErrorBuilderInterface:
  113. """ see hapic.context.ContextInterface#get_default_error_builder"""
  114. return self.default_error_builder
  115. def handle_exception(
  116. self,
  117. exception_class: typing.Type[Exception],
  118. http_code: int,
  119. ) -> None:
  120. self._add_exception_class_to_catch(exception_class, http_code)
  121. def handle_exceptions(
  122. self,
  123. exception_classes: typing.List[typing.Type[Exception]],
  124. http_code: int,
  125. ) -> None:
  126. for exception_class in exception_classes:
  127. self._add_exception_class_to_catch(exception_class, http_code)
  128. def handle_exceptions_decorator_builder(
  129. self,
  130. func: typing.Callable[..., typing.Any],
  131. ) -> typing.Callable[..., typing.Any]:
  132. def decorator(*args, **kwargs):
  133. try:
  134. return func(*args, **kwargs)
  135. except Exception as exc:
  136. # Reverse list to read first user given exception before
  137. # the hapic default Exception catch
  138. handled = reversed(self._get_handled_exception_classes())
  139. for handled_exception_class, http_code in handled:
  140. # TODO BS 2018-05-04: How to be attentive to hierarchy ?
  141. if isinstance(exc, handled_exception_class):
  142. error_builder = self.get_default_error_builder()
  143. error_body = error_builder.build_from_exception(exc)
  144. return self.get_response(
  145. json.dumps(error_body),
  146. http_code,
  147. )
  148. raise exc
  149. return decorator
  150. def _get_handled_exception_classes(
  151. self,
  152. ) -> typing.List[typing.Tuple[typing.Type[Exception], int]]:
  153. raise NotImplementedError()
  154. def _add_exception_class_to_catch(
  155. self,
  156. exception_class: typing.Type[Exception],
  157. http_code: int,
  158. ) -> None:
  159. raise NotImplementedError()