Browse Source

Merge branch 'develop' into feature/global_exception_handler_for_pyramid

Bastien Sevajol 6 years ago
parent
commit
8e37758f77
No account linked to committer's email

+ 9 - 0
hapic/context.py View File

135
         """
135
         """
136
         raise NotImplementedError()
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
 class HandledException(object):
148
 class HandledException(object):
140
     """
149
     """

+ 4 - 1
hapic/decorator.py View File

420
                 func_kwargs,
420
                 func_kwargs,
421
             )
421
             )
422
         except self.handled_exception_class as exc:
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
             # Check error format
428
             # Check error format
426
             dumped = self.error_builder.dump(response_content).data
429
             dumped = self.error_builder.dump(response_content).data

+ 24 - 4
hapic/error.py View File

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
+import traceback
3
+
2
 import marshmallow
4
 import marshmallow
3
 
5
 
4
 from hapic.processor import ProcessValidationError
6
 from hapic.processor import ProcessValidationError
9
     ErrorBuilder is a class who represent a Schema (marshmallow.Schema) and
11
     ErrorBuilder is a class who represent a Schema (marshmallow.Schema) and
10
     can generate a response content from exception (build_from_exception)
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
         Build the error response content from given exception
20
         Build the error response content from given exception
15
         :param exception: Original exception who invoke this method
21
         :param exception: Original exception who invoke this method
34
     details = marshmallow.fields.Dict(required=False, missing={})
40
     details = marshmallow.fields.Dict(required=False, missing={})
35
     code = marshmallow.fields.Raw(missing=None)
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
         See hapic.error.ErrorBuilderInterface#build_from_exception docstring
49
         See hapic.error.ErrorBuilderInterface#build_from_exception docstring
40
         """
50
         """
41
         # TODO: "error_detail" attribute name should be configurable
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
         return {
62
         return {
43
-            'message': str(exception),
44
-            'details': getattr(exception, 'error_detail', {}),
63
+            'message': message,
64
+            'details': details,
45
             'code': None,
65
             'code': None,
46
         }
66
         }
47
 
67
 

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

33
         self,
33
         self,
34
         app: bottle.Bottle,
34
         app: bottle.Bottle,
35
         default_error_builder: ErrorBuilderInterface=None,
35
         default_error_builder: ErrorBuilderInterface=None,
36
+        debug: bool = False,
36
     ):
37
     ):
37
         self._handled_exceptions = []  # type: typing.List[HandledException]  # nopep8
38
         self._handled_exceptions = []  # type: typing.List[HandledException]  # nopep8
38
         self._exceptions_handler_installed = False
39
         self._exceptions_handler_installed = False
39
         self.app = app
40
         self.app = app
40
         self.default_error_builder = \
41
         self.default_error_builder = \
41
             default_error_builder or DefaultErrorBuilder()  # FDV
42
             default_error_builder or DefaultErrorBuilder()  # FDV
43
+        self.debug = debug
42
 
44
 
43
     def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
45
     def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
44
         path_parameters = dict(bottle.request.url_args)
46
         path_parameters = dict(bottle.request.url_args)
164
         See hapic.context.BaseContext#_get_handled_exception_class_and_http_codes  # nopep8
166
         See hapic.context.BaseContext#_get_handled_exception_class_and_http_codes  # nopep8
165
         """
167
         """
166
         return self._handled_exceptions
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
         self,
32
         self,
33
         app: Flask,
33
         app: Flask,
34
         default_error_builder: ErrorBuilderInterface=None,
34
         default_error_builder: ErrorBuilderInterface=None,
35
+        debug: bool = False,
35
     ):
36
     ):
36
         self._handled_exceptions = []  # type: typing.List[HandledException]  # nopep8
37
         self._handled_exceptions = []  # type: typing.List[HandledException]  # nopep8
37
         self.app = app
38
         self.app = app
38
         self.default_error_builder = \
39
         self.default_error_builder = \
39
             default_error_builder or DefaultErrorBuilder()  # FDV
40
             default_error_builder or DefaultErrorBuilder()  # FDV
41
+        self.debug = debug
40
 
42
 
41
     def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
43
     def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
42
         from flask import request
44
         from flask import request
165
         http_code: int,
167
         http_code: int,
166
     ) -> None:
168
     ) -> None:
167
         raise NotImplementedError('TODO')
169
         raise NotImplementedError('TODO')
170
+
171
+    def is_debug(self) -> bool:
172
+        return self.debug

+ 6 - 1
hapic/ext/pyramid/context.py View File

31
         self,
31
         self,
32
         configurator: 'Configurator',
32
         configurator: 'Configurator',
33
         default_error_builder: ErrorBuilderInterface = None,
33
         default_error_builder: ErrorBuilderInterface = None,
34
+        debug: bool = False,
34
     ):
35
     ):
35
         self._handled_exceptions = []  # type: typing.List[HandledException]  # nopep8
36
         self._handled_exceptions = []  # type: typing.List[HandledException]  # nopep8
36
         self.configurator = configurator
37
         self.configurator = configurator
37
         self.default_error_builder = \
38
         self.default_error_builder = \
38
             default_error_builder or DefaultErrorBuilder()  # FDV
39
             default_error_builder or DefaultErrorBuilder()  # FDV
40
+        self.debug = debug
39
 
41
 
40
     def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
42
     def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
41
         req = args[-1]  # TODO : Check
43
         req = args[-1]  # TODO : Check
188
         exception_class: typing.Type[Exception],
190
         exception_class: typing.Type[Exception],
189
         http_code: int,
191
         http_code: int,
190
     ) -> None:
192
     ) -> None:
191
-
192
         def factory_view_func(exception_class, http_code):
193
         def factory_view_func(exception_class, http_code):
193
             def view_func(exc, request):
194
             def view_func(exc, request):
194
                 # TODO BS 2018-05-04: How to be attentive to hierarchy ?
195
                 # TODO BS 2018-05-04: How to be attentive to hierarchy ?
208
             context=exception_class,
209
             context=exception_class,
209
         )
210
         )
210
 
211
 
212
+        raise NotImplementedError('TODO')
213
+
214
+    def is_debug(self) -> bool:
215
+        return self.debug

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

276
         response = func(42)
276
         response = func(42)
277
         assert HTTPStatus.INTERNAL_SERVER_ERROR == response.status_code
277
         assert HTTPStatus.INTERNAL_SERVER_ERROR == response.status_code
278
         assert {
278
         assert {
279
-                   'details': {},
279
+                   'details': {'error_detail': {}},
280
                    'message': 'We are testing',
280
                    'message': 'We are testing',
281
                    'code': None,
281
                    'code': None,
282
                } == json.loads(response.body)
282
                } == json.loads(response.body)
305
         assert response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
305
         assert response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
306
         assert {
306
         assert {
307
             'message': 'We are testing',
307
             'message': 'We are testing',
308
-            'details': {'foo': 'bar'},
308
+            'details': {'error_detail': {'foo': 'bar'}},
309
             'code': None,
309
             'code': None,
310
         } == json.loads(response.body)
310
         } == json.loads(response.body)
311
 
311
 
314
             pass
314
             pass
315
 
315
 
316
         class MyErrorBuilder(DefaultErrorBuilder):
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
                 # this is not matching with DefaultErrorBuilder schema
322
                 # this is not matching with DefaultErrorBuilder schema
319
                 return {}
323
                 return {}
320
 
324