Browse Source

Merge pull request #40 from inkhey/feature/autoref-compat

Damien Accorsi 6 years ago
parent
commit
293331ca33
No account linked to committer's email

+ 19 - 0
example/fake_api/bottle_api.py View File

@@ -51,6 +51,24 @@ class BottleController(object):
51 51
         }
52 52
 
53 53
     @hapic.with_api_doc()
54
+    @hapic.output_body(UserSchema(
55
+        many=True,
56
+        only=('id', 'username', 'display_name', 'company')
57
+    ))
58
+    def get_users2(self):
59
+        """
60
+        Obtain users list.
61
+        """
62
+        return [
63
+            {
64
+                'id': 4,
65
+                'username': 'some_user',
66
+                'display_name': 'Damien Accorsi',
67
+                'company': 'Algoo',
68
+            }
69
+        ]
70
+
71
+    @hapic.with_api_doc()
54 72
     @hapic.input_path(UserPathSchema())
55 73
     @hapic.output_body(UserSchema())
56 74
     def get_user(self, id, hapic_data: HapicData):
@@ -99,6 +117,7 @@ class BottleController(object):
99 117
     def bind(self, app:bottle.Bottle):
100 118
         app.route('/about', callback=self.about)
101 119
         app.route('/users', callback=self.get_users)
120
+        app.route('/users2', callback=self.get_users2)
102 121
         app.route('/users/<id>', callback=self.get_user)
103 122
         app.route('/users/', callback=self.add_user,  method='POST')
104 123
         app.route('/users/<id>', callback=self.del_user, method='DELETE')

+ 21 - 0
example/fake_api/flask_api.py View File

@@ -49,6 +49,24 @@ class FlaskController(object):
49 49
         }
50 50
 
51 51
     @hapic.with_api_doc()
52
+    @hapic.output_body(UserSchema(
53
+        many=True,
54
+        only=('id', 'username', 'display_name', 'company')
55
+    ))
56
+    def get_users2(self):
57
+        """
58
+        Obtain users list.
59
+        """
60
+        return [
61
+            {
62
+                'id': 4,
63
+                'username': 'some_user',
64
+                'display_name': 'Damien Accorsi',
65
+                'company': 'Algoo',
66
+            }
67
+        ]
68
+
69
+    @hapic.with_api_doc()
52 70
     @hapic.input_path(UserPathSchema())
53 71
     @hapic.output_body(UserSchema())
54 72
     def get_user(self, id, hapic_data: HapicData):
@@ -104,6 +122,8 @@ class FlaskController(object):
104 122
                          view_func=self.about)
105 123
         app.add_url_rule('/users',
106 124
                          view_func=self.get_users)
125
+        app.add_url_rule('/users2',
126
+                         view_func=self.get_users2)
107 127
         app.add_url_rule('/users/<id>',
108 128
                          view_func=self.get_user)
109 129
         app.add_url_rule('/users/',
@@ -113,6 +133,7 @@ class FlaskController(object):
113 133
                          view_func=self.del_user,
114 134
                          methods=['DELETE'])
115 135
 
136
+
116 137
 if __name__ == "__main__":
117 138
     app = flask.Flask(__name__)
118 139
     controllers = FlaskController()

+ 21 - 0
example/fake_api/pyramid_api.py View File

@@ -49,6 +49,24 @@ class PyramidController(object):
49 49
         }
50 50
 
51 51
     @hapic.with_api_doc()
52
+    @hapic.output_body(UserSchema(
53
+        many=True,
54
+        only=('id', 'username', 'display_name', 'company')
55
+    ))
56
+    def get_users2(self, context, request):
57
+        """
58
+        Obtain users list.
59
+        """
60
+        return [
61
+            {
62
+                'id': 4,
63
+                'username': 'some_user',
64
+                'display_name': 'Damien Accorsi',
65
+                'company': 'Algoo',
66
+            }
67
+        ]
68
+
69
+    @hapic.with_api_doc()
52 70
     @hapic.input_path(UserPathSchema())
53 71
     @hapic.output_body(UserSchema())
54 72
     def get_user(self, context, request, hapic_data: HapicData):
@@ -101,6 +119,9 @@ class PyramidController(object):
101 119
         configurator.add_route('get_users', '/users', request_method='GET')  # nopep8
102 120
         configurator.add_view(self.get_users, route_name='get_users', renderer='json')  # nopep8
103 121
 
122
+        configurator.add_route('get_users2', '/users2', request_method='GET')  # nopep8
123
+        configurator.add_view(self.get_users2, route_name='get_users2', renderer='json')  # nopep8
124
+
104 125
         configurator.add_route('get_user', '/users/{id}', request_method='GET')  # nopep8
105 126
         configurator.add_view(self.get_user, route_name='get_user', renderer='json')  # nopep8
106 127
 

+ 70 - 20
hapic/doc.py View File

@@ -2,6 +2,7 @@
2 2
 import json
3 3
 
4 4
 import typing
5
+import marshmallow
5 6
 import yaml
6 7
 
7 8
 from apispec import APISpec
@@ -14,32 +15,48 @@ from hapic.decorator import DecoratedController
14 15
 from hapic.description import ControllerDescription
15 16
 
16 17
 
