decorator.py 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  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 __init__(
  110. self,
  111. context: ContextInterface,
  112. processor: ProcessorInterface,
  113. error_http_code: HTTPStatus=HTTPStatus.INTERNAL_SERVER_ERROR,
  114. default_http_code: HTTPStatus=HTTPStatus.OK,
  115. ) -> None:
  116. self.context = context
  117. self.processor = processor
  118. self.error_http_code = error_http_code
  119. self.default_http_code = default_http_code
  120. def get_error_response(
  121. self,
  122. response: typing.Any,
  123. ) -> typing.Any:
  124. error = self.processor.get_validation_error(response)
  125. error_response = self.context.get_validation_error_response(
  126. error,
  127. http_code=self.error_http_code,
  128. )
  129. return error_response
  130. def after_wrapped_function(self, response: typing.Any) -> typing.Any:
  131. try:
  132. processed_response = self.processor.process(response)
  133. prepared_response = self.context.get_response(
  134. processed_response,
  135. self.default_http_code,
  136. )
  137. return prepared_response
  138. except ProcessException:
  139. # TODO: ici ou ailleurs: il faut pas forcement donner le detail
  140. # de l'erreur (mode debug par exemple)
  141. error_response = self.get_error_response(response)
  142. return error_response
  143. class DecoratedController(object):
  144. def __init__(
  145. self,
  146. reference: 'typing.Callable[..., typing.Any]',
  147. description: ControllerDescription,
  148. ) -> None:
  149. self._reference = reference
  150. self._description = description
  151. @property
  152. def reference(self) -> 'typing.Callable[..., typing.Any]':
  153. return self._reference
  154. @property
  155. def description(self) -> ControllerDescription:
  156. return self._description
  157. class OutputBodyControllerWrapper(OutputControllerWrapper):
  158. pass
  159. class OutputHeadersControllerWrapper(OutputControllerWrapper):
  160. # TODO: write me
  161. pass
  162. class InputPathControllerWrapper(InputControllerWrapper):
  163. def update_hapic_data(
  164. self, hapic_data: HapicData,
  165. processed_data: typing.Any,
  166. ) -> None:
  167. hapic_data.path = processed_data
  168. def get_processed_data(
  169. self,
  170. request_parameters: RequestParameters,
  171. ) -> typing.Any:
  172. processed_data = self.processor.process(
  173. request_parameters.path_parameters,
  174. )
  175. return processed_data
  176. class InputQueryControllerWrapper(InputControllerWrapper):
  177. def update_hapic_data(
  178. self, hapic_data: HapicData,
  179. processed_data: typing.Any,
  180. ) -> None:
  181. hapic_data.query = processed_data
  182. def get_processed_data(
  183. self,
  184. request_parameters: RequestParameters,
  185. ) -> typing.Any:
  186. processed_data = self.processor.process(
  187. request_parameters.query_parameters,
  188. )
  189. return processed_data
  190. class InputBodyControllerWrapper(InputControllerWrapper):
  191. def update_hapic_data(
  192. self, hapic_data: HapicData,
  193. processed_data: typing.Any,
  194. ) -> None:
  195. hapic_data.body = processed_data
  196. def get_processed_data(
  197. self,
  198. request_parameters: RequestParameters,
  199. ) -> typing.Any:
  200. processed_data = self.processor.process(
  201. request_parameters.body_parameters,
  202. )
  203. return processed_data
  204. class InputHeadersControllerWrapper(InputControllerWrapper):
  205. def update_hapic_data(
  206. self, hapic_data: HapicData,
  207. processed_data: typing.Any,
  208. ) -> None:
  209. hapic_data.headers = processed_data
  210. def get_processed_data(
  211. self,
  212. request_parameters: RequestParameters,
  213. ) -> typing.Any:
  214. processed_data = self.processor.process(
  215. request_parameters.header_parameters,
  216. )
  217. return processed_data
  218. class InputFormsControllerWrapper(InputControllerWrapper):
  219. def update_hapic_data(
  220. self, hapic_data: HapicData,
  221. processed_data: typing.Any,
  222. ) -> None:
  223. hapic_data.forms = processed_data
  224. def get_processed_data(
  225. self,
  226. request_parameters: RequestParameters,
  227. ) -> typing.Any:
  228. processed_data = self.processor.process(
  229. request_parameters.form_parameters,
  230. )
  231. return processed_data