sc-oa 0.7.0.14__py3-none-any.whl → 0.7.0.15__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.

Potentially problematic release.


This version of sc-oa might be problematic. Click here for more details.

@@ -1,422 +1,426 @@
1
- from . import abc
2
- from .. import utils
3
-
4
- import re
5
- import hashlib
6
- import json
7
- import textwrap
8
-
9
- import jsonschema
10
- from jsonschema import validate
11
- from docutils.parsers.rst import directives
12
-
13
- from sphinx.locale import get_translation
14
- _ = get_translation('openapi')
15
-
16
-
17
- def _get_description(obj, convert):
18
- d = obj.get('description', '')
19
- D = convert(d).strip()
20
- # if d.strip()!=D:
21
- if '\n' in D:
22
- D += '\n\n'
23
- if 'default' in obj:
24
- if D and D.splitlines()[-1] and D[-1] != '.':
25
- D += '.'
26
- if D and D.splitlines()[-1]:
27
- D += ' '
28
- D += 'Default: ``' + json.dumps(obj['default']) + "``."
29
- return D
30
-
31
-
32
- def _get_contraints(obj):
33
- c = []
34
- if 'minItems' in obj:
35
- c.append(_('minItems is {}').format(obj['minItems']))
36
- if 'maxItems' in obj:
37
- c.append(_('maxItems is {}').format(obj['maxItems']))
38
- if 'minLength' in obj:
39
- c.append(_('minLength is {}').format(obj['minLength']))
40
- if 'maxLength' in obj:
41
- c.append(_('maxLength is {}').format(obj['maxLength']))
42
- if 'minimum' in obj:
43
- c.append(_('minimum is {}').format(obj['minimum']))
44
- if 'maximum' in obj:
45
- c.append(_('maximum is {}').format(obj['maximum']))
46
- if 'uniqueItems' in obj:
47
- c.append(_("items must be unique"))
48
- if "pattern" in obj:
49
- c.append(_("pattern ``{}``").format(obj["pattern"]))
50
- if 'enum' in obj:
51
- c.append(_('possible values are {}').format(
52
- ', '.join(
53
- [
54
- '``{}``'.format(x) for x in obj['enum']
55
- ]
56
- )))
57
- if 'readOnly' in obj:
58
- c.append(_("read only"))
59
- if 'writeOnly' in obj:
60
- c.append(_("write only"))
61
- c = [str(x) for x in c]
62
- s = '; '.join(c)
63
- return s
64
-
65
-
66
- def _add_constraints(obj, D, C):
67
- if C:
68
- if str(_('Constraints')) not in D:
69
- C = _("Constraints: {}").format(C)
70
- if D and D.splitlines()[-1] and D[-1] != '.':
71
- D += '.'
72
- else:
73
- if C and D and D.splitlines()[-1] and D[-1] != ';':
74
- D += ';'
75
- if D and D.splitlines()[-1] and C and C[0] != '\n':
76
- D += ' '
77
- D += C
78
- else:
79
- if D and D.splitlines()[-1] and D[-1] != '.':
80
- D += '.'
81
- if 'deprecated' in obj:
82
- D += "\n\n**{}**".format(_("DEPRECATED"))
83
- return D
84
-
85
-
86
- def _get_multi_type(schema, entities):
87
- T = []
88
- duplicate = set()
89
- if 'oneOf' in schema:
90
- k = 'oneOf'
91
- elif 'allOf' in schema:
92
- k = 'allOf'
93
- else:
94
- k = 'anyOf'
95
- for t in schema[k]:
96
- type = t.get('type', 'object')
97
- if '$entity_ref' in t and type == 'object':
98
- T.append(ref2link(entities, t['$entity_ref']))
99
- else:
100
- if type == 'string' and 'enum' in t:
101
- type = 'enumerate'
102
- vals = ', '.join(
103
- [
104
- '``{}``'.format(x) for x in t['enum']
105
- ]
106
- )
107
- if vals:
108
- type += ' (' + vals + ')'
109
- if type not in duplicate:
110
- T.append(type)
111
- duplicate.add(type)
112
- return T
113
-
114
-
115
- def _process_one(prefix, schema, mandatory, entities, convert):
116
- if 'oneOf' in schema:
117
- type = 'oneOf'
118
- elif 'allOf' in schema:
119
- type = 'allOf'
120
- elif 'anyOf' in schema:
121
- type = 'anyOf'
122
- else:
123
- type = schema.get('type', 'object')
124
- if '$entity_ref' in schema and type == 'object' and prefix:
125
- # does not apply to first level types (prefix empty)
126
- T = _('Object of type {}').format(ref2link(entities, schema['$entity_ref']))
127
- D = _get_description(schema, convert)
128
- C = _get_contraints(schema)
129
- D = _add_constraints(schema, D, C)
130
- ret = ['.'.join(prefix), T, D, mandatory]
131
- yield ret
132
- elif type == 'array':
133
- ref = schema['items'].get('$entity_ref', None)
134
- type_items = schema['items'].get('type', None)
135
- if ref:
136
- D = _get_description(schema, convert)
137
- C = _get_contraints(schema)
138
- D = _add_constraints(schema, D, C)
139
- if type_items not in ['object', 'array']:
140
- # Since only object and array are described on their own
141
- # Add here the constraints of the referenced type
142
- D = _get_description(schema['items'], convert)
143
- C = _get_contraints(schema['items'])
144
- D = _add_constraints(schema['items'], D, C)
145
- C = _get_contraints(schema)
146
- D = _add_constraints(schema, D, C)
147
- yield [
148
- '.'.join(prefix),
149
- _('Array of {}').format(type_items),
150
- D,
151
- mandatory
152
- ]
153
- else:
154
- yield [
155
- '.'.join(prefix),
156
- _('Array of {}').format(ref2link(entities, ref)),
157
- D,
158
- mandatory
159
- ]
160
- elif type_items == 'object':
161
- T = _("Array")
162
- D = _get_description(schema, convert)
163
- C = _get_contraints(schema)
164
- D = _add_constraints(schema, D, C)
165
- yield ['.'.join(prefix), T, D, mandatory]
166
- if prefix:
167
- prefix[-1] += '[]'
168
- else:
169
- prefix = ['[]']
170
- for x in _process_one(prefix, schema['items'], False, entities, convert):
171
- yield x
172
- else:
173
- # Support array of simple types (string, etc.)
174
- D = _get_description(schema, convert)
175
- C = _get_contraints(schema)
176
- for x in _process_one(prefix, schema['items'], False, entities, convert):
177
- # Add C to x[2] now and not before (to avoid double "Constraints:")
178
- if D and x[2]:
179
- DD = D + ' ' + x[2]
180
- else:
181
- DD = D + x[2]
182
- DD = _add_constraints(schema, DD, C)
183
- yield [x[0], _('Array of {}').format(x[1]), DD, mandatory]
184
- elif type == 'object':
185
- required = schema.get('required', [])
186
- for prop_name, prop in schema.get('properties', {}).items():
187
- for x in _process_one(
188
- prefix+[prop_name],
189
- prop,
190
- prop_name in required,
191
- entities,
192
- convert):
193
- yield x
194
- if 'additionalProperties' in schema:
195
- D = _('Additional properties')
196
- if schema['additionalProperties'] is True:
197
- T = ''
198
- elif schema['additionalProperties'] is False:
199
- return
200
- else:
201
- if 'oneOf' in schema['additionalProperties']:
202
- T = _("One of {}").format(
203
- ", ".join(_get_multi_type(schema['additionalProperties'], entities)))
204
- elif 'allOf' in schema['additionalProperties']:
205
- T = _("All of {}").format(
206
- ", ".join(_get_multi_type(schema['additionalProperties'], entities)))
207
- elif 'anyOf' in schema['additionalProperties']:
208
- T = _("Any of {}").format(
209
- ", ".join(_get_multi_type(schema['additionalProperties'], entities)))
210
- else:
211
- T = schema['additionalProperties'].get('type', 'object')
212
- yield ['.'.join(prefix+['*']), T, D, '']
213
- elif 'oneOf' in schema:
214
- # One of the subtype, must be basic types or ref
215
- D = _get_description(schema, convert)
216
- C = _get_contraints(schema)
217
- D = _add_constraints(schema, D, C)
218
- T = _get_multi_type(schema, entities)
219
- T = _("One of {}").format(", ".join(T))
220
- yield ['.'.join(prefix), T, D, mandatory]
221
- elif 'allOf' in schema:
222
- # All of the subtype, must be basic types or ref
223
- D = _get_description(schema, convert)
224
- C = _get_contraints(schema)
225
- D = _add_constraints(schema, D, C)
226
- T = _get_multi_type(schema, entities)
227
- T = _("All of {}").format(", ".join(T))
228
- yield ['.'.join(prefix), T, D, mandatory]
229
- elif 'anyOf' in schema:
230
- # Any of the subtype, must be basic types or ref
231
- D = _get_description(schema, convert)
232
- C = _get_contraints(schema)
233
- D = _add_constraints(schema, D, C)
234
- T = _get_multi_type(schema, entities)
235
- T = _("Any of {}").format(", ".join(T))
236
- yield ['.'.join(prefix), T, D, mandatory]
237
- elif type in ['string', 'integer', 'number', 'boolean']:
238
- T = type
239
- if schema.get('format', ''):
240
- T += '/' + schema.get('format', '')
241
- D = _get_description(schema, convert)
242
- C = _get_contraints(schema)
243
- D = _add_constraints(schema, D, C)
244
- yield ['.'.join(prefix), T, D, mandatory]
245
-
246
-
247
- def _build(name, schema, entities, convert, options):
248
- if 'type' not in schema:
249
- schema['type'] = 'object'
250
- if schema.get('type', '') not in ['object', 'array']:
251
- return ''
252
-
253
- yield ''
254
- yield '.. _'+entities('/components/schemas/'+name)+':'
255
- yield ''
256
- yield name
257
- yield options['header'] * len(name)
258
- yield ''
259
- D = _get_description(schema, convert)
260
- if D:
261
- yield D
262
- yield ''
263
- yield '.. list-table:: ' + name
264
- yield ' :header-rows: 1'
265
- yield ' :widths: 25 25 45 15'
266
- yield ' :class: longtable'
267
- yield ''
268
- yield ' * - ' + _('Attribute')
269
- yield ' - ' + _('Type')
270
- yield ' - ' + _('Description')
271
- yield ' - ' + _('Required')
272
-
273
- for item in _process_one([], schema, False, entities, convert):
274
- if str(item[0]):
275
- yield ' * - ``' + str(item[0]) + '``'
276
- else:
277
- yield ' * - ' + _('N/A')
278
- yield ' - ' + textwrap.indent(str(item[1]), ' ').lstrip()
279
- yield ' - ' + textwrap.indent(str(item[2]), ' ').lstrip()
280
- yield ' - ' + _('Yes') if item[3] else ' -'
281
-
282
- if 'example' in schema or 'examples' in schema:
283
- N = 1
284
- for ex in [schema.get('example', None)] + schema.get('examples', []):
285
- if ex is None:
286
- continue
287
- yield ''
288
- yield _('Example #{}:').format(N)
289
- N += 1
290
- # validate the example against this schema
291
- try:
292
- validate(instance=ex, schema=schema)
293
- yield ''
294
- yield '.. code-block:: json'
295
- yield ''
296
- for line in json.dumps(ex, indent=2).splitlines():
297
- yield ' ' + line
298
- except jsonschema.ValidationError:
299
- yield ''
300
- yield '**{}**'.format(_('Invalid example'))
301
-
302
-
303
- def ref2link(entities, ref):
304
- if ref in ['object', 'string']:
305
- return ref
306
- name = ref.split('/')[-1]
307
- if ref[0] == '#':
308
- ref = ref[1:]
309
- if callable(entities):
310
- ref = entities(ref)
311
- return ':ref:`{name} <{ref}>`'.format(**locals())
312
- else:
313
- return '{name}'.format(**locals())
314
-
315
-
316
- def _entities(spec, ref):
317
- m = hashlib.md5()
318
- m.update(spec.get('info', {}).get('title', '').encode('utf-8'))
319
- m.update(spec.get('info', {}).get('version', '0.0').encode('utf-8'))
320
- key = m.hexdigest()
321
- # for unit tests
322
- if key == '30565a8911a6bb487e3745c0ea3c8224':
323
- key = ''
324
- if '#' in ref:
325
- ref = ref.split('#')[1]
326
- return key + ref
327
-
328
-
329
- class ModelRenderer(abc.RestructuredTextRenderer):
330
-
331
- option_spec = {
332
- # prefix (components/schemas)
333
- "prefix": str,
334
- # header marker (')
335
- "header": directives.single_char_or_unicode,
336
- # Markup format to render OpenAPI descriptions.
337
- "format": str,
338
- # A list of entities to be rendered. Must be whitespace delimited.
339
- "entities": lambda s: s.split(),
340
- # Regular expression patterns to include/exclude entities to/from
341
- # rendering. The patterns must be whitespace delimited.
342
- "include": lambda s: s.split(),
343
- "exclude": lambda s: s.split(),
344
- }
345
-
346
- def __init__(self, state, options):
347
- self._state = state
348
- self._options = options
349
- if 'header' not in self._options:
350
- self._options["header"] = "'"
351
- if 'prefix' not in self._options:
352
- self._options["prefix"] = "/components/schemas"
353
-
354
- def render_restructuredtext_markup(self, spec):
355
-
356
- utils.normalize_spec(spec, **self._options)
357
-
358
- convert = utils.get_text_converter(self._options)
359
-
360
- schemas = spec
361
- for p in filter(None, self._options["prefix"].split('/')):
362
- schemas = schemas.get(p, {})
363
-
364
- # Entities list to be processed
365
- entities = []
366
-
367
- # If 'entities' are passed we've got to ensure they exist within an OpenAPI
368
- # spec; otherwise raise error and ask user to fix that.
369
- if 'entities' in self._options:
370
- if not set(self._options['entities']).issubset(schemas.keys()):
371
- raise ValueError(
372
- 'One or more entities are not defined in the spec: %s.' % (
373
- ', '.join(set(self._options['entities']) - set(schemas.keys())),
374
- )
375
- )
376
- entities = self._options['entities']
377
-
378
- # Check against regular expressions to be included
379
- if 'include' in self._options:
380
- # use a set to avoid duplicates
381
- new_entities = set()
382
- for i in self._options['include']:
383
- ir = re.compile(i)
384
- for entity in schemas.keys():
385
- if ir.match(entity):
386
- new_entities.add(entity)
387
- # preserve order
388
- new_list = []
389
- for i in schemas.keys():
390
- if i in new_entities or i in entities:
391
- new_list.append(i)
392
- entities = new_list
393
-
394
- # If no include nor entities option, then take full entity
395
- if 'include' not in self._options and 'entities' not in self._options:
396
- entities = list(schemas.keys())
397
-
398
- # Remove entities matching regexp
399
- if 'exclude' in self._options:
400
- exc_entities = set()
401
- for e in self._options['exclude']:
402
- er = re.compile(e)
403
- for entity in entities:
404
- if er.match(entity):
405
- exc_entities.add(entity)
406
- # remove like that to preserve order
407
- for entity in exc_entities:
408
- entities.remove(entity)
409
-
410
- def __entities(x):
411
- return _entities(spec, x)
412
-
413
- for name in entities:
414
- schema = schemas[name]
415
- for line in _build(name, schema, __entities, convert, self._options):
416
- line_stripped = line.rstrip()
417
- if '\n' in line_stripped:
418
- for line_splitted in line_stripped.splitlines():
419
- yield line_splitted
420
- else:
421
- yield line_stripped
422
- yield ''
1
+ from . import abc
2
+ from .. import utils
3
+
4
+ import re
5
+ import hashlib
6
+ import json
7
+ import textwrap
8
+
9
+ import jsonschema
10
+ from jsonschema import validate
11
+ from docutils.parsers.rst import directives
12
+
13
+ from sphinx.locale import get_translation
14
+ _ = get_translation('openapi')
15
+
16
+
17
+ def _get_description(obj, convert):
18
+ d = obj.get('description', '')
19
+ D = convert(d).strip()
20
+ # if d.strip()!=D:
21
+ if '\n' in D:
22
+ D += '\n\n'
23
+ if 'default' in obj:
24
+ if D and D.splitlines()[-1] and D[-1] != '.':
25
+ D += '.'
26
+ if D and D.splitlines()[-1]:
27
+ D += ' '
28
+ D += 'Default: ``' + json.dumps(obj['default']) + "``."
29
+ return D
30
+
31
+
32
+ def _get_contraints(obj):
33
+ c = []
34
+ if 'minItems' in obj:
35
+ c.append(_('minItems is {}').format(obj['minItems']))
36
+ if 'maxItems' in obj:
37
+ c.append(_('maxItems is {}').format(obj['maxItems']))
38
+ if 'minLength' in obj:
39
+ c.append(_('minLength is {}').format(obj['minLength']))
40
+ if 'maxLength' in obj:
41
+ c.append(_('maxLength is {}').format(obj['maxLength']))
42
+ if 'minimum' in obj:
43
+ c.append(_('minimum is {}').format(obj['minimum']))
44
+ if 'maximum' in obj:
45
+ c.append(_('maximum is {}').format(obj['maximum']))
46
+ if 'uniqueItems' in obj:
47
+ c.append(_("items must be unique"))
48
+ if "pattern" in obj:
49
+ c.append(_("pattern ``{}``").format(obj["pattern"]))
50
+ if 'enum' in obj:
51
+ c.append(_('possible values are {}').format(
52
+ ', '.join(
53
+ [
54
+ '``{}``'.format(x) for x in obj['enum']
55
+ ]
56
+ )))
57
+ if 'readOnly' in obj:
58
+ c.append(_("read only"))
59
+ if 'writeOnly' in obj:
60
+ c.append(_("write only"))
61
+ c = [str(x) for x in c]
62
+ s = '; '.join(c)
63
+ return s
64
+
65
+
66
+ def _add_constraints(obj, D, C):
67
+ if C:
68
+ if str(_('Constraints')) not in D:
69
+ C = _("Constraints: {}").format(C)
70
+ if D and D.splitlines()[-1] and D[-1] != '.':
71
+ D += '.'
72
+ else:
73
+ if C and D and D.splitlines()[-1] and D[-1] != ';':
74
+ D += ';'
75
+ if D and D.splitlines()[-1] and C and C[0] != '\n':
76
+ D += ' '
77
+ D += C
78
+ else:
79
+ if D and D.splitlines()[-1] and D[-1] != '.':
80
+ D += '.'
81
+ if 'deprecated' in obj:
82
+ D += "\n\n**{}**".format(_("DEPRECATED"))
83
+ return D
84
+
85
+
86
+ def _get_multi_type(schema, entities):
87
+ T = []
88
+ duplicate = set()
89
+ if 'oneOf' in schema:
90
+ k = 'oneOf'
91
+ elif 'allOf' in schema:
92
+ k = 'allOf'
93
+ else:
94
+ k = 'anyOf'
95
+ for t in schema[k]:
96
+ type = t.get('type', 'object')
97
+ if '$entity_ref' in t and type == 'object':
98
+ T.append(ref2link(entities, t['$entity_ref']))
99
+ else:
100
+ if type == 'string' and 'enum' in t:
101
+ type = 'enumerate'
102
+ vals = ', '.join(
103
+ [
104
+ '``{}``'.format(x) for x in t['enum']
105
+ ]
106
+ )
107
+ if vals:
108
+ type += ' (' + vals + ')'
109
+ if type not in duplicate:
110
+ T.append(type)
111
+ duplicate.add(type)
112
+ return T
113
+
114
+
115
+ def _process_one(prefix, schema, mandatory, entities, convert):
116
+ if 'oneOf' in schema:
117
+ type = 'oneOf'
118
+ elif 'allOf' in schema:
119
+ type = 'allOf'
120
+ elif 'anyOf' in schema:
121
+ type = 'anyOf'
122
+ else:
123
+ type = schema.get('type', 'object')
124
+ if '$entity_ref' in schema and type == 'object' and prefix:
125
+ # does not apply to first level types (prefix empty)
126
+ T = _('Object of type {}').format(ref2link(entities, schema['$entity_ref']))
127
+ D = _get_description(schema, convert)
128
+ C = _get_contraints(schema)
129
+ D = _add_constraints(schema, D, C)
130
+ ret = ['.'.join(prefix), T, D, mandatory]
131
+ yield ret
132
+ elif type == 'array':
133
+ ref = schema['items'].get('$entity_ref', None)
134
+ type_items = schema['items'].get('type', None)
135
+ if ref:
136
+ D = _get_description(schema, convert)
137
+ C = _get_contraints(schema)
138
+ D = _add_constraints(schema, D, C)
139
+ if type_items not in ['object', 'array']:
140
+ # Since only object and array are described on their own
141
+ # Add here the constraints of the referenced type
142
+ D = _get_description(schema['items'], convert)
143
+ C = _get_contraints(schema['items'])
144
+ D = _add_constraints(schema['items'], D, C)
145
+ C = _get_contraints(schema)
146
+ D = _add_constraints(schema, D, C)
147
+ yield [
148
+ '.'.join(prefix),
149
+ _('Array of {}').format(type_items),
150
+ D,
151
+ mandatory
152
+ ]
153
+ else:
154
+ yield [
155
+ '.'.join(prefix),
156
+ _('Array of {}').format(ref2link(entities, ref)),
157
+ D,
158
+ mandatory
159
+ ]
160
+ elif type_items == 'object':
161
+ T = _("Array")
162
+ D = _get_description(schema, convert)
163
+ C = _get_contraints(schema)
164
+ D = _add_constraints(schema, D, C)
165
+ yield ['.'.join(prefix), T, D, mandatory]
166
+ if prefix:
167
+ prefix[-1] += '[]'
168
+ else:
169
+ prefix = ['[]']
170
+ for x in _process_one(prefix, schema['items'], False, entities, convert):
171
+ yield x
172
+ else:
173
+ # Support array of simple types (string, etc.)
174
+ D = _get_description(schema, convert)
175
+ C = _get_contraints(schema)
176
+ for x in _process_one(prefix, schema['items'], False, entities, convert):
177
+ # Add C to x[2] now and not before (to avoid double "Constraints:")
178
+ if D and x[2]:
179
+ DD = D + ' ' + x[2]
180
+ else:
181
+ DD = D + x[2]
182
+ DD = _add_constraints(schema, DD, C)
183
+ yield [x[0], _('Array of {}').format(x[1]), DD, mandatory]
184
+ elif type == 'object':
185
+ required = schema.get('required', [])
186
+ for prop_name, prop in schema.get('properties', {}).items():
187
+ for x in _process_one(
188
+ prefix+[prop_name],
189
+ prop,
190
+ prop_name in required,
191
+ entities,
192
+ convert):
193
+ yield x
194
+ if 'additionalProperties' in schema:
195
+ D = _('Additional properties')
196
+ if schema['additionalProperties'] is True:
197
+ T = ''
198
+ elif schema['additionalProperties'] is False:
199
+ return
200
+ else:
201
+ if 'oneOf' in schema['additionalProperties']:
202
+ T = _("One of {}").format(
203
+ ", ".join(_get_multi_type(schema['additionalProperties'], entities)))
204
+ elif 'allOf' in schema['additionalProperties']:
205
+ T = _("All of {}").format(
206
+ ", ".join(_get_multi_type(schema['additionalProperties'], entities)))
207
+ elif 'anyOf' in schema['additionalProperties']:
208
+ T = _("Any of {}").format(
209
+ ", ".join(_get_multi_type(schema['additionalProperties'], entities)))
210
+ else:
211
+ T = schema['additionalProperties'].get('type', 'object')
212
+ yield ['.'.join(prefix+['*']), T, D, '']
213
+ elif 'oneOf' in schema:
214
+ # One of the subtype, must be basic types or ref
215
+ D = _get_description(schema, convert)
216
+ C = _get_contraints(schema)
217
+ D = _add_constraints(schema, D, C)
218
+ T = _get_multi_type(schema, entities)
219
+ T = _("One of {}").format(", ".join(T))
220
+ yield ['.'.join(prefix), T, D, mandatory]
221
+ elif 'allOf' in schema:
222
+ # All of the subtype, must be basic types or ref
223
+ D = _get_description(schema, convert)
224
+ C = _get_contraints(schema)
225
+ D = _add_constraints(schema, D, C)
226
+ T = _get_multi_type(schema, entities)
227
+ T = _("All of {}").format(", ".join(T))
228
+ yield ['.'.join(prefix), T, D, mandatory]
229
+ elif 'anyOf' in schema:
230
+ # Any of the subtype, must be basic types or ref
231
+ D = _get_description(schema, convert)
232
+ C = _get_contraints(schema)
233
+ D = _add_constraints(schema, D, C)
234
+ T = _get_multi_type(schema, entities)
235
+ T = _("Any of {}").format(", ".join(T))
236
+ yield ['.'.join(prefix), T, D, mandatory]
237
+ elif type in ['string', 'integer', 'number', 'boolean']:
238
+ T = type
239
+ if schema.get('format', ''):
240
+ T += '/' + schema.get('format', '')
241
+ D = _get_description(schema, convert)
242
+ C = _get_contraints(schema)
243
+ D = _add_constraints(schema, D, C)
244
+ yield ['.'.join(prefix), T, D, mandatory]
245
+
246
+
247
+ def _build(name, schema, entities, convert, options):
248
+ if 'type' in schema and schema['type'] not in ['object', 'array']:
249
+ return ''
250
+
251
+ yield ''
252
+ yield '.. _'+entities('/components/schemas/'+name)+':'
253
+ yield ''
254
+ yield name
255
+ yield options['header'] * len(name)
256
+ yield ''
257
+ D = _get_description(schema, convert)
258
+ if 'type' not in schema and not (set(['oneOf', 'allOf', 'anyOf']) & schema.keys()):
259
+ D += '\n' + _('Any type of content is accepted (number, string or object).')
260
+ if D:
261
+ yield D
262
+ yield ''
263
+ if 'type' not in schema and not (set(['oneOf', 'allOf', 'anyOf']) & schema.keys()):
264
+ pass
265
+ else:
266
+ yield '.. list-table:: ' + name
267
+ yield ' :header-rows: 1'
268
+ yield ' :widths: 25 25 45 15'
269
+ yield ' :class: longtable'
270
+ yield ''
271
+ yield ' * - ' + _('Attribute')
272
+ yield ' - ' + _('Type')
273
+ yield ' - ' + _('Description')
274
+ yield ' - ' + _('Required')
275
+
276
+ for item in _process_one([], schema, False, entities, convert):
277
+ if str(item[0]):
278
+ yield ' * - ``' + str(item[0]) + '``'
279
+ else:
280
+ yield ' * - ' + _('N/A')
281
+ yield ' - ' + textwrap.indent(str(item[1]), ' ').lstrip()
282
+ yield ' - ' + textwrap.indent(str(item[2]), ' ').lstrip()
283
+ yield ' - ' + _('Yes') if item[3] else ' -'
284
+
285
+ if 'example' in schema or 'examples' in schema:
286
+ N = 1
287
+ for ex in [schema.get('example', None)] + schema.get('examples', []):
288
+ if ex is None:
289
+ continue
290
+ yield ''
291
+ yield _('Example #{}:').format(N)
292
+ N += 1
293
+ # validate the example against this schema
294
+ try:
295
+ if 'type' in schema:
296
+ validate(instance=ex, schema=schema)
297
+ yield ''
298
+ yield '.. code-block:: json'
299
+ yield ''
300
+ for line in json.dumps(ex, indent=2).splitlines():
301
+ yield ' ' + line
302
+ except jsonschema.ValidationError:
303
+ yield ''
304
+ yield '**{}**'.format(_('Invalid example'))
305
+
306
+
307
+ def ref2link(entities, ref):
308
+ if ref in ['object', 'string']:
309
+ return ref
310
+ name = ref.split('/')[-1]
311
+ if ref[0] == '#':
312
+ ref = ref[1:]
313
+ if callable(entities):
314
+ ref = entities(ref)
315
+ return ':ref:`{name} <{ref}>`'.format(**locals())
316
+ else:
317
+ return '{name}'.format(**locals())
318
+
319
+
320
+ def _entities(spec, ref):
321
+ m = hashlib.md5()
322
+ m.update(spec.get('info', {}).get('title', '').encode('utf-8'))
323
+ m.update(spec.get('info', {}).get('version', '0.0').encode('utf-8'))
324
+ key = m.hexdigest()
325
+ # for unit tests
326
+ if key == '30565a8911a6bb487e3745c0ea3c8224':
327
+ key = ''
328
+ if '#' in ref:
329
+ ref = ref.split('#')[1]
330
+ return key + ref
331
+
332
+
333
+ class ModelRenderer(abc.RestructuredTextRenderer):
334
+
335
+ option_spec = {
336
+ # prefix (components/schemas)
337
+ "prefix": str,
338
+ # header marker (')
339
+ "header": directives.single_char_or_unicode,
340
+ # Markup format to render OpenAPI descriptions.
341
+ "format": str,
342
+ # A list of entities to be rendered. Must be whitespace delimited.
343
+ "entities": lambda s: s.split(),
344
+ # Regular expression patterns to include/exclude entities to/from
345
+ # rendering. The patterns must be whitespace delimited.
346
+ "include": lambda s: s.split(),
347
+ "exclude": lambda s: s.split(),
348
+ }
349
+
350
+ def __init__(self, state, options):
351
+ self._state = state
352
+ self._options = options
353
+ if 'header' not in self._options:
354
+ self._options["header"] = "'"
355
+ if 'prefix' not in self._options:
356
+ self._options["prefix"] = "/components/schemas"
357
+
358
+ def render_restructuredtext_markup(self, spec):
359
+
360
+ utils.normalize_spec(spec, **self._options)
361
+
362
+ convert = utils.get_text_converter(self._options)
363
+
364
+ schemas = spec
365
+ for p in filter(None, self._options["prefix"].split('/')):
366
+ schemas = schemas.get(p, {})
367
+
368
+ # Entities list to be processed
369
+ entities = []
370
+
371
+ # If 'entities' are passed we've got to ensure they exist within an OpenAPI
372
+ # spec; otherwise raise error and ask user to fix that.
373
+ if 'entities' in self._options:
374
+ if not set(self._options['entities']).issubset(schemas.keys()):
375
+ raise ValueError(
376
+ 'One or more entities are not defined in the spec: %s.' % (
377
+ ', '.join(set(self._options['entities']) - set(schemas.keys())),
378
+ )
379
+ )
380
+ entities = self._options['entities']
381
+
382
+ # Check against regular expressions to be included
383
+ if 'include' in self._options:
384
+ # use a set to avoid duplicates
385
+ new_entities = set()
386
+ for i in self._options['include']:
387
+ ir = re.compile(i)
388
+ for entity in schemas.keys():
389
+ if ir.match(entity):
390
+ new_entities.add(entity)
391
+ # preserve order
392
+ new_list = []
393
+ for i in schemas.keys():
394
+ if i in new_entities or i in entities:
395
+ new_list.append(i)
396
+ entities = new_list
397
+
398
+ # If no include nor entities option, then take full entity
399
+ if 'include' not in self._options and 'entities' not in self._options:
400
+ entities = list(schemas.keys())
401
+
402
+ # Remove entities matching regexp
403
+ if 'exclude' in self._options:
404
+ exc_entities = set()
405
+ for e in self._options['exclude']:
406
+ er = re.compile(e)
407
+ for entity in entities:
408
+ if er.match(entity):
409
+ exc_entities.add(entity)
410
+ # remove like that to preserve order
411
+ for entity in exc_entities:
412
+ entities.remove(entity)
413
+
414
+ def __entities(x):
415
+ return _entities(spec, x)
416
+
417
+ for name in entities:
418
+ schema = schemas[name]
419
+ for line in _build(name, schema, __entities, convert, self._options):
420
+ line_stripped = line.rstrip()
421
+ if '\n' in line_stripped:
422
+ for line_splitted in line_stripped.splitlines():
423
+ yield line_splitted
424
+ else:
425
+ yield line_stripped
426
+ yield ''