Browse Source

Merge pyramid and doc generation refactorisation with current bottle context

Bastien Sevajol 6 years ago
parent
commit
c61ab77b94

+ 2 - 0
hapic/context.py View File

@@ -14,9 +14,11 @@ class RouteRepresentation(object):
14 14
         self,
15 15
         rule: str,
16 16
         method: str,
17
+        original_route_object: typing.Any=None,
17 18
     ) -> None:
18 19
         self.rule = rule
19 20
         self.method = method
21
+        self.original_route_object = original_route_object
20 22
 
21 23
 
22 24
 class ContextInterface(object):

+ 2 - 34
hapic/doc.py View File

@@ -1,46 +1,14 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 import typing
3 3
 
4
-import bottle
5 4
 from apispec import APISpec
6 5
 from apispec import Path
7 6
 from apispec.ext.marshmallow.swagger import schema2jsonschema
8 7
 
9
-from hapic.context import ContextInterface, RouteRepresentation
10
-from hapic.decorator import DECORATION_ATTRIBUTE_NAME
8
+from hapic.context import ContextInterface
9
+from hapic.context import RouteRepresentation
11 10
 from hapic.decorator import DecoratedController
12 11
 from hapic.description import ControllerDescription
13
-from hapic.exception import NoRoutesException
14
-from hapic.exception import RouteNotFound
15
-
16
-
17
-def find_bottle_route(
18
-    decorated_controller: DecoratedController,
19
-    app: bottle.Bottle,
20
-):
21
-    if not app.routes:
22
-        raise NoRoutesException('There is no routes in your bottle app')
23
-
24
-    reference = decorated_controller.reference
25
-    for route in app.routes:
26
-        route_token = getattr(
27
-            route.callback,
28
-            DECORATION_ATTRIBUTE_NAME,
29
-            None,
30
-        )
31
-
32
-        match_with_wrapper = route.callback == reference.wrapper
33
-        match_with_wrapped = route.callback == reference.wrapped
34
-        match_with_token = route_token == reference.token
35
-
36
-        if match_with_wrapper or match_with_wrapped or match_with_token:
37
-            return route
38
-    # TODO BS 20171010: Raise exception or print error ? see #10
39
-    raise RouteNotFound(
40
-        'Decorated route "{}" was not found in bottle routes'.format(
41
-            decorated_controller.name,
42
-        )
43
-    )
44 12
 
45 13
 
46 14
 def bottle_generate_operations(

+ 46 - 2
hapic/ext/bottle/context.py View File

@@ -8,14 +8,23 @@ import bottle
8 8
 from multidict import MultiDict
9 9
 
10 10
 from hapic.context import ContextInterface
11
+from hapic.context import RouteRepresentation
12
+from hapic.decorator import DecoratedController
13
+from hapic.decorator import DECORATION_ATTRIBUTE_NAME
11 14
 from hapic.exception import OutputValidationException
12
-from hapic.processor import RequestParameters, ProcessValidationError
15
+from hapic.exception import NoRoutesException
16
+from hapic.exception import RouteNotFound
17
+from hapic.processor import RequestParameters
18
+from hapic.processor import ProcessValidationError
13 19
 
14 20
 # Bottle regular expression to locate url parameters
15 21
 BOTTLE_RE_PATH_URL = re.compile(r'<(?:[^:<>]+:)?([^<>]+)>')
16 22
 
17 23
 
18 24
 class BottleContext(ContextInterface):
25
+    def __init__(self, app: bottle.Bottle):
26
+        self.app = app
27
+
19 28
     def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
20 29
         path_parameters = dict(bottle.request.url_args)
21 30
         query_parameters = MultiDict(bottle.request.query.allitems())
@@ -26,7 +35,7 @@ class BottleContext(ContextInterface):
26 35
 
27 36
         return RequestParameters(
28 37
             path_parameters=path_parameters,
29
-            query_parameters=query_parameters, ## query?
38
+            query_parameters=query_parameters,
30 39
             body_parameters=body_parameters,
31 40
             form_parameters=form_parameters,
32 41
             header_parameters=header_parameters,
@@ -68,3 +77,38 @@ class BottleContext(ContextInterface):
68 77
             ],
69 78
             status=int(http_code),
70 79
         )