18
+def generate_schema_ref(spec:APISpec, schema: marshmallow.Schema) -> str:
19
+    schema_class = spec.schema_class_resolver(
20
+        spec,
21
+        schema
22
+    )
23
+    ref = {
24
+        '$ref': '#/definitions/{}'.format(
25
+            spec.schema_name_resolver(schema_class)
26
+        )
27
+    }
28
+    if schema.many:
29
+        ref = {
30
+            'type': 'array',
31
+            'items': ref
32
+        }
33
+    return ref
34
+
35
+
17 36
 def bottle_generate_operations(
18 37
     spec,
19 38
     route: RouteRepresentation,
20 39
     description: ControllerDescription,
21 40
 ):
22 41
     method_operations = dict()
23
-
24
-    # schema based
25 42
     if description.input_body:
26
-        schema_class = type(description.input_body.wrapper.processor.schema)
27 43
         method_operations.setdefault('parameters', []).append({
28 44
             'in': 'body',
29 45
             'name': 'body',
30
-            'schema': {
31
-                '$ref': '#/definitions/{}'.format(schema_class.__name__)
32
-            }
46
+            'schema': generate_schema_ref(
47
+                spec,
48
+                description.input_body.wrapper.processor.schema,
49
+            )
33 50
         })
34 51
 
35 52
     if description.output_body:
36
-        schema_class = type(description.output_body.wrapper.processor.schema)
37 53
         method_operations.setdefault('responses', {})\
38 54
             [int(description.output_body.wrapper.default_http_code)] = {
39 55
                 'description': str(int(description.output_body.wrapper.default_http_code)),  # nopep8
40
-                'schema': {
41
-                    '$ref': '#/definitions/{}'.format(schema_class.__name__)
42
-                }
56
+                'schema': generate_schema_ref(
57
+                    spec,
58
+                    description.output_body.wrapper.processor.schema,
59
+                )
43 60
             }
44 61
 
45 62
     if description.output_file:
@@ -58,13 +75,18 @@ def bottle_generate_operations(
58 75
                 [int(error.wrapper.http_code)] = {
59 76
                     'description': str(int(error.wrapper.http_code)),
60 77
                     'schema': {
61
-                        '$ref': '#/definitions/{}'.format(schema_class.__name__)  # nopep8
78
+                        '$ref': '#/definitions/{}'.format(
79
+                            spec.schema_name_resolver(schema_class)
80
+                        )
62 81
                     }
63 82
                 }
64 83
 
65 84
     # jsonschema based
66 85
     if description.input_path:
67
-        schema_class = type(description.input_path.wrapper.processor.schema)
86
+        schema_class = spec.schema_class_resolver(
87
+            spec,
88
+            description.input_path.wrapper.processor.schema
89
+        )
68 90
         # TODO: look schema2parameters ?
69 91
         jsonschema = schema2jsonschema(schema_class, spec=spec)
70 92
         for name, schema in jsonschema.get('properties', {}).items():
@@ -76,7 +98,10 @@ def bottle_generate_operations(
76 98
             })
77 99
 
78 100
     if description.input_query:
79
-        schema_class = type(description.input_query.wrapper.processor.schema)
101
+        schema_class = spec.schema_class_resolver(
102
+            spec,
103
+            description.input_query.wrapper.processor.schema
104
+        )
80 105
         jsonschema = schema2jsonschema(schema_class, spec=spec)
81 106
         for name, schema in jsonschema.get('properties', {}).items():
