Browse Source

use an attribute to identify controller

Bastien Sevajol 6 years ago
parent
commit
8c0d334625
5 changed files with 114 additions and 81 deletions
  1. 1 0
      .gitignore
  2. 86 67
      example_a.py
  3. 8 4
      hapic/decorator.py
  4. 9 5
      hapic/doc.py
  5. 10 5
      hapic/hapic.py

+ 1 - 0
.gitignore View File

4
 *~
4
 *~
5
 /venv
5
 /venv
6
 .cache
6
 .cache
7
+*.egg-info

+ 86 - 67
example_a.py View File

5
 import bottle
5
 import bottle
6
 import time
6
 import time
7
 import yaml
7
 import yaml
8
+from beaker.middleware import SessionMiddleware
8
 
9
 
9
 import hapic
10
 import hapic
10
 from example import HelloResponseSchema, HelloPathSchema, HelloJsonSchema, \
11
 from example import HelloResponseSchema, HelloPathSchema, HelloJsonSchema, \
11
     ErrorResponseSchema, HelloQuerySchema
12
     ErrorResponseSchema, HelloQuerySchema
12
 from hapic.data import HapicData
13
 from hapic.data import HapicData
13
 
14
 
14
-app = bottle.Bottle()
15
 # hapic.global_exception_handler(UnAuthExc, StandardErrorSchema)
15
 # hapic.global_exception_handler(UnAuthExc, StandardErrorSchema)
16
 # hapic.global_exception_handler(UnAuthExc2, StandardErrorSchema)
16
 # hapic.global_exception_handler(UnAuthExc2, StandardErrorSchema)
17
 # hapic.global_exception_handler(UnAuthExc3, StandardErrorSchema)
17
 # hapic.global_exception_handler(UnAuthExc3, StandardErrorSchema)
18
-bottle.default_app.push(app)
19
-hapic.set_context(hapic.ext.bottle.bottle_context)
18
+# bottle.default_app.push(app)
19
+
20
+# session_opts = {
21
+#     'session.type': 'file',
22
+#     'session.data_dir': '/tmp',
23
+#     'session.cookie_expires': 3600,
24
+#     'session.auto': True
25
+# }
26
+# session_middleware = SessionMiddleware(
27
+#     app,
28
+#     session_opts,
29
+#     environ_key='beaker.session',
30
+#     key='beaker.session.id',
31
+# )
32
+# app = session_middleware.wrap_app
20
 
33
 
21
 
34
 
22
 def bob(f):
35
 def bob(f):
25
     return boby
38
     return boby
26
 
39
 
27
 
40
 