80
+
81
+    def find_route(
82
+        self,
83
+        decorated_controller: DecoratedController,
84
+    ) -> RouteRepresentation:
85
+        if not self.app.routes:
86
+            raise NoRoutesException('There is no routes in your bottle app')
87
+
88
+        reference = decorated_controller.reference
89
+        for route in self.app.routes:
90
+            route_token = getattr(
91
+                route.callback,
92
+                DECORATION_ATTRIBUTE_NAME,
93
+                None,
94
+            )
95
+
96
+            match_with_wrapper = route.callback == reference.wrapper
97
+            match_with_wrapped = route.callback == reference.wrapped
98
+            match_with_token = route_token == reference.token
99
+
100
+            if match_with_wrapper or match_with_wrapped or match_with_token:
101
+                return RouteRepresentation(
102
+                    rule=self.get_swagger_path(route.rule),
103
+                    method=route.method.lower(),
104
+                    original_route_object=route,
105
+                )
106
+        # TODO BS 20171010: Raise exception or print error ? see #10
107
+        raise RouteNotFound(
108
+            'Decorated route "{}" was not found in bottle routes'.format(
109
+                decorated_controller.name,
110
+            )
111
+        )
112
+
113
+    def get_swagger_path(self, contextualised_rule: str) -> str:
114
+        return BOTTLE_RE_PATH_URL.sub(r'{\1}', contextualised_rule)

+ 10 - 13
hapic/ext/pyramid/context.py View File

@@ -4,10 +4,6 @@ import re
4 4
 import typing
5 5
 from http import HTTPStatus
6 6
 
7
-from pyramid.request import Request
8
-from pyramid.response import Response
9
-from pyramid.config import Configurator
10
-
11 7
 from hapic.context import ContextInterface
12 8
 from hapic.context import RouteRepresentation
13 9
 from hapic.decorator import DecoratedController
@@ -17,17 +13,20 @@ from hapic.exception import OutputValidationException
17 13
 from hapic.processor import RequestParameters
18 14
 from hapic.processor import ProcessValidationError
19 15
 
16
+if typing.TYPE_CHECKING:
17
+    from pyramid.response import Response
18
+    from pyramid.config import Configurator
19
+
20 20
 # Bottle regular expression to locate url parameters
21 21
 PYRAMID_RE_PATH_URL = re.compile(r'')
22 22
 
23 23
 
24 24
 class PyramidContext(ContextInterface):
25
-    def __init__(self, configurator: Configurator):
25
+    def __init__(self, configurator: 'Configurator'):
26 26
         self.configurator = configurator
27 27
 
28 28
     def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
29 29
         req = args[-1]  # TODO : Check
30
-        assert isinstance(req, Request)
31 30
         # TODO : move this code to check_json
32 31
         # same idea as in : https://bottlepy.org/docs/dev/_modules/bottle.html#BaseRequest.json
33 32
         if req.body and req.content_type in ('application/json', 'application/json-rpc'):
@@ -48,7 +47,8 @@ class PyramidContext(ContextInterface):
48 47
         self,
49 48
         response: dict,
50 49
         http_code: int,