82 107
             method_operations.setdefault('parameters', []).append({
@@ -131,7 +156,8 @@ class DocGenerator(object):
131 156
             plugins=(
132 157
                 'apispec.ext.marshmallow',
133 158
             ),
134
-            schema_name_resolver=generate_schema_name,
159
+            auto_referencing=True,
160
+            schema_name_resolver=generate_schema_name
135 161
         )
136 162
 
137 163
         schemas = []
@@ -140,17 +166,20 @@ class DocGenerator(object):
140 166
             description = controller.description
141 167
 
142 168
             if description.input_body:
143
-                schemas.append(type(
169
+                schemas.append(spec.schema_class_resolver(
170
+                    spec,
144 171
                     description.input_body.wrapper.processor.schema
145 172
                 ))
146 173
 
147 174
             if description.input_forms:
148
-                schemas.append(type(
175
+                schemas.append(spec.schema_class_resolver(
176
+                    spec,
149 177
                     description.input_forms.wrapper.processor.schema
150 178
                 ))
151 179
 
152 180
             if description.output_body:
153
-                schemas.append(type(
181
+                schemas.append(spec.schema_class_resolver(
182
+                    spec,
154 183
                     description.output_body.wrapper.processor.schema
155 184
                 ))
156 185
 
@@ -159,7 +188,10 @@ class DocGenerator(object):
159 188
                     schemas.append(type(error.wrapper.error_builder))
160 189
 
161 190
         for schema in set(schemas):
162
-            spec.definition(schema.__name__, schema=schema)
191
+            spec.definition(
192
+                spec.schema_name_resolver(schema),
193
+                schema=schema
194
+            )
163 195
 
164 196
         # add views
165 197
         # with app.test_request_context():
@@ -230,5 +262,23 @@ class DocGenerator(object):
230 262
 
231 263
 
232 264
 # TODO BS 20171109: Must take care of already existing definition names
233
-def generate_schema_name(schema):
234
-    return schema.__name__
265
+def generate_schema_name(schema: marshmallow.Schema):
266
+    """
267
+    Return best candidate name for one schema cls or instance.
268
+    :param schema: instance or cls schema
269
+    :return: best schema name
270
+    """
271
+    if not isinstance(schema, type):
272
+        schema = type(schema)
273
+
274
+    if getattr(schema, '_schema_name', None):
275
+        if schema.opts.exclude:
276
+            schema_name = "{}_without".format(schema.__name__)
277
+            for elem in sorted(schema.opts.exclude):
278
+                schema_name="{}_{}".format(schema_name, elem)
279
+        else:
280
+            schema_name = schema._schema_name
281
+    else:
282
+        schema_name = schema.__name__
283
+
284
+    return schema_name

+ 4 - 2
setup.py View File

@@ -9,7 +9,7 @@ here = path.abspath(path.dirname(__file__))
9 9
 
10 10
 install_requires = [
11 11
     'marshmallow >2.0.0,<3.0.0a1',
12
-    'apispec',
12
+    'apispec==0.35.0-algoo',
13 13
     'multidict'
14 14
 ]
15 15
 tests_require = [
@@ -94,8 +94,10 @@ setup(
94 94
     # "scripts" keyword. Entry points provide cross-platform support and allow
95 95
     # pip to create the appropriate form of executable for the target platform.
96 96
     entry_points={},
97
-
98 97
     setup_requires=[],
98
+    dependency_links=[
99
+        'git+https://github.com/algoo/apispec.git@hapic_apispec#egg=apispec-0.35.0-algoo'
100
+    ],
99 101
     tests_require=tests_require,
100 102
     include_package_data=True,
101 103
 )

+ 36 - 7
tests/func/fake_api/common.py View File

@@ -1,4 +1,6 @@
1 1
 from collections import OrderedDict
2
+
3
+
2 4
 SWAGGER_DOC_API = {
3 5
  'definitions': {
4 6
      'AboutResponseSchema': {'properties': {
@@ -13,7 +15,7 @@ SWAGGER_DOC_API = {
13 15
                                         'minimum': 0,
14 16
                                         'type': 'integer'},
15 17
                             'items': {
16
-                                'items': {'$ref': '#/definitions/UserSchema'},
18
+                                'items': {'$ref': '#/definitions/UserSchema_without_email_address_first_name_last_name'},
17 19
                                 'type': 'array'},
18 20
                             'pagination': {'$ref': '#/definitions/PaginationSchema'}},
19 21
                          'required': ['item_nb'],
@@ -41,10 +43,27 @@ SWAGGER_DOC_API = {
41 43
                                  'id',
42 44
                                  'last_name',
43 45
                                  'username'],
44
-                    'type': 'object'}},
45
- 'info': {'description': 'just an example of hapic API',
46
-          'title': 'Fake API',
47
-          'version': '1.0.0'},
46
+                    'type': 'object'},
47
+    'UserSchema_without_id': {
48
+        'properties': {
49
+            'username': {'type': 'string'},
50
+            'display_name': {'type': 'string'},
51
+            'company': {'type': 'string'},
52
+            'last_name': {'type': 'string'},
53
+            'email_address': {'format': 'email', 'type': 'string'},
54
+            'first_name': {'type': 'string'}},
55
+        'required': ['company', 'display_name', 'email_address', 'first_name',
56
+                     'last_name', 'username'], 'type': 'object'},
57
+    'UserSchema_without_email_address_first_name_last_name': {
58
+        'properties': {
59
+            'username': {'type': 'string'},
60
+            'id': {'format': 'int32', 'type': 'integer'},
61
+            'company': {'type': 'string'},
62
+            'display_name': {'type': 'string'}},
63
+        'required': ['company', 'display_name', 'id', 'username'], 'type': 'object'
64
+    },
65
+    },
66
+ 'info': {'description': 'just an example of hapic API', 'title': 'Fake API', 'version': '1.0.0'},
48 67
  'parameters': {},
49 68
  'paths': OrderedDict(
50 69
      [('/about', {
@@ -83,10 +102,20 @@ SWAGGER_DOC_API = {
83 102
              'description': 'Add new user',
84 103
              'parameters': [{'in': 'body',
85 104
                              'name': 'body',
86
-                             'schema': {'$ref': '#/definitions/UserSchema'}}],
105
+                             'schema': {'$ref': '#/definitions/UserSchema_without_id'}}],
87 106
              'responses': {200: {
88 107
                  'description': '200',
89
-                 'schema': {'$ref': '#/definitions/UserSchema'}}}}})]),
108
+                 'schema': {'$ref': '#/definitions/UserSchema'}}}}}),
109
+      ( '/users2', {
110
+          'get': {
111
+              'description': 'Obtain users list.',
112
+              'responses': {200: {
113
+                  'description': '200',
114
+                  'schema': {'type': 'array',
115
+                             'items': {'$ref': '#/definitions/UserSchema_without_email_address_first_name_last_name'}
116
+                             }
117
+          }}}}
118
+        )]),
90 119
  'swagger': '2.0',
91 120
  'tags': []
92 121
 }

