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,3 +4,4 @@ __pycache__
4 4
 *~
5 5
 /venv
6 6
 .cache
7
+*.egg-info

+ 86 - 67
example_a.py View File

@@ -5,18 +5,31 @@ from http import HTTPStatus
5 5
 import bottle
6 6
 import time
7 7
 import yaml
8
+from beaker.middleware import SessionMiddleware
8 9
 
9 10
 import hapic
10 11
 from example import HelloResponseSchema, HelloPathSchema, HelloJsonSchema, \
11 12
     ErrorResponseSchema, HelloQuerySchema
12 13
 from hapic.data import HapicData
13 14
 
14
-app = bottle.Bottle()
15 15
 # hapic.global_exception_handler(UnAuthExc, StandardErrorSchema)
16 16
 # hapic.global_exception_handler(UnAuthExc2, StandardErrorSchema)
17 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 35
 def bob(f):
@@ -25,69 +38,74 @@ def bob(f):
25 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 110
 # time.sleep(1)
93 111
 # s = hapic.generate_doc(app)
@@ -100,6 +118,7 @@ app.route('/hello3/<name>', callback=hello3)
100 118
 # print(yaml.dump(ss, default_flow_style=False))
101 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 124
 app.run(host='localhost', port=8080, debug=True)

+ 8 - 4
hapic/decorator.py View File

@@ -10,6 +10,10 @@ from hapic.context import ContextInterface
10 10
 from hapic.processor import ProcessorInterface
11 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 18
 class ControllerWrapper(object):
15 19
     def before_wrapped_func(
@@ -177,15 +181,15 @@ class OutputControllerWrapper(InputOutputControllerWrapper):
177 181
 class DecoratedController(object):
178 182
     def __init__(
179 183
         self,
180
-        reference: 'typing.Callable[..., typing.Any]',
184
+        token: str,
181 185
         description: ControllerDescription,
182 186
     ) -> None:
183
-        self._reference = reference
187
+        self._token = token
184 188
         self._description = description
185 189
 
186 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 194
     @property
191 195
     def description(self) -> ControllerDescription:

+ 9 - 5
hapic/doc.py View File

@@ -6,8 +6,7 @@ import bottle
6 6
 from apispec import APISpec, Path
7 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 11
 # Bottle regular expression to locate url parameters
13 12
 from hapic.description import ControllerDescription
@@ -15,9 +14,14 @@ from hapic.description import ControllerDescription
15 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 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 25
             return route
22 26
     # TODO: specialize exception
23 27
     raise Exception('Not found')
@@ -139,7 +143,7 @@ class DocGenerator(object):
139 143
         # with app.test_request_context():
140 144
         paths = {}
141 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 147
             swagger_path = BOTTLE_RE_PATH_URL.sub(r'{\1}', bottle_route.rule)
144 148
 
145 149
             operations = bottle_generate_operations(

+ 10 - 5
hapic/hapic.py View File

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