decorator.py 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  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 before_wrapped_func(
  12. self,
  13. func_args: typing.Tuple[typing.Any, ...],
  14. func_kwargs: typing.Dict[str, typing.Any],
  15. ) -> typing.Union[None, typing.Any]:
  16. pass
  17. def after_wrapped_function(self, response: typing.Any) -> typing.Any:
  18. return response
  19. def get_wrapper(
  20. self,
  21. func: 'typing.Callable[..., typing.Any]',
  22. ) -> 'typing.Callable[..., typing.Any]':
  23. def wrapper(*args, **kwargs) -> typing.Any:
  24. # Note: Design of before_wrapped_func can be to update kwargs
  25. # by reference here
  26. replacement_response = self.before_wrapped_func(args, kwargs)
  27. if replacement_response:
  28. return replacement_response
  29. response = self._execute_wrapped_function(func, args, kwargs)
  30. new_response = self.after_wrapped_function(response)
  31. return new_response
  32. return wrapper
  33. def _execute_wrapped_function(
  34. self,
  35. func,
  36. func_args,
  37. func_kwargs,
  38. ) -> typing.Any:
  39. return func(*func_args, **func_kwargs)
  40. class InputOutputControllerWrapper(ControllerWrapper):
  41. def __init__(
  42. self,
  43. context: ContextInterface,
  44. processor: ProcessorInterface,
  45. error_http_code: HTTPStatus=HTTPStatus.BAD_REQUEST,
  46. default_http_code: HTTPStatus=HTTPStatus.OK,
  47. ) -> None:
  48. self.context = context
  49. self.processor = processor
  50. self.error_http_code = error_http_code
  51. self.default_http_code = default_http_code
  52. class InputControllerWrapper(InputOutputControllerWrapper):
  53. def before_wrapped_func(
  54. self,
  55. func_args: typing.Tuple[typing.Any, ...],
  56. func_kwargs: typing.Dict[str, typing.Any],
  57. ) -> typing.Any:
  58. # Retrieve hapic_data instance or create new one
  59. # hapic_data is given though decorators
  60. # Important note here: func_kwargs is update by reference !
  61. hapic_data = self.ensure_hapic_data(func_kwargs)
  62. request_parameters = self.get_request_parameters(
  63. func_args,
  64. func_kwargs,
  65. )
  66. try:
  67. processed_data = self.get_processed_data(request_parameters)
  68. self.update_hapic_data(hapic_data, processed_data)
  69. except ProcessException:
  70. error_response = self.get_error_response(request_parameters)
  71. return error_response
  72. @classmethod
  73. def ensure_hapic_data(
  74. cls,
  75. func_kwargs: typing.Dict[str, typing.Any],
  76. ) -> HapicData:
  77. # TODO: Permit other name than "hapic_data" ?
  78. try:
  79. return func_kwargs['hapic_data']
  80. except KeyError:
  81. hapic_data = HapicData()
  82. func_kwargs['hapic_data'] = hapic_data
  83. return hapic_data
  84. def get_request_parameters(
  85. self,
  86. func_args: typing.Tuple[typing.Any, ...],
  87. func_kwargs: typing.Dict[str, typing.Any],
  88. ) -> RequestParameters:
  89. return self.context.get_request_parameters(
  90. *func_args,
  91. **func_kwargs
  92. )
  93. def get_processed_data(
  94. self,
  95. request_parameters: RequestParameters,
  96. ) -> typing.Any:
  97. raise NotImplementedError()
  98. def update_hapic_data(
  99. self,
  100. hapic_data: HapicData,
  101. processed_data: typing.Dict[str, typing.Any],
  102. ) -> None:
  103. raise NotImplementedError()
  104. def get_error_response(
  105. self,
  106. request_parameters: RequestParameters,
  107. ) -> typing.Any:
  108. error = self.processor.get_validation_error(
  109. request_parameters.body_parameters,
  110. )
  111. error_response = self.context.get_validation_error_response(
  112. error,
  113. http_code=self.error_http_code,
  114. )
  115. return error_response
  116. class OutputControllerWrapper(InputOutputControllerWrapper):
  117. def __init__(
  118. self,
  119. context: ContextInterface,
  120. processor: ProcessorInterface,
  121. error_http_code: HTTPStatus=HTTPStatus.INTERNAL_SERVER_ERROR,
  122. default_http_code: HTTPStatus=HTTPStatus.OK,
  123. ) -> None:
  124. super().__init__(
  125. context,
  126. processor,
  127. error_http_code,
  128. default_http_code,
  129. )
  130. def get_error_response(
  131. self,
  132. response: typing.Any,
  133. ) -> typing.Any:
  134. error = self.processor.get_validation_error(response)
  135. error_response = self.context.get_validation_error_response(
  136. error,
  137. http_code=self.error_http_code,
  138. )
  139. return error_response
  140. def after_wrapped_function(self, response: typing.Any) -> typing.Any:
  141. try:
  142. processed_response = self.processor.process(response)
  143. prepared_response = self.context.get_response(
  144. processed_response,
  145. self.default_http_code,
  146. )
  147. return prepared_response
  148. except ProcessException:
  149. # TODO: ici ou ailleurs: il faut pas forcement donner le detail
  150. # de l'erreur (mode debug par exemple)
  151. error_response = self.get_error_response(response)
  152. return error_response
  153. class DecoratedController(object):
  154. def __init__(
  155. self,
  156. reference: 'typing.Callable[..., typing.Any]',
  157. description: ControllerDescription,
  158. ) -> None:
  159. self._reference = reference
  160. self._description = description
  161. @property
  162. def reference(self) -> 'typing.Callable[..., typing.Any]':
  163. return self._reference
  164. @property
  165. def description(self) -> ControllerDescription:
  166. return self._description
  167. class OutputBodyControllerWrapper(OutputControllerWrapper):
  168. pass
  169. class OutputHeadersControllerWrapper(OutputControllerWrapper):
  170. # TODO: write me
  171. pass
  172. class InputPathControllerWrapper(InputControllerWrapper):
  173. def update_hapic_data(
  174. self, hapic_data: HapicData,
  175. processed_data: typing.Any,
  176. ) -> None:
  177. hapic_data.path = processed_data
  178. def get_processed_data(
  179. self,
  180. request_parameters: RequestParameters,
  181. ) -> typing.Any:
  182. processed_data = self.processor.process(
  183. request_parameters.path_parameters,
  184. )
  185. return processed_data
  186. class InputQueryControllerWrapper(InputControllerWrapper):
  187. def update_hapic_data(
  188. self, hapic_data: HapicData,
  189. processed_data: typing.Any,
  190. ) -> None:
  191. hapic_data.query = processed_data
  192. def get_processed_data(
  193. self,
  194. request_parameters: RequestParameters,
  195. ) -> typing.Any:
  196. processed_data = self.processor.process(
  197. request_parameters.query_parameters,
  198. )
  199. return processed_data
  200. class InputBodyControllerWrapper(InputControllerWrapper):
  201. def update_hapic_data(
  202. self, hapic_data: HapicData,
  203. processed_data: typing.Any,
  204. ) -> None:
  205. hapic_data.body = processed_data
  206. def get_processed_data(
  207. self,
  208. request_parameters: RequestParameters,
  209. ) -> typing.Any:
  210. processed_data = self.processor.process(
  211. request_parameters.body_parameters,
  212. )
  213. return processed_data
  214. class InputHeadersControllerWrapper(InputControllerWrapper):
  215. def update_hapic_data(
  216. self, hapic_data: HapicData,
  217. processed_data: typing.Any,
  218. ) -> None:
  219. hapic_data.headers = processed_data
  220. def get_processed_data(
  221. self,
  222. request_parameters: RequestParameters,
  223. ) -> typing.Any:
  224. processed_data = self.processor.process(
  225. request_parameters.header_parameters,
  226. )
  227. return processed_data
  228. class InputFormsControllerWrapper(InputControllerWrapper):
  229. def update_hapic_data(
  230. self, hapic_data: HapicData,
  231. processed_data: typing.Any,
  232. ) -> None:
  233. hapic_data.forms = processed_data
  234. def get_processed_data(
  235. self,
  236. request_parameters: RequestParameters,
  237. ) -> typing.Any:
  238. processed_data = self.processor.process(
  239. request_parameters.form_parameters,
  240. )
  241. return processed_data
  242. class ExceptionHandlerControllerWrapper(ControllerWrapper):
  243. def __init__(
  244. self,
  245. handled_exception_class: typing.Type[Exception],
  246. context: ContextInterface,
  247. http_code: HTTPStatus=HTTPStatus.INTERNAL_SERVER_ERROR,
  248. ) -> None:
  249. self.handled_exception_class = handled_exception_class
  250. self.context = context
  251. self.http_code = http_code
  252. def _execute_wrapped_function(
  253. self,
  254. func,
  255. func_args,
  256. func_kwargs,
  257. ) -> typing.Any:
  258. try:
  259. return super()._execute_wrapped_function(
  260. func,
  261. func_args,
  262. func_kwargs,
  263. )
  264. except self.handled_exception_class as exc:
  265. # TODO: error_dict configurable name
  266. # TODO: Who assume error structure ? We have to rethink it
  267. error_dict = {
  268. 'error_message': str(exc),
  269. }
  270. if hasattr(exc, 'error_dict'):
  271. error_dict.update(exc.error_dict)
  272. error_response = self.context.get_response(
  273. error_dict,
  274. self.http_code,
  275. )
  276. return error_response