+ 0 - 95
tests/func/fake_api/test_bottle.py View File

@@ -1,95 +0,0 @@
1
-from webtest import TestApp
2
-from bottle import Bottle
3
-
4
-from hapic.ext.bottle import BottleContext
5
-from example.fake_api.bottle_api import BottleController
6
-from tests.func.fake_api.common import SWAGGER_DOC_API
7
-from example.fake_api.bottle_api import hapic
8
-
9
-
10
-def test_func_bottle_fake_api():
11
-    bottle_app = Bottle()
12
-    controllers = BottleController()
13
-    controllers.bind(bottle_app)
14
-
15
-    hapic.set_context(BottleContext(bottle_app))
16
-    app = TestApp(bottle_app)
17
-    doc = hapic.generate_doc(
18
-        title='Fake API',
19
-        description='just an example of hapic API'
20
-    )
21
-
22
-    assert doc == SWAGGER_DOC_API
23
-    resp = app.get('/about')
24
-    assert resp.status_int == 200
25
-    assert resp.json == {'datetime': '2017-12-07T10:55:08.488996+00:00',
26
-                         'version': '1.2.3'}
27
-
28
-    resp = app.get('/users')
29
-    assert resp.status_int == 200
30
-    assert resp.json == {
31
-        'items':
32
-        [
33
-            {
34
-                'username': 'some_user',
35
-                'display_name': 'Damien Accorsi',
36
-                'company': 'Algoo', 'id': 4
37
-            }
38
-        ],
39
-        'pagination': {
40
-            'first_id': 0,
41
-            'last_id': 5,
42
-            'current_id': 0,
43
-        },
44
-        'item_nb': 1,
45
-    }
46
-
47
-    resp = app.get('/users/1')
48
-    assert resp.status_int == 200
49
-    assert resp.json == {
50
-        'last_name': 'Accorsi',
51
-        'username': 'some_user',
52
-        'first_name': 'Damien',
53
-        'id': 4,
54
-        'display_name': 'Damien Accorsi',
55
-        'email_address': 'some.user@hapic.com',
56
-        'company': 'Algoo'
57
-    }
58
-
59
-    resp = app.post('/users/', status='*')
60
-    assert resp.status_int == 400
61
-    assert resp.json == {
62
-        'code': None,
63
-        'details': {
64
-            'email_address': ['Missing data for required field.'],
65
-            'username': ['Missing data for required field.'],
66
-            'display_name': ['Missing data for required field.'],
67
-            'last_name': ['Missing data for required field.'],
68
-            'first_name': ['Missing data for required field.'],
69
-            'company': ['Missing data for required field.']},
70
-        'message': 'Validation error of input data'}
71
-
72
-    user = {
73
-        'email_address': 'some.user@hapic.com',
74
-        'username': 'some_user',
75
-        'display_name': 'Damien Accorsi',
76
-        'last_name': 'Accorsi',
77
-        'first_name': 'Damien',
78
-        'company': 'Algoo',
79
-    }
80
-
81
-    resp = app.post_json('/users/', user)
82
-    assert resp.status_int == 200
83
-    assert resp.json == {
84
-        'last_name': 'Accorsi',
85
-        'username': 'some_user',
86
-        'first_name': 'Damien',
87
-        'id': 4,
88
-        'display_name': 'Damien Accorsi',
89
-        'email_address': 'some.user@hapic.com',
90
-        'company': 'Algoo',
91
-    }
92
-
93
-    resp = app.delete('/users/1', status='*')
94
-    assert resp.status_int == 204
95
-

+ 192 - 0
tests/func/fake_api/test_fake_api.py View File