51
-    ) -> Response:
50
+    ) -> 'Response':
51
+        from pyramid.response import Response
52 52
         return Response(
53 53
             body=json.dumps(response),
54 54
             headers=[
@@ -63,6 +63,7 @@ class PyramidContext(ContextInterface):
63 63
         http_code: HTTPStatus=HTTPStatus.BAD_REQUEST,
64 64
     ) -> typing.Any:
65 65
         # TODO BS 20171010: Manage error schemas, see #4
66
+        from pyramid.response import Response
66 67
         from hapic.hapic import _default_global_error_schema
67 68
         unmarshall = _default_global_error_schema.dump(error)
68 69
         if unmarshall.errors:
@@ -109,13 +110,9 @@ class PyramidContext(ContextInterface):
109 110
                 route_method = route_intr[0].get('request_methods')[0]
110 111
 
111 112
                 return RouteRepresentation(
112
-                    # TODO BS 20171107: ce code n'es pas du tout finis
113
-                    # (import bottle)
114
-                    rule=BOTTLE_RE_PATH_URL.sub(
115
-                        r'{\1}',
116
-                        route_pattern,
117
-                    ),
113
+                    rule=self.get_swagger_path(route_pattern),
118 114
                     method=route_method,
115
+                    original_route_object=route_intr[0],
119 116
                 )
120 117
 
121 118
     def get_swagger_path(self, contextualised_rule: str) -> str:

+ 5 - 2
tests/base.py View File

@@ -4,7 +4,7 @@ from http import HTTPStatus
4 4
 
5 5
 from multidict import MultiDict
6 6
 
7
-from hapic.context import ContextInterface
7
+from hapic.ext.bottle import BottleContext
8 8
 from hapic.processor import RequestParameters
9 9
 from hapic.processor import ProcessValidationError
10 10
 
@@ -13,9 +13,11 @@ class Base(object):
13 13
     pass
14 14
 
15 15
 
16
-class MyContext(ContextInterface):
16
+# TODO BS 20171105: Make this bottle agnostic !
17
+class MyContext(BottleContext):
17 18
     def __init__(
18 19
         self,
20
+        app,
19 21
         fake_path_parameters=None,
20 22
         fake_query_parameters=None,
21 23
         fake_body_parameters=None,
@@ -23,6 +25,7 @@ class MyContext(ContextInterface):
23 25
         fake_header_parameters=None,
24 26
         fake_files_parameters=None,
25 27
     ) -> None:
28
+        super().__init__(app=app)
26 29
         self.fake_path_parameters = fake_path_parameters or {}
27 30
         self.fake_query_parameters = fake_query_parameters or MultiDict()
28 31
         self.fake_body_parameters = fake_body_parameters or {}

+ 18 - 19
tests/ext/unit/test_bottle.py View File

@@ -2,16 +2,15 @@
2 2
 import bottle
3 3
 
4 4
 import hapic
5
-from hapic.doc import find_bottle_route
6 5
 from tests.base import Base
7 6
 
8 7
 
9 8
 class TestBottleExt(Base):
10 9
     def test_unit__map_binding__ok__decorated_function(self):
11 10
         hapic_ = hapic.Hapic()
12
-        hapic_.set_context(hapic.ext.bottle.BottleContext())
13
-
14 11
         app = bottle.Bottle()
12
+        context = hapic.ext.bottle.BottleContext(app=app)
13
+        hapic_.set_context(context)
15 14
 
16 15
         @hapic_.with_api_doc()
17 16
         @app.route('/')
@@ -20,18 +19,18 @@ class TestBottleExt(Base):
20 19
 
21 20
         assert hapic_.controllers
22 21
         decoration = hapic_.controllers[0]
23
-        route = find_bottle_route(decoration, app)
22
+        route = context.find_route(decoration)
24 23
 
25 24
         assert route
26
-        assert route.callback != controller_a
27
-        assert route.callback == decoration.reference.wrapped
28
-        assert route.callback != decoration.reference.wrapper
25
+        assert route.original_route_object.callback != controller_a
26
+        assert route.original_route_object.callback == decoration.reference.wrapped  # nopep8
27
+        assert route.original_route_object.callback != decoration.reference.wrapper  # nopep8
29 28
 
30 29
     def test_unit__map_binding__ok__mapped_function(self):
31 30
         hapic_ = hapic.Hapic()
32
-        hapic_.set_context(hapic.ext.bottle.BottleContext())
33
-
34 31
         app = bottle.Bottle()
32
+        context = hapic.ext.bottle.BottleContext(app=app)
33
+        hapic_.set_context(context)
35 34
 
36 35
         @hapic_.with_api_doc()
37 36
         def controller_a():
@@ -41,18 +40,18 @@ class TestBottleExt(Base):
41 40
 
42 41
         assert hapic_.controllers
43 42
         decoration = hapic_.controllers[0]
44
-        route = find_bottle_route(decoration, app)
43
+        route = context.find_route(decoration)
45 44
 
46 45
         assert route
47
-        assert route.callback == controller_a
48
-        assert route.callback == decoration.reference.wrapper
49
-        assert route.callback != decoration.reference.wrapped
46
+        assert route.original_route_object.callback == controller_a
47
+        assert route.original_route_object.callback == decoration.reference.wrapper  # nopep8
48
+        assert route.original_route_object.callback != decoration.reference.wrapped  # nopep8
50 49
 
51 50
     def test_unit__map_binding__ok__mapped_method(self):
52 51
         hapic_ = hapic.Hapic()
53
-        hapic_.set_context(hapic.ext.bottle.BottleContext())
54
-
55 52
         app = bottle.Bottle()
53
+        context = hapic.ext.bottle.BottleContext(app=app)
54
+        hapic_.set_context(context)
56 55
 
57 56
         class MyControllers(object):
58 57
             def bind(self, app):
@@ -67,11 +66,11 @@ class TestBottleExt(Base):
67 66
 
68 67
         assert hapic_.controllers
69 68
         decoration = hapic_.controllers[0]
70
-        route = find_bottle_route(decoration, app)
69
+        route = context.find_route(decoration)
71 70
 
72 71
         assert route
73 72
         # Important note: instance controller_a method is
74 73
         # not class controller_a, so no matches with callbacks
75
-        assert route.callback != MyControllers.controller_a
76
-        assert route.callback != decoration.reference.wrapped
77
-        assert route.callback != decoration.reference.wrapper
74
+        assert route.original_route_object.callback != MyControllers.controller_a  # nopep8
75
+        assert route.original_route_object.callback != decoration.reference.wrapped  # nopep8
76
+        assert route.original_route_object.callback != decoration.reference.wrapper  # nopep8

+ 10 - 10
tests/func/test_doc.py View File

@@ -10,8 +10,8 @@ from tests.base import MyContext
10 10
 class TestDocGeneration(Base):
11 11
     def test_func__input_files_doc__ok__one_file(self):
12 12
         hapic = Hapic()
13
-        hapic.set_context(MyContext())
14 13
         app = bottle.Bottle()
14
+        hapic.set_context(MyContext(app=app))
15 15
 
16 16
         class MySchema(marshmallow.Schema):
17 17
             file_abc = marshmallow.fields.Raw(required=True)
@@ -23,7 +23,7 @@ class TestDocGeneration(Base):
23 23
             assert hapic_data.files
24 24
 
25 25
         app.route('/upload', method='POST', callback=my_controller)
26
-        doc = hapic.generate_doc(app)
26
+        doc = hapic.generate_doc()
27 27
 
28 28
         assert doc
29 29
         assert '/upload' in doc['paths']
@@ -39,8 +39,8 @@ class TestDocGeneration(Base):
39 39
 
40 40
     def test_func__input_files_doc__ok__two_file(self):
41 41
         hapic = Hapic()
42
-        hapic.set_context(MyContext())
43 42
         app = bottle.Bottle()
43
+        hapic.set_context(MyContext(app=app))
44 44
 
45 45
         class MySchema(marshmallow.Schema):
46 46
             file_abc = marshmallow.fields.Raw(required=True)
@@ -53,7 +53,7 @@ class TestDocGeneration(Base):
53 53
             assert hapic_data.files
54 54
 
55 55
         app.route('/upload', method='POST', callback=my_controller)
56
-        doc = hapic.generate_doc(app)
56
+        doc = hapic.generate_doc()
57 57
 
58 58
         assert doc
59 59
         assert '/upload' in doc['paths']
@@ -75,8 +75,8 @@ class TestDocGeneration(Base):
75 75
 
76 76
     def test_func__output_file_doc__ok__nominal_case(self):
77 77
         hapic = Hapic()
78
-        hapic.set_context(MyContext())
79 78
         app = bottle.Bottle()
79
+        hapic.set_context(MyContext(app=app))
80 80
 
81 81
         @hapic.with_api_doc()
82 82
         @hapic.output_file(['image/jpeg'])
@@ -84,7 +84,7 @@ class TestDocGeneration(Base):
84 84
             return b'101010100101'
85 85
 
86 86
         app.route('/avatar', method='GET', callback=my_controller)
87
-        doc = hapic.generate_doc(app)
87
+        doc = hapic.generate_doc()
88 88
 
89 89
         assert doc
90 90
         assert '/avatar' in doc['paths']
@@ -94,8 +94,8 @@ class TestDocGeneration(Base):
94 94
 
95 95
     def test_func__input_files_doc__ok__one_file_and_text(self):
96 96
         hapic = Hapic()
97
-        hapic.set_context(MyContext())
98 97
         app = bottle.Bottle()
98
+        hapic.set_context(MyContext(app=app))
99 99
 
100 100
         class MySchema(marshmallow.Schema):
101 101
             name = marshmallow.fields.String(required=True)
@@ -111,7 +111,7 @@ class TestDocGeneration(Base):
111 111
             assert hapic_data.files
112 112
 
113 113
         app.route('/upload', method='POST', callback=my_controller)
114
-        doc = hapic.generate_doc(app)
114
+        doc = hapic.generate_doc()
115 115
 
116 116
         assert doc
117 117
         assert '/upload' in doc['paths']
@@ -127,8 +127,8 @@ class TestDocGeneration(Base):
127 127
 
128 128
     def test_func__docstring__ok__simple_case(self):
129 129
         hapic = Hapic()
130
-        hapic.set_context(MyContext())
131 130
         app = bottle.Bottle()
131
+        hapic.set_context(MyContext(app=app))
132 132
 
133 133
         # TODO BS 20171113: Make this test non-bottle
134 134
         @hapic.with_api_doc()
@@ -140,7 +140,7 @@ class TestDocGeneration(Base):
140 140
             assert hapic_data.files
141 141
 
142 142
         app.route('/upload', method='POST', callback=my_controller)
143
-        doc = hapic.generate_doc(app)
143
+        doc = hapic.generate_doc()
144 144
 
145 145
         assert doc.get('paths')
146 146
         assert '/upload' in doc['paths']

+ 18 - 9
tests/func/test_marshmallow_decoration.py View File

@@ -11,9 +11,12 @@ from tests.base import MyContext
11 11
 class TestMarshmallowDecoration(Base):
12 12
     def test_unit__input_files__ok__file_is_present(self):
13 13
         hapic = Hapic()
14
-        hapic.set_context(MyContext(fake_files_parameters={
15
-            'file_abc': '10101010101',
16
-        }))
14
+        hapic.set_context(MyContext(
15
+            app=None,
16
+            fake_files_parameters={
17
+                'file_abc': '10101010101',
18
+            }
19
+        ))
17 20
 
18 21
         class MySchema(marshmallow.Schema):
19 22
             file_abc = marshmallow.fields.Raw(required=True)
@@ -30,9 +33,12 @@ class TestMarshmallowDecoration(Base):
30 33
 
31 34
     def test_unit__input_files__ok__file_is_not_present(self):
32 35
         hapic = Hapic()
33
-        hapic.set_context(MyContext(fake_files_parameters={
34
-            # No file here
35
-        }))
36
+        hapic.set_context(MyContext(
37
+            app=None,
38
+            fake_files_parameters={
39
+                # No file here
40
+            }
41
+        ))
36 42
 
37 43
         class MySchema(marshmallow.Schema):
38 44
             file_abc = marshmallow.fields.Raw(required=True)
@@ -53,9 +59,12 @@ class TestMarshmallowDecoration(Base):
53 59
 
54 60
     def test_unit__input_files__ok__file_is_empty_string(self):
55 61
         hapic = Hapic()
56
-        hapic.set_context(MyContext(fake_files_parameters={
57
-            'file_abc': '',
58
-        }))
62
+        hapic.set_context(MyContext(
63
+            app=None,
64
+            fake_files_parameters={
65
+                'file_abc': '',
66
+            }
67
+        ))
59 68
 
60 69
         class MySchema(marshmallow.Schema):
61 70
             file_abc = marshmallow.fields.Raw(required=True)

+ 32 - 23
tests/unit/test_decorator.py View File

@@ -90,7 +90,7 @@ class MySchema(marshmallow.Schema):
90 90
 
91 91
 class TestControllerWrapper(Base):
92 92
     def test_unit__base_controller_wrapper__ok__no_behaviour(self):
93
-        context = MyContext()
93
+        context = MyContext(app=None)
94 94
         processor = MyProcessor()
95 95
         wrapper = InputOutputControllerWrapper(context, processor)
96 96
 
@@ -102,7 +102,7 @@ class TestControllerWrapper(Base):
102 102
         assert result == 42
103 103
 
104 104
     def test_unit__base_controller__ok__replaced_response(self):
105
-        context = MyContext()
105
+        context = MyContext(app=None)
106 106
         processor = MyProcessor()
107 107
         wrapper = MyControllerWrapper(context, processor)
108 108
 
@@ -116,7 +116,7 @@ class TestControllerWrapper(Base):
116 116
         assert {'error_response': 'we are testing'} == result
117 117
 
118 118
     def test_unit__controller_wrapper__ok__overload_input(self):
119
-        context = MyContext()
119
+        context = MyContext(app=None)
120 120
         processor = MyProcessor()
121 121
         wrapper = MyControllerWrapper(context, processor)
122 122
 
@@ -133,11 +133,14 @@ class TestControllerWrapper(Base):
133 133
 
134 134
 class TestInputControllerWrapper(Base):
135 135
     def test_unit__input_data_wrapping__ok__nominal_case(self):
136
-        context = MyContext(fake_query_parameters=MultiDict(
137
-            (
138
-                ('foo', 'bar',),
136
+        context = MyContext(
137
+            app=None,
138
+            fake_query_parameters=MultiDict(
139
+                (
140
+                    ('foo', 'bar',),
141
+                )
139 142
             )
140
-        ))
143
+        )
141 144
         processor = MyProcessor()
142 145
         wrapper = MyInputQueryControllerWrapper(context, processor)
143 146
 
@@ -153,12 +156,15 @@ class TestInputControllerWrapper(Base):
153 156
         assert result == 42
154 157
 
155 158
     def test_unit__multi_query_param_values__ok__use_as_list(self):
156
-        context = MyContext(fake_query_parameters=MultiDict(
157
-            (
158
-                ('user_id', 'abc'),
159
-                ('user_id', 'def'),
160
-            ),
161
-        ))
159
+        context = MyContext(
160
+            app=None,
161
+            fake_query_parameters=MultiDict(
162
+                (
163
+                    ('user_id', 'abc'),
164
+                    ('user_id', 'def'),
165
+                ),
166
+            )
167
+        )
162 168
         processor = MySimpleProcessor()
163 169
         wrapper = InputQueryControllerWrapper(
164 170
             context,
@@ -178,12 +184,15 @@ class TestInputControllerWrapper(Base):
178 184
         assert result == ['abc', 'def']
179 185
 
180 186
     def test_unit__multi_query_param_values__ok__without_as_list(self):
181
-        context = MyContext(fake_query_parameters=MultiDict(
182
-            (
183
-                ('user_id', 'abc'),
184
-                ('user_id', 'def'),
185
-            ),
186
-        ))
187
+        context = MyContext(
188
+            app=None,
189
+            fake_query_parameters=MultiDict(
190
+                (
191
+                    ('user_id', 'abc'),
192
+                    ('user_id', 'def'),
193
+                ),
194
+            )
195
+        )
187 196
         processor = MySimpleProcessor()
188 197
         wrapper = InputQueryControllerWrapper(
189 198
             context,
@@ -204,7 +213,7 @@ class TestInputControllerWrapper(Base):
204 213
 
205 214
 class TestOutputControllerWrapper(Base):
206 215
     def test_unit__output_data_wrapping__ok__nominal_case(self):
207
-        context = MyContext()
216
+        context = MyContext(app=None)
208 217
         processor = MyProcessor()
209 218
         wrapper = OutputControllerWrapper(context, processor)
210 219
 
@@ -222,7 +231,7 @@ class TestOutputControllerWrapper(Base):
222 231
                } == result
223 232
 
224 233
     def test_unit__output_data_wrapping__fail__error_response(self):
225
-        context = MyContext()
234
+        context = MyContext(app=None)
226 235
         processor = MarshmallowOutputProcessor()
227 236
         processor.schema = MySchema()
228 237
         wrapper = OutputControllerWrapper(context, processor)
@@ -244,7 +253,7 @@ class TestOutputControllerWrapper(Base):
244 253
 
245 254
 class TestExceptionHandlerControllerWrapper(Base):
246 255
     def test_unit__exception_handled__ok__nominal_case(self):
247
-        context = MyContext()
256
+        context = MyContext(app=None)
248 257
         wrapper = ExceptionHandlerControllerWrapper(
249 258
             ZeroDivisionError,
250 259
             context,
@@ -272,7 +281,7 @@ class TestExceptionHandlerControllerWrapper(Base):
272 281
                 super().__init__(*args, **kwargs)
273 282
                 self.error_dict = {}
274 283
 
275
-        context = MyContext()
284
+        context = MyContext(app=None)
276 285
         wrapper = ExceptionHandlerControllerWrapper(
277 286
             MyException,
278 287
             context,