Browse Source

WIP flask compat

Guénaël Muller 6 years ago
parent
commit
7b21c2a368
4 changed files with 212 additions and 1 deletions
  1. 156 0
      example_a_flask.py
  2. 1 1
      hapic/ext/__init__.py
  3. 2 0
      hapic/ext/flask/__init__.py
  4. 53 0
      hapic/ext/flask/context.py

+ 156 - 0
example_a_flask.py View File

@@ -0,0 +1,156 @@
1
+# -*- coding: utf-8 -*-
2
+import json
3
+from http import HTTPStatus
4
+
5
+from flask import Flask
6
+import time
7
+import yaml
8
+from beaker.middleware import SessionMiddleware
9
+
10
+import hapic
11
+from example import HelloResponseSchema, HelloPathSchema, HelloJsonSchema, \
12
+    ErrorResponseSchema, HelloQuerySchema
13
+from hapic.data import HapicData
14
+
15
+# hapic.global_exception_handler(UnAuthExc, StandardErrorSchema)
16
+# hapic.global_exception_handler(UnAuthExc2, StandardErrorSchema)
17
+# hapic.global_exception_handler(UnAuthExc3, StandardErrorSchema)
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
33
+
34
+
35
+def bob(f):
36
+    def boby(*args, **kwargs):
37
+        return f(*args, **kwargs)
38
+    return boby
39
+
40
+
41
+class FlaskRoute(object):
42
+
43
+    def __init__(self, app, rule, method, callback, name, **options):
44
+        self.app = app
45
+        self.rule = rule
46
+        self.method = method
47
+        self.callback = callback
48
+        self.name = name
49
+
50
+
51
+class Flaskapp(Flask):
52
+    @property
53
+    def routes(self):
54
+        result = []
55
+        for r in self.url_map.iter_rules():
56
+            rule = r.rule
57
+            callback = self.view_functions[r.endpoint]
58
+            method = [x for x in r.methods if x not in [
59
+                'OPTIONS', 'HEAD']][0]  # TODO : other solution ?
60
+            name = r.endpoint
61
+            app = self
62
+            f = FlaskRoute(self, rule, method, callback, name)
63
+            result.append(f)
64
+        return result
65
+
66
+
67
+app = Flaskapp(__name__)
68
+
69
+
70
+class Controllers(object):
71
+    @hapic.with_api_doc()
72
+    # @hapic.ext.bottle.bottle_context()
73
+    @hapic.handle_exception(ZeroDivisionError, http_code=HTTPStatus.BAD_REQUEST)
74
+    @hapic.input_path(HelloPathSchema())
75
+    @hapic.input_query(HelloQuerySchema())
76
+    @hapic.output_body(HelloResponseSchema())
77
+    def hello(self, name: str, hapic_data: HapicData):
78
+        """
79
+        my endpoint hello
80
+        ---
81
+        get:
82
+            description: my description
83
+            parameters:
84
+                - in: "path"
85
+                  description: "hello"
86
+                  name: "name"
87
+                  type: "string"
88
+            responses:
89
+                200:
90
+                    description: A pet to be returned
91
+                    schema: HelloResponseSchema
92
+        """
93
+        if name == 'zero':
94
+            raise ZeroDivisionError('Don\'t call him zero !')
95
+
96
+        return {
97
+            'sentence': 'Hello !',
98
+            'name': name,
99
+        }
100
+
101
+    @hapic.with_api_doc()
102
+    # @hapic.ext.bottle.bottle_context()
103
+    # @hapic.error_schema(ErrorResponseSchema())
104
+    @hapic.input_path(HelloPathSchema())
105
+    @hapic.input_body(HelloJsonSchema())
106
+    @hapic.output_body(HelloResponseSchema())
107
+    @bob
108
+    def hello2(self, name: str, hapic_data: HapicData):
109
+        return {
110
+            'sentence': 'Hello !',
111
+            'name': name,
112
+            'color': hapic_data.body.get('color'),
113
+        }
114
+
115
+    kwargs = {'validated_data': {'name': 'bob'}, 'name': 'bob'}
116
+
117
+    @hapic.with_api_doc()
118
+    # @hapic.ext.bottle.bottle_context()
119
+    # @hapic.error_schema(ErrorResponseSchema())
120
+    @hapic.input_path(HelloPathSchema())
121
+    @hapic.output_body(HelloResponseSchema())
122
+    def hello3(self, name: str):
123
+        return {
124
+            'sentence': 'Hello !',
125
+            'name': name,
126
+        }
127
+
128
+    def bind(self, app):
129
+        pass
130
+        app.add_url_rule('/hello/<name>', "hello", self.hello)
131
+        app.add_url_rule('/hello/<name>', "hello2",
132
+                         self.hello2, methods=['POST', ])
133
+        app.add_url_rule('/hello3/<name>', "hello3", self.hello3)
134
+
135
+#app = bottle.Bottle()
136
+
137
+
138
+controllers = Controllers()
139
+controllers.bind(app)
140
+
141
+
142
+# time.sleep(1)
143
+# s = hapic.generate_doc(app)
144
+# ss = json.loads(json.dumps(s))
145
+# for path in ss['paths']:
146
+#     for method in ss['paths'][path]:
147
+#         for response_code in ss['paths'][path][method]['responses']:
148
+#             ss['paths'][path][method]['responses'][int(response_code)] = ss['paths'][path][method]['responses'][response_code]
149
+#             del ss['paths'][path][method]['responses'][int(response_code)]
150
+# print(yaml.dump(ss, default_flow_style=False))
151
+# time.sleep(1)
152
+
153
+hapic.set_context(hapic.ext.flask.FlaskContext())
154
+print(json.dumps(hapic.generate_doc(app)))
155
+#import pdb; pdb.set_trace()
156
+app.run(host='localhost', port=8080, debug=True)

