decorator.py 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. # -*- coding: utf-8 -*-
  2. import typing
  3. from http import HTTPStatus
  4. from hapic.data import HapicData
  5. from hapic.description import ControllerDescription
  6. from hapic.exception import ProcessException
  7. from hapic.context import ContextInterface
  8. from hapic.processor import ProcessorInterface
  9. from hapic.processor import RequestParameters
  10. class ControllerWrapper(object):
  11. def __init__(
  12. self,
  13. context: ContextInterface,
  14. processor: ProcessorInterface,
  15. error_http_code: HTTPStatus=HTTPStatus.BAD_REQUEST,
  16. default_http_code: HTTPStatus=HTTPStatus.OK,
  17. ) -> None:
  18. self.context = context
  19. self.processor = processor
  20. self.error_http_code = error_http_code
  21. self.default_http_code = default_http_code
  22. def before_wrapped_func(
  23. self,
  24. func_args: typing.Tuple[typing.Any, ...],
  25. func_kwargs: typing.Dict[str, typing.Any],
  26. ) -> typing.Union[None, typing.Any]:
  27. pass
  28. def after_wrapped_function(self, response: typing.Any) -> typing.Any:
  29. return response
  30. def get_wrapper(
  31. self,
  32. func: 'typing.Callable[..., typing.Any]',
  33. ) -> 'typing.Callable[..., typing.Any]':
  34. def wrapper(*args, **kwargs) -> typing.Any:
  35. # Note: Design of before_wrapped_func can be to update kwargs
  36. # by reference here
  37. replacement_response = self.before_wrapped_func(args, kwargs)
  38. if replacement_response:
  39. return replacement_response
  40. response = func(*args, **kwargs)
  41. new_response = self.after_wrapped_function(response)
  42. return new_response
  43. return wrapper
  44. class InputControllerWrapper(ControllerWrapper):
  45. def before_wrapped_func(
  46. self,
  47. func_args: typing.Tuple[typing.Any, ...],
  48. func_kwargs: typing.Dict[str, typing.Any],
  49. ) -> typing.Any:
  50. # Retrieve hapic_data instance or create new one
  51. # hapic_data is given though decorators
  52. # Important note here: func_kwargs is update by reference !
  53. hapic_data = self.ensure_hapic_data(func_kwargs)
  54. request_parameters = self.get_request_parameters(
  55. func_args,
  56. func_kwargs,
  57. )
  58. try:
  59. processed_data = self.get_processed_data(request_parameters)
  60. self.update_hapic_data(hapic_data, processed_data)
  61. except ProcessException:
  62. error_response = self.get_error_response(request_parameters)
  63. return error_response
  64. @classmethod
  65. def ensure_hapic_data(
  66. cls,
  67. func_kwargs: typing.Dict[str, typing.Any],
  68. ) -> HapicData:
  69. # TODO: Permit other name than "hapic_data" ?
  70. try:
  71. return func_kwargs['hapic_data']
  72. except KeyError:
  73. hapic_data = HapicData()
  74. func_kwargs['hapic_data'] = hapic_data
  75. return hapic_data
  76. def get_request_parameters(
  77. self,
  78. func_args: typing.Tuple[typing.Any, ...],
  79. func_kwargs: typing.Dict[str, typing.Any],
  80. ) -> RequestParameters:
  81. return self.context.get_request_parameters(
  82. *func_args,
  83. **func_kwargs
  84. )
  85. def get_processed_data(
  86. self,
  87. request_parameters: RequestParameters,
  88. ) -> typing.Any:
  89. raise NotImplementedError()
  90. def update_hapic_data(
  91. self,
  92. hapic_data: HapicData,
  93. processed_data: typing.Dict[str, typing.Any],
  94. ) -> None:
  95. raise NotImplementedError()
  96. def get_error_response(
  97. self,
  98. request_parameters: RequestParameters,
  99. ) -> typing.Any:
  100. error = self.processor.get_validation_error(
  101. request_parameters.body_parameters,
  102. )
  103. error_response = self.context.get_validation_error_response(
  104. error,
  105. http_code=self.error_http_code,
  106. )
  107. return error_response
  108. class OutputControllerWrapper(ControllerWrapper):
  109. def get_error_response(
  110. self,
  111. response: typing.Any,
  112. ) -> typing.Any:
  113. error = self.processor.get_validation_error(response)
  114. error_response = self.context.get_validation_error_response(
  115. error,
  116. http_code=self.error_http_code,
  117. )
  118. return error_response
  119. def after_wrapped_function(self, response: typing.Any) -> typing.Any:
  120. try:
  121. processed_response = self.processor.process(response)
  122. prepared_response = self.context.get_response(
  123. processed_response,
  124. self.default_http_code,
  125. )
  126. return prepared_response
  127. except ProcessException:
  128. # TODO: ici ou ailleurs: il faut pas forcement donner le detail
  129. # de l'erreur (mode debug par exemple)
  130. error_response = self.get_error_response(response)
  131. return error_response
  132. class DecoratedController(object):
  133. def __init__(
  134. self,
  135. reference: 'typing.Callable[..., typing.Any]',
  136. description: ControllerDescription,
  137. ) -> None:
  138. self._reference = reference
  139. self._description = description
  140. @property
  141. def reference(self) -> 'typing.Callable[..., typing.Any]':
  142. return self._reference
  143. @property
  144. def description(self) -> ControllerDescription:
  145. return self._description
  146. class OutputBodyControllerWrapper(OutputControllerWrapper):
  147. pass
  148. class OutputHeadersControllerWrapper(OutputControllerWrapper):
  149. # TODO: write me
  150. pass
  151. class InputPathControllerWrapper(InputControllerWrapper):
  152. def update_hapic_data(
  153. self, hapic_data: HapicData,
  154. processed_data: typing.Any,
  155. ) -> None:
  156. hapic_data.path = processed_data
  157. def get_processed_data(
  158. self,
  159. request_parameters: RequestParameters,
  160. ) -> typing.Any:
  161. processed_data = self.processor.process(
  162. request_parameters.path_parameters,
  163. )
  164. return processed_data
  165. class InputQueryControllerWrapper(InputControllerWrapper):
  166. def update_hapic_data(
  167. self, hapic_data: HapicData,
  168. processed_data: typing.Any,
  169. ) -> None:
  170. hapic_data.query = processed_data
  171. def get_processed_data(
  172. self,
  173. request_parameters: RequestParameters,
  174. ) -> typing.Any:
  175. processed_data = self.processor.process(
  176. request_parameters.query_parameters,
  177. )
  178. return processed_data
  179. class InputBodyControllerWrapper(InputControllerWrapper):
  180. def update_hapic_data(
  181. self, hapic_data: HapicData,
  182. processed_data: typing.Any,
  183. ) -> None:
  184. hapic_data.body = processed_data
  185. def get_processed_data(
  186. self,
  187. request_parameters: RequestParameters,
  188. ) -> typing.Any:
  189. processed_data = self.processor.process(
  190. request_parameters.body_parameters,
  191. )
  192. return processed_data
  193. class InputHeadersControllerWrapper(InputControllerWrapper):
  194. def update_hapic_data(
  195. self, hapic_data: HapicData,
  196. processed_data: typing.Any,
  197. ) -> None:
  198. hapic_data.headers = processed_data
  199. def get_processed_data(
  200. self,
  201. request_parameters: RequestParameters,
  202. ) -> typing.Any:
  203. processed_data = self.processor.process(
  204. request_parameters.header_parameters,
  205. )
  206. return processed_data
  207. class InputFormsControllerWrapper(InputControllerWrapper):
  208. def update_hapic_data(
  209. self, hapic_data: HapicData,
  210. processed_data: typing.Any,
  211. ) -> None:
  212. hapic_data.forms = processed_data
  213. def get_processed_data(
  214. self,
  215. request_parameters: RequestParameters,
  216. ) -> typing.Any:
  217. processed_data = self.processor.process(
  218. request_parameters.form_parameters,
  219. )
  220. return processed_data