@@ -0,0 +1,192 @@
1
+import pytest
2
+from flask import Flask
3
+from pyramid.config import Configurator
4
+from webtest import TestApp
5
+from bottle import Bottle
6
+
7
+from example.fake_api.flask_api import FlaskController
8
+from example.fake_api.pyramid_api import PyramidController
9
+from hapic.ext.bottle import BottleContext
10
+from example.fake_api.bottle_api import BottleController
11
+from hapic.ext.flask import FlaskContext
12
+from hapic.ext.pyramid import PyramidContext
13
+from tests.func.fake_api.common import SWAGGER_DOC_API
14
+
15
+
16
+def get_bottle_context():
17
+    from example.fake_api.bottle_api import hapic as h
18
+    bottle_app = Bottle()
19
+    h.reset_context()
20
+    h.set_context(BottleContext(bottle_app))
21
+    controllers = BottleController()
22
+    controllers.bind(bottle_app)
23
+    return {
24
+        'hapic': h,
25
+        'app': bottle_app,
26
+    }
27
+
28
+
29
+def get_flask_context():
30
+    from example.fake_api.flask_api import hapic as h
31
+    flask_app = Flask(__name__)
32
+    controllers = FlaskController()
33
+    controllers.bind(flask_app)
34
+    h.reset_context()
35
+    h.set_context(FlaskContext(flask_app))
36
+    return {
37
+        'hapic': h,
38
+        'app': flask_app,
39
+    }
40
+
41
+
42
+def get_pyramid_context():
43
+    from example.fake_api.pyramid_api import hapic as h
44
+    configurator = Configurator(autocommit=True)
45
+    controllers = PyramidController()
46
+    controllers.bind(configurator)
47
+    h.reset_context()
48
+    h.set_context(PyramidContext(configurator))
49
+    pyramid_app = configurator.make_wsgi_app()
50
+    return {
51
+        'hapic': h,
52
+        'app': pyramid_app,
53
+    }
54
+
55
+
56
+@pytest.mark.parametrize('context',
57
+                         [
58
+                             get_bottle_context(),
59
+                             get_flask_context(),
60
+                             get_pyramid_context()
61
+                         ])
62
+def test_func__test_fake_api_endpoints_ok__all_framework(context):
63
+    hapic = context['hapic']
64
+    app = context['app']
65
+    app = TestApp(app)
66
+    resp = app.get('/about')
67
+    assert resp.status_int == 200
68
+    assert resp.json == {'datetime': '2017-12-07T10:55:08.488996+00:00',
69
+                         'version': '1.2.3'}
70
+
71
+    resp = app.get('/users')
72
+    assert resp.status_int == 200
73
+    assert resp.json == {
74
+        'items':
75
+        [
76
+            {
77
+                'username': 'some_user',
78
+                'display_name': 'Damien Accorsi',
79
+                'company': 'Algoo', 'id': 4
80
+            }
81
+        ],
82
+        'pagination': {
83
+            'first_id': 0,
84
+            'last_id': 5,
85
+            'current_id': 0,
86
+        },
87
+        'item_nb': 1,
88
+    }
89
+
90
+    resp = app.get('/users2')
91
+    assert resp.status_int == 200
92
+    assert resp.json == [
93
+        {
94
+           'username': 'some_user',
95
+           'id': 4,
96
+           'display_name': 'Damien Accorsi',
97
+           'company': 'Algoo'
98
+        }
99
+    ]
100
+
101
+    resp = app.get('/users/1')
102
+    assert resp.status_int == 200
103
+    assert resp.json == {
104
+        'last_name': 'Accorsi',
105
+        'username': 'some_user',
106
+        'first_name': 'Damien',
107
+        'id': 4,
108
+        'display_name': 'Damien Accorsi',
109
+        'email_address': 'some.user@hapic.com',
110
+        'company': 'Algoo'
111
+    }
112
+
113
+    resp = app.post('/users/', status='*')
114
+    assert resp.status_int == 400
115
+    assert resp.json == {
116
+        'code': None,
117
+        'details': {
118
+            'email_address': ['Missing data for required field.'],
119
+            'username': ['Missing data for required field.'],
120
+            'display_name': ['Missing data for required field.'],
121
+            'last_name': ['Missing data for required field.'],
122
+            'first_name': ['Missing data for required field.'],
123
+            'company': ['Missing data for required field.']},
124
+        'message': 'Validation error of input data'}
125
+
126
+    user = {
127
+        'email_address': 'some.user@hapic.com',
128
+        'username': 'some_user',
129
+        'display_name': 'Damien Accorsi',
130
+        'last_name': 'Accorsi',
131
+        'first_name': 'Damien',
132
+        'company': 'Algoo',
133
+    }
134
+
135
+    resp = app.post_json('/users/', user)
136
+    assert resp.status_int == 200
137
+    assert resp.json == {
138
+        'last_name': 'Accorsi',
139
+        'username': 'some_user',
140
+        'first_name': 'Damien',
141
+        'id': 4,
142
+        'display_name': 'Damien Accorsi',
143
+        'email_address': 'some.user@hapic.com',
144
+        'company': 'Algoo',
145
+    }
146
+
147
+    resp = app.delete('/users/1', status='*')
148
+    assert resp.status_int == 204
149
+
150
+
151
+@pytest.mark.parametrize('context',
152
+                         [
153
+                             get_bottle_context(),
154
+                             get_flask_context(),
155
+                             get_pyramid_context()
156
+                         ])
157
+def test_func__test_fake_api_doc_ok__all_framework(context):
158
+    hapic = context['hapic']
159
+    app = context['app']
160
+    app = TestApp(app)
161
+    doc = hapic.generate_doc(
162
+        title='Fake API',
163
+        description='just an example of hapic API'
164
+    )
165
+
166
+    assert doc['info'] == SWAGGER_DOC_API['info']
167
+    assert doc['tags'] == SWAGGER_DOC_API['tags']
168
+    assert doc['swagger'] == SWAGGER_DOC_API['swagger']
169
+    assert doc['parameters'] == SWAGGER_DOC_API['parameters']
170
+    assert doc['paths']['/about'] == SWAGGER_DOC_API['paths']['/about']
171
+    assert doc['paths']['/users'] == SWAGGER_DOC_API['paths']['/users']
172
+    assert doc['paths']['/users/{id}'] == SWAGGER_DOC_API['paths']['/users/{id}']
173
+    assert doc['paths']['/users/'] == SWAGGER_DOC_API['paths']['/users/']
174
+    assert doc['paths']['/users2'] == SWAGGER_DOC_API['paths']['/users2']
175
+
176
+    assert doc['definitions']['AboutResponseSchema'] == \
177
+           SWAGGER_DOC_API['definitions']['AboutResponseSchema']
178
+    assert doc['definitions']['ListsUserSchema'] == \
179
+           SWAGGER_DOC_API['definitions']['ListsUserSchema']
180
+    assert doc['definitions']['NoContentSchema'] == \
181
+           SWAGGER_DOC_API['definitions']['NoContentSchema']
182
+    assert doc['definitions']['PaginationSchema'] == \
183
+           SWAGGER_DOC_API['definitions']['PaginationSchema']
184
+    assert doc['definitions']['UserSchema'] == \
185
+           SWAGGER_DOC_API['definitions']['UserSchema']
186
+    assert doc['definitions']['UserSchema'] == \
187
+           SWAGGER_DOC_API['definitions']['UserSchema']
188
+
189
+    assert doc['definitions']['UserSchema_without_id'] == \
190
+           SWAGGER_DOC_API['definitions']['UserSchema_without_id']
191
+    assert doc['definitions']['UserSchema_without_email_address_first_name_last_name'] == \
192
+           SWAGGER_DOC_API['definitions']['UserSchema_without_email_address_first_name_last_name']

