123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- # -*- coding: utf-8 -*-
- import typing
- from http import HTTPStatus
-
- import bottle
- import functools
-
- # CHANGE
- import marshmallow
-
- from hapic.buffer import DecorationBuffer
- from hapic.context import ContextInterface, BottleContext
- from hapic.decorator import DecoratedController
- from hapic.decorator import InputBodyControllerWrapper
- from hapic.decorator import InputHeadersControllerWrapper
- from hapic.decorator import InputPathControllerWrapper
- from hapic.decorator import InputQueryControllerWrapper
- from hapic.decorator import OutputBodyControllerWrapper
- from hapic.decorator import OutputHeadersControllerWrapper
- from hapic.description import InputBodyDescription
- from hapic.description import InputFormsDescription
- from hapic.description import InputHeadersDescription
- from hapic.description import InputPathDescription
- from hapic.description import InputQueryDescription
- from hapic.description import OutputBodyDescription
- from hapic.description import OutputHeadersDescription
- from hapic.processor import ProcessorInterface, MarshmallowInputProcessor
-
- flatten = lambda l: [item for sublist in l for item in sublist]
-
- # TODO: Gérer les erreurs de schema
- # TODO: Gérer les cas ou c'est une liste la réponse (items, item_nb)
- # TODO: Confusion nommage body/json/forms
-
- # _waiting = {}
- # _endpoints = {}
- # TODO NOW: C'est un gros gros fake !
- class ErrorResponseSchema(marshmallow.Schema):
- error_message = marshmallow.fields.String(required=True)
- error_details = marshmallow.fields.Dict(required=True)
-
- _default_global_context = BottleContext()
- _default_global_error_schema = ErrorResponseSchema()
-
-
- class Hapic(object):
- def __init__(self):
- self._buffer = DecorationBuffer()
- self._controllers = []
-
- def with_api_doc(self):
- def decorator(func):
-
- @functools.wraps(func)
- def wrapper(*args, **kwargs):
- return func(*args, **kwargs)
-
- description = self._buffer.get_description()
- decorated_controller = DecoratedController(
- reference=wrapper,
- description=description,
- )
- self._buffer.clear()
- self._controllers.append(decorated_controller)
- return wrapper
-
- return decorator
-
- def output_body(
- self,
- schema: typing.Any,
- processor: ProcessorInterface = None,
- context: ContextInterface = None,
- error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
- default_http_code: HTTPStatus = HTTPStatus.OK,
- ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
- processor = processor or MarshmallowInputProcessor()
- processor.schema = schema
- context = context or _default_global_context
-
- decoration = OutputBodyControllerWrapper(
- context=context,
- processor=processor,
- error_http_code=error_http_code,
- default_http_code=default_http_code,
- )
-
- def decorator(func):
- self._buffer.output_body = OutputBodyDescription(decoration)
- return decoration.get_wrapper(func)
- return decorator
-
- def output_headers(
- self,
- schema: typing.Any,
- processor: ProcessorInterface = None,
- context: ContextInterface = None,
- error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
- default_http_code: HTTPStatus = HTTPStatus.OK,
- ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
- processor = processor or MarshmallowInputProcessor()
- processor.schema = schema
- context = context or _default_global_context
-
- decoration = OutputHeadersControllerWrapper(
- context=context,
- processor=processor,
- error_http_code=error_http_code,
- default_http_code=default_http_code,
- )
-
- def decorator(func):
- self._buffer.output_headers = OutputHeadersDescription(decoration)
- return decoration.get_wrapper(func)
- return decorator
-
- def input_headers(
- self,
- schema: typing.Any,
- processor: ProcessorInterface = None,
- context: ContextInterface = None,
- error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
- default_http_code: HTTPStatus = HTTPStatus.OK,
- ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
- processor = processor or MarshmallowInputProcessor()
- processor.schema = schema
- context = context or _default_global_context
-
- decoration = InputHeadersControllerWrapper(
- context=context,
- processor=processor,
- error_http_code=error_http_code,
- default_http_code=default_http_code,
- )
-
- def decorator(func):
- self._buffer.input_headers = InputHeadersDescription(decoration)
- return decoration.get_wrapper(func)
- return decorator
-
- def input_path(
- self,
- schema: typing.Any,
- processor: ProcessorInterface = None,
- context: ContextInterface = None,
- error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
- default_http_code: HTTPStatus = HTTPStatus.OK,
- ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
- processor = processor or MarshmallowInputProcessor()
- processor.schema = schema
- context = context or _default_global_context
-
- decoration = InputPathControllerWrapper(
- context=context,
- processor=processor,
- error_http_code=error_http_code,
- default_http_code=default_http_code,
- )
-
- def decorator(func):
- self._buffer.input_path = InputPathDescription(decoration)
- return decoration.get_wrapper(func)
- return decorator
-
- def input_query(
- self,
- schema: typing.Any,
- processor: ProcessorInterface = None,
- context: ContextInterface = None,
- error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
- default_http_code: HTTPStatus = HTTPStatus.OK,
- ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
- processor = processor or MarshmallowInputProcessor()
- processor.schema = schema
- context = context or _default_global_context
-
- decoration = InputQueryControllerWrapper(
- context=context,
- processor=processor,
- error_http_code=error_http_code,
- default_http_code=default_http_code,
- )
-
- def decorator(func):
- self._buffer.input_query = InputQueryDescription(decoration)
- return decoration.get_wrapper(func)
- return decorator
-
- def input_body(
- self,
- schema: typing.Any,
- processor: ProcessorInterface = None,
- context: ContextInterface = None,
- error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
- default_http_code: HTTPStatus = HTTPStatus.OK,
- ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
- processor = processor or MarshmallowInputProcessor()
- processor.schema = schema
- context = context or _default_global_context
-
- decoration = InputBodyControllerWrapper(
- context=context,
- processor=processor,
- error_http_code=error_http_code,
- default_http_code=default_http_code,
- )
-
- def decorator(func):
- self._buffer.input_body = InputBodyDescription(decoration)
- return decoration.get_wrapper(func)
- return decorator
-
- def input_forms(
- self,
- schema: typing.Any,
- processor: ProcessorInterface=None,
- context: ContextInterface=None,
- error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
- default_http_code: HTTPStatus = HTTPStatus.OK,
- ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
- processor = processor or MarshmallowInputProcessor()
- processor.schema = schema
- context = context or _default_global_context
-
- decoration = InputBodyControllerWrapper(
- context=context,
- processor=processor,
- error_http_code=error_http_code,
- default_http_code=default_http_code,
- )
-
- def decorator(func):
- self._buffer.input_forms = InputFormsDescription(decoration)
- return decoration.get_wrapper(func)
- return decorator
-
- def generate_doc(self, app=None):
- # TODO @Damien bottle specific code !
- app = app or bottle.default_app()
-
- route_by_callbacks = []
- routes = flatten(app.router.dyna_routes.values())
- for path, path_regex, route, func_ in routes:
- route_by_callbacks.append(route.callback)
-
- for description in self._controllers:
- for path, path_regex, route, func_ in routes:
- if route.callback == description.reference:
- # TODO: use description to feed apispec
- print(route.method, path, description)
- continue
|