Browse Source

Merge pull request #53 from algoo/develop

Bastien Sevajol 6 years ago
parent
commit
7feefc0e15
No account linked to committer's email

+ 9 - 0
hapic/context.py View File

@@ -135,6 +135,15 @@ class ContextInterface(object):
135 135
         """
136 136
         raise NotImplementedError()
137 137
 
138
+    def is_debug(self) -> bool:
139
+        """
140
+        Method called to know if Hapic has been called in debug mode.
141
+        Debug mode provide some informations like debug trace and error
142
+        message in body when internal error happen.
143
+        :return: True if in debug mode
144
+        """
145
+        raise NotImplementedError()
146
+
138 147
 
139 148
 class HandledException(object):
140 149
     """

+ 4 - 1
hapic/decorator.py View File

@@ -420,7 +420,10 @@ class ExceptionHandlerControllerWrapper(ControllerWrapper):
420 420
                 func_kwargs,
421 421
             )
422 422
         except self.handled_exception_class as exc:
423
-            response_content = self.error_builder.build_from_exception(exc)
423
+            response_content = self.error_builder.build_from_exception(
424
+                exc,
425
+                include_traceback=self.context.is_debug(),
426
+            )
424 427
 
425 428
             # Check error format
426 429
             dumped = self.error_builder.dump(response_content).data

+ 24 - 4
hapic/error.py View File

@@ -1,4 +1,6 @@
1 1
 # -*- coding: utf-8 -*-
2
+import traceback
3
+
2 4
 import marshmallow
3 5
 
4 6
 from hapic.processor import ProcessValidationError
@@ -9,7 +11,11 @@ class ErrorBuilderInterface(marshmallow.Schema):
9 11
     ErrorBuilder is a class who represent a Schema (marshmallow.Schema) and
10 12
     can generate a response content from exception (build_from_exception)
11 13
     """
12
-    def build_from_exception(self, exception: Exception) -> dict:
14
+    def build_from_exception(
15
+        self,
16
+        exception: Exception,
17
+        include_traceback: bool = False,
18
+    ) -> dict:
13 19
         """
14 20
         Build the error response content from given exception
15 21
         :param exception: Original exception who invoke this method
@@ -34,14 +40,28 @@ class DefaultErrorBuilder(ErrorBuilderInterface):
34 40
     details = marshmallow.fields.Dict(required=False, missing={})
35 41
     code = marshmallow.fields.Raw(missing=None)
36 42
 
37
-    def build_from_exception(self, exception: Exception) -> dict:
43
+    def build_from_exception(
44
+        self,
45
+        exception: Exception,
46
+        include_traceback: bool = False,
47
+    ) -> dict:
38 48
         """
39 49
         See hapic.error.ErrorBuilderInterface#build_from_exception docstring
40 50
         """
41 51
         # TODO: "error_detail" attribute name should be configurable
52
+        message = str(exception)
53
+        if not message:
54
+            message = type(exception).__name__
55
+
56
+        details = {
57
+            'error_detail': getattr(exception, 'error_detail', {}),
58
+        }
59
+        if include_traceback:
60
+            details['traceback'] = traceback.format_exc()
61
+
42 62
         return {
43
-            'message': str(exception),
44
-            'details': getattr(exception, 'error_detail', {}),
63
+            'message': message,
64
+            'details': details,
45 65
             'code': None,
46 66
         }
47 67
 

+ 5 - 0
hapic/ext/bottle/context.py View File

@@ -33,12 +33,14 @@ class BottleContext(BaseContext):
33 33
         self,
34 34
         app: bottle.Bottle,
35 35
         default_error_builder: ErrorBuilderInterface=None,
36
+        debug: bool = False,
36 37
     ):
37 38
         self._handled_exceptions = []  # type: typing.List[HandledException]  # nopep8
38 39
         self._exceptions_handler_installed = False
39 40
         self.app = app
40 41
         self.default_error_builder = \
41 42
             default_error_builder or DefaultErrorBuilder()  # FDV
43
+        self.debug = debug
42 44
 
43 45
     def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
44 46
         path_parameters = dict(bottle.request.url_args)
@@ -164,3 +166,6 @@ class BottleContext(BaseContext):
164 166
         See hapic.context.BaseContext#_get_handled_exception_class_and_http_codes  # nopep8
165 167
         """
166 168
         return self._handled_exceptions
169
+
170
+    def is_debug(self) -> bool:
171
+        return self.debug

+ 5 - 0
hapic/ext/flask/context.py View File

@@ -32,11 +32,13 @@ class FlaskContext(BaseContext):
32 32
         self,
33 33
         app: Flask,
34 34
         default_error_builder: ErrorBuilderInterface=None,
35
+        debug: bool = False,
35 36
     ):
36 37
         self._handled_exceptions = []  # type: typing.List[HandledException]  # nopep8
37 38
         self.app = app
38 39
         self.default_error_builder = \
39 40
             default_error_builder or DefaultErrorBuilder()  # FDV
41
+        self.debug = debug
40 42
 
41 43
     def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
42 44
         from flask import request
@@ -165,3 +167,6 @@ class FlaskContext(BaseContext):
165 167
         http_code: int,
166 168
     ) -> None:
167 169
         raise NotImplementedError('TODO')
170
+
171
+    def is_debug(self) -> bool:
172
+        return self.debug

+ 5 - 0
hapic/ext/pyramid/context.py View File

@@ -31,11 +31,13 @@ class PyramidContext(BaseContext):
31 31
         self,
32 32
         configurator: 'Configurator',
33 33
         default_error_builder: ErrorBuilderInterface = None,
34
+        debug: bool = False,
34 35
     ):
35 36
         self._handled_exceptions = []  # type: typing.List[HandledException]  # nopep8
36 37
         self.configurator = configurator
37 38
         self.default_error_builder = \
38 39
             default_error_builder or DefaultErrorBuilder()  # FDV
40
+        self.debug = debug
39 41
 
40 42
     def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
41 43
         req = args[-1]  # TODO : Check
@@ -189,3 +191,6 @@ class PyramidContext(BaseContext):
189 191
         http_code: int,
190 192
     ) -> None:
191 193
         raise NotImplementedError('TODO')
194
+
195
+    def is_debug(self) -> bool:
196
+        return self.debug

+ 7 - 3
tests/unit/test_decorator.py View File

@@ -276,7 +276,7 @@ class TestExceptionHandlerControllerWrapper(Base):
276 276
         response = func(42)
277 277
         assert HTTPStatus.INTERNAL_SERVER_ERROR == response.status_code
278 278
         assert {
279
-                   'details': {},
279
+                   'details': {'error_detail': {}},
280 280
                    'message': 'We are testing',
281 281
                    'code': None,
282 282
                } == json.loads(response.body)
@@ -305,7 +305,7 @@ class TestExceptionHandlerControllerWrapper(Base):
305 305
         assert response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
306 306
         assert {
307 307
             'message': 'We are testing',
308
-            'details': {'foo': 'bar'},
308
+            'details': {'error_detail': {'foo': 'bar'}},
309 309
             'code': None,
310 310
         } == json.loads(response.body)
311 311
 
@@ -314,7 +314,11 @@ class TestExceptionHandlerControllerWrapper(Base):
314 314
             pass
315 315
 
316 316
         class MyErrorBuilder(DefaultErrorBuilder):
317
-            def build_from_exception(self, exception: Exception) -> dict:
317
+            def build_from_exception(
318
+                self,
319
+                exception: Exception,
320
+                include_traceback: bool = False,
321
+            ) -> dict:
318 322
                 # this is not matching with DefaultErrorBuilder schema
319 323
                 return {}
320 324