28
-@hapic.with_api_doc()
29
-# @hapic.ext.bottle.bottle_context()
30
-@hapic.handle_exception(ZeroDivisionError, http_code=HTTPStatus.BAD_REQUEST)
31
-@hapic.input_path(HelloPathSchema())
32
-@hapic.input_query(HelloQuerySchema())
33
-@hapic.output_body(HelloResponseSchema())
34
-def hello(name: str, hapic_data: HapicData):
35
-    """
36
-    my endpoint hello
37
-    ---
38
-    get:
39
-        description: my description
40
-        parameters:
41
-            - in: "path"
42
-              description: "hello"
43
-              name: "name"
44
-              type: "string"
45
-        responses:
46
-            200:
47
-                description: A pet to be returned
48
-                schema: HelloResponseSchema
49
-    """
50
-    if name == 'zero':
51
-        raise ZeroDivisionError('Don\'t call him zero !')
52
-
53
-    return {
54
-        'sentence': 'Hello !',
55
-        'name': name,
56
-    }
57
-
58
-
59
-@hapic.with_api_doc()
60
-# @hapic.ext.bottle.bottle_context()
61
-# @hapic.error_schema(ErrorResponseSchema())
62
-@hapic.input_path(HelloPathSchema())
63
-@hapic.input_body(HelloJsonSchema())
64
-@hapic.output_body(HelloResponseSchema())
65
-@bob
66
-def hello2(name: str, hapic_data: HapicData):
67
-    return {
68
-        'sentence': 'Hello !',
69
-        'name': name,
70
-        'color': hapic_data.body.get('color'),
71
-    }
72
-
73
-kwargs = {'validated_data': {'name': 'bob'}, 'name': 'bob'}
74
-
75
-
76
-@hapic.with_api_doc()
77
-# @hapic.ext.bottle.bottle_context()
78
-# @hapic.error_schema(ErrorResponseSchema())
79
-@hapic.input_path(HelloPathSchema())
80
-@hapic.output_body(HelloResponseSchema())
81
-def hello3(name: str):
82
-    return {
83
-        'sentence': 'Hello !',
84
-        'name': name,
85
-    }
86
-
87
-
88
-app.route('/hello/<name>', callback=hello)
89
-app.route('/hello/<name>', callback=hello2, method='POST')
90
-app.route('/hello3/<name>', callback=hello3)
41
+class Controllers(object):
42
+    @hapic.with_api_doc()
43
+    # @hapic.ext.bottle.bottle_context()
44
+    @hapic.handle_exception(ZeroDivisionError, http_code=HTTPStatus.BAD_REQUEST)
45
+    @hapic.input_path(HelloPathSchema())
46
+    @hapic.input_query(HelloQuerySchema())
47
+    @hapic.output_body(HelloResponseSchema())
48
+    def hello(self, name: str, hapic_data: HapicData):
49
+        """
50
+        my endpoint hello
51
+        ---
52
+        get:
53
+            description: my description
54
+            parameters:
55
+                - in: "path"
56
+                  description: "hello"
57
+                  name: "name"
58
+                  type: "string"
59
+            responses:
60
+                200:
61
+                    description: A pet to be returned
62
+                    schema: HelloResponseSchema
63
+        """
64
+        if name == 'zero':
65
+            raise ZeroDivisionError('Don\'t call him zero !')
66
+
67
+        return {
68
+            'sentence': 'Hello !',
69
+            'name': name,
70
+        }
71
+
72
+    @hapic.with_api_doc()
73
+    # @hapic.ext.bottle.bottle_context()
74
+    # @hapic.error_schema(ErrorResponseSchema())
75
+    @hapic.input_path(HelloPathSchema())
76
+    @hapic.input_body(HelloJsonSchema())
77
+    @hapic.output_body(HelloResponseSchema())
78
+    @bob
79
+    def hello2(self, name: str, hapic_data: HapicData):
80
+        return {
81
+            'sentence': 'Hello !',
82
+            'name': name,
83
+            'color': hapic_data.body.get('color'),
84
+        }
85
+
86
+    kwargs = {'validated_data': {'name': 'bob'}, 'name': 'bob'}
87
+
88
+    @hapic.with_api_doc()
89
+    # @hapic.ext.bottle.bottle_context()
90
+    # @hapic.error_schema(ErrorResponseSchema())
91
+    @hapic.input_path(HelloPathSchema())
92
+    @hapic.output_body(HelloResponseSchema())
93
+    def hello3(self, name: str):
94
+        return {
95
+            'sentence': 'Hello !',
96
+            'name': name,
97
+        }
98
+
99
+    def bind(self, app):
100
+        app.route('/hello/<name>', callback=self.hello)
101
+        app.route('/hello/<name>', callback=self.hello2, method='POST')
102
+        app.route('/hello3/<name>', callback=self.hello3)
103
+
104
+app = bottle.Bottle()
105
+
106
+controllers = Controllers()
107
+controllers.bind(app)
108
+
91
 
109
 
92
 # time.sleep(1)
110
 # time.sleep(1)
93
 # s = hapic.generate_doc(app)
111
 # s = hapic.generate_doc(app)
100
 # print(yaml.dump(ss, default_flow_style=False))
118
 # print(yaml.dump(ss, default_flow_style=False))
101
 # time.sleep(1)
119
 # time.sleep(1)
102
 
120
 
103
-print(json.dumps(hapic.generate_doc()))
121
+hapic.set_context(hapic.ext.bottle.bottle_context)
122
+print(json.dumps(hapic.generate_doc(app)))
104
 
123
 
105
 app.run(host='localhost', port=8080, debug=True)
124
 app.run(host='localhost', port=8080, debug=True)

+ 8 - 4
hapic/decorator.py View File

10
 from hapic.processor import ProcessorInterface
10
 from hapic.processor import ProcessorInterface
11
 from hapic.processor import RequestParameters
11
 from hapic.processor import RequestParameters
12
 
12
 
13
+# TODO: Ensure usage of DECORATION_ATTRIBUTE_NAME is documented and
14
+# var names correctly choose.
15
+DECORATION_ATTRIBUTE_NAME = '_hapic_decoration_token'
16
+
13
 
17
 
14
 class ControllerWrapper(object):
18
 class ControllerWrapper(object):