+ 0 - 95
tests/func/fake_api/test_flask.py View File

@@ -1,95 +0,0 @@
1
-from webtest import TestApp
2
-from hapic.ext.flask import FlaskContext
3
-from flask import Flask
4
-from example.fake_api.flask_api import FlaskController
5
-from example.fake_api.flask_api import hapic
6
-from tests.func.fake_api.common import SWAGGER_DOC_API
7
-
8
-
9
-def test_func_flask_fake_api():
10
-    flask_app = Flask(__name__)
11
-    controllers = FlaskController()
12
-    controllers.bind(flask_app)
13
-    hapic.set_context(FlaskContext(flask_app))
14
-    app = TestApp(flask_app)
15
-    doc =  hapic.generate_doc(
16
-        title='Fake API',
17
-        description='just an example of hapic API'
18
-    )
19
-
20
-    assert doc == SWAGGER_DOC_API
21
-    resp = app.get('/about', status='*')
22
-    assert resp.status_int == 200
23
-    assert resp.json == {'datetime': '2017-12-07T10:55:08.488996+00:00',
24
-                         'version': '1.2.3'}
25
-
26
-    resp = app.get('/users')
27
-    assert resp.status_int == 200
28
-    assert resp.json == {
29
-        'items':
30
-        [
31
-            {
32
-                'username': 'some_user',
33
-                'display_name': 'Damien Accorsi',
34
-                'company': 'Algoo', 'id': 4
35
-            }
36
-        ],
37
-        'pagination': {
38
-            'first_id': 0,
39
-            'last_id': 5,
40
-            'current_id': 0,
41
-        },
42
-        'item_nb': 1,
43
-    }
44
-
45
-    resp = app.get('/users/1')
46
-    assert resp.status_int == 200
47
-    assert resp.json == {
48
-        'last_name': 'Accorsi',
49
-        'username': 'some_user',
50
-        'first_name': 'Damien',
51
-        'id': 4,
52
-        'display_name': 'Damien Accorsi',
53
-        'email_address': 'some.user@hapic.com',
54
-        'company': 'Algoo'
55
-    }
56
-
57
-    resp = app.post('/users/', status='*')
58
-    assert resp.status_int == 400
59
-    assert resp.json == {
60
-        'code': None,
61
-        'details': {
62
-            'email_address': ['Missing data for required field.'],
63
-            'username': ['Missing data for required field.'],
64
-            'display_name': ['Missing data for required field.'],
65
-            'last_name': ['Missing data for required field.'],
66
-            'first_name': ['Missing data for required field.'],
67
-            'company': ['Missing data for required field.']},
68
-        'message': 'Validation error of input data'}
69
-
70
-    user = {
71
-        'email_address': 'some.user@hapic.com',
72
-        'username': 'some_user',
73
-        'display_name': 'Damien Accorsi',
74
-        'last_name': 'Accorsi',
75
-        'first_name': 'Damien',
76
-        'company': 'Algoo',
77
-    }
78
-
79
-    resp = app.post_json('/users/', user)
80
-    assert resp.status_int == 200
81
-    assert resp.json == {
82
-        'last_name': 'Accorsi',
83
-        'username': 'some_user',
84
-        'first_name': 'Damien',
85
-        'id': 4,
86
-        'display_name': 'Damien Accorsi',
87
-        'email_address': 'some.user@hapic.com',
88
-        'company': 'Algoo',
89
-    }
90
-    # INFO - G.M - 2017-12-07 - Warning due to Webtest check
91
-    # Webtest check content_type. Up to know flask_api return json as
92
-    # content_type with 204 NO CONTENT status code which return a warning in
93
-    # WebTest check.
94
-    resp = app.delete('/users/1', status='*')
95
-    assert resp.status_int == 204

+ 0 - 97
tests/func/fake_api/test_pyramid.py View File

