|  | @@ -1,422 +1,251 @@
 | 
	
		
			
			| 1 | 1 |  # -*- coding: utf-8 -*-
 | 
	
		
			
			| 2 |  | -import json
 | 
	
		
			
			| 3 | 2 |  import typing
 | 
	
		
			
			| 4 | 3 |  from http import HTTPStatus
 | 
	
		
			
			| 5 | 4 |  
 | 
	
		
			
			| 6 |  | -import functools
 | 
	
		
			
			| 7 |  | -
 | 
	
		
			
			| 8 | 5 |  import bottle
 | 
	
		
			
			| 9 |  | -
 | 
	
		
			
			| 10 |  | -# TODO: Gérer les erreurs de schema
 | 
	
		
			
			| 11 |  | -# TODO: Gérer les cas ou c'est une liste la réponse (items, item_nb)
 | 
	
		
			
			| 12 |  | -
 | 
	
		
			
			|  | 6 | +import functools
 | 
	
		
			
			| 13 | 7 |  
 | 
	
		
			
			| 14 | 8 |  # CHANGE
 | 
	
		
			
			| 15 |  | -from hapic.exception import InputValidationException, \
 | 
	
		
			
			| 16 |  | -    OutputValidationException, InputWorkflowException, ProcessException
 | 
	
		
			
			|  | 9 | +import marshmallow
 | 
	
		
			
			|  | 10 | +
 | 
	
		
			
			|  | 11 | +from hapic.buffer import DecorationBuffer
 | 
	
		
			
			|  | 12 | +from hapic.context import ContextInterface, BottleContext
 | 
	
		
			
			|  | 13 | +from hapic.decorator import DecoratedController
 | 
	
		
			
			|  | 14 | +from hapic.decorator import InputBodyControllerWrapper
 | 
	
		
			
			|  | 15 | +from hapic.decorator import InputHeadersControllerWrapper
 | 
	
		
			
			|  | 16 | +from hapic.decorator import InputPathControllerWrapper
 | 
	
		
			
			|  | 17 | +from hapic.decorator import InputQueryControllerWrapper
 | 
	
		
			
			|  | 18 | +from hapic.decorator import OutputBodyControllerWrapper
 | 
	
		
			
			|  | 19 | +from hapic.decorator import OutputHeadersControllerWrapper
 | 
	
		
			
			|  | 20 | +from hapic.description import InputBodyDescription
 | 
	
		
			
			|  | 21 | +from hapic.description import InputFormsDescription
 | 
	
		
			
			|  | 22 | +from hapic.description import InputHeadersDescription
 | 
	
		
			
			|  | 23 | +from hapic.description import InputPathDescription
 | 
	
		
			
			|  | 24 | +from hapic.description import InputQueryDescription
 | 
	
		
			
			|  | 25 | +from hapic.description import OutputBodyDescription
 | 
	
		
			
			|  | 26 | +from hapic.description import OutputHeadersDescription
 | 
	
		
			
			|  | 27 | +from hapic.processor import ProcessorInterface, MarshmallowInputProcessor
 | 
	
		
			
			| 17 | 28 |  
 | 
	
		
			
			| 18 | 29 |  flatten = lambda l: [item for sublist in l for item in sublist]
 | 
	
		
			
			| 19 | 30 |  
 | 
	
		
			
			|  | 31 | +# TODO: Gérer les erreurs de schema
 | 
	
		
			
			|  | 32 | +# TODO: Gérer les cas ou c'est une liste la réponse (items, item_nb)
 | 
	
		
			
			|  | 33 | +# TODO: Confusion nommage body/json/forms
 | 
	
		
			
			| 20 | 34 |  
 | 
	
		
			
			| 21 |  | -_waiting = {}
 | 
	
		
			
			| 22 |  | -_endpoints = {}
 | 
	
		
			
			| 23 |  | -_default_global_context = None
 | 
	
		
			
			| 24 |  | -_default_global_error_schema = None
 | 
	
		
			
			| 25 |  | -
 | 
	
		
			
			| 26 |  | -
 | 
	
		
			
			| 27 |  | -def error_schema(schema):
 | 
	
		
			
			| 28 |  | -    global _default_global_error_schema
 | 
	
		
			
			| 29 |  | -    _default_global_error_schema = schema
 | 
	
		
			
			| 30 |  | -
 | 
	
		
			
			| 31 |  | -    def decorator(func):
 | 
	
		
			
			| 32 |  | -        @functools.wraps(func)
 | 
	
		
			
			| 33 |  | -        def wrapper(*args, **kwargs):
 | 
	
		
			
			| 34 |  | -            return func(*args, **kwargs)
 | 
	
		
			
			| 35 |  | -        return wrapper
 | 
	
		
			
			| 36 |  | -    return decorator
 | 
	
		
			
			| 37 |  | -
 | 
	
		
			
			| 38 |  | -
 | 
	
		
			
			| 39 |  | -def set_fake_default_context(context):
 | 
	
		
			
			| 40 |  | -    global _default_global_context
 | 
	
		
			
			| 41 |  | -    _default_global_context = context
 | 
	
		
			
			| 42 |  | -
 | 
	
		
			
			| 43 |  | -
 | 
	
		
			
			| 44 |  | -def _register(func):
 | 
	
		
			
			| 45 |  | -    assert func not in _endpoints
 | 
	
		
			
			| 46 |  | -    global _waiting
 | 
	
		
			
			| 47 |  | -
 | 
	
		
			
			| 48 |  | -    _endpoints[func] = _waiting
 | 
	
		
			
			| 49 |  | -    _waiting = {}
 | 
	
		
			
			| 50 |  | -
 | 
	
		
			
			| 51 |  | -
 | 
	
		
			
			| 52 |  | -def with_api_doc():
 | 
	
		
			
			| 53 |  | -    def decorator(func):
 | 
	
		
			
			| 54 |  | -
 | 
	
		
			
			| 55 |  | -        @functools.wraps(func)
 | 
	
		
			
			| 56 |  | -        def wrapper(*args, **kwargs):
 | 
	
		
			
			| 57 |  | -            return func(*args, **kwargs)
 | 
	
		
			
			| 58 |  | -
 | 
	
		
			
			| 59 |  | -        _register(wrapper)
 | 
	
		
			
			| 60 |  | -        return wrapper
 | 
	
		
			
			| 61 |  | -
 | 
	
		
			
			| 62 |  | -    return decorator
 | 
	
		
			
			| 63 |  | -
 | 
	
		
			
			| 64 |  | -
 | 
	
		
			
			| 65 |  | -def with_api_doc_bis():
 | 
	
		
			
			| 66 |  | -    def decorator(func):
 | 
	
		
			
			| 67 |  | -
 | 
	
		
			
			| 68 |  | -        @functools.wraps(func)
 | 
	
		
			
			| 69 |  | -        def wrapper(*args, **kwargs):
 | 
	
		
			
			| 70 |  | -            return func(*args, **kwargs)
 | 
	
		
			
			|  | 35 | +# _waiting = {}
 | 
	
		
			
			|  | 36 | +# _endpoints = {}
 | 
	
		
			
			|  | 37 | +# TODO NOW: C'est un gros gros fake !
 | 
	
		
			
			|  | 38 | +class ErrorResponseSchema(marshmallow.Schema):
 | 
	
		
			
			|  | 39 | +    error_message = marshmallow.fields.String(required=True)
 | 
	
		
			
			|  | 40 | +    error_details = marshmallow.fields.Dict(required=True)
 | 
	
		
			
			| 71 | 41 |  
 | 
	
		
			
			| 72 |  | -        _register(func)
 | 
	
		
			
			| 73 |  | -        return wrapper
 | 
	
		
			
			|  | 42 | +_default_global_context = BottleContext()
 | 
	
		
			
			|  | 43 | +_default_global_error_schema = ErrorResponseSchema()
 | 
	
		
			
			| 74 | 44 |  
 | 
	
		
			
			| 75 |  | -    return decorator
 | 
	
		
			
			| 76 | 45 |  
 | 
	
		
			
			|  | 46 | +class Hapic(object):
 | 
	
		
			
			|  | 47 | +    def __init__(self):
 | 
	
		
			
			|  | 48 | +        self._buffer = DecorationBuffer()
 | 
	
		
			
			|  | 49 | +        self._controllers = []
 | 
	
		
			
			| 77 | 50 |  
 | 
	
		
			
			| 78 |  | -def generate_doc(app=None):
 | 
	
		
			
			| 79 |  | -    # TODO @Damien bottle specific code !
 | 
	
		
			
			| 80 |  | -    app = app or bottle.default_app()
 | 
	
		
			
			|  | 51 | +    def with_api_doc(self):
 | 
	
		
			
			|  | 52 | +        def decorator(func):
 | 
	
		
			
			| 81 | 53 |  
 | 
	
		
			
			| 82 |  | -    route_by_callbacks = []
 | 
	
		
			
			| 83 |  | -    routes = flatten(app.router.dyna_routes.values())
 | 
	
		
			
			| 84 |  | -    for path, path_regex, route, func_ in routes:
 | 
	
		
			
			| 85 |  | -        route_by_callbacks.append(route.callback)
 | 
	
		
			
			|  | 54 | +            @functools.wraps(func)
 | 
	
		
			
			|  | 55 | +            def wrapper(*args, **kwargs):
 | 
	
		
			
			|  | 56 | +                return func(*args, **kwargs)
 | 
	
		
			
			| 86 | 57 |  
 | 
	
		
			
			| 87 |  | -    for func, descriptions in _endpoints.items():
 | 
	
		
			
			| 88 |  | -        routes = flatten(app.router.dyna_routes.values())
 | 
	
		
			
			| 89 |  | -        for path, path_regex, route, func_ in routes:
 | 
	
		
			
			| 90 |  | -            if route.callback == func:
 | 
	
		
			
			| 91 |  | -                print(route.method, path, descriptions)
 | 
	
		
			
			| 92 |  | -                continue
 | 
	
		
			
			|  | 58 | +            description = self._buffer.get_description()
 | 
	
		
			
			|  | 59 | +            decorated_controller = DecoratedController(
 | 
	
		
			
			|  | 60 | +                reference=wrapper,
 | 
	
		
			
			|  | 61 | +                description=description,
 | 
	
		
			
			|  | 62 | +            )
 | 
	
		
			
			|  | 63 | +            self._buffer.clear()
 | 
	
		
			
			|  | 64 | +            self._controllers.append(decorated_controller)
 | 
	
		
			
			|  | 65 | +            return wrapper
 | 
	
		
			
			| 93 | 66 |  
 | 
	
		
			
			|  | 67 | +        return decorator
 | 
	
		
			
			| 94 | 68 |  
 | 
	
		
			
			| 95 |  | -class RequestParameters(object):
 | 
	
		
			
			| 96 |  | -    def __init__(
 | 
	
		
			
			| 97 |  | -        self,
 | 
	
		
			
			| 98 |  | -        path_parameters,
 | 
	
		
			
			| 99 |  | -        query_parameters,
 | 
	
		
			
			| 100 |  | -        body_parameters,
 | 
	
		
			
			| 101 |  | -        form_parameters,
 | 
	
		
			
			| 102 |  | -        header_parameters,
 | 
	
		
			
			| 103 |  | -    ):
 | 
	
		
			
			| 104 |  | -        self.path_parameters = path_parameters
 | 
	
		
			
			| 105 |  | -        self.query_parameters = query_parameters
 | 
	
		
			
			| 106 |  | -        self.body_parameters = body_parameters
 | 
	
		
			
			| 107 |  | -        self.form_parameters = form_parameters
 | 
	
		
			
			| 108 |  | -        self.header_parameters = header_parameters
 | 
	
		
			
			| 109 |  | -
 | 
	
		
			
			| 110 |  | -
 | 
	
		
			
			| 111 |  | -class ProcessValidationError(object):
 | 
	
		
			
			| 112 |  | -    def __init__(
 | 
	
		
			
			|  | 69 | +    def output_body(
 | 
	
		
			
			| 113 | 70 |          self,
 | 
	
		
			
			| 114 |  | -        error_message: str,
 | 
	
		
			
			| 115 |  | -        error_details: dict,
 | 
	
		
			
			| 116 |  | -    ) -> None:
 | 
	
		
			
			| 117 |  | -        self.error_message = error_message
 | 
	
		
			
			| 118 |  | -        self.error_details = error_details
 | 
	
		
			
			| 119 |  | -
 | 
	
		
			
			|  | 71 | +        schema: typing.Any,
 | 
	
		
			
			|  | 72 | +        processor: ProcessorInterface = None,
 | 
	
		
			
			|  | 73 | +        context: ContextInterface = None,
 | 
	
		
			
			|  | 74 | +        error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
 | 
	
		
			
			|  | 75 | +        default_http_code: HTTPStatus = HTTPStatus.OK,
 | 
	
		
			
			|  | 76 | +    ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
 | 
	
		
			
			|  | 77 | +        processor = processor or MarshmallowInputProcessor()
 | 
	
		
			
			|  | 78 | +        processor.schema = schema
 | 
	
		
			
			|  | 79 | +        context = context or _default_global_context
 | 
	
		
			
			|  | 80 | +
 | 
	
		
			
			|  | 81 | +        decoration = OutputBodyControllerWrapper(
 | 
	
		
			
			|  | 82 | +            context=context,
 | 
	
		
			
			|  | 83 | +            processor=processor,
 | 
	
		
			
			|  | 84 | +            error_http_code=error_http_code,
 | 
	
		
			
			|  | 85 | +            default_http_code=default_http_code,
 | 
	
		
			
			|  | 86 | +        )
 | 
	
		
			
			| 120 | 87 |  
 | 
	
		
			
			| 121 |  | -class ContextInterface(object):
 | 
	
		
			
			| 122 |  | -    def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
 | 
	
		
			
			| 123 |  | -        raise NotImplementedError()
 | 
	
		
			
			|  | 88 | +        def decorator(func):
 | 
	
		
			
			|  | 89 | +            self._buffer.output_body = OutputBodyDescription(decoration)
 | 
	
		
			
			|  | 90 | +            return decoration.get_wrapper(func)
 | 
	
		
			
			|  | 91 | +        return decorator
 | 
	
		
			
			| 124 | 92 |  
 | 
	
		
			
			| 125 |  | -    def get_response(
 | 
	
		
			
			|  | 93 | +    def output_headers(
 | 
	
		
			
			| 126 | 94 |          self,
 | 
	
		
			
			| 127 |  | -        response: dict,
 | 
	
		
			
			| 128 |  | -        http_code: int,
 | 
	
		
			
			| 129 |  | -    ) -> typing.Any:
 | 
	
		
			
			| 130 |  | -        raise NotImplementedError()
 | 
	
		
			
			| 131 |  | -
 | 
	
		
			
			| 132 |  | -    def get_validation_error_response(
 | 
	
		
			
			| 133 |  | -        self,
 | 
	
		
			
			| 134 |  | -        error: ProcessValidationError,
 | 
	
		
			
			| 135 |  | -        http_code: HTTPStatus=HTTPStatus.BAD_REQUEST,
 | 
	
		
			
			| 136 |  | -    ) -> typing.Any:
 | 
	
		
			
			| 137 |  | -        raise NotImplementedError()
 | 
	
		
			
			| 138 |  | -
 | 
	
		
			
			| 139 |  | -
 | 
	
		
			
			| 140 |  | -class BottleContext(ContextInterface):
 | 
	
		
			
			| 141 |  | -    def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
 | 
	
		
			
			| 142 |  | -        return RequestParameters(
 | 
	
		
			
			| 143 |  | -            path_parameters=bottle.request.url_args,
 | 
	
		
			
			| 144 |  | -            query_parameters=bottle.request.params,
 | 
	
		
			
			| 145 |  | -            body_parameters=bottle.request.json,
 | 
	
		
			
			| 146 |  | -            form_parameters=bottle.request.forms,
 | 
	
		
			
			| 147 |  | -            header_parameters=bottle.request.headers,
 | 
	
		
			
			|  | 95 | +        schema: typing.Any,
 | 
	
		
			
			|  | 96 | +        processor: ProcessorInterface = None,
 | 
	
		
			
			|  | 97 | +        context: ContextInterface = None,
 | 
	
		
			
			|  | 98 | +        error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
 | 
	
		
			
			|  | 99 | +        default_http_code: HTTPStatus = HTTPStatus.OK,
 | 
	
		
			
			|  | 100 | +    ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
 | 
	
		
			
			|  | 101 | +        processor = processor or MarshmallowInputProcessor()
 | 
	
		
			
			|  | 102 | +        processor.schema = schema
 | 
	
		
			
			|  | 103 | +        context = context or _default_global_context
 | 
	
		
			
			|  | 104 | +
 | 
	
		
			
			|  | 105 | +        decoration = OutputHeadersControllerWrapper(
 | 
	
		
			
			|  | 106 | +            context=context,
 | 
	
		
			
			|  | 107 | +            processor=processor,
 | 
	
		
			
			|  | 108 | +            error_http_code=error_http_code,
 | 
	
		
			
			|  | 109 | +            default_http_code=default_http_code,
 | 
	
		
			
			| 148 | 110 |          )
 | 
	
		
			
			| 149 | 111 |  
 | 
	
		
			
			| 150 |  | -    def get_response(
 | 
	
		
			
			| 151 |  | -        self,
 | 
	
		
			
			| 152 |  | -        response: dict,
 | 
	
		
			
			| 153 |  | -        http_code: int,
 | 
	
		
			
			| 154 |  | -    ) -> bottle.HTTPResponse:
 | 
	
		
			
			| 155 |  | -        return bottle.HTTPResponse(
 | 
	
		
			
			| 156 |  | -            body=json.dumps(response),
 | 
	
		
			
			| 157 |  | -            headers=[
 | 
	
		
			
			| 158 |  | -                ('Content-Type', 'application/json'),
 | 
	
		
			
			| 159 |  | -            ],
 | 
	
		
			
			| 160 |  | -            status=http_code,
 | 
	
		
			
			| 161 |  | -        )
 | 
	
		
			
			|  | 112 | +        def decorator(func):
 | 
	
		
			
			|  | 113 | +            self._buffer.output_headers = OutputHeadersDescription(decoration)
 | 
	
		
			
			|  | 114 | +            return decoration.get_wrapper(func)
 | 
	
		
			
			|  | 115 | +        return decorator
 | 
	
		
			
			| 162 | 116 |  
 | 
	
		
			
			| 163 |  | -    def get_validation_error_response(
 | 
	
		
			
			|  | 117 | +    def input_headers(
 | 
	
		
			
			| 164 | 118 |          self,
 | 
	
		
			
			| 165 |  | -        error: ProcessValidationError,
 | 
	
		
			
			| 166 |  | -        http_code: HTTPStatus=HTTPStatus.BAD_REQUEST,
 | 
	
		
			
			| 167 |  | -    ) -> typing.Any:
 | 
	
		
			
			| 168 |  | -        unmarshall = _default_global_error_schema.dump(error)
 | 
	
		
			
			| 169 |  | -        if unmarshall.errors:
 | 
	
		
			
			| 170 |  | -            raise OutputValidationException(
 | 
	
		
			
			| 171 |  | -                'Validation error during dump of error response: {}'.format(
 | 
	
		
			
			| 172 |  | -                    str(unmarshall.errors)
 | 
	
		
			
			| 173 |  | -                )
 | 
	
		
			
			| 174 |  | -            )
 | 
	
		
			
			| 175 |  | -
 | 
	
		
			
			| 176 |  | -        return bottle.HTTPResponse(
 | 
	
		
			
			| 177 |  | -            body=json.dumps(unmarshall.data),
 | 
	
		
			
			| 178 |  | -            headers=[
 | 
	
		
			
			| 179 |  | -                ('Content-Type', 'application/json'),
 | 
	
		
			
			| 180 |  | -            ],
 | 
	
		
			
			| 181 |  | -            status=int(http_code),
 | 
	
		
			
			|  | 119 | +        schema: typing.Any,
 | 
	
		
			
			|  | 120 | +        processor: ProcessorInterface = None,
 | 
	
		
			
			|  | 121 | +        context: ContextInterface = None,
 | 
	
		
			
			|  | 122 | +        error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
 | 
	
		
			
			|  | 123 | +        default_http_code: HTTPStatus = HTTPStatus.OK,
 | 
	
		
			
			|  | 124 | +    ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
 | 
	
		
			
			|  | 125 | +        processor = processor or MarshmallowInputProcessor()
 | 
	
		
			
			|  | 126 | +        processor.schema = schema
 | 
	
		
			
			|  | 127 | +        context = context or _default_global_context
 | 
	
		
			
			|  | 128 | +
 | 
	
		
			
			|  | 129 | +        decoration = InputHeadersControllerWrapper(
 | 
	
		
			
			|  | 130 | +            context=context,
 | 
	
		
			
			|  | 131 | +            processor=processor,
 | 
	
		
			
			|  | 132 | +            error_http_code=error_http_code,
 | 
	
		
			
			|  | 133 | +            default_http_code=default_http_code,
 | 
	
		
			
			| 182 | 134 |          )
 | 
	
		
			
			| 183 | 135 |  
 | 
	
		
			
			|  | 136 | +        def decorator(func):
 | 
	
		
			
			|  | 137 | +            self._buffer.input_headers = InputHeadersDescription(decoration)
 | 
	
		
			
			|  | 138 | +            return decoration.get_wrapper(func)
 | 
	
		
			
			|  | 139 | +        return decorator
 | 
	
		
			
			| 184 | 140 |  
 | 
	
		
			
			| 185 |  | -class OutputProcessorInterface(object):
 | 
	
		
			
			| 186 |  | -    def __init__(self):
 | 
	
		
			
			| 187 |  | -        self.schema = None
 | 
	
		
			
			| 188 |  | -
 | 
	
		
			
			| 189 |  | -    def process(self, value):
 | 
	
		
			
			| 190 |  | -        raise NotImplementedError
 | 
	
		
			
			| 191 |  | -
 | 
	
		
			
			| 192 |  | -    def get_validation_error(
 | 
	
		
			
			|  | 141 | +    def input_path(
 | 
	
		
			
			| 193 | 142 |          self,
 | 
	
		
			
			| 194 |  | -        request_context: RequestParameters,
 | 
	
		
			
			| 195 |  | -    ) -> ProcessValidationError:
 | 
	
		
			
			| 196 |  | -        raise NotImplementedError
 | 
	
		
			
			| 197 |  | -
 | 
	
		
			
			| 198 |  | -
 | 
	
		
			
			| 199 |  | -class InputProcessorInterface(object):
 | 
	
		
			
			| 200 |  | -    def __init__(self):
 | 
	
		
			
			| 201 |  | -        self.schema = None
 | 
	
		
			
			|  | 143 | +        schema: typing.Any,
 | 
	
		
			
			|  | 144 | +        processor: ProcessorInterface = None,
 | 
	
		
			
			|  | 145 | +        context: ContextInterface = None,
 | 
	
		
			
			|  | 146 | +        error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
 | 
	
		
			
			|  | 147 | +        default_http_code: HTTPStatus = HTTPStatus.OK,
 | 
	
		
			
			|  | 148 | +    ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
 | 
	
		
			
			|  | 149 | +        processor = processor or MarshmallowInputProcessor()
 | 
	
		
			
			|  | 150 | +        processor.schema = schema
 | 
	
		
			
			|  | 151 | +        context = context or _default_global_context
 | 
	
		
			
			|  | 152 | +
 | 
	
		
			
			|  | 153 | +        decoration = InputPathControllerWrapper(
 | 
	
		
			
			|  | 154 | +            context=context,
 | 
	
		
			
			|  | 155 | +            processor=processor,
 | 
	
		
			
			|  | 156 | +            error_http_code=error_http_code,
 | 
	
		
			
			|  | 157 | +            default_http_code=default_http_code,
 | 
	
		
			
			|  | 158 | +        )
 | 
	
		
			
			| 202 | 159 |  
 | 
	
		
			
			| 203 |  | -    def process(
 | 
	
		
			
			| 204 |  | -        self,
 | 
	
		
			
			| 205 |  | -        request_context: RequestParameters,
 | 
	
		
			
			| 206 |  | -    ) -> typing.Any:
 | 
	
		
			
			| 207 |  | -        raise NotImplementedError
 | 
	
		
			
			|  | 160 | +        def decorator(func):
 | 
	
		
			
			|  | 161 | +            self._buffer.input_path = InputPathDescription(decoration)
 | 
	
		
			
			|  | 162 | +            return decoration.get_wrapper(func)
 | 
	
		
			
			|  | 163 | +        return decorator
 | 
	
		
			
			| 208 | 164 |  
 | 
	
		
			
			| 209 |  | -    def get_validation_error(
 | 
	
		
			
			|  | 165 | +    def input_query(
 | 
	
		
			
			| 210 | 166 |          self,
 | 
	
		
			
			| 211 |  | -        request_context: RequestParameters,
 | 
	
		
			
			| 212 |  | -    ) -> ProcessValidationError:
 | 
	
		
			
			| 213 |  | -        raise NotImplementedError
 | 
	
		
			
			| 214 |  | -
 | 
	
		
			
			| 215 |  | -
 | 
	
		
			
			| 216 |  | -class MarshmallowOutputProcessor(OutputProcessorInterface):
 | 
	
		
			
			| 217 |  | -    def process(self, data: typing.Any):
 | 
	
		
			
			| 218 |  | -        unmarshall = self.schema.dump(data)
 | 
	
		
			
			| 219 |  | -        if unmarshall.errors:
 | 
	
		
			
			| 220 |  | -            raise InputValidationException(
 | 
	
		
			
			| 221 |  | -                'Error when validate input: {}'.format(
 | 
	
		
			
			| 222 |  | -                    str(unmarshall.errors),
 | 
	
		
			
			| 223 |  | -                )
 | 
	
		
			
			| 224 |  | -            )
 | 
	
		
			
			| 225 |  | -
 | 
	
		
			
			| 226 |  | -        return unmarshall.data
 | 
	
		
			
			| 227 |  | -
 | 
	
		
			
			| 228 |  | -    def get_validation_error(self, data: dict) -> ProcessValidationError:
 | 
	
		
			
			| 229 |  | -        marshmallow_errors = self.schema.dump(data).errors
 | 
	
		
			
			| 230 |  | -        return ProcessValidationError(
 | 
	
		
			
			| 231 |  | -            error_message='Validation error of output data',
 | 
	
		
			
			| 232 |  | -            error_details=marshmallow_errors,
 | 
	
		
			
			|  | 167 | +        schema: typing.Any,
 | 
	
		
			
			|  | 168 | +        processor: ProcessorInterface = None,
 | 
	
		
			
			|  | 169 | +        context: ContextInterface = None,
 | 
	
		
			
			|  | 170 | +        error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
 | 
	
		
			
			|  | 171 | +        default_http_code: HTTPStatus = HTTPStatus.OK,
 | 
	
		
			
			|  | 172 | +    ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
 | 
	
		
			
			|  | 173 | +        processor = processor or MarshmallowInputProcessor()
 | 
	
		
			
			|  | 174 | +        processor.schema = schema
 | 
	
		
			
			|  | 175 | +        context = context or _default_global_context
 | 
	
		
			
			|  | 176 | +
 | 
	
		
			
			|  | 177 | +        decoration = InputQueryControllerWrapper(
 | 
	
		
			
			|  | 178 | +            context=context,
 | 
	
		
			
			|  | 179 | +            processor=processor,
 | 
	
		
			
			|  | 180 | +            error_http_code=error_http_code,
 | 
	
		
			
			|  | 181 | +            default_http_code=default_http_code,
 | 
	
		
			
			| 233 | 182 |          )
 | 
	
		
			
			| 234 | 183 |  
 | 
	
		
			
			|  | 184 | +        def decorator(func):
 | 
	
		
			
			|  | 185 | +            self._buffer.input_query = InputQueryDescription(decoration)
 | 
	
		
			
			|  | 186 | +            return decoration.get_wrapper(func)
 | 
	
		
			
			|  | 187 | +        return decorator
 | 
	
		
			
			| 235 | 188 |  
 | 
	
		
			
			| 236 |  | -class MarshmallowInputProcessor(OutputProcessorInterface):
 | 
	
		
			
			| 237 |  | -    def process(self, data: dict):
 | 
	
		
			
			| 238 |  | -        unmarshall = self.schema.load(data)
 | 
	
		
			
			| 239 |  | -        if unmarshall.errors:
 | 
	
		
			
			| 240 |  | -            raise OutputValidationException(
 | 
	
		
			
			| 241 |  | -                'Error when validate ouput: {}'.format(
 | 
	
		
			
			| 242 |  | -                    str(unmarshall.errors),
 | 
	
		
			
			| 243 |  | -                )
 | 
	
		
			
			| 244 |  | -            )
 | 
	
		
			
			| 245 |  | -
 | 
	
		
			
			| 246 |  | -        return unmarshall.data
 | 
	
		
			
			| 247 |  | -
 | 
	
		
			
			| 248 |  | -    def get_validation_error(self, data: dict) -> ProcessValidationError:
 | 
	
		
			
			| 249 |  | -        marshmallow_errors = self.schema.load(data).errors
 | 
	
		
			
			| 250 |  | -        return ProcessValidationError(
 | 
	
		
			
			| 251 |  | -            error_message='Validation error of input data',
 | 
	
		
			
			| 252 |  | -            error_details=marshmallow_errors,
 | 
	
		
			
			|  | 189 | +    def input_body(
 | 
	
		
			
			|  | 190 | +        self,
 | 
	
		
			
			|  | 191 | +        schema: typing.Any,
 | 
	
		
			
			|  | 192 | +        processor: ProcessorInterface = None,
 | 
	
		
			
			|  | 193 | +        context: ContextInterface = None,
 | 
	
		
			
			|  | 194 | +        error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
 | 
	
		
			
			|  | 195 | +        default_http_code: HTTPStatus = HTTPStatus.OK,
 | 
	
		
			
			|  | 196 | +    ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
 | 
	
		
			
			|  | 197 | +        processor = processor or MarshmallowInputProcessor()
 | 
	
		
			
			|  | 198 | +        processor.schema = schema
 | 
	
		
			
			|  | 199 | +        context = context or _default_global_context
 | 
	
		
			
			|  | 200 | +
 | 
	
		
			
			|  | 201 | +        decoration = InputBodyControllerWrapper(
 | 
	
		
			
			|  | 202 | +            context=context,
 | 
	
		
			
			|  | 203 | +            processor=processor,
 | 
	
		
			
			|  | 204 | +            error_http_code=error_http_code,
 | 
	
		
			
			|  | 205 | +            default_http_code=default_http_code,
 | 
	
		
			
			| 253 | 206 |          )
 | 
	
		
			
			| 254 | 207 |  
 | 
	
		
			
			|  | 208 | +        def decorator(func):
 | 
	
		
			
			|  | 209 | +            self._buffer.input_body = InputBodyDescription(decoration)
 | 
	
		
			
			|  | 210 | +            return decoration.get_wrapper(func)
 | 
	
		
			
			|  | 211 | +        return decorator
 | 
	
		
			
			| 255 | 212 |  
 | 
	
		
			
			| 256 |  | -class HapicData(object):
 | 
	
		
			
			| 257 |  | -    def __init__(self):
 | 
	
		
			
			| 258 |  | -        self.body = {}
 | 
	
		
			
			| 259 |  | -        self.path = {}
 | 
	
		
			
			| 260 |  | -        self.query = {}
 | 
	
		
			
			| 261 |  | -        self.headers = {}
 | 
	
		
			
			| 262 |  | -
 | 
	
		
			
			| 263 |  | -
 | 
	
		
			
			| 264 |  | -# TODO: Il faut un output_body et un output_header
 | 
	
		
			
			| 265 |  | -def output(
 | 
	
		
			
			| 266 |  | -    schema,
 | 
	
		
			
			| 267 |  | -    processor: OutputProcessorInterface=None,
 | 
	
		
			
			| 268 |  | -    context: ContextInterface=None,
 | 
	
		
			
			| 269 |  | -    default_http_code=200,
 | 
	
		
			
			| 270 |  | -    default_error_code=500,
 | 
	
		
			
			| 271 |  | -):
 | 
	
		
			
			| 272 |  | -    processor = processor or MarshmallowOutputProcessor()
 | 
	
		
			
			| 273 |  | -    processor.schema = schema
 | 
	
		
			
			| 274 |  | -    context = context or _default_global_context
 | 
	
		
			
			| 275 |  | -
 | 
	
		
			
			| 276 |  | -    def decorator(func):
 | 
	
		
			
			| 277 |  | -        # @functools.wraps(func)
 | 
	
		
			
			| 278 |  | -        def wrapper(*args, **kwargs):
 | 
	
		
			
			| 279 |  | -            raw_response = func(*args, **kwargs)
 | 
	
		
			
			| 280 |  | -            processed_response = processor.process(raw_response)
 | 
	
		
			
			| 281 |  | -            prepared_response = context.get_response(
 | 
	
		
			
			| 282 |  | -                processed_response,
 | 
	
		
			
			| 283 |  | -                default_http_code,
 | 
	
		
			
			| 284 |  | -            )
 | 
	
		
			
			| 285 |  | -            return prepared_response
 | 
	
		
			
			| 286 |  | -
 | 
	
		
			
			| 287 |  | -        _waiting['output'] = schema
 | 
	
		
			
			| 288 |  | -
 | 
	
		
			
			| 289 |  | -        return wrapper
 | 
	
		
			
			| 290 |  | -    return decorator
 | 
	
		
			
			| 291 |  | -
 | 
	
		
			
			| 292 |  | -# TODO: raccourcis 'input' tout court ?
 | 
	
		
			
			| 293 |  | -def input_body(
 | 
	
		
			
			| 294 |  | -    schema,
 | 
	
		
			
			| 295 |  | -    processor: InputProcessorInterface=None,
 | 
	
		
			
			| 296 |  | -    context: ContextInterface=None,
 | 
	
		
			
			| 297 |  | -    error_http_code: HTTPStatus=HTTPStatus.BAD_REQUEST,
 | 
	
		
			
			| 298 |  | -):
 | 
	
		
			
			| 299 |  | -    processor = processor or MarshmallowInputProcessor()
 | 
	
		
			
			| 300 |  | -    processor.schema = schema
 | 
	
		
			
			| 301 |  | -    context = context or _default_global_context
 | 
	
		
			
			| 302 |  | -
 | 
	
		
			
			| 303 |  | -    def decorator(func):
 | 
	
		
			
			| 304 |  | -
 | 
	
		
			
			| 305 |  | -        # @functools.wraps(func)
 | 
	
		
			
			| 306 |  | -        def wrapper(*args, **kwargs):
 | 
	
		
			
			| 307 |  | -            updated_kwargs = {'hapic_data': HapicData()}
 | 
	
		
			
			| 308 |  | -            updated_kwargs.update(kwargs)
 | 
	
		
			
			| 309 |  | -            hapic_data = updated_kwargs['hapic_data']
 | 
	
		
			
			| 310 |  | -
 | 
	
		
			
			| 311 |  | -            request_parameters = context.get_request_parameters(
 | 
	
		
			
			| 312 |  | -                *args,
 | 
	
		
			
			| 313 |  | -                **updated_kwargs
 | 
	
		
			
			| 314 |  | -            )
 | 
	
		
			
			| 315 |  | -
 | 
	
		
			
			| 316 |  | -            try:
 | 
	
		
			
			| 317 |  | -                hapic_data.body = processor.process(
 | 
	
		
			
			| 318 |  | -                    request_parameters.body_parameters,
 | 
	
		
			
			| 319 |  | -                )
 | 
	
		
			
			| 320 |  | -            except ProcessException:
 | 
	
		
			
			| 321 |  | -                error = processor.get_validation_error(
 | 
	
		
			
			| 322 |  | -                    request_parameters.body_parameters,
 | 
	
		
			
			| 323 |  | -                )
 | 
	
		
			
			| 324 |  | -                error_response = context.get_validation_error_response(
 | 
	
		
			
			| 325 |  | -                    error,
 | 
	
		
			
			| 326 |  | -                    http_code=error_http_code,
 | 
	
		
			
			| 327 |  | -                )
 | 
	
		
			
			| 328 |  | -                return error_response
 | 
	
		
			
			| 329 |  | -
 | 
	
		
			
			| 330 |  | -            return func(*args, **updated_kwargs)
 | 
	
		
			
			| 331 |  | -
 | 
	
		
			
			| 332 |  | -        _waiting.setdefault('input', []).append(schema)
 | 
	
		
			
			| 333 |  | -
 | 
	
		
			
			| 334 |  | -        return wrapper
 | 
	
		
			
			| 335 |  | -    return decorator
 | 
	
		
			
			| 336 |  | -
 | 
	
		
			
			| 337 |  | -
 | 
	
		
			
			| 338 |  | -def input_path(
 | 
	
		
			
			| 339 |  | -    schema,
 | 
	
		
			
			| 340 |  | -    processor: InputProcessorInterface=None,
 | 
	
		
			
			| 341 |  | -    context: ContextInterface=None,
 | 
	
		
			
			| 342 |  | -    error_http_code=400,
 | 
	
		
			
			| 343 |  | -):
 | 
	
		
			
			| 344 |  | -    processor = processor or MarshmallowInputProcessor()
 | 
	
		
			
			| 345 |  | -    processor.schema = schema
 | 
	
		
			
			| 346 |  | -    context = context or _default_global_context
 | 
	
		
			
			| 347 |  | -
 | 
	
		
			
			| 348 |  | -    def decorator(func):
 | 
	
		
			
			| 349 |  | -
 | 
	
		
			
			| 350 |  | -        # @functools.wraps(func)
 | 
	
		
			
			| 351 |  | -        def wrapper(*args, **kwargs):
 | 
	
		
			
			| 352 |  | -            updated_kwargs = {'hapic_data': HapicData()}
 | 
	
		
			
			| 353 |  | -            updated_kwargs.update(kwargs)
 | 
	
		
			
			| 354 |  | -            hapic_data = updated_kwargs['hapic_data']
 | 
	
		
			
			| 355 |  | -
 | 
	
		
			
			| 356 |  | -            request_parameters = context.get_request_parameters(*args, **updated_kwargs)
 | 
	
		
			
			| 357 |  | -            hapic_data.path = processor.process(request_parameters.path_parameters)
 | 
	
		
			
			| 358 |  | -
 | 
	
		
			
			| 359 |  | -            return func(*args, **updated_kwargs)
 | 
	
		
			
			| 360 |  | -
 | 
	
		
			
			| 361 |  | -        _waiting.setdefault('input', []).append(schema)
 | 
	
		
			
			| 362 |  | -
 | 
	
		
			
			| 363 |  | -        return wrapper
 | 
	
		
			
			| 364 |  | -    return decorator
 | 
	
		
			
			| 365 |  | -
 | 
	
		
			
			| 366 |  | -
 | 
	
		
			
			| 367 |  | -def input_query(
 | 
	
		
			
			| 368 |  | -    schema,
 | 
	
		
			
			| 369 |  | -    processor: InputProcessorInterface=None,
 | 
	
		
			
			| 370 |  | -    context: ContextInterface=None,
 | 
	
		
			
			| 371 |  | -    error_http_code=400,
 | 
	
		
			
			| 372 |  | -):
 | 
	
		
			
			| 373 |  | -    processor = processor or MarshmallowInputProcessor()
 | 
	
		
			
			| 374 |  | -    processor.schema = schema
 | 
	
		
			
			| 375 |  | -    context = context or _default_global_context
 | 
	
		
			
			| 376 |  | -
 | 
	
		
			
			| 377 |  | -    def decorator(func):
 | 
	
		
			
			| 378 |  | -
 | 
	
		
			
			| 379 |  | -        # @functools.wraps(func)
 | 
	
		
			
			| 380 |  | -        def wrapper(*args, **kwargs):
 | 
	
		
			
			| 381 |  | -            updated_kwargs = {'hapic_data': HapicData()}
 | 
	
		
			
			| 382 |  | -            updated_kwargs.update(kwargs)
 | 
	
		
			
			| 383 |  | -            hapic_data = updated_kwargs['hapic_data']
 | 
	
		
			
			| 384 |  | -
 | 
	
		
			
			| 385 |  | -            request_parameters = context.get_request_parameters(*args, **updated_kwargs)
 | 
	
		
			
			| 386 |  | -            hapic_data.query = processor.process(request_parameters.query_parameters)
 | 
	
		
			
			| 387 |  | -
 | 
	
		
			
			| 388 |  | -            return func(*args, **updated_kwargs)
 | 
	
		
			
			| 389 |  | -
 | 
	
		
			
			| 390 |  | -        _waiting.setdefault('input', []).append(schema)
 | 
	
		
			
			| 391 |  | -
 | 
	
		
			
			| 392 |  | -        return wrapper
 | 
	
		
			
			| 393 |  | -    return decorator
 | 
	
		
			
			| 394 |  | -
 | 
	
		
			
			| 395 |  | -
 | 
	
		
			
			| 396 |  | -def input_headers(
 | 
	
		
			
			| 397 |  | -    schema,
 | 
	
		
			
			| 398 |  | -    processor: InputProcessorInterface,
 | 
	
		
			
			| 399 |  | -    context: ContextInterface=None,
 | 
	
		
			
			| 400 |  | -    error_http_code=400,
 | 
	
		
			
			| 401 |  | -):
 | 
	
		
			
			| 402 |  | -    processor = processor or MarshmallowInputProcessor()
 | 
	
		
			
			| 403 |  | -    processor.schema = schema
 | 
	
		
			
			| 404 |  | -    context = context or _default_global_context
 | 
	
		
			
			| 405 |  | -
 | 
	
		
			
			| 406 |  | -    def decorator(func):
 | 
	
		
			
			| 407 |  | -
 | 
	
		
			
			| 408 |  | -        # @functools.wraps(func)
 | 
	
		
			
			| 409 |  | -        def wrapper(*args, **kwargs):
 | 
	
		
			
			| 410 |  | -            updated_kwargs = {'hapic_data': HapicData()}
 | 
	
		
			
			| 411 |  | -            updated_kwargs.update(kwargs)
 | 
	
		
			
			| 412 |  | -            hapic_data = updated_kwargs['hapic_data']
 | 
	
		
			
			| 413 |  | -
 | 
	
		
			
			| 414 |  | -            request_parameters = context.get_request_parameters(*args, **updated_kwargs)
 | 
	
		
			
			| 415 |  | -            hapic_data.headers = processor.process(request_parameters.header_parameters)
 | 
	
		
			
			|  | 213 | +    def input_forms(
 | 
	
		
			
			|  | 214 | +        self,
 | 
	
		
			
			|  | 215 | +        schema: typing.Any,
 | 
	
		
			
			|  | 216 | +        processor: ProcessorInterface=None,
 | 
	
		
			
			|  | 217 | +        context: ContextInterface=None,
 | 
	
		
			
			|  | 218 | +        error_http_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
 | 
	
		
			
			|  | 219 | +        default_http_code: HTTPStatus = HTTPStatus.OK,
 | 
	
		
			
			|  | 220 | +    ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
 | 
	
		
			
			|  | 221 | +        processor = processor or MarshmallowInputProcessor()
 | 
	
		
			
			|  | 222 | +        processor.schema = schema
 | 
	
		
			
			|  | 223 | +        context = context or _default_global_context
 | 
	
		
			
			|  | 224 | +
 | 
	
		
			
			|  | 225 | +        decoration = InputBodyControllerWrapper(
 | 
	
		
			
			|  | 226 | +            context=context,
 | 
	
		
			
			|  | 227 | +            processor=processor,
 | 
	
		
			
			|  | 228 | +            error_http_code=error_http_code,
 | 
	
		
			
			|  | 229 | +            default_http_code=default_http_code,
 | 
	
		
			
			|  | 230 | +        )
 | 
	
		
			
			| 416 | 231 |  
 | 
	
		
			
			| 417 |  | -            return func(*args, **updated_kwargs)
 | 
	
		
			
			|  | 232 | +        def decorator(func):
 | 
	
		
			
			|  | 233 | +            self._buffer.input_forms = InputFormsDescription(decoration)
 | 
	
		
			
			|  | 234 | +            return decoration.get_wrapper(func)
 | 
	
		
			
			|  | 235 | +        return decorator
 | 
	
		
			
			| 418 | 236 |  
 | 
	
		
			
			| 419 |  | -        _waiting.setdefault('input', []).append(schema)
 | 
	
		
			
			|  | 237 | +    def generate_doc(self, app=None):
 | 
	
		
			
			|  | 238 | +        # TODO @Damien bottle specific code !
 | 
	
		
			
			|  | 239 | +        app = app or bottle.default_app()
 | 
	
		
			
			| 420 | 240 |  
 | 
	
		
			
			| 421 |  | -        return wrapper
 | 
	
		
			
			| 422 |  | -    return decorator
 | 
	
		
			
			|  | 241 | +        route_by_callbacks = []
 | 
	
		
			
			|  | 242 | +        routes = flatten(app.router.dyna_routes.values())
 | 
	
		
			
			|  | 243 | +        for path, path_regex, route, func_ in routes:
 | 
	
		
			
			|  | 244 | +            route_by_callbacks.append(route.callback)
 | 
	
		
			
			|  | 245 | +
 | 
	
		
			
			|  | 246 | +        for description in self._controllers:
 | 
	
		
			
			|  | 247 | +            for path, path_regex, route, func_ in routes:
 | 
	
		
			
			|  | 248 | +                if route.callback == description.reference:
 | 
	
		
			
			|  | 249 | +                    # TODO: use description to feed apispec
 | 
	
		
			
			|  | 250 | +                    print(route.method, path, description)
 | 
	
		
			
			|  | 251 | +                    continue
 |