15
     def before_wrapped_func(
19
     def before_wrapped_func(
177
 class DecoratedController(object):
181
 class DecoratedController(object):
178
     def __init__(
182
     def __init__(
179
         self,
183
         self,
180
-        reference: 'typing.Callable[..., typing.Any]',
184
+        token: str,
181
         description: ControllerDescription,
185
         description: ControllerDescription,
182
     ) -> None:
186
     ) -> None:
183
-        self._reference = reference
187
+        self._token = token
184
         self._description = description
188
         self._description = description
185
 
189
 
186
     @property
190
     @property
187
-    def reference(self) -> 'typing.Callable[..., typing.Any]':
188
-        return self._reference
191
+    def token(self) -> str:
192
+        return self._token
189
 
193
 
190
     @property
194
     @property
191
     def description(self) -> ControllerDescription:
195
     def description(self) -> ControllerDescription:

+ 9 - 5
hapic/doc.py View File

6
 from apispec import APISpec, Path
6
 from apispec import APISpec, Path
7
 from apispec.ext.marshmallow.swagger import schema2jsonschema
7
 from apispec.ext.marshmallow.swagger import schema2jsonschema
8
 
8
 
9
-from hapic.decorator import DecoratedController
10
-
9
+from hapic.decorator import DecoratedController, DECORATION_ATTRIBUTE_NAME
11
 
10
 
12
 # Bottle regular expression to locate url parameters
11
 # Bottle regular expression to locate url parameters
13
 from hapic.description import ControllerDescription
12
 from hapic.description import ControllerDescription
15
 BOTTLE_RE_PATH_URL = re.compile(r'<(?:[^:<>]+:)?([^<>]+)>')
14
 BOTTLE_RE_PATH_URL = re.compile(r'<(?:[^:<>]+:)?([^<>]+)>')
16
 
15
 
17
 
16
 
18
-def bottle_route_for_view(reference, app):
17
+def bottle_route_for_view(token, app):
19
     for route in app.routes:
18
     for route in app.routes:
20
-        if route.callback == reference:
19
+        route_token = getattr(
20
+            route.callback,
21
+            DECORATION_ATTRIBUTE_NAME,
22
+            None,
23
+        )
24
+        if route_token == token:
21
             return route
25
             return route
22
     # TODO: specialize exception
26
     # TODO: specialize exception
23
     raise Exception('Not found')
27
     raise Exception('Not found')
139
         # with app.test_request_context():
143
         # with app.test_request_context():
140
         paths = {}
144
         paths = {}
141
         for controller in controllers:
145
         for controller in controllers:
142
-            bottle_route = bottle_route_for_view(controller.reference, app)
146
+            bottle_route = bottle_route_for_view(controller.token, app)
143
             swagger_path = BOTTLE_RE_PATH_URL.sub(r'{\1}', bottle_route.rule)
147
             swagger_path = BOTTLE_RE_PATH_URL.sub(r'{\1}', bottle_route.rule)
144
 
148
 
145
             operations = bottle_generate_operations(
149
             operations = bottle_generate_operations(

+ 10 - 5
hapic/hapic.py View File

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 import typing
2
 import typing
3
+import uuid
3
 from http import HTTPStatus
4
 from http import HTTPStatus
4
 import functools
5
 import functools
5
 
6
 
6
 import marshmallow
7
 import marshmallow
7
 
8
 
8
 from hapic.buffer import DecorationBuffer
9
 from hapic.buffer import DecorationBuffer
9
-from hapic.context import ContextInterface, BottleContext
10
-from hapic.decorator import DecoratedController
10
+from hapic.context import ContextInterface
11
+from hapic.decorator import DecoratedController, DECORATION_ATTRIBUTE_NAME
11
 from hapic.decorator import ExceptionHandlerControllerWrapper
12
 from hapic.decorator import ExceptionHandlerControllerWrapper
12
 from hapic.decorator import InputBodyControllerWrapper
13
 from hapic.decorator import InputBodyControllerWrapper
13
 from hapic.decorator import InputHeadersControllerWrapper
14
 from hapic.decorator import InputHeadersControllerWrapper
15
 from hapic.decorator import InputQueryControllerWrapper
16
 from hapic.decorator import InputQueryControllerWrapper
16
 from hapic.decorator import OutputBodyControllerWrapper
17
 from hapic.decorator import OutputBodyControllerWrapper
17
 from hapic.decorator import OutputHeadersControllerWrapper
18
 from hapic.decorator import OutputHeadersControllerWrapper
18
-from hapic.description import InputBodyDescription, ErrorDescription
19
+from hapic.description import InputBodyDescription
20
+from hapic.description import ErrorDescription
19
 from hapic.description import InputFormsDescription
21
 from hapic.description import InputFormsDescription
20
 from hapic.description import InputHeadersDescription
22
 from hapic.description import InputHeadersDescription
21
 from hapic.description import InputPathDescription
23
 from hapic.description import InputPathDescription
54
         def decorator(func):
56
         def decorator(func):
55
 
57
 
56
             # FIXME: casse ou casse pas le bis ?
58
             # FIXME: casse ou casse pas le bis ?
57
-            # @functools.wraps(func)
59
+            @functools.wraps(func)
58
             def wrapper(*args, **kwargs):
60
             def wrapper(*args, **kwargs):
59
                 return func(*args, **kwargs)
61
                 return func(*args, **kwargs)
60
 
62
 
63
+            token = uuid.uuid4().hex
64
+            setattr(wrapper, DECORATION_ATTRIBUTE_NAME, token)
61
             description = self._buffer.get_description()
65
             description = self._buffer.get_description()
66
+
62
             decorated_controller = DecoratedController(
67
             decorated_controller = DecoratedController(
63
-                reference=wrapper,
68
+                token=token,
64
                 description=description,
69
                 description=description,
65
             )
70
             )
66
             self._buffer.clear()
71
             self._buffer.clear()