@@ -1,97 +0,0 @@
1
-from webtest import TestApp
2
-from pyramid.config import Configurator
3
-import hapic
4
-from hapic.ext.pyramid import PyramidContext
5
-from example.fake_api.pyramid_api import hapic
6
-from example.fake_api.pyramid_api import PyramidController
7
-from tests.func.fake_api.common import SWAGGER_DOC_API
8
-
9
-
10
-def test_func_pyramid_fake_api_doc():
11
-    configurator = Configurator(autocommit=True)
12
-    controllers = PyramidController()
13
-    controllers.bind(configurator)
14
-    hapic.set_context(PyramidContext(configurator))
15
-    app = TestApp(configurator.make_wsgi_app())
16
-    doc = hapic.generate_doc(
17
-        title='Fake API',
18
-        description='just an example of hapic API'
19
-    )
20
-
21
-    assert doc == SWAGGER_DOC_API
22
-    resp = app.get('/about')
23
-    assert resp.status_int == 200
24
-    assert resp.json == {'datetime': '2017-12-07T10:55:08.488996+00:00',
25
-                         'version': '1.2.3'}
26
-
27
-    resp = app.get('/users')
28
-    assert resp.status_int == 200
29
-    assert resp.json == {
30
-        'items':
31
-        [
32
-            {
33
-                'username': 'some_user',
34
-                'display_name': 'Damien Accorsi',
35
-                'company': 'Algoo', 'id': 4
36
-            }
37
-        ],
38
-        'pagination': {
39
-            'first_id': 0,
40
-            'last_id': 5,
41
-            'current_id': 0,
42
-        },
43
-        'item_nb': 1,
44
-    }
45
-
46
-    resp = app.get('/users/1')
47
-    assert resp.status_int == 200
48
-    assert resp.json == {
49
-        'last_name': 'Accorsi',
50
-        'username': 'some_user',
51
-        'first_name': 'Damien',
52
-        'id': 4,
53
-        'display_name': 'Damien Accorsi',
54
-        'email_address': 'some.user@hapic.com',
55
-        'company': 'Algoo'
56
-    }
57
-
58
-    resp = app.post('/users/', status='*')
59
-    assert resp.status_int == 400
60
-    assert resp.json == {
61
-        'code': None,
62
-        'details': {
63
-            'email_address': ['Missing data for required field.'],
64
-            'username': ['Missing data for required field.'],
65
-            'display_name': ['Missing data for required field.'],
66
-            'last_name': ['Missing data for required field.'],
67
-            'first_name': ['Missing data for required field.'],
68
-            'company': ['Missing data for required field.']},
69
-        'message': 'Validation error of input data'}
70
-
71
-    user = {
72
-        'email_address': 'some.user@hapic.com',
73
-        'username': 'some_user',
74
-        'display_name': 'Damien Accorsi',
75
-        'last_name': 'Accorsi',
76
-        'first_name': 'Damien',
77
-        'company': 'Algoo',
78
-    }
79
-
80
-    resp = app.post_json('/users/', user)
81
-    assert resp.status_int == 200
82
-    assert resp.json == {
83
-        'last_name': 'Accorsi',
84
-        'username': 'some_user',
85
-        'first_name': 'Damien',
86
-        'id': 4,
87
-        'display_name': 'Damien Accorsi',
88
-        'email_address': 'some.user@hapic.com',
89
-        'company': 'Algoo',
90
-    }
91
-
92
-    # INFO - G.M - 2017-12-07 - Test for delete desactivated(Webtest check fail)
93
-    # Webtest check content_type. Up to know pyramid_api return json as
94
-    # content_type with 204 NO CONTENT status code which return an error in
95
-    # WebTest check.
96
-    # resp = app.delete('/users/1', status='*')
97
-    # assert resp.status_int == 204

+ 151 - 0
tests/func/test_doc.py View File

@@ -221,3 +221,154 @@ class TestDocGeneration(Base):
221 221
             .get('properties', {})\
222 222
             .get('category', {})\
223 223
             .get('enum')
