kinto 23.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kinto/__init__.py +92 -0
- kinto/__main__.py +249 -0
- kinto/authorization.py +134 -0
- kinto/config/__init__.py +94 -0
- kinto/config/kinto.tpl +270 -0
- kinto/contribute.json +27 -0
- kinto/core/__init__.py +246 -0
- kinto/core/authentication.py +48 -0
- kinto/core/authorization.py +311 -0
- kinto/core/cache/__init__.py +131 -0
- kinto/core/cache/memcached.py +112 -0
- kinto/core/cache/memory.py +104 -0
- kinto/core/cache/postgresql/__init__.py +178 -0
- kinto/core/cache/postgresql/schema.sql +23 -0
- kinto/core/cache/testing.py +208 -0
- kinto/core/cornice/__init__.py +93 -0
- kinto/core/cornice/cors.py +144 -0
- kinto/core/cornice/errors.py +40 -0
- kinto/core/cornice/pyramidhook.py +373 -0
- kinto/core/cornice/renderer.py +89 -0
- kinto/core/cornice/resource.py +205 -0
- kinto/core/cornice/service.py +641 -0
- kinto/core/cornice/util.py +138 -0
- kinto/core/cornice/validators/__init__.py +94 -0
- kinto/core/cornice/validators/_colander.py +142 -0
- kinto/core/cornice/validators/_marshmallow.py +182 -0
- kinto/core/cornice_swagger/__init__.py +92 -0
- kinto/core/cornice_swagger/converters/__init__.py +21 -0
- kinto/core/cornice_swagger/converters/exceptions.py +6 -0
- kinto/core/cornice_swagger/converters/parameters.py +90 -0
- kinto/core/cornice_swagger/converters/schema.py +249 -0
- kinto/core/cornice_swagger/swagger.py +725 -0
- kinto/core/cornice_swagger/templates/index.html +73 -0
- kinto/core/cornice_swagger/templates/index_script_template.html +21 -0
- kinto/core/cornice_swagger/util.py +42 -0
- kinto/core/cornice_swagger/views.py +78 -0
- kinto/core/decorators.py +74 -0
- kinto/core/errors.py +216 -0
- kinto/core/events.py +301 -0
- kinto/core/initialization.py +738 -0
- kinto/core/listeners/__init__.py +9 -0
- kinto/core/metrics.py +94 -0
- kinto/core/openapi.py +115 -0
- kinto/core/permission/__init__.py +202 -0
- kinto/core/permission/memory.py +167 -0
- kinto/core/permission/postgresql/__init__.py +489 -0
- kinto/core/permission/postgresql/migrations/migration_001_002.sql +18 -0
- kinto/core/permission/postgresql/schema.sql +41 -0
- kinto/core/permission/testing.py +487 -0
- kinto/core/resource/__init__.py +1311 -0
- kinto/core/resource/model.py +412 -0
- kinto/core/resource/schema.py +502 -0
- kinto/core/resource/viewset.py +230 -0
- kinto/core/schema.py +119 -0
- kinto/core/scripts.py +50 -0
- kinto/core/statsd.py +1 -0
- kinto/core/storage/__init__.py +436 -0
- kinto/core/storage/exceptions.py +53 -0
- kinto/core/storage/generators.py +58 -0
- kinto/core/storage/memory.py +651 -0
- kinto/core/storage/postgresql/__init__.py +1131 -0
- kinto/core/storage/postgresql/client.py +120 -0
- kinto/core/storage/postgresql/migrations/migration_001_002.sql +10 -0
- kinto/core/storage/postgresql/migrations/migration_002_003.sql +33 -0
- kinto/core/storage/postgresql/migrations/migration_003_004.sql +18 -0
- kinto/core/storage/postgresql/migrations/migration_004_005.sql +20 -0
- kinto/core/storage/postgresql/migrations/migration_005_006.sql +11 -0
- kinto/core/storage/postgresql/migrations/migration_006_007.sql +74 -0
- kinto/core/storage/postgresql/migrations/migration_007_008.sql +66 -0
- kinto/core/storage/postgresql/migrations/migration_008_009.sql +41 -0
- kinto/core/storage/postgresql/migrations/migration_009_010.sql +98 -0
- kinto/core/storage/postgresql/migrations/migration_010_011.sql +14 -0
- kinto/core/storage/postgresql/migrations/migration_011_012.sql +9 -0
- kinto/core/storage/postgresql/migrations/migration_012_013.sql +71 -0
- kinto/core/storage/postgresql/migrations/migration_013_014.sql +14 -0
- kinto/core/storage/postgresql/migrations/migration_014_015.sql +95 -0
- kinto/core/storage/postgresql/migrations/migration_015_016.sql +4 -0
- kinto/core/storage/postgresql/migrations/migration_016_017.sql +81 -0
- kinto/core/storage/postgresql/migrations/migration_017_018.sql +25 -0
- kinto/core/storage/postgresql/migrations/migration_018_019.sql +8 -0
- kinto/core/storage/postgresql/migrations/migration_019_020.sql +7 -0
- kinto/core/storage/postgresql/migrations/migration_020_021.sql +68 -0
- kinto/core/storage/postgresql/migrations/migration_021_022.sql +62 -0
- kinto/core/storage/postgresql/migrations/migration_022_023.sql +5 -0
- kinto/core/storage/postgresql/migrations/migration_023_024.sql +6 -0
- kinto/core/storage/postgresql/migrations/migration_024_025.sql +6 -0
- kinto/core/storage/postgresql/migrator.py +98 -0
- kinto/core/storage/postgresql/pool.py +55 -0
- kinto/core/storage/postgresql/schema.sql +143 -0
- kinto/core/storage/testing.py +1857 -0
- kinto/core/storage/utils.py +37 -0
- kinto/core/testing.py +182 -0
- kinto/core/utils.py +553 -0
- kinto/core/views/__init__.py +0 -0
- kinto/core/views/batch.py +163 -0
- kinto/core/views/errors.py +145 -0
- kinto/core/views/heartbeat.py +106 -0
- kinto/core/views/hello.py +69 -0
- kinto/core/views/openapi.py +35 -0
- kinto/core/views/version.py +50 -0
- kinto/events.py +3 -0
- kinto/plugins/__init__.py +0 -0
- kinto/plugins/accounts/__init__.py +94 -0
- kinto/plugins/accounts/authentication.py +63 -0
- kinto/plugins/accounts/scripts.py +61 -0
- kinto/plugins/accounts/utils.py +13 -0
- kinto/plugins/accounts/views.py +136 -0
- kinto/plugins/admin/README.md +3 -0
- kinto/plugins/admin/VERSION +1 -0
- kinto/plugins/admin/__init__.py +40 -0
- kinto/plugins/admin/build/VERSION +1 -0
- kinto/plugins/admin/build/assets/index-CYFwtKtL.css +6 -0
- kinto/plugins/admin/build/assets/index-DJ0m93zA.js +149 -0
- kinto/plugins/admin/build/assets/logo-VBRiKSPX.png +0 -0
- kinto/plugins/admin/build/index.html +18 -0
- kinto/plugins/admin/public/help.html +25 -0
- kinto/plugins/admin/views.py +42 -0
- kinto/plugins/default_bucket/__init__.py +191 -0
- kinto/plugins/flush.py +28 -0
- kinto/plugins/history/__init__.py +65 -0
- kinto/plugins/history/listener.py +181 -0
- kinto/plugins/history/views.py +66 -0
- kinto/plugins/openid/__init__.py +131 -0
- kinto/plugins/openid/utils.py +14 -0
- kinto/plugins/openid/views.py +193 -0
- kinto/plugins/prometheus.py +300 -0
- kinto/plugins/statsd.py +85 -0
- kinto/schema_validation.py +135 -0
- kinto/views/__init__.py +34 -0
- kinto/views/admin.py +195 -0
- kinto/views/buckets.py +45 -0
- kinto/views/collections.py +58 -0
- kinto/views/contribute.py +39 -0
- kinto/views/groups.py +90 -0
- kinto/views/permissions.py +235 -0
- kinto/views/records.py +133 -0
- kinto-23.2.1.dist-info/METADATA +232 -0
- kinto-23.2.1.dist-info/RECORD +142 -0
- kinto-23.2.1.dist-info/WHEEL +5 -0
- kinto-23.2.1.dist-info/entry_points.txt +5 -0
- kinto-23.2.1.dist-info/licenses/LICENSE +13 -0
- kinto-23.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
"""Cornice Swagger 2.0 documentor"""
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import warnings
|
|
5
|
+
from collections import OrderedDict
|
|
6
|
+
|
|
7
|
+
import colander
|
|
8
|
+
from pyramid.threadlocal import get_current_registry
|
|
9
|
+
|
|
10
|
+
from kinto.core.cornice import Service
|
|
11
|
+
from kinto.core.cornice.util import to_list
|
|
12
|
+
from kinto.core.cornice_swagger.converters import (
|
|
13
|
+
ParameterConversionDispatcher as ParameterConverter,
|
|
14
|
+
)
|
|
15
|
+
from kinto.core.cornice_swagger.converters import TypeConversionDispatcher as TypeConverter
|
|
16
|
+
from kinto.core.cornice_swagger.util import body_schema_transformer, merge_dicts, trim
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CorniceSwaggerException(Exception):
|
|
20
|
+
"""Raised when cornice services have structural problems to be converted."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DefinitionHandler(object):
|
|
24
|
+
"""Handles Swagger object definitions provided by cornice as colander schemas."""
|
|
25
|
+
|
|
26
|
+
json_pointer = "#/definitions/"
|
|
27
|
+
|
|
28
|
+
def __init__(self, ref=0, type_converter=TypeConverter()):
|
|
29
|
+
"""
|
|
30
|
+
:param ref:
|
|
31
|
+
The depth that should be used by self.ref when calling self.from_schema.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
self.definition_registry = {}
|
|
35
|
+
self.ref = ref
|
|
36
|
+
self.type_converter = type_converter
|
|
37
|
+
|
|
38
|
+
def from_schema(self, schema_node, base_name=None):
|
|
39
|
+
"""
|
|
40
|
+
Creates a Swagger definition from a colander schema.
|
|
41
|
+
|
|
42
|
+
:param schema_node:
|
|
43
|
+
Colander schema to be transformed into a Swagger definition.
|
|
44
|
+
:param base_name:
|
|
45
|
+
Schema alternative title.
|
|
46
|
+
|
|
47
|
+
:rtype: dict
|
|
48
|
+
:returns: Swagger schema.
|
|
49
|
+
"""
|
|
50
|
+
return self._ref_recursive(self.type_converter(schema_node), self.ref, base_name)
|
|
51
|
+
|
|
52
|
+
def _ref_recursive(self, schema, depth, base_name=None):
|
|
53
|
+
"""
|
|
54
|
+
Dismantle nested swagger schemas into several definitions using JSON pointers.
|
|
55
|
+
Note: This can be dangerous since definition titles must be unique.
|
|
56
|
+
|
|
57
|
+
:param schema:
|
|
58
|
+
Base swagger schema.
|
|
59
|
+
:param depth:
|
|
60
|
+
How many levels of the swagger object schemas should be split into
|
|
61
|
+
swaggger definitions with JSON pointers. Default (0) is no split.
|
|
62
|
+
You may use negative values to split everything.
|
|
63
|
+
:param base_name:
|
|
64
|
+
If schema doesn't have a name, the caller may provide it to be
|
|
65
|
+
used as reference.
|
|
66
|
+
|
|
67
|
+
:rtype: dict
|
|
68
|
+
:returns:
|
|
69
|
+
JSON pointer to the root definition schema,
|
|
70
|
+
or the original definition if depth is zero.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
if depth == 0:
|
|
74
|
+
return schema
|
|
75
|
+
|
|
76
|
+
if schema["type"] != "object":
|
|
77
|
+
return schema
|
|
78
|
+
|
|
79
|
+
name = base_name or schema["title"]
|
|
80
|
+
|
|
81
|
+
pointer = self.json_pointer + name
|
|
82
|
+
for child_name, child in schema.get("properties", {}).items():
|
|
83
|
+
schema["properties"][child_name] = self._ref_recursive(child, depth - 1)
|
|
84
|
+
|
|
85
|
+
self.definition_registry[name] = schema
|
|
86
|
+
|
|
87
|
+
return {"$ref": pointer}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ParameterHandler(object):
|
|
91
|
+
"""Handles swagger parameter definitions."""
|
|
92
|
+
|
|
93
|
+
json_pointer = "#/parameters/"
|
|
94
|
+
|
|
95
|
+
def __init__(
|
|
96
|
+
self,
|
|
97
|
+
definition_handler=DefinitionHandler(),
|
|
98
|
+
ref=False,
|
|
99
|
+
type_converter=TypeConverter(),
|
|
100
|
+
parameter_converter=ParameterConverter(TypeConverter()),
|
|
101
|
+
):
|
|
102
|
+
"""
|
|
103
|
+
:param definition_handler:
|
|
104
|
+
Callable that handles swagger definition schemas.
|
|
105
|
+
:param ref:
|
|
106
|
+
Specifies the ref value when calling from_xxx methods.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
self.parameter_registry = {}
|
|
110
|
+
|
|
111
|
+
self.type_converter = type_converter
|
|
112
|
+
self.parameter_converter = parameter_converter
|
|
113
|
+
self.definitions = definition_handler
|
|
114
|
+
self.ref = ref
|
|
115
|
+
|
|
116
|
+
def from_schema(self, schema_node):
|
|
117
|
+
"""
|
|
118
|
+
Creates a list of Swagger params from a colander request schema.
|
|
119
|
+
|
|
120
|
+
:param schema_node:
|
|
121
|
+
Request schema to be transformed into Swagger.
|
|
122
|
+
:param validators:
|
|
123
|
+
Validators used in colander with the schema.
|
|
124
|
+
|
|
125
|
+
:rtype: list
|
|
126
|
+
:returns: List of Swagger parameters.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
params = []
|
|
130
|
+
|
|
131
|
+
for param_schema in schema_node.children:
|
|
132
|
+
location = param_schema.name
|
|
133
|
+
if location == "body":
|
|
134
|
+
name = param_schema.__class__.__name__
|
|
135
|
+
if name == "body":
|
|
136
|
+
name = schema_node.__class__.__name__ + "Body"
|
|
137
|
+
param = self.parameter_converter(location, param_schema)
|
|
138
|
+
param["name"] = name
|
|
139
|
+
if self.ref:
|
|
140
|
+
param = self._ref(param)
|
|
141
|
+
params.append(param)
|
|
142
|
+
|
|
143
|
+
elif location in (("path", "header", "headers", "querystring", "GET")):
|
|
144
|
+
for node_schema in param_schema.children:
|
|
145
|
+
param = self.parameter_converter(location, node_schema)
|
|
146
|
+
if self.ref:
|
|
147
|
+
param = self._ref(param)
|
|
148
|
+
params.append(param)
|
|
149
|
+
|
|
150
|
+
return params
|
|
151
|
+
|
|
152
|
+
def from_path(self, path):
|
|
153
|
+
"""
|
|
154
|
+
Create a list of Swagger path params from a cornice service path.
|
|
155
|
+
|
|
156
|
+
:type path: string
|
|
157
|
+
:rtype: list
|
|
158
|
+
"""
|
|
159
|
+
path_components = path.split("/")
|
|
160
|
+
param_names = [
|
|
161
|
+
comp[1:-1] for comp in path_components if comp.startswith("{") and comp.endswith("}")
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
params = []
|
|
165
|
+
for name in param_names:
|
|
166
|
+
param_schema = colander.SchemaNode(colander.String(), name=name)
|
|
167
|
+
param = self.parameter_converter("path", param_schema)
|
|
168
|
+
if self.ref:
|
|
169
|
+
param = self._ref(param)
|
|
170
|
+
params.append(param)
|
|
171
|
+
|
|
172
|
+
return params
|
|
173
|
+
|
|
174
|
+
def _ref(self, param, base_name=None):
|
|
175
|
+
"""
|
|
176
|
+
Store a parameter schema and return a reference to it.
|
|
177
|
+
|
|
178
|
+
:param schema:
|
|
179
|
+
Swagger parameter definition.
|
|
180
|
+
:param base_name:
|
|
181
|
+
Name that should be used for the reference.
|
|
182
|
+
|
|
183
|
+
:rtype: dict
|
|
184
|
+
:returns: JSON pointer to the original parameter definition.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
name = base_name or param.get("title", "") or param.get("name", "")
|
|
188
|
+
|
|
189
|
+
pointer = self.json_pointer + name
|
|
190
|
+
self.parameter_registry[name] = param
|
|
191
|
+
|
|
192
|
+
return {"$ref": pointer}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class ResponseHandler(object):
|
|
196
|
+
"""Handles swagger response definitions."""
|
|
197
|
+
|
|
198
|
+
json_pointer = "#/responses/"
|
|
199
|
+
|
|
200
|
+
def __init__(
|
|
201
|
+
self, definition_handler=DefinitionHandler(), type_converter=TypeConverter(), ref=False
|
|
202
|
+
):
|
|
203
|
+
"""
|
|
204
|
+
:param definition_handler:
|
|
205
|
+
Callable that handles swagger definition schemas.
|
|
206
|
+
:param ref:
|
|
207
|
+
Specifies the ref value when calling from_xxx methods.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
self.response_registry = {}
|
|
211
|
+
|
|
212
|
+
self.type_converter = type_converter
|
|
213
|
+
self.definitions = definition_handler
|
|
214
|
+
self.ref = ref
|
|
215
|
+
|
|
216
|
+
def from_schema_mapping(self, schema_mapping):
|
|
217
|
+
"""
|
|
218
|
+
Creates a Swagger response object from a dict of response schemas.
|
|
219
|
+
|
|
220
|
+
:param schema_mapping:
|
|
221
|
+
Dict with entries matching ``{status_code: response_schema}``.
|
|
222
|
+
:rtype: dict
|
|
223
|
+
:returns: Response schema.
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
responses = {}
|
|
227
|
+
|
|
228
|
+
for status, response_schema in schema_mapping.items():
|
|
229
|
+
response = {}
|
|
230
|
+
if response_schema.description:
|
|
231
|
+
response["description"] = response_schema.description
|
|
232
|
+
else:
|
|
233
|
+
raise CorniceSwaggerException("Responses must have a description.")
|
|
234
|
+
|
|
235
|
+
for field_schema in response_schema.children:
|
|
236
|
+
location = field_schema.name
|
|
237
|
+
|
|
238
|
+
if location == "body":
|
|
239
|
+
title = field_schema.__class__.__name__
|
|
240
|
+
if title == "body":
|
|
241
|
+
title = response_schema.__class__.__name__ + "Body"
|
|
242
|
+
field_schema.title = title
|
|
243
|
+
response["schema"] = self.definitions.from_schema(field_schema)
|
|
244
|
+
|
|
245
|
+
elif location in ("header", "headers"):
|
|
246
|
+
header_schema = self.type_converter(field_schema)
|
|
247
|
+
headers = header_schema.get("properties")
|
|
248
|
+
if headers:
|
|
249
|
+
# Response headers doesn't accept titles
|
|
250
|
+
for header in headers.values():
|
|
251
|
+
header.pop("title")
|
|
252
|
+
|
|
253
|
+
response["headers"] = headers
|
|
254
|
+
|
|
255
|
+
pointer = response_schema.__class__.__name__
|
|
256
|
+
if self.ref:
|
|
257
|
+
response = self._ref(response, pointer)
|
|
258
|
+
responses[status] = response
|
|
259
|
+
|
|
260
|
+
return responses
|
|
261
|
+
|
|
262
|
+
def _ref(self, resp, base_name=None):
|
|
263
|
+
"""
|
|
264
|
+
Store a response schema and return a reference to it.
|
|
265
|
+
|
|
266
|
+
:param schema:
|
|
267
|
+
Swagger response definition.
|
|
268
|
+
:param base_name:
|
|
269
|
+
Name that should be used for the reference.
|
|
270
|
+
|
|
271
|
+
:rtype: dict
|
|
272
|
+
:returns: JSON pointer to the original response definition.
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
name = base_name or resp.get("title", "") or resp.get("name", "")
|
|
276
|
+
|
|
277
|
+
pointer = self.json_pointer + name
|
|
278
|
+
self.response_registry[name] = resp
|
|
279
|
+
|
|
280
|
+
return {"$ref": pointer}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class CorniceSwagger(object):
|
|
284
|
+
"""Handles the creation of a swagger document from a cornice application."""
|
|
285
|
+
|
|
286
|
+
services = []
|
|
287
|
+
"""List of cornice services to document. You may use
|
|
288
|
+
`cornice.service.get_services()` to get it."""
|
|
289
|
+
|
|
290
|
+
definitions = DefinitionHandler
|
|
291
|
+
"""Default :class:`cornice_swagger.swagger.DefinitionHandler` class to use when
|
|
292
|
+
handling OpenAPI schema definitions from kinto.core.cornice payload schemas."""
|
|
293
|
+
|
|
294
|
+
parameters = ParameterHandler
|
|
295
|
+
"""Default :class:`cornice_swagger.swagger.ParameterHandler` class to use when
|
|
296
|
+
handling OpenAPI operation parameters from kinto.core.cornice request schemas."""
|
|
297
|
+
|
|
298
|
+
responses = ResponseHandler
|
|
299
|
+
"""Default :class:`cornice_swagger.swagger.ResponseHandler` class to use when
|
|
300
|
+
handling OpenAPI responses from kinto.core.cornice_swagger defined responses."""
|
|
301
|
+
|
|
302
|
+
schema_transformers = [body_schema_transformer]
|
|
303
|
+
"""List of request schema transformers that should be applied to a request
|
|
304
|
+
schema to make it comply with a cornice default request schema."""
|
|
305
|
+
|
|
306
|
+
type_converter = TypeConverter
|
|
307
|
+
"""Default :class:`cornice_swagger.converters.schema.TypeConversionDispatcher`
|
|
308
|
+
class used for converting colander schema Types to Swagger Types."""
|
|
309
|
+
|
|
310
|
+
parameter_converter = ParameterConverter
|
|
311
|
+
"""Default :class:`cornice_swagger.converters.parameters.ParameterConversionDispatcher`
|
|
312
|
+
class used for converting colander/cornice request schemas to Swagger Parameters."""
|
|
313
|
+
|
|
314
|
+
custom_type_converters = {}
|
|
315
|
+
"""Mapping for supporting custom types conversion on the default TypeConverter.
|
|
316
|
+
Should map `colander.TypeSchema` to `cornice_swagger.converters.schema.TypeConverter`
|
|
317
|
+
callables."""
|
|
318
|
+
|
|
319
|
+
default_type_converter = None
|
|
320
|
+
"""Supplies a default type converter matching the interface of
|
|
321
|
+
`cornice_swagger.converters.schema.TypeConverter` to be used with unknown types."""
|
|
322
|
+
|
|
323
|
+
default_tags = None
|
|
324
|
+
"""Provide a default list of tags or a callable that takes a cornice
|
|
325
|
+
service and the method name (e.g GET) and returns a list of Swagger
|
|
326
|
+
tags to be used if not provided by the view."""
|
|
327
|
+
|
|
328
|
+
default_op_ids = None
|
|
329
|
+
"""Provide a callable that takes a cornice service and the method name
|
|
330
|
+
(e.g. GET) and returns an operation Id that is used if an operation Id is
|
|
331
|
+
not provided. Each operation Id should be unique."""
|
|
332
|
+
|
|
333
|
+
default_security = None
|
|
334
|
+
"""Provide a default list or a callable that takes a cornice service and
|
|
335
|
+
the method name (e.g. GET) and returns a list of OpenAPI security policies."""
|
|
336
|
+
|
|
337
|
+
summary_docstrings = False
|
|
338
|
+
"""Enable extracting operation summaries from view docstrings."""
|
|
339
|
+
|
|
340
|
+
ignore_methods = ["HEAD", "OPTIONS"]
|
|
341
|
+
"""List of service methods that should NOT be presented on the
|
|
342
|
+
documentation. You may use this to remove methods that are not
|
|
343
|
+
essential on the API documentation. Default ignores HEAD and OPTIONS."""
|
|
344
|
+
|
|
345
|
+
ignore_ctypes = []
|
|
346
|
+
"""List of service content-types that should NOT be presented on the
|
|
347
|
+
documentation. You may use this when a Cornice service definition has
|
|
348
|
+
multiple view definitions for a same method, which is not supported on
|
|
349
|
+
OpenAPI 2.0."""
|
|
350
|
+
|
|
351
|
+
api_title = ""
|
|
352
|
+
"""Title of the OpenAPI document."""
|
|
353
|
+
|
|
354
|
+
api_version = ""
|
|
355
|
+
"""Version of the OpenAPI document."""
|
|
356
|
+
|
|
357
|
+
base_path = "/"
|
|
358
|
+
"""Base path of the documented API. Default is "/"."""
|
|
359
|
+
|
|
360
|
+
swagger = {"info": {}}
|
|
361
|
+
"""Base OpenAPI document that should be merged with the extracted info
|
|
362
|
+
from the generate call."""
|
|
363
|
+
|
|
364
|
+
def __init__(
|
|
365
|
+
self,
|
|
366
|
+
services=None,
|
|
367
|
+
def_ref_depth=0,
|
|
368
|
+
param_ref=False,
|
|
369
|
+
resp_ref=False,
|
|
370
|
+
pyramid_registry=None,
|
|
371
|
+
):
|
|
372
|
+
"""
|
|
373
|
+
:param services:
|
|
374
|
+
List of cornice services to document. You may use
|
|
375
|
+
cornice.service.get_services() to get it.
|
|
376
|
+
:param def_ref_depth:
|
|
377
|
+
How depth swagger object schemas should be split into
|
|
378
|
+
swaggger definitions with JSON pointers. Default (0) is no split.
|
|
379
|
+
You may use negative values to split everything.
|
|
380
|
+
:param param_ref:
|
|
381
|
+
Defines if swagger parameters should be put inline on the operation
|
|
382
|
+
or on the parameters section and referenced by JSON pointers.
|
|
383
|
+
Default is inline.
|
|
384
|
+
:param resp_ref:
|
|
385
|
+
Defines if swagger responses should be put inline on the operation
|
|
386
|
+
or on the responses section and referenced by JSON pointers.
|
|
387
|
+
Default is inline.
|
|
388
|
+
:param pyramid_registry:
|
|
389
|
+
Pyramid registry, should be passed if you use pyramid routes
|
|
390
|
+
instead of service level paths.
|
|
391
|
+
"""
|
|
392
|
+
super(CorniceSwagger, self).__init__()
|
|
393
|
+
|
|
394
|
+
type_converter = self.type_converter(
|
|
395
|
+
self.custom_type_converters, self.default_type_converter
|
|
396
|
+
)
|
|
397
|
+
parameter_converter = self.parameter_converter(type_converter)
|
|
398
|
+
self.pyramid_registry = pyramid_registry
|
|
399
|
+
if services is not None:
|
|
400
|
+
self.services = services
|
|
401
|
+
|
|
402
|
+
# Instantiate handlers
|
|
403
|
+
self.definitions = self.definitions(ref=def_ref_depth, type_converter=type_converter)
|
|
404
|
+
self.parameters = self.parameters(
|
|
405
|
+
self.definitions,
|
|
406
|
+
ref=param_ref,
|
|
407
|
+
type_converter=type_converter,
|
|
408
|
+
parameter_converter=parameter_converter,
|
|
409
|
+
)
|
|
410
|
+
self.responses = self.responses(
|
|
411
|
+
self.definitions, ref=resp_ref, type_converter=type_converter
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
def generate(
|
|
415
|
+
self, title=None, version=None, base_path=None, info=None, swagger=None, **kwargs
|
|
416
|
+
):
|
|
417
|
+
"""Generate a Swagger 2.0 documentation. Keyword arguments may be used
|
|
418
|
+
to provide additional information to build methods as such ignores.
|
|
419
|
+
|
|
420
|
+
:param title:
|
|
421
|
+
The name presented on the swagger document.
|
|
422
|
+
:param version:
|
|
423
|
+
The version of the API presented on the swagger document.
|
|
424
|
+
:param base_path:
|
|
425
|
+
The path that all requests to the API must refer to.
|
|
426
|
+
:param info:
|
|
427
|
+
Swagger info field.
|
|
428
|
+
:param swagger:
|
|
429
|
+
Extra fields that should be provided on the swagger documentation.
|
|
430
|
+
|
|
431
|
+
:rtype: dict
|
|
432
|
+
:returns: Full OpenAPI/Swagger compliant specification for the application.
|
|
433
|
+
"""
|
|
434
|
+
title = title or self.api_title
|
|
435
|
+
version = version or self.api_version
|
|
436
|
+
info = info or self.swagger.get("info", {})
|
|
437
|
+
swagger = swagger or self.swagger
|
|
438
|
+
base_path = base_path or self.base_path
|
|
439
|
+
|
|
440
|
+
swagger = swagger.copy()
|
|
441
|
+
info.update(title=title, version=version)
|
|
442
|
+
swagger.update(swagger="2.0", info=info, basePath=base_path)
|
|
443
|
+
|
|
444
|
+
paths, tags = self._build_paths()
|
|
445
|
+
|
|
446
|
+
# Update the provided tags with the extracted ones preserving order
|
|
447
|
+
if tags:
|
|
448
|
+
swagger.setdefault("tags", [])
|
|
449
|
+
tag_names = {t["name"] for t in swagger["tags"]}
|
|
450
|
+
for tag in tags:
|
|
451
|
+
if tag["name"] not in tag_names:
|
|
452
|
+
swagger["tags"].append(tag)
|
|
453
|
+
|
|
454
|
+
# Create/Update swagger sections with extracted values where not provided
|
|
455
|
+
if paths:
|
|
456
|
+
swagger.setdefault("paths", {})
|
|
457
|
+
merge_dicts(swagger["paths"], paths)
|
|
458
|
+
|
|
459
|
+
definitions = self.definitions.definition_registry
|
|
460
|
+
if definitions:
|
|
461
|
+
swagger.setdefault("definitions", {})
|
|
462
|
+
merge_dicts(swagger["definitions"], definitions)
|
|
463
|
+
|
|
464
|
+
parameters = self.parameters.parameter_registry
|
|
465
|
+
if parameters:
|
|
466
|
+
swagger.setdefault("parameters", {})
|
|
467
|
+
merge_dicts(swagger["parameters"], parameters)
|
|
468
|
+
|
|
469
|
+
responses = self.responses.response_registry
|
|
470
|
+
if responses:
|
|
471
|
+
swagger.setdefault("responses", {})
|
|
472
|
+
merge_dicts(swagger["responses"], responses)
|
|
473
|
+
|
|
474
|
+
return swagger
|
|
475
|
+
|
|
476
|
+
def __call__(self, *args, **kwargs):
|
|
477
|
+
"""Deprecated alias of `generate`."""
|
|
478
|
+
self.__dict__.update(**kwargs)
|
|
479
|
+
|
|
480
|
+
message = "Calling `CorniceSwagger is deprecated, call `generate` instead"
|
|
481
|
+
warnings.warn(message, DeprecationWarning)
|
|
482
|
+
return self.generate(*args, **kwargs)
|
|
483
|
+
|
|
484
|
+
def _check_tags(self, tags):
|
|
485
|
+
"""Check if tags was correctly defined as a list"""
|
|
486
|
+
if not isinstance(tags, list):
|
|
487
|
+
raise CorniceSwaggerException("tags should be a list or callable")
|
|
488
|
+
|
|
489
|
+
def _get_tags(self, current_tags, new_tags):
|
|
490
|
+
tags = list(current_tags)
|
|
491
|
+
for tag in new_tags:
|
|
492
|
+
root_tag = {"name": tag}
|
|
493
|
+
if root_tag not in tags:
|
|
494
|
+
tags.append(root_tag)
|
|
495
|
+
return tags
|
|
496
|
+
|
|
497
|
+
def _build_paths(self):
|
|
498
|
+
"""
|
|
499
|
+
Build the Swagger "paths" and "tags" attributes from kinto.core.cornice service
|
|
500
|
+
definitions.
|
|
501
|
+
"""
|
|
502
|
+
paths = {}
|
|
503
|
+
tags = []
|
|
504
|
+
|
|
505
|
+
for service in self.services:
|
|
506
|
+
path, path_obj = self._extract_path_from_service(service)
|
|
507
|
+
|
|
508
|
+
service_tags = getattr(service, "tags", [])
|
|
509
|
+
self._check_tags(service_tags)
|
|
510
|
+
tags = self._get_tags(tags, service_tags)
|
|
511
|
+
|
|
512
|
+
for method, view, args in service.definitions:
|
|
513
|
+
if method.lower() in map(str.lower, self.ignore_methods):
|
|
514
|
+
continue
|
|
515
|
+
|
|
516
|
+
op = self._extract_operation_from_view(view, args)
|
|
517
|
+
|
|
518
|
+
if any(ctype in op.get("consumes", []) for ctype in self.ignore_ctypes):
|
|
519
|
+
continue
|
|
520
|
+
|
|
521
|
+
# XXX: Swagger doesn't support different schemas for for a same method
|
|
522
|
+
# with different ctypes as cornice. If this happens, you may ignore one
|
|
523
|
+
# content-type from the documentation otherwise we raise an Exception
|
|
524
|
+
# Related to https://github.com/OAI/OpenAPI-Specification/issues/146
|
|
525
|
+
previous_definition = path_obj.get(method.lower())
|
|
526
|
+
if previous_definition:
|
|
527
|
+
raise CorniceSwaggerException(
|
|
528
|
+
(
|
|
529
|
+
"Swagger doesn't support multiple "
|
|
530
|
+
"views for a same method. You may "
|
|
531
|
+
"ignore one."
|
|
532
|
+
)
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
# If tag not defined and a default tag is provided
|
|
536
|
+
if "tags" not in op and self.default_tags:
|
|
537
|
+
if callable(self.default_tags):
|
|
538
|
+
op["tags"] = self.default_tags(service, method)
|
|
539
|
+
else:
|
|
540
|
+
op["tags"] = self.default_tags
|
|
541
|
+
|
|
542
|
+
op_tags = op.get("tags", [])
|
|
543
|
+
self._check_tags(op_tags)
|
|
544
|
+
|
|
545
|
+
# Add service tags
|
|
546
|
+
if service_tags:
|
|
547
|
+
new_tags = service_tags + op_tags
|
|
548
|
+
op["tags"] = list(OrderedDict.fromkeys(new_tags))
|
|
549
|
+
|
|
550
|
+
# Add method tags to root tags
|
|
551
|
+
tags = self._get_tags(tags, op_tags)
|
|
552
|
+
|
|
553
|
+
# If operation id is not defined and a default generator is provided
|
|
554
|
+
if "operationId" not in op and self.default_op_ids:
|
|
555
|
+
if not callable(self.default_op_ids):
|
|
556
|
+
raise CorniceSwaggerException("default_op_id should be a callable.")
|
|
557
|
+
op["operationId"] = self.default_op_ids(service, method)
|
|
558
|
+
|
|
559
|
+
# If security options not defined and default is provided
|
|
560
|
+
if "security" not in op and self.default_security:
|
|
561
|
+
if callable(self.default_security):
|
|
562
|
+
op["security"] = self.default_security(service, method)
|
|
563
|
+
else:
|
|
564
|
+
op["security"] = self.default_security
|
|
565
|
+
|
|
566
|
+
if not isinstance(op.get("security", []), list):
|
|
567
|
+
raise CorniceSwaggerException("security should be a list or callable")
|
|
568
|
+
|
|
569
|
+
path_obj[method.lower()] = op
|
|
570
|
+
paths[path] = path_obj
|
|
571
|
+
|
|
572
|
+
return paths, tags
|
|
573
|
+
|
|
574
|
+
def _extract_path_from_service(self, service):
|
|
575
|
+
"""
|
|
576
|
+
Extract path object and its parameters from service definitions.
|
|
577
|
+
|
|
578
|
+
:param service:
|
|
579
|
+
Cornice service to extract information from.
|
|
580
|
+
|
|
581
|
+
:rtype: dict
|
|
582
|
+
:returns: Path definition.
|
|
583
|
+
"""
|
|
584
|
+
|
|
585
|
+
path_obj = {}
|
|
586
|
+
path = service.path
|
|
587
|
+
route_name = getattr(service, "pyramid_route", None)
|
|
588
|
+
# handle services that don't create fresh routes,
|
|
589
|
+
# we still need the paths so we need to grab pyramid introspector to
|
|
590
|
+
# extract that information
|
|
591
|
+
if route_name:
|
|
592
|
+
# avoid failure if someone forgets to pass registry
|
|
593
|
+
registry = self.pyramid_registry or get_current_registry()
|
|
594
|
+
route_intr = registry.introspector.get("routes", route_name)
|
|
595
|
+
if route_intr:
|
|
596
|
+
path = route_intr["pattern"]
|
|
597
|
+
else:
|
|
598
|
+
msg = "Route `{}` is not found by pyramid introspector".format(route_name)
|
|
599
|
+
raise ValueError(msg)
|
|
600
|
+
|
|
601
|
+
# handle traverse and subpath as regular parameters
|
|
602
|
+
# docs.pylonsproject.org/projects/pyramid/en/latest/narr/hybrid.html
|
|
603
|
+
for subpath_marker in ("*subpath", "*traverse"):
|
|
604
|
+
path = path.replace(subpath_marker, "{subpath}")
|
|
605
|
+
|
|
606
|
+
# Extract path parameters
|
|
607
|
+
parameters = self.parameters.from_path(path)
|
|
608
|
+
if parameters:
|
|
609
|
+
path_obj["parameters"] = parameters
|
|
610
|
+
|
|
611
|
+
return path, path_obj
|
|
612
|
+
|
|
613
|
+
def _extract_operation_from_view(self, view, args):
|
|
614
|
+
"""
|
|
615
|
+
Extract swagger operation details from colander view definitions.
|
|
616
|
+
|
|
617
|
+
:param view:
|
|
618
|
+
View to extract information from.
|
|
619
|
+
:param args:
|
|
620
|
+
Arguments from the view decorator.
|
|
621
|
+
|
|
622
|
+
:rtype: dict
|
|
623
|
+
:returns: Operation definition.
|
|
624
|
+
"""
|
|
625
|
+
|
|
626
|
+
op = {
|
|
627
|
+
"responses": {"default": {"description": "UNDOCUMENTED RESPONSE"}},
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
# If 'produces' are not defined in the view, try get from renderers
|
|
631
|
+
renderer = args.get("renderer", "")
|
|
632
|
+
|
|
633
|
+
is_json_renderer = (
|
|
634
|
+
"json" in renderer # allows for "json" or "simplejson"
|
|
635
|
+
or renderer == Service.renderer # default renderer is json.
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
if is_json_renderer:
|
|
639
|
+
produces = ["application/json"]
|
|
640
|
+
elif renderer == "xml":
|
|
641
|
+
produces = ["text/xml"]
|
|
642
|
+
else:
|
|
643
|
+
produces = None
|
|
644
|
+
|
|
645
|
+
if produces:
|
|
646
|
+
op.setdefault("produces", produces)
|
|
647
|
+
|
|
648
|
+
# Get explicit accepted content-types
|
|
649
|
+
consumes = args.get("content_type")
|
|
650
|
+
|
|
651
|
+
if consumes is not None:
|
|
652
|
+
# convert to a list, if it's not yet one
|
|
653
|
+
consumes = to_list(consumes)
|
|
654
|
+
|
|
655
|
+
# It is possible to add callables for content_type, so we have to
|
|
656
|
+
# to filter those out, since we cannot evaluate those here.
|
|
657
|
+
consumes = [x for x in consumes if not callable(x)]
|
|
658
|
+
op["consumes"] = consumes
|
|
659
|
+
|
|
660
|
+
# Get parameters from view schema
|
|
661
|
+
is_colander = self._is_colander_schema(args)
|
|
662
|
+
if is_colander:
|
|
663
|
+
schema = self._extract_transform_colander_schema(args)
|
|
664
|
+
parameters = self.parameters.from_schema(schema)
|
|
665
|
+
else:
|
|
666
|
+
# Bail out for now
|
|
667
|
+
parameters = None
|
|
668
|
+
if parameters:
|
|
669
|
+
op["parameters"] = parameters
|
|
670
|
+
|
|
671
|
+
# Get summary from docstring
|
|
672
|
+
if isinstance(view, str):
|
|
673
|
+
if "klass" in args:
|
|
674
|
+
ob = args["klass"]
|
|
675
|
+
view_ = getattr(ob, view.lower())
|
|
676
|
+
docstring = trim(view_.__doc__)
|
|
677
|
+
else:
|
|
678
|
+
docstring = str(trim(view.__doc__))
|
|
679
|
+
|
|
680
|
+
if docstring and self.summary_docstrings:
|
|
681
|
+
op["summary"] = docstring
|
|
682
|
+
|
|
683
|
+
# Get response definitions
|
|
684
|
+
if "response_schemas" in args:
|
|
685
|
+
op["responses"] = self.responses.from_schema_mapping(args["response_schemas"])
|
|
686
|
+
|
|
687
|
+
# Get response tags
|
|
688
|
+
if "tags" in args:
|
|
689
|
+
op["tags"] = args["tags"]
|
|
690
|
+
|
|
691
|
+
# Get response operationId
|
|
692
|
+
if "operation_id" in args:
|
|
693
|
+
op["operationId"] = args["operation_id"]
|
|
694
|
+
|
|
695
|
+
# Get security policies
|
|
696
|
+
if "api_security" in args:
|
|
697
|
+
op["security"] = args["api_security"]
|
|
698
|
+
|
|
699
|
+
return op
|
|
700
|
+
|
|
701
|
+
def _is_colander_schema(self, args):
|
|
702
|
+
schema = args.get("schema")
|
|
703
|
+
return isinstance(schema, colander.Schema) or (
|
|
704
|
+
inspect.isclass(schema) and issubclass(schema, colander.MappingSchema)
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
def _extract_transform_colander_schema(self, args):
|
|
708
|
+
"""
|
|
709
|
+
Extract schema from view args and transform it using
|
|
710
|
+
the pipeline of schema transformers
|
|
711
|
+
|
|
712
|
+
:param args:
|
|
713
|
+
Arguments from the view decorator.
|
|
714
|
+
|
|
715
|
+
:rtype: colander.MappingSchema()
|
|
716
|
+
:returns: View schema cloned and transformed
|
|
717
|
+
"""
|
|
718
|
+
|
|
719
|
+
schema = args.get("schema", colander.MappingSchema())
|
|
720
|
+
if not isinstance(schema, colander.Schema):
|
|
721
|
+
schema = schema()
|
|
722
|
+
schema = schema.clone()
|
|
723
|
+
for transformer in self.schema_transformers:
|
|
724
|
+
schema = transformer(schema, args)
|
|
725
|
+
return schema
|