+ 1 - 1
hapic/ext/__init__.py View File

@@ -1,2 +1,2 @@
1 1
 # -*- coding: utf-8 -*-
2
-from hapic.ext import bottle
2
+from hapic.ext import bottle,flask

+ 2 - 0
hapic/ext/flask/__init__.py View File

@@ -0,0 +1,2 @@
1
+# -*- coding: utf-8 -*-
2
+from hapic.ext.flask.context import FlaskContext

+ 53 - 0
hapic/ext/flask/context.py View File

@@ -0,0 +1,53 @@
1
+# -*- coding: utf-8 -*-
2
+import json
3
+import typing
4
+from http import HTTPStatus
5
+
6
+from flask import request, Response
7
+
8
+
9
+from hapic.context import ContextInterface
10
+from hapic.exception import OutputValidationException
11
+from hapic.processor import RequestParameters, ProcessValidationError
12
+
13
+
14
+class FlaskContext(ContextInterface):
15
+    def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
16
+        return RequestParameters(
17
+            path_parameters=request.view_args,
18
+            query_parameters=request.args,  # TODO: Check
19
+            body_parameters=request.get_json(),  # TODO: Check
20
+            form_parameters=request.form,
21
+            header_parameters=request.headers,
22
+        )
23
+
24
+    def get_response(
25
+        self,
26
+        response: dict,
27
+        http_code: int,
28
+    ) -> Response:
29
+        return Response(
30
+            response=json.dumps(response),
31
+            mimetype='application/json',
32
+            status=http_code,
33
+        )
34
+
35
+    def get_validation_error_response(
36
+        self,
37
+        error: ProcessValidationError,
38
+        http_code: HTTPStatus=HTTPStatus.BAD_REQUEST,
39
+    ) -> typing.Any:
40
+        # TODO BS 20171010: Manage error schemas, see #4
41
+        from hapic.hapic import _default_global_error_schema
42
+        unmarshall = _default_global_error_schema.dump(error)
43
+        if unmarshall.errors:
44
+            raise OutputValidationException(
45
+                'Validation error during dump of error response: {}'.format(
46
+                    str(unmarshall.errors)
47
+                )
48
+            )
49
+        return Response(
50
+            response=json.dumps(unmarshall.data),
51
+            mimetype='application/json',
52
+            status=int(http_code),
53
+        )