224
+
225
+    def test_func__schema_in_doc__ok__nominal_case(self):
226
+        hapic = Hapic()
227
+        app = bottle.Bottle()
228
+        hapic.set_context(MyContext(app=app))
229
+
230
+        class MySchema(marshmallow.Schema):
231
+            name = marshmallow.fields.String(required=True)
232
+
233
+        @hapic.with_api_doc()
234
+        @hapic.input_body(MySchema())
235
+        def my_controller():
236
+            return {'name': 'test',}
237
+
238
+        app.route('/paper', method='POST', callback=my_controller)
239
+        doc = hapic.generate_doc()
240
+
241
+        assert doc.get('definitions', {}).get('MySchema', {})
242
+        schema_def = doc.get('definitions', {}).get('MySchema', {})
243
+        assert schema_def.get('properties', {}).get('name', {}).get('type')
244
+
245
+        assert doc.get('paths').get('/paper').get('post').get('parameters')[0]
246
+        schema_ref = doc.get('paths').get('/paper').get('post').get('parameters')[0]
247
+        assert schema_ref.get('in') == 'body'
248
+        assert schema_ref.get('name') == 'body'
249
+        assert schema_ref['schema']['$ref'] == '#/definitions/MySchema'
250
+
251
+    def test_func__schema_in_doc__ok__many_case(self):
252
+        hapic = Hapic()
253
+        app = bottle.Bottle()
254
+        hapic.set_context(MyContext(app=app))
255
+
256
+        class MySchema(marshmallow.Schema):
257
+            name = marshmallow.fields.String(required=True)
258
+
259
+        @hapic.with_api_doc()
260
+        @hapic.input_body(MySchema(many=True))
261
+        def my_controller():
262
+            return {'name': 'test'}
263
+
264
+        app.route('/paper', method='POST', callback=my_controller)
265
+        doc = hapic.generate_doc()
266
+
267
+        assert doc.get('definitions', {}).get('MySchema', {})
268
+        schema_def = doc.get('definitions', {}).get('MySchema', {})
269
+        assert schema_def.get('properties', {}).get('name', {}).get('type')
270
+
271
+        assert doc.get('paths').get('/paper').get('post').get('parameters')[0]
272
+        schema_ref = doc.get('paths').get('/paper').get('post').get('parameters')[0]
273
+        assert schema_ref.get('in') == 'body'
274
+        assert schema_ref.get('name') == 'body'
275
+        assert schema_ref['schema'] == {
276
+            'items': {'$ref': '#/definitions/MySchema'},
277
+            'type': 'array'
278
+        }
279
+
280
+    def test_func__schema_in_doc__ok__exclude_case(self):
281
+        hapic = Hapic()
282
+        app = bottle.Bottle()
283
+        hapic.set_context(MyContext(app=app))
284
+
285
+        class MySchema(marshmallow.Schema):
286
+            name = marshmallow.fields.String(required=True)
287
+            name2 = marshmallow.fields.String(required=True)
288
+
289
+        @hapic.with_api_doc()
290
+        @hapic.input_body(MySchema(exclude=('name2',)))
291
+        def my_controller():
292
+            return {'name': 'test',}
293
+
294
+        app.route('/paper', method='POST', callback=my_controller)
295
+        doc = hapic.generate_doc()
296
+
297
+        definitions = doc.get('definitions', {})
298
+        # TODO - G-M - Find better way to find our new schema
299
+        # Do Better test when we were able to set correctly schema name
300
+        # according to content
301
+        schema_name = None
302
+        for elem in definitions.keys():
303
+            if elem != 'MySchema':
304
+                schema_name = elem
305
+                break
306
+        assert schema_name
307
+        schema_def = definitions[schema_name]
308
+        assert schema_def.get('properties', {}).get('name', {}).get('type') == 'string'
309
+        assert doc.get('paths').get('/paper').get('post').get('parameters')[0]
310
+        schema_ref = doc.get('paths').get('/paper').get('post').get('parameters')[0]
311
+        assert schema_ref.get('in') == 'body'
312
+        assert schema_ref['schema']['$ref'] == '#/definitions/{}'.format(schema_name)
313
+
314
+        @hapic.with_api_doc()
315
+        @hapic.input_body(MySchema(only=('name',)))
316
+        def my_controller():
317
+            return {'name': 'test'}
318
+
319
+        app.route('/paper', method='POST', callback=my_controller)
320
+        doc = hapic.generate_doc()
321
+
322
+        # TODO - G-M - Find better way to find our new schema
323
+        # Do Better test when we were able to set correctly schema name
324
+        # according to content
325
+        definitions = doc.get('definitions', {})
326
+        schema_name = None
327
+        for elem in definitions.keys():
328
+            if elem != 'MySchema':
329
+                schema_name = elem
330
+                break
331
+        assert schema_name
332
+        schema_def = definitions[schema_name]
333
+        assert schema_def.get('properties', {}).get('name', {}).get('type') == 'string'
334
+        assert doc.get('paths').get('/paper').get('post').get('parameters')[0]
335
+        schema_ref = doc.get('paths').get('/paper').get('post').get('parameters')[0]
336
+        assert schema_ref.get('in') == 'body'
337
+        assert schema_ref['schema']['$ref'] == '#/definitions/{}'.format(schema_name)
338
+
339
+    def test_func__schema_in_doc__ok__many_and_exclude_case(self):
340
+        hapic = Hapic()
341
+        app = bottle.Bottle()
342
+        hapic.set_context(MyContext(app=app))
343
+
344
+        class MySchema(marshmallow.Schema):
345
+            name = marshmallow.fields.String(required=True)
346
+            name2 = marshmallow.fields.String(required=True)
347
+
348
+        @hapic.with_api_doc()
349
+        @hapic.input_body(MySchema(exclude=('name2',), many=True))
350
+        def my_controller():
351
+            return {'name': 'test',}
352
+
353
+        app.route('/paper', method='POST', callback=my_controller)
354
+        doc = hapic.generate_doc()
355
+
356
+        definitions = doc.get('definitions', {})
357
+        # TODO - G-M - Find better way to find our new schema
358
+        # Do Better test when we were able to set correctly schema name
359
+        # according to content
360
+        schema_name = None
361
+        for elem in definitions.keys():
362
+            if elem != 'MySchema':
363
+                schema_name = elem
364
+                break
365
+        assert schema_name
366
+        schema_def = definitions[schema_name]
367
+        assert schema_def.get('properties', {}).get('name', {}).get('type') == 'string'
368
+        assert doc.get('paths').get('/paper').get('post').get('parameters')[0]
369
+        schema_ref = doc.get('paths').get('/paper').get('post').get('parameters')[0]
370
+        assert schema_ref.get('in') == 'body'
371
+        assert schema_ref['schema'] == {
372
+            'items': {'$ref': '#/definitions/{}'.format(schema_name)},
373
+            'type': 'array'
374
+        }