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
         self,
14
         self,
15
         rule: str,
15
         rule: str,
16
         method: str,
16
         method: str,
17
+        original_route_object: typing.Any=None,
17
     ) -> None:
18
     ) -> None:
18
         self.rule = rule
19
         self.rule = rule
19
         self.method = method
20
         self.method = method
21
+        self.original_route_object = original_route_object
20
 
22
 
21
 
23
 
22
 class ContextInterface(object):
24
 class ContextInterface(object):

+ 2 - 34
hapic/doc.py View File

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 import typing
2
 import typing
3
 
3
 
4
-import bottle
5
 from apispec import APISpec
4
 from apispec import APISpec
6
 from apispec import Path
5
 from apispec import Path
7
 from apispec.ext.marshmallow.swagger import schema2jsonschema
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
 from hapic.decorator import DecoratedController
10
 from hapic.decorator import DecoratedController
12
 from hapic.description import ControllerDescription
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
 def bottle_generate_operations(
14
 def bottle_generate_operations(

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

8
 from multidict import MultiDict
8
 from multidict import MultiDict
9
 
9
 
10
 from hapic.context import ContextInterface
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
 from hapic.exception import OutputValidationException
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
 # Bottle regular expression to locate url parameters
20
 # Bottle regular expression to locate url parameters
15
 BOTTLE_RE_PATH_URL = re.compile(r'<(?:[^:<>]+:)?([^<>]+)>')
21
 BOTTLE_RE_PATH_URL = re.compile(r'<(?:[^:<>]+:)?([^<>]+)>')
16
 
22
 
17
 
23
 
18
 class BottleContext(ContextInterface):
24
 class BottleContext(ContextInterface):
25
+    def __init__(self, app: bottle.Bottle):
26
+        self.app = app
27
+
19
     def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
28
     def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
20
         path_parameters = dict(bottle.request.url_args)
29
         path_parameters = dict(bottle.request.url_args)
21
         query_parameters = MultiDict(bottle.request.query.allitems())
30
         query_parameters = MultiDict(bottle.request.query.allitems())
26
 
35
 
27
         return RequestParameters(
36
         return RequestParameters(
28
             path_parameters=path_parameters,
37
             path_parameters=path_parameters,
29
-            query_parameters=query_parameters, ## query?
38
+            query_parameters=query_parameters,
30
             body_parameters=body_parameters,
39
             body_parameters=body_parameters,
31
             form_parameters=form_parameters,
40
             form_parameters=form_parameters,
32
             header_parameters=header_parameters,
41
             header_parameters=header_parameters,
68
             ],
77
             ],
69
             status=int(http_code),
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
 import typing
4
 import typing
5
 from http import HTTPStatus
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
 from hapic.context import ContextInterface
7
 from hapic.context import ContextInterface
12
 from hapic.context import RouteRepresentation
8
 from hapic.context import RouteRepresentation
13
 from hapic.decorator import DecoratedController
9
 from hapic.decorator import DecoratedController
17
 from hapic.processor import RequestParameters
13
 from hapic.processor import RequestParameters
18
 from hapic.processor import ProcessValidationError
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
 # Bottle regular expression to locate url parameters
20
 # Bottle regular expression to locate url parameters
21
 PYRAMID_RE_PATH_URL = re.compile(r'')
21
 PYRAMID_RE_PATH_URL = re.compile(r'')
22
 
22
 
23
 
23
 
24
 class PyramidContext(ContextInterface):
24
 class PyramidContext(ContextInterface):
25
-    def __init__(self, configurator: Configurator):
25
+    def __init__(self, configurator: 'Configurator'):
26
         self.configurator = configurator
26
         self.configurator = configurator
27
 
27
 
28
     def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
28
     def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
29
         req = args[-1]  # TODO : Check
29
         req = args[-1]  # TODO : Check
30
-        assert isinstance(req, Request)
31
         # TODO : move this code to check_json
30
         # TODO : move this code to check_json
32
         # same idea as in : https://bottlepy.org/docs/dev/_modules/bottle.html#BaseRequest.json
31
         # same idea as in : https://bottlepy.org/docs/dev/_modules/bottle.html#BaseRequest.json
33
         if req.body and req.content_type in ('application/json', 'application/json-rpc'):
32
         if req.body and req.content_type in ('application/json', 'application/json-rpc'):
48
         self,
47
         self,
49
         response: dict,
48
         response: dict,
50
         http_code: int,
49
         http_code: int,
51
-    ) -> Response:
50
+    ) -> 'Response':
51
+        from pyramid.response import Response
52
         return Response(
52
         return Response(
53
             body=json.dumps(response),
53
             body=json.dumps(response),
54
             headers=[
54
             headers=[
63
         http_code: HTTPStatus=HTTPStatus.BAD_REQUEST,
63
         http_code: HTTPStatus=HTTPStatus.BAD_REQUEST,
64
     ) -> typing.Any:
64
     ) -> typing.Any:
65
         # TODO BS 20171010: Manage error schemas, see #4
65
         # TODO BS 20171010: Manage error schemas, see #4
66
+        from pyramid.response import Response
66
         from hapic.hapic import _default_global_error_schema
67
         from hapic.hapic import _default_global_error_schema
67
         unmarshall = _default_global_error_schema.dump(error)
68
         unmarshall = _default_global_error_schema.dump(error)
68
         if unmarshall.errors:
69
         if unmarshall.errors:
109
                 route_method = route_intr[0].get('request_methods')[0]
110
                 route_method = route_intr[0].get('request_methods')[0]
110
 
111
 
111
                 return RouteRepresentation(
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
                     method=route_method,
114
                     method=route_method,
115
+                    original_route_object=route_intr[0],
119
                 )
116
                 )
120
 
117
 
121
     def get_swagger_path(self, contextualised_rule: str) -> str:
118
     def get_swagger_path(self, contextualised_rule: str) -> str:

+ 5 - 2
tests/base.py View File

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

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

2
 import bottle
2
 import bottle
3
 
3
 
4
 import hapic
4
 import hapic
5
-from hapic.doc import find_bottle_route
6
 from tests.base import Base
5
 from tests.base import Base
7
 
6
 
8
 
7
 
9
 class TestBottleExt(Base):
8
 class TestBottleExt(Base):
10
     def test_unit__map_binding__ok__decorated_function(self):
9
     def test_unit__map_binding__ok__decorated_function(self):
11
         hapic_ = hapic.Hapic()
10
         hapic_ = hapic.Hapic()
12
-        hapic_.set_context(hapic.ext.bottle.BottleContext())
13
-
14
         app = bottle.Bottle()
11
         app = bottle.Bottle()
12
+        context = hapic.ext.bottle.BottleContext(app=app)
13
+        hapic_.set_context(context)
15
 
14
 
16
         @hapic_.with_api_doc()
15
         @hapic_.with_api_doc()
17
         @app.route('/')
16
         @app.route('/')
20
 
19
 
21
         assert hapic_.controllers
20
         assert hapic_.controllers
22
         decoration = hapic_.controllers[0]
21
         decoration = hapic_.controllers[0]
23
-        route = find_bottle_route(decoration, app)
22
+        route = context.find_route(decoration)
24
 
23
 
25
         assert route
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
     def test_unit__map_binding__ok__mapped_function(self):
29
     def test_unit__map_binding__ok__mapped_function(self):
31
         hapic_ = hapic.Hapic()
30
         hapic_ = hapic.Hapic()
32
-        hapic_.set_context(hapic.ext.bottle.BottleContext())
33
-
34
         app = bottle.Bottle()
31
         app = bottle.Bottle()
32
+        context = hapic.ext.bottle.BottleContext(app=app)
33
+        hapic_.set_context(context)
35
 
34
 
36
         @hapic_.with_api_doc()
35
         @hapic_.with_api_doc()
37
         def controller_a():
36
         def controller_a():
41
 
40
 
42
         assert hapic_.controllers
41
         assert hapic_.controllers
43
         decoration = hapic_.controllers[0]
42
         decoration = hapic_.controllers[0]
44
-        route = find_bottle_route(decoration, app)
43
+        route = context.find_route(decoration)
45
 
44
 
46
         assert route
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
     def test_unit__map_binding__ok__mapped_method(self):
50
     def test_unit__map_binding__ok__mapped_method(self):
52
         hapic_ = hapic.Hapic()
51
         hapic_ = hapic.Hapic()
53
-        hapic_.set_context(hapic.ext.bottle.BottleContext())
54
-
55
         app = bottle.Bottle()
52
         app = bottle.Bottle()
53
+        context = hapic.ext.bottle.BottleContext(app=app)
54
+        hapic_.set_context(context)
56
 
55
 
57
         class MyControllers(object):
56
         class MyControllers(object):
58
             def bind(self, app):
57
             def bind(self, app):
67
 
66
 
68
         assert hapic_.controllers
67
         assert hapic_.controllers
69
         decoration = hapic_.controllers[0]
68
         decoration = hapic_.controllers[0]
70
-        route = find_bottle_route(decoration, app)
69
+        route = context.find_route(decoration)
71
 
70
 
72
         assert route
71
         assert route
73
         # Important note: instance controller_a method is
72
         # Important note: instance controller_a method is
74
         # not class controller_a, so no matches with callbacks
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
 class TestDocGeneration(Base):
10
 class TestDocGeneration(Base):
11
     def test_func__input_files_doc__ok__one_file(self):
11
     def test_func__input_files_doc__ok__one_file(self):
12
         hapic = Hapic()
12
         hapic = Hapic()
13
-        hapic.set_context(MyContext())
14
         app = bottle.Bottle()
13
         app = bottle.Bottle()
14
+        hapic.set_context(MyContext(app=app))
15
 
15
 
16
         class MySchema(marshmallow.Schema):
16
         class MySchema(marshmallow.Schema):
17
             file_abc = marshmallow.fields.Raw(required=True)
17
             file_abc = marshmallow.fields.Raw(required=True)
23
             assert hapic_data.files
23
             assert hapic_data.files
24
 
24
 
25
         app.route('/upload', method='POST', callback=my_controller)
25
         app.route('/upload', method='POST', callback=my_controller)
26
-        doc = hapic.generate_doc(app)
26
+        doc = hapic.generate_doc()
27
 
27
 
28
         assert doc
28
         assert doc
29
         assert '/upload' in doc['paths']
29
         assert '/upload' in doc['paths']
39
 
39
 
40
     def test_func__input_files_doc__ok__two_file(self):
40
     def test_func__input_files_doc__ok__two_file(self):
41
         hapic = Hapic()
41
         hapic = Hapic()
42
-        hapic.set_context(MyContext())
43
         app = bottle.Bottle()
42
         app = bottle.Bottle()
43
+        hapic.set_context(MyContext(app=app))
44
 
44
 
45
         class MySchema(marshmallow.Schema):
45
         class MySchema(marshmallow.Schema):
46
             file_abc = marshmallow.fields.Raw(required=True)
46
             file_abc = marshmallow.fields.Raw(required=True)
53
             assert hapic_data.files
53
             assert hapic_data.files
54
 
54
 
55
         app.route('/upload', method='POST', callback=my_controller)
55
         app.route('/upload', method='POST', callback=my_controller)
56
-        doc = hapic.generate_doc(app)
56
+        doc = hapic.generate_doc()
57
 
57
 
58
         assert doc
58
         assert doc
59
         assert '/upload' in doc['paths']
59
         assert '/upload' in doc['paths']
75
 
75
 
76
     def test_func__output_file_doc__ok__nominal_case(self):
76
     def test_func__output_file_doc__ok__nominal_case(self):
77
         hapic = Hapic()
77
         hapic = Hapic()
78
-        hapic.set_context(MyContext())
79
         app = bottle.Bottle()
78
         app = bottle.Bottle()
79
+        hapic.set_context(MyContext(app=app))
80
 
80
 
81
         @hapic.with_api_doc()
81
         @hapic.with_api_doc()
82
         @hapic.output_file(['image/jpeg'])
82
         @hapic.output_file(['image/jpeg'])
84
             return b'101010100101'
84
             return b'101010100101'
85
 
85
 
86
         app.route('/avatar', method='GET', callback=my_controller)
86
         app.route('/avatar', method='GET', callback=my_controller)
87
-        doc = hapic.generate_doc(app)
87
+        doc = hapic.generate_doc()
88
 
88
 
89
         assert doc
89
         assert doc
90
         assert '/avatar' in doc['paths']
90
         assert '/avatar' in doc['paths']
94
 
94
 
95
     def test_func__input_files_doc__ok__one_file_and_text(self):
95
     def test_func__input_files_doc__ok__one_file_and_text(self):
96
         hapic = Hapic()
96
         hapic = Hapic()
97
-        hapic.set_context(MyContext())
98
         app = bottle.Bottle()
97
         app = bottle.Bottle()
98
+        hapic.set_context(MyContext(app=app))
99
 
99
 
100
         class MySchema(marshmallow.Schema):
100
         class MySchema(marshmallow.Schema):
101
             name = marshmallow.fields.String(required=True)
101
             name = marshmallow.fields.String(required=True)
111
             assert hapic_data.files
111
             assert hapic_data.files
112
 
112
 
113
         app.route('/upload', method='POST', callback=my_controller)
113
         app.route('/upload', method='POST', callback=my_controller)
114
-        doc = hapic.generate_doc(app)
114
+        doc = hapic.generate_doc()
115
 
115
 
116
         assert doc
116
         assert doc
117
         assert '/upload' in doc['paths']
117
         assert '/upload' in doc['paths']
127
 
127
 
128
     def test_func__docstring__ok__simple_case(self):
128
     def test_func__docstring__ok__simple_case(self):
129
         hapic = Hapic()
129
         hapic = Hapic()
130
-        hapic.set_context(MyContext())
131
         app = bottle.Bottle()
130
         app = bottle.Bottle()
131
+        hapic.set_context(MyContext(app=app))
132
 
132
 
133
         # TODO BS 20171113: Make this test non-bottle
133
         # TODO BS 20171113: Make this test non-bottle
134
         @hapic.with_api_doc()
134
         @hapic.with_api_doc()
140
             assert hapic_data.files
140
             assert hapic_data.files
141
 
141
 
142
         app.route('/upload', method='POST', callback=my_controller)
142
         app.route('/upload', method='POST', callback=my_controller)
143
-        doc = hapic.generate_doc(app)
143
+        doc = hapic.generate_doc()
144
 
144
 
145
         assert doc.get('paths')
145
         assert doc.get('paths')
146
         assert '/upload' in doc['paths']
146
         assert '/upload' in doc['paths']

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

11
 class TestMarshmallowDecoration(Base):
11
 class TestMarshmallowDecoration(Base):
12
     def test_unit__input_files__ok__file_is_present(self):
12
     def test_unit__input_files__ok__file_is_present(self):
13
         hapic = Hapic()
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
         class MySchema(marshmallow.Schema):
21
         class MySchema(marshmallow.Schema):
19
             file_abc = marshmallow.fields.Raw(required=True)
22
             file_abc = marshmallow.fields.Raw(required=True)
30
 
33
 
31
     def test_unit__input_files__ok__file_is_not_present(self):
34
     def test_unit__input_files__ok__file_is_not_present(self):
32
         hapic = Hapic()
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
         class MySchema(marshmallow.Schema):
43
         class MySchema(marshmallow.Schema):
38
             file_abc = marshmallow.fields.Raw(required=True)
44
             file_abc = marshmallow.fields.Raw(required=True)
53
 
59
 
54
     def test_unit__input_files__ok__file_is_empty_string(self):
60
     def test_unit__input_files__ok__file_is_empty_string(self):
55
         hapic = Hapic()
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
         class MySchema(marshmallow.Schema):
69
         class MySchema(marshmallow.Schema):
61
             file_abc = marshmallow.fields.Raw(required=True)
70
             file_abc = marshmallow.fields.Raw(required=True)

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

90
 
90
 
91
 class TestControllerWrapper(Base):
91
 class TestControllerWrapper(Base):
92
     def test_unit__base_controller_wrapper__ok__no_behaviour(self):
92
     def test_unit__base_controller_wrapper__ok__no_behaviour(self):
93
-        context = MyContext()
93
+        context = MyContext(app=None)
94
         processor = MyProcessor()
94
         processor = MyProcessor()
95
         wrapper = InputOutputControllerWrapper(context, processor)
95
         wrapper = InputOutputControllerWrapper(context, processor)
96
 
96
 
102
         assert result == 42
102
         assert result == 42
103
 
103
 
104
     def test_unit__base_controller__ok__replaced_response(self):
104
     def test_unit__base_controller__ok__replaced_response(self):
105
-        context = MyContext()
105
+        context = MyContext(app=None)
106
         processor = MyProcessor()
106
         processor = MyProcessor()
107
         wrapper = MyControllerWrapper(context, processor)
107
         wrapper = MyControllerWrapper(context, processor)
108
 
108
 
116
         assert {'error_response': 'we are testing'} == result
116
         assert {'error_response': 'we are testing'} == result
117
 
117
 
118
     def test_unit__controller_wrapper__ok__overload_input(self):
118
     def test_unit__controller_wrapper__ok__overload_input(self):
119
-        context = MyContext()
119
+        context = MyContext(app=None)
120
         processor = MyProcessor()
120
         processor = MyProcessor()
121
         wrapper = MyControllerWrapper(context, processor)
121
         wrapper = MyControllerWrapper(context, processor)
122
 
122
 
133
 
133
 
134
 class TestInputControllerWrapper(Base):
134
 class TestInputControllerWrapper(Base):
135
     def test_unit__input_data_wrapping__ok__nominal_case(self):
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
         processor = MyProcessor()
144
         processor = MyProcessor()
142
         wrapper = MyInputQueryControllerWrapper(context, processor)
145
         wrapper = MyInputQueryControllerWrapper(context, processor)
143
 
146
 
153
         assert result == 42
156
         assert result == 42
154
 
157
 
155
     def test_unit__multi_query_param_values__ok__use_as_list(self):
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
         processor = MySimpleProcessor()
168
         processor = MySimpleProcessor()
163
         wrapper = InputQueryControllerWrapper(
169
         wrapper = InputQueryControllerWrapper(
164
             context,
170
             context,
178
         assert result == ['abc', 'def']
184
         assert result == ['abc', 'def']
179
 
185
 
180
     def test_unit__multi_query_param_values__ok__without_as_list(self):
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
         processor = MySimpleProcessor()
196
         processor = MySimpleProcessor()
188
         wrapper = InputQueryControllerWrapper(
197
         wrapper = InputQueryControllerWrapper(
189
             context,
198
             context,
204
 
213
 
205
 class TestOutputControllerWrapper(Base):
214
 class TestOutputControllerWrapper(Base):
206
     def test_unit__output_data_wrapping__ok__nominal_case(self):
215
     def test_unit__output_data_wrapping__ok__nominal_case(self):
207
-        context = MyContext()
216
+        context = MyContext(app=None)
208
         processor = MyProcessor()
217
         processor = MyProcessor()
209
         wrapper = OutputControllerWrapper(context, processor)
218
         wrapper = OutputControllerWrapper(context, processor)
210
 
219
 
222
                } == result
231
                } == result
223
 
232
 
224
     def test_unit__output_data_wrapping__fail__error_response(self):
233
     def test_unit__output_data_wrapping__fail__error_response(self):
225
-        context = MyContext()
234
+        context = MyContext(app=None)
226
         processor = MarshmallowOutputProcessor()
235
         processor = MarshmallowOutputProcessor()
227
         processor.schema = MySchema()
236
         processor.schema = MySchema()
228
         wrapper = OutputControllerWrapper(context, processor)
237
         wrapper = OutputControllerWrapper(context, processor)
244
 
253
 
245
 class TestExceptionHandlerControllerWrapper(Base):
254
 class TestExceptionHandlerControllerWrapper(Base):
246
     def test_unit__exception_handled__ok__nominal_case(self):
255
     def test_unit__exception_handled__ok__nominal_case(self):
247
-        context = MyContext()
256
+        context = MyContext(app=None)
248
         wrapper = ExceptionHandlerControllerWrapper(
257
         wrapper = ExceptionHandlerControllerWrapper(
249
             ZeroDivisionError,
258
             ZeroDivisionError,
250
             context,
259
             context,
272
                 super().__init__(*args, **kwargs)
281
                 super().__init__(*args, **kwargs)
273
                 self.error_dict = {}
282
                 self.error_dict = {}
274
 
283
 
275
-        context = MyContext()
284
+        context = MyContext(app=None)
276
         wrapper = ExceptionHandlerControllerWrapper(
285
         wrapper = ExceptionHandlerControllerWrapper(
277
             MyException,
286
             MyException,
278
             context,
287
             context,