| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 | 
							- # -*- coding: utf-8 -*-
 - import os
 - import typing
 - import uuid
 - import functools
 - try:  # Python 3.5+
 -     from http import HTTPStatus
 - except ImportError:
 -     from http import client as HTTPStatus
 - 
 - from hapic.buffer import DecorationBuffer
 - from hapic.context import ContextInterface
 - from hapic.decorator import DecoratedController
 - from hapic.decorator import DECORATION_ATTRIBUTE_NAME
 - from hapic.decorator import ControllerReference
 - from hapic.decorator import ExceptionHandlerControllerWrapper
 - from hapic.decorator import InputBodyControllerWrapper
 - from hapic.decorator import InputHeadersControllerWrapper
 - from hapic.decorator import InputPathControllerWrapper
 - from hapic.decorator import InputQueryControllerWrapper
 - from hapic.decorator import InputFilesControllerWrapper
 - from hapic.decorator import OutputBodyControllerWrapper
 - from hapic.decorator import OutputHeadersControllerWrapper
 - from hapic.decorator import OutputFileControllerWrapper
 - from hapic.description import InputBodyDescription
 - from hapic.description import ErrorDescription
 - from hapic.description import InputFormsDescription
 - from hapic.description import InputHeadersDescription
 - from hapic.description import InputPathDescription
 - from hapic.description import InputQueryDescription
 - from hapic.description import InputFilesDescription
 - from hapic.description import OutputBodyDescription
 - from hapic.description import OutputHeadersDescription
 - from hapic.description import OutputFileDescription
 - from hapic.doc import DocGenerator
 - from hapic.processor import ProcessorInterface
 - from hapic.processor import MarshmallowInputProcessor
 - from hapic.processor import MarshmallowInputFilesProcessor
 - from hapic.processor import MarshmallowOutputProcessor
 - from hapic.error import ErrorBuilderInterface
 - 
 - 
 - # TODO: Gérer les cas ou c'est une liste la réponse (items, item_nb), see #12
 - # TODO: Confusion nommage body/json/forms, see #13
 - 
 - 
 - class Hapic(object):
 -     def __init__(self):
 -         self._buffer = DecorationBuffer()
 -         self._controllers = []  # type: typing.List[DecoratedController]
 -         self._context = None  # type: ContextInterface
 -         self._error_builder = None  # type: ErrorBuilderInterface
 -         self.doc_generator = DocGenerator()
 - 
 -         # This local function will be pass to different components
 -         # who will need context but declared (like with decorator)
 -         # before context declaration
 -         def context_getter():
 -             return self._context
 - 
 -         # This local function will be pass to different components
 -         # who will need error_builder but declared (like with decorator)
 -         # before error_builder declaration
 -         def error_builder_getter():
 -             return self._context.get_default_error_builder()
 - 
 -         self._context_getter = context_getter
 -         self._error_builder_getter = error_builder_getter
 - 
 -         # TODO: Permettre la surcharge des classes utilisés ci-dessous, see #14
 - 
 -     @property
 -     def controllers(self) -> typing.List[DecoratedController]:
 -         return self._controllers
 - 
 -     @property
 -     def context(self) -> ContextInterface:
 -         return self._context
 - 
 -     def set_context(self, context: ContextInterface) -> None:
 -         assert not self._context
 -         self._context = context
 - 
 -     def reset_context(self) -> None:
 -         self._context = None
 - 
 -     def with_api_doc(self, tags: typing.List['str']=None):
 -         """
 -         Permit to generate doc about a controller. Use as a decorator:
 - 
 -         ```
 -         @hapic.with_api_doc()
 -         def my_controller(self):
 -             # ...
 -         ```
 - 
 -         What it do: Register this controller with all previous given
 -         information like `@hapic.input_path(...)` etc.
 - 
 -         :param tags: list of string tags (OpenApi)
 -         :return: The decorator
 -         """
 -         # FIXME BS 20171228: Documenter sur ce que ça fait vraiment (tester:
 -         # on peut l'enlever si on veut pas generer la doc ?)
 -         tags = tags or []  # FDV
 - 
 -         def decorator(func):
 -             @functools.wraps(func)
 -             def wrapper(*args, **kwargs):
 -                 return func(*args, **kwargs)
 - 
 -             token = uuid.uuid4().hex
 -             setattr(wrapper, DECORATION_ATTRIBUTE_NAME, token)
 -             setattr(func, DECORATION_ATTRIBUTE_NAME, token)
 - 
 -             description = self._buffer.get_description()
 -             description.tags = tags
 - 
 -             reference = ControllerReference(
 -                 wrapper=wrapper,
 -                 wrapped=func,
 -                 token=token,
 -             )
 -             decorated_controller = DecoratedController(
 -                 reference=reference,
 -                 description=description,
 -                 name=func.__name__,
 -             )
 -             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.INTERNAL_SERVER_ERROR,
 -         default_http_code: HTTPStatus = HTTPStatus.OK,
 -     ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
 -         processor = processor or MarshmallowOutputProcessor()
 -         processor.schema = schema
 -         context = context or self._context_getter
 -         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 MarshmallowOutputProcessor()
 -         processor.schema = schema
 -         context = context or self._context_getter
 - 
 -         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
 - 
 -     # TODO BS 20171102: Think about possibilities to validate output ?
 -     # (with mime type, or validator)
 -     def output_file(
 -         self,
 -         output_types: typing.List[str],
 -         default_http_code: HTTPStatus = HTTPStatus.OK,
 -     ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
 -         decoration = OutputFileControllerWrapper(
 -             output_types=output_types,
 -             default_http_code=default_http_code,
 -         )
 - 
 -         def decorator(func):
 -             self._buffer.output_file = OutputFileDescription(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 self._context_getter
 - 
 -         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 self._context_getter
 - 
 -         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,
 -         as_list: typing.List[str]=None,
 -     ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
 -         processor = processor or MarshmallowInputProcessor()
 -         processor.schema = schema
 -         context = context or self._context_getter
 - 
 -         decoration = InputQueryControllerWrapper(
 -             context=context,
 -             processor=processor,
 -             error_http_code=error_http_code,
 -             default_http_code=default_http_code,
 -             as_list=as_list,
 -         )
 - 
 -         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 self._context_getter
 - 
 -         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 self._context_getter
 - 
 -         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 input_files(
 -         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 MarshmallowInputFilesProcessor()
 -         processor.schema = schema
 -         context = context or self._context_getter
 - 
 -         decoration = InputFilesControllerWrapper(
 -             context=context,
 -             processor=processor,
 -             error_http_code=error_http_code,
 -             default_http_code=default_http_code,
 -         )
 - 
 -         def decorator(func):
 -             self._buffer.input_files = InputFilesDescription(decoration)
 -             return decoration.get_wrapper(func)
 -         return decorator
 - 
 -     def handle_exception(
 -         self,
 -         handled_exception_class: typing.Type[Exception]=Exception,
 -         http_code: HTTPStatus = HTTPStatus.INTERNAL_SERVER_ERROR,
 -         error_builder: ErrorBuilderInterface=None,
 -         context: ContextInterface = None,
 -     ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
 -         context = context or self._context_getter
 -         error_builder = error_builder or self._error_builder_getter
 - 
 -         decoration = ExceptionHandlerControllerWrapper(
 -             handled_exception_class,
 -             context,
 -             error_builder=error_builder,
 -             http_code=http_code,
 -         )
 - 
 -         def decorator(func):
 -             self._buffer.errors.append(ErrorDescription(decoration))
 -             return decoration.get_wrapper(func)
 -         return decorator
 - 
 -     def generate_doc(self, title: str='', description: str='') -> dict:
 -         """
 -         See hapic.doc.DocGenerator#get_doc docstring
 -         :param title: Title of generated doc
 -         :param description: Description of generated doc
 -         :return: dict containing apispec doc
 -         """
 -         return self.doc_generator.get_doc(
 -             self._controllers,
 -             self.context,
 -             title=title,
 -             description=description,
 -         )
 - 
 -     def save_doc_in_file(
 -         self,
 -         file_path: str,
 -         title: str='',
 -         description: str='',
 -     ) -> None:
 -         """
 -         See hapic.doc.DocGenerator#get_doc docstring
 -         :param file_path: The file path to write doc in YAML format
 -         :param title: Title of generated doc
 -         :param description: Description of generated doc
 -         """
 -         self.doc_generator.save_in_file(
 -             file_path,
 -             controllers=self._controllers,
 -             context=self.context,
 -             title=title,
 -             description=description,
 -         )
 - 
 -     def add_documentation_view(
 -         self,
 -         route: str,
 -         title: str='',
 -         description: str='',
 -     ) -> None:
 -         # Ensure "/" at end of route, else web browser will not consider it as
 -         # a path
 -         if not route.endswith('/'):
 -             route = '{}/'.format(route)
 - 
 -         swaggerui_path = os.path.join(
 -             os.path.dirname(os.path.abspath(__file__)),
 -             'static',
 -             'swaggerui',
 -         )
 - 
 -         # Documentation file view
 -         doc_yaml = self.doc_generator.get_doc_yaml(
 -             controllers=self._controllers,
 -             context=self.context,
 -             title=title,
 -             description=description,
 -         )
 - 
 -         def spec_yaml_view(*args, **kwargs):
 -             """
 -             Method to return swagger generated yaml spec file.
 - 
 -             This method will be call as a framework view, like those,
 -             it need to handle the default arguments of a framework view.
 -             As frameworks have different arguments patterns, we should
 -             allow any arguments patterns (args, kwargs).
 -             """
 -             return self.context.get_response(
 -                 doc_yaml,
 -                 mimetype='text/x-yaml',
 -                 http_code=HTTPStatus.OK,
 -             )
 - 
 -         # Prepare views html content
 -         doc_index_path = os.path.join(swaggerui_path, 'index.html')
 -         with open(doc_index_path, 'r') as doc_page:
 -             doc_page_content = doc_page.read()
 -         doc_page_content = doc_page_content.replace(
 -             '{{ spec_uri }}',
 -             'spec.yml',
 -         )
 - 
 -         # Declare the swaggerui view
 -         def api_doc_view(*args, **kwargs):
 -             """
 -             Method to return html index view of swagger ui.
 - 
 -             This method will be call as a framework view, like those,
 -             it need to handle the default arguments of a framework view.
 -             As frameworks have different arguments patterns, we should
 -             allow any arguments patterns (args, kwargs).
 -             """
 -             return self.context.get_response(
 -                 doc_page_content,
 -                 http_code=HTTPStatus.OK,
 -                 mimetype='text/html',
 -             )
 - 
 -         # Add a view to generate the html index page of swagger-ui
 -         self.context.add_view(
 -             route=route,
 -             http_method='GET',
 -             view_func=api_doc_view,
 -         )
 - 
 -         # Add a doc yaml view
 -         self.context.add_view(
 -             route=os.path.join(route, 'spec.yml'),
 -             http_method='GET',
 -             view_func=spec_yaml_view,
 -         )
 - 
 -         # Add swagger directory as served static dir
 -         self.context.serve_directory(
 -             route,
 -             swaggerui_path,
 -         )
 
 
  |