Browse Source

Fix input/output for files

Bastien Sevajol 6 years ago
parent
commit
4eac960776
7 changed files with 32 additions and 18 deletions
  1. 4 0
      example.py
  2. 8 1
      example_a.py
  3. 2 0
      hapic/__init__.py
  4. 2 2
      hapic/decorator.py
  5. 3 3
      hapic/doc.py
  6. 4 3
      hapic/hapic.py
  7. 9 9
      tests/func/test_doc.py

+ 4 - 0
example.py View File

31
         required=True,
31
         required=True,
32
         validate=marshmallow.validate.Length(min=3),
32
         validate=marshmallow.validate.Length(min=3),
33
     )
33
     )
34
+
35
+
36
+class HelloFileSchema(marshmallow.Schema):
37
+    myfile = marshmallow.fields.Raw(required=True)

+ 8 - 1
example_a.py View File

9
 
9
 
10
 import hapic
10
 import hapic
11
 from example import HelloResponseSchema, HelloPathSchema, HelloJsonSchema, \
11
 from example import HelloResponseSchema, HelloPathSchema, HelloJsonSchema, \
12
-    ErrorResponseSchema, HelloQuerySchema
12
+    ErrorResponseSchema, HelloQuerySchema, HelloFileSchema
13
 from hapic.data import HapicData
13
 from hapic.data import HapicData
14
 
14
 
15
 # hapic.global_exception_handler(UnAuthExc, StandardErrorSchema)
15
 # hapic.global_exception_handler(UnAuthExc, StandardErrorSchema)
96
             'name': name,
96
             'name': name,
97
         }
97
         }
98
 
98
 
99
+    @hapic.with_api_doc()
100
+    @hapic.input_files(HelloFileSchema())
101
+    @hapic.output_file(['image/jpeg'])
102
+    def hellofile(self, hapic_data: HapicData):
103
+        return hapic_data.files['myfile']
104
+
99
     def bind(self, app):
105
     def bind(self, app):
100
         app.route('/hello/<name>', callback=self.hello)
106
         app.route('/hello/<name>', callback=self.hello)
101
         app.route('/hello/<name>', callback=self.hello2, method='POST')
107
         app.route('/hello/<name>', callback=self.hello2, method='POST')
102
         app.route('/hello3/<name>', callback=self.hello3)
108
         app.route('/hello3/<name>', callback=self.hello3)
109
+        app.route('/hellofile', callback=self.hellofile)
103
 
110
 
104
 app = bottle.Bottle()
111
 app = bottle.Bottle()
105
 
112
 

+ 2 - 0
hapic/__init__.py View File

11
 input_path = _hapic_default.input_path
11
 input_path = _hapic_default.input_path
12
 input_query = _hapic_default.input_query
12
 input_query = _hapic_default.input_query
13
 input_forms = _hapic_default.input_forms
13
 input_forms = _hapic_default.input_forms
14
+input_files = _hapic_default.input_files
14
 output_headers = _hapic_default.output_headers
15
 output_headers = _hapic_default.output_headers
15
 output_body = _hapic_default.output_body
16
 output_body = _hapic_default.output_body
17
+output_file = _hapic_default.output_file
16
 generate_doc = _hapic_default.generate_doc
18
 generate_doc = _hapic_default.generate_doc
17
 set_context = _hapic_default.set_context
19
 set_context = _hapic_default.set_context
18
 handle_exception = _hapic_default.handle_exception
20
 handle_exception = _hapic_default.handle_exception

+ 2 - 2
hapic/decorator.py View File

256
 class OutputFileControllerWrapper(ControllerWrapper):
256
 class OutputFileControllerWrapper(ControllerWrapper):
257
     def __init__(
257
     def __init__(
258
         self,
258
         self,
259
-        output_type: str,
259
+        output_types: typing.List[str],
260
         default_http_code: HTTPStatus=HTTPStatus.OK,
260
         default_http_code: HTTPStatus=HTTPStatus.OK,
261
     ) -> None:
261
     ) -> None:
262
-        self.output_type = output_type
262
+        self.output_types = output_types
263
         self.default_http_code = default_http_code
263
         self.default_http_code = default_http_code
264
 
264
 
265
 
265
 

+ 3 - 3
hapic/doc.py View File

75
             }
75
             }
76
 
76
 
77
     if description.output_file:
77
     if description.output_file:
78
-        method_operations.setdefault('produce', []).append(
79
-            description.output_file.wrapper.output_type
78
+        method_operations.setdefault('produces', []).extend(
79
+            description.output_file.wrapper.output_types
80
         )
80
         )
81
         method_operations.setdefault('responses', {})\
81
         method_operations.setdefault('responses', {})\
82
             [int(description.output_file.wrapper.default_http_code)] = {
82
             [int(description.output_file.wrapper.default_http_code)] = {
119
             })
119
             })
120
 
120
 
121
     if description.input_files:
121
     if description.input_files:
122
-        method_operations.setdefault('consume', []).append('multipart/form-data')
122
+        method_operations.setdefault('consumes', []).append('multipart/form-data')
123
         for field_name, field in description.input_files.wrapper.processor.schema.fields.items():
123
         for field_name, field in description.input_files.wrapper.processor.schema.fields.items():
124
             method_operations.setdefault('parameters', []).append({
124
             method_operations.setdefault('parameters', []).append({
125
                 'in': 'formData',
125
                 'in': 'formData',

+ 4 - 3
hapic/hapic.py View File

153
             return decoration.get_wrapper(func)
153
             return decoration.get_wrapper(func)
154
         return decorator
154
         return decorator
155
 
155
 
156
-    # TODO BS 20171102: Think about possibilities to validate output ? (with mime type, or validator)
156
+    # TODO BS 20171102: Think about possibilities to validate output ?
157
+    # (with mime type, or validator)
157
     def output_file(
158
     def output_file(
158
         self,
159
         self,
159
-        output_type: str,
160
+        output_types: typing.List[str],
160
         default_http_code: HTTPStatus = HTTPStatus.OK,
161
         default_http_code: HTTPStatus = HTTPStatus.OK,
161
     ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
162
     ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
162
         decoration = OutputFileControllerWrapper(
163
         decoration = OutputFileControllerWrapper(
163
-            output_type=output_type,
164
+            output_types=output_types,
164
             default_http_code=default_http_code,
165
             default_http_code=default_http_code,
165
         )
166
         )
166
 
167
 

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

27
 
27
 
28
         assert doc
28
         assert doc
29
         assert '/upload' in doc['paths']
29
         assert '/upload' in doc['paths']
30
-        assert 'consume' in doc['paths']['/upload']['post']
31
-        assert 'multipart/form-data' in doc['paths']['/upload']['post']['consume']
30
+        assert 'consumes' in doc['paths']['/upload']['post']
31
+        assert 'multipart/form-data' in doc['paths']['/upload']['post']['consumes']  # nopep8
32
         assert 'parameters' in doc['paths']['/upload']['post']
32
         assert 'parameters' in doc['paths']['/upload']['post']
33
         assert {
33
         assert {
34
                    'name': 'file_abc',
34
                    'name': 'file_abc',
57
 
57
 
58
         assert doc
58
         assert doc
59
         assert '/upload' in doc['paths']
59
         assert '/upload' in doc['paths']
60
-        assert 'consume' in doc['paths']['/upload']['post']
61
-        assert 'multipart/form-data' in doc['paths']['/upload']['post']['consume']
60
+        assert 'consumes' in doc['paths']['/upload']['post']
61
+        assert 'multipart/form-data' in doc['paths']['/upload']['post']['consumes']  # nopep8
62
         assert 'parameters' in doc['paths']['/upload']['post']
62
         assert 'parameters' in doc['paths']['/upload']['post']
63
         assert {
63
         assert {
64
                    'name': 'file_abc',
64
                    'name': 'file_abc',
79
         app = bottle.Bottle()
79
         app = bottle.Bottle()
80
 
80
 
81
         @hapic.with_api_doc()
81
         @hapic.with_api_doc()
82
-        @hapic.output_file('image/jpeg')
82
+        @hapic.output_file(['image/jpeg'])
83
         def my_controller():
83
         def my_controller():
84
             return b'101010100101'
84
             return b'101010100101'
85
 
85
 
88
 
88
 
89
         assert doc
89
         assert doc
90
         assert '/avatar' in doc['paths']
90
         assert '/avatar' in doc['paths']
91
-        assert 'produce' in doc['paths']['/avatar']['get']
92
-        assert 'image/jpeg' in doc['paths']['/avatar']['get']['produce']
91
+        assert 'produces' in doc['paths']['/avatar']['get']
92
+        assert 'image/jpeg' in doc['paths']['/avatar']['get']['produces']
93
         assert 200 in doc['paths']['/avatar']['get']['responses']
93
         assert 200 in doc['paths']['/avatar']['get']['responses']
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):
115
 
115
 
116
         assert doc
116
         assert doc
117
         assert '/upload' in doc['paths']
117
         assert '/upload' in doc['paths']
118
-        assert 'consume' in doc['paths']['/upload']['post']
119
-        assert 'multipart/form-data' in doc['paths']['/upload']['post']['consume']
118
+        assert 'consumes' in doc['paths']['/upload']['post']
119
+        assert 'multipart/form-data' in doc['paths']['/upload']['post']['consumes']  # nopep8
120
         assert 'parameters' in doc['paths']['/upload']['post']
120
         assert 'parameters' in doc['paths']['/upload']['post']
121
         assert {
121
         assert {
122
                    'name': 'file_abc',
122
                    'name': 'file_abc',