decorator.py 9.5KB

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