sc-oa 0.7.0.13__py3-none-any.whl → 0.7.0.14__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,422 @@
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' 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 ''