hapic.py 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. # -*- coding: utf-8 -*-
  2. import typing
  3. from http import HTTPStatus
  4. import functools
  5. import marshmallow
  6. from hapic.buffer import DecorationBuffer
  7. from hapic.context import ContextInterface, BottleContext
  8. from hapic.decorator import DecoratedController
  9. from hapic.decorator import ExceptionHandlerControllerWrapper
  10. from hapic.decorator import InputBodyControllerWrapper
  11. from hapic.decorator import InputHeadersControllerWrapper
  12. from hapic.decorator import InputPathControllerWrapper
  13. from hapic.decorator import InputQueryControllerWrapper
  14. from hapic.decorator import OutputBodyControllerWrapper
  15. from hapic.decorator import OutputHeadersControllerWrapper
  16. from hapic.description import InputBodyDescription, ErrorDescription
  17. from hapic.description import InputFormsDescription
  18. from hapic.description import InputHeadersDescription
  19. from hapic.description import InputPathDescription
  20. from hapic.description import InputQueryDescription
  21. from hapic.description import OutputBodyDescription
  22. from hapic.description import OutputHeadersDescription
  23. from hapic.doc import DocGenerator
  24. from hapic.processor import ProcessorInterface
  25. from hapic.processor import MarshmallowInputProcessor
  26. # TODO: Gérer les erreurs de schema
  27. # TODO: Gérer les cas ou c'est une liste la réponse (items, item_nb)
  28. # TODO: Confusion nommage body/json/forms
  29. # _waiting = {}
  30. # _endpoints = {}
  31. # TODO NOW: C'est un gros gros fake !
  32. class ErrorResponseSchema(marshmallow.Schema):
  33. error_message = marshmallow.fields.String(required=True)
  34. error_details = marshmallow.fields.Dict(required=True)
  35. _default_global_context = BottleContext()
  36. _default_global_error_schema = ErrorResponseSchema()
  37. class Hapic(object):
  38. def __init__(self):
  39. self._buffer = DecorationBuffer()
  40. self._controllers = [] # type: typing.List[DecoratedController]
  41. # TODO: Permettre la surcharge des classes utilisés ci-dessous
  42. def with_api_doc(self):
  43. def decorator(func):
  44. @functools.wraps(func)
  45. def wrapper(*args, **kwargs):
  46. return func(*args, **kwargs)
  47. description = self._buffer.get_description()
  48. decorated_controller = DecoratedController(
  49. reference=wrapper,
  50. description=description,
  51. )
  52. self._buffer.clear()
  53. self._controllers.append(decorated_controller)
  54. return wrapper
  55. return decorator
  56. def output_body(
  57. self,
  58. schema: typing.Any,
  59. processor: ProcessorInterface = None,
  60. context: ContextInterface = None,
  61. error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
  62. default_http_code: HTTPStatus = HTTPStatus.OK,
  63. ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
  64. processor = processor or MarshmallowInputProcessor()
  65. processor.schema = schema
  66. context = context or _default_global_context
  67. decoration = OutputBodyControllerWrapper(
  68. context=context,
  69. processor=processor,
  70. error_http_code=error_http_code,
  71. default_http_code=default_http_code,
  72. )
  73. def decorator(func):
  74. self._buffer.output_body = OutputBodyDescription(decoration)
  75. return decoration.get_wrapper(func)
  76. return decorator
  77. def output_headers(
  78. self,
  79. schema: typing.Any,
  80. processor: ProcessorInterface = None,
  81. context: ContextInterface = None,
  82. error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
  83. default_http_code: HTTPStatus = HTTPStatus.OK,
  84. ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
  85. processor = processor or MarshmallowInputProcessor()
  86. processor.schema = schema
  87. context = context or _default_global_context
  88. decoration = OutputHeadersControllerWrapper(
  89. context=context,
  90. processor=processor,
  91. error_http_code=error_http_code,
  92. default_http_code=default_http_code,
  93. )
  94. def decorator(func):
  95. self._buffer.output_headers = OutputHeadersDescription(decoration)
  96. return decoration.get_wrapper(func)
  97. return decorator
  98. def input_headers(
  99. self,
  100. schema: typing.Any,
  101. processor: ProcessorInterface = None,
  102. context: ContextInterface = None,
  103. error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
  104. default_http_code: HTTPStatus = HTTPStatus.OK,
  105. ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
  106. processor = processor or MarshmallowInputProcessor()
  107. processor.schema = schema
  108. context = context or _default_global_context
  109. decoration = InputHeadersControllerWrapper(
  110. context=context,
  111. processor=processor,
  112. error_http_code=error_http_code,
  113. default_http_code=default_http_code,
  114. )
  115. def decorator(func):
  116. self._buffer.input_headers = InputHeadersDescription(decoration)
  117. return decoration.get_wrapper(func)
  118. return decorator
  119. def input_path(
  120. self,
  121. schema: typing.Any,
  122. processor: ProcessorInterface = None,
  123. context: ContextInterface = None,
  124. error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
  125. default_http_code: HTTPStatus = HTTPStatus.OK,
  126. ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
  127. processor = processor or MarshmallowInputProcessor()
  128. processor.schema = schema
  129. context = context or _default_global_context
  130. decoration = InputPathControllerWrapper(
  131. context=context,
  132. processor=processor,
  133. error_http_code=error_http_code,
  134. default_http_code=default_http_code,
  135. )
  136. def decorator(func):
  137. self._buffer.input_path = InputPathDescription(decoration)
  138. return decoration.get_wrapper(func)
  139. return decorator
  140. def input_query(
  141. self,
  142. schema: typing.Any,
  143. processor: ProcessorInterface = None,
  144. context: ContextInterface = None,
  145. error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
  146. default_http_code: HTTPStatus = HTTPStatus.OK,
  147. ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
  148. processor = processor or MarshmallowInputProcessor()
  149. processor.schema = schema
  150. context = context or _default_global_context
  151. decoration = InputQueryControllerWrapper(
  152. context=context,
  153. processor=processor,
  154. error_http_code=error_http_code,
  155. default_http_code=default_http_code,
  156. )
  157. def decorator(func):
  158. self._buffer.input_query = InputQueryDescription(decoration)
  159. return decoration.get_wrapper(func)
  160. return decorator
  161. def input_body(
  162. self,
  163. schema: typing.Any,
  164. processor: ProcessorInterface = None,
  165. context: ContextInterface = None,
  166. error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
  167. default_http_code: HTTPStatus = HTTPStatus.OK,
  168. ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
  169. processor = processor or MarshmallowInputProcessor()
  170. processor.schema = schema
  171. context = context or _default_global_context
  172. decoration = InputBodyControllerWrapper(
  173. context=context,
  174. processor=processor,
  175. error_http_code=error_http_code,
  176. default_http_code=default_http_code,
  177. )
  178. def decorator(func):
  179. self._buffer.input_body = InputBodyDescription(decoration)
  180. return decoration.get_wrapper(func)
  181. return decorator
  182. def input_forms(
  183. self,
  184. schema: typing.Any,
  185. processor: ProcessorInterface=None,
  186. context: ContextInterface=None,
  187. error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
  188. default_http_code: HTTPStatus = HTTPStatus.OK,
  189. ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
  190. processor = processor or MarshmallowInputProcessor()
  191. processor.schema = schema
  192. context = context or _default_global_context
  193. decoration = InputBodyControllerWrapper(
  194. context=context,
  195. processor=processor,
  196. error_http_code=error_http_code,
  197. default_http_code=default_http_code,
  198. )
  199. def decorator(func):
  200. self._buffer.input_forms = InputFormsDescription(decoration)
  201. return decoration.get_wrapper(func)
  202. return decorator
  203. def handle_exception(
  204. self,
  205. handled_exception_class: typing.Type[Exception],
  206. http_code: HTTPStatus = HTTPStatus.INTERNAL_SERVER_ERROR,
  207. context: ContextInterface = None,
  208. ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
  209. context = context or _default_global_context
  210. decoration = ExceptionHandlerControllerWrapper(
  211. handled_exception_class,
  212. context,
  213. http_code,
  214. )
  215. def decorator(func):
  216. self._buffer.errors.append(ErrorDescription(decoration))
  217. return decoration.get_wrapper(func)
  218. return decorator
  219. def generate_doc(self, app=None):
  220. # TODO @Damien bottle specific code !
  221. doc_generator = DocGenerator()
  222. return doc_generator.get_doc(self._controllers, app)