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,3 +31,7 @@ class HelloJsonSchema(marshmallow.Schema):
31 31
         required=True,
32 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,7 +9,7 @@ from beaker.middleware import SessionMiddleware
9 9
 
10 10
 import hapic
11 11
 from example import HelloResponseSchema, HelloPathSchema, HelloJsonSchema, \
12
-    ErrorResponseSchema, HelloQuerySchema
12
+    ErrorResponseSchema, HelloQuerySchema, HelloFileSchema
13 13
 from hapic.data import HapicData
14 14
 
15 15
 # hapic.global_exception_handler(UnAuthExc, StandardErrorSchema)
@@ -96,10 +96,17 @@ class Controllers(object):
96 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 105
     def bind(self, app):
100 106
         app.route('/hello/<name>', callback=self.hello)
101 107
         app.route('/hello/<name>', callback=self.hello2, method='POST')
102 108
         app.route('/hello3/<name>', callback=self.hello3)
109
+        app.route('/hellofile', callback=self.hellofile)
103 110
 
104 111
 app = bottle.Bottle()
105 112
 

+ 2 - 0
hapic/__init__.py View File

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

+ 2 - 2
hapic/decorator.py View File

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

+ 3 - 3
hapic/doc.py View File

@@ -75,8 +75,8 @@ def bottle_generate_operations(
75 75
             }
76 76
 
77 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 81
         method_operations.setdefault('responses', {})\
82 82
             [int(description.output_file.wrapper.default_http_code)] = {
@@ -119,7 +119,7 @@ def bottle_generate_operations(
119 119
             })
120 120
 
121 121
     if description.input_files:
122
-        method_operations.setdefault('consume', []).append('multipart/form-data')
122
+        method_operations.setdefault('consumes', []).append('multipart/form-data')
123 123
         for field_name, field in description.input_files.wrapper.processor.schema.fields.items():
124 124
             method_operations.setdefault('parameters', []).append({
125 125
                 'in': 'formData',

+ 4 - 3
hapic/hapic.py View File

@@ -153,14 +153,15 @@ class Hapic(object):
153 153
             return decoration.get_wrapper(func)
154 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 158
     def output_file(
158 159
         self,
159
-        output_type: str,
160
+        output_types: typing.List[str],
160 161
         default_http_code: HTTPStatus = HTTPStatus.OK,
161 162
     ) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Any]:
162 163
         decoration = OutputFileControllerWrapper(
163
-            output_type=output_type,
164
+            output_types=output_types,
164 165
             default_http_code=default_http_code,
165 166
         )
166 167
 

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

@@ -27,8 +27,8 @@ class TestDocGeneration(Base):
27 27
 
28 28
         assert doc
29 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 32
         assert 'parameters' in doc['paths']['/upload']['post']
33 33
         assert {
34 34
                    'name': 'file_abc',
@@ -57,8 +57,8 @@ class TestDocGeneration(Base):
57 57
 
58 58
         assert doc
59 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 62
         assert 'parameters' in doc['paths']['/upload']['post']
63 63
         assert {
64 64
                    'name': 'file_abc',
@@ -79,7 +79,7 @@ class TestDocGeneration(Base):
79 79
         app = bottle.Bottle()
80 80
 
81 81
         @hapic.with_api_doc()
82
-        @hapic.output_file('image/jpeg')
82
+        @hapic.output_file(['image/jpeg'])
83 83
         def my_controller():
84 84
             return b'101010100101'
85 85
 
@@ -88,8 +88,8 @@ class TestDocGeneration(Base):
88 88
 
89 89
         assert doc
90 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 93
         assert 200 in doc['paths']['/avatar']['get']['responses']
94 94
 
95 95
     def test_func__input_files_doc__ok__one_file_and_text(self):
@@ -115,8 +115,8 @@ class TestDocGeneration(Base):
115 115
 
116 116
         assert doc
117 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 120
         assert 'parameters' in doc['paths']['/upload']['post']
121 121
         assert {
122 122
                    'name': 'file_abc',