sc-oa 0.7.0.17__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.
@@ -0,0 +1,137 @@
1
+ """OpenAPI schema utility functions."""
2
+
3
+ from io import StringIO
4
+
5
+
6
+ _DEFAULT_EXAMPLES = {
7
+ "string": "string",
8
+ "integer": 1,
9
+ "number": 1.0,
10
+ "boolean": True,
11
+ "array": [],
12
+ }
13
+
14
+
15
+ _DEFAULT_STRING_EXAMPLES = {
16
+ "date": "2020-01-01",
17
+ "date-time": "2020-01-01T01:01:01Z",
18
+ "password": "********",
19
+ "byte": "QG1pY2hhZWxncmFoYW1ldmFucw==",
20
+ "ipv4": "127.0.0.1",
21
+ "ipv6": "::1",
22
+ }
23
+
24
+
25
+ def example_from_schema(schema):
26
+ """
27
+ Generates an example request/response body from the provided schema.
28
+
29
+ >>> schema = {
30
+ ... "type": "object",
31
+ ... "required": ["id", "name"],
32
+ ... "properties": {
33
+ ... "id": {
34
+ ... "type": "integer",
35
+ ... "format": "int64"
36
+ ... },
37
+ ... "name": {
38
+ ... "type": "string",
39
+ ... "example": "John Smith"
40
+ ... },
41
+ ... "tag": {
42
+ ... "type": "string"
43
+ ... }
44
+ ... }
45
+ ... }
46
+ >>> example = example_from_schema(schema)
47
+ >>> assert example == {
48
+ ... "id": 1,
49
+ ... "name": "John Smith",
50
+ ... "tag": "string"
51
+ ... }
52
+ """
53
+ # If an example was provided then we use that
54
+ if "example" in schema:
55
+ return schema["example"]
56
+
57
+ elif "oneOf" in schema:
58
+ return example_from_schema(schema["oneOf"][0])
59
+
60
+ elif "anyOf" in schema:
61
+ return example_from_schema(schema["anyOf"][0])
62
+
63
+ elif "allOf" in schema:
64
+ # Combine schema examples
65
+ example = {}
66
+ for sub_schema in schema["allOf"]:
67
+ example.update(example_from_schema(sub_schema))
68
+ return example
69
+
70
+ elif "enum" in schema:
71
+ return schema["enum"][0]
72
+
73
+ elif "type" not in schema:
74
+ # Any type
75
+ return _DEFAULT_EXAMPLES["integer"]
76
+
77
+ elif schema["type"] == "object" or "properties" in schema:
78
+ example = {}
79
+ for prop, prop_schema in schema.get("properties", {}).items():
80
+ example[prop] = example_from_schema(prop_schema)
81
+ return example
82
+
83
+ elif schema["type"] == "array":
84
+ items = schema["items"]
85
+ min_length = schema.get("minItems", 0)
86
+ max_length = schema.get("maxItems", max(min_length, 2))
87
+ assert min_length <= max_length
88
+ # Try generate at least 2 example array items
89
+ gen_length = min(2, max_length) if min_length <= 2 else min_length
90
+
91
+ example_items = []
92
+ if items == {}:
93
+ # Any-type arrays
94
+ example_items.extend(_DEFAULT_EXAMPLES.values())
95
+ elif isinstance(items, dict) and "oneOf" in items:
96
+ # Mixed-type arrays
97
+ example_items.append(_DEFAULT_EXAMPLES[sorted(items["oneOf"])[0]])
98
+ else:
99
+ example_items.append(example_from_schema(items))
100
+
101
+ # Generate array containing example_items and satisfying min_length and max_length
102
+ return [example_items[i % len(example_items)] for i in range(gen_length)]
103
+
104
+ elif schema["type"] == "string":
105
+ example_string = _DEFAULT_STRING_EXAMPLES.get(
106
+ schema.get("format", None), _DEFAULT_EXAMPLES["string"]
107
+ )
108
+ min_length = schema.get("minLength", 0)
109
+ max_length = schema.get("maxLength", max(min_length, len(example_string)))
110
+ gen_length = (
111
+ min(len(example_string), max_length)
112
+ if min_length <= len(example_string)
113
+ else min_length
114
+ )
115
+ assert 0 <= min_length <= max_length
116
+ if min_length <= len(example_string) <= max_length:
117
+ return example_string
118
+ else:
119
+ example_builder = StringIO()
120
+ for i in range(gen_length):
121
+ example_builder.write(example_string[i % len(example_string)])
122
+ example_builder.seek(0)
123
+ return example_builder.read()
124
+
125
+ elif schema["type"] in ("integer", "number"):
126
+ example = _DEFAULT_EXAMPLES[schema["type"]]
127
+ if "minimum" in schema and "maximum" in schema:
128
+ # Take average
129
+ example = schema["minimum"] + (schema["maximum"] - schema["minimum"]) / 2
130
+ elif "minimum" in schema and example <= schema["minimum"]:
131
+ example = schema["minimum"] + 1
132
+ elif "maximum" in schema and example >= schema["maximum"]:
133
+ example = schema["maximum"] - 1
134
+ return float(example) if schema["type"] == "number" else int(example)
135
+
136
+ else:
137
+ return _DEFAULT_EXAMPLES[schema["type"]]
@@ -0,0 +1,137 @@
1
+ """
2
+ sphinxcontrib.openapi.utils
3
+ ---------------------------
4
+
5
+ Common functionality shared across the various renderers.
6
+
7
+ :copyright: (c) 2016, Ihor Kalnytskyi.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+
11
+ from __future__ import unicode_literals
12
+
13
+ import collections
14
+ import collections.abc
15
+
16
+ from contextlib import closing
17
+ import jsonschema
18
+ import yaml
19
+ try:
20
+ from m2r2 import convert as convert_markdown
21
+ except ImportError:
22
+ convert_markdown = None
23
+
24
+ from urllib.parse import urlsplit
25
+ from urllib.request import urlopen
26
+
27
+ import os.path
28
+
29
+
30
+ class OpenApiRefResolver(jsonschema.RefResolver):
31
+ """
32
+ Overrides resolve_remote to support both YAML and JSON
33
+ OpenAPI schemas.
34
+ """
35
+
36
+ try:
37
+ import requests
38
+ _requests = requests
39
+ except ImportError:
40
+ _requests = None
41
+
42
+ def resolve_remote(self, uri):
43
+ scheme, _, path, _, _ = urlsplit(uri)
44
+ _, extension = os.path.splitext(path)
45
+
46
+ if extension not in [".yml", ".yaml"] or scheme in self.handlers:
47
+ return super(OpenApiRefResolver, self).resolve_remote(uri)
48
+
49
+ if scheme in [u"http", u"https"] and self._requests:
50
+ response = self._requests.get(uri)
51
+ result = yaml.safe_load(response)
52
+ else:
53
+ # Otherwise, pass off to urllib and assume utf-8
54
+ with closing(urlopen(uri)) as url:
55
+ response = url.read().decode("utf-8")
56
+ result = yaml.safe_load(response)
57
+
58
+ if self.cache_remote:
59
+ self.store[uri] = result
60
+ return result
61
+
62
+
63
+ def _resolve_refs(uri, spec):
64
+ """Resolve JSON references in a given dictionary.
65
+
66
+ OpenAPI spec may contain JSON references to its nodes or external
67
+ sources, so any attempt to rely that there's some expected attribute
68
+ in the spec may fail. So we need to resolve JSON references before
69
+ we use it (i.e. replace with referenced object). For details see:
70
+
71
+ https://tools.ietf.org/html/draft-pbryan-zyp-json-ref-02
72
+
73
+ The input spec is modified in-place despite being returned from
74
+ the function.
75
+ """
76
+
77
+ resolver = OpenApiRefResolver(uri, spec)
78
+
79
+ def _do_resolve(node):
80
+ if isinstance(node, collections.abc.Mapping) and '$ref' in node:
81
+ ref = node['$ref']
82
+ with resolver.resolving(node['$ref']) as resolved:
83
+ ret = _do_resolve(resolved) # might have recursive references
84
+ # Restore the $ref in case we want to
85
+ # separate the entities in the document
86
+ if isinstance(ret, collections.abc.Mapping):
87
+ ret['$entity_ref'] = ref
88
+ return ret
89
+ elif isinstance(node, collections.abc.Mapping):
90
+ for k, v in node.items():
91
+ node[k] = _do_resolve(v)
92
+ elif isinstance(node, (list, tuple)):
93
+ for i in range(len(node)):
94
+ node[i] = _do_resolve(node[i])
95
+ return node
96
+
97
+ return _do_resolve(spec)
98
+
99
+
100
+ def normalize_spec(spec, **options):
101
+ # OpenAPI spec may contain JSON references, so we need resolve them
102
+ # before we access the actual values trying to build an httpdomain
103
+ # markup. Since JSON references may be relative, it's crucial to
104
+ # pass a document URI in order to properly resolve them.
105
+ spec = _resolve_refs(options.get('uri', ''), spec)
106
+
107
+ # OpenAPI spec may contain common endpoint's parameters top-level.
108
+ # In order to do not place if-s around the code to handle special
109
+ # cases, let's normalize the spec and push common parameters inside
110
+ # endpoints definitions.
111
+ for endpoint in spec['paths'].values():
112
+ parameters = endpoint.pop('parameters', [])
113
+ for method in endpoint.values():
114
+ method.setdefault('parameters', [])
115
+ method['parameters'].extend(parameters)
116
+
117
+
118
+ def _conv_md(s):
119
+ try:
120
+ return convert_markdown(s)
121
+ except Exception:
122
+ return s
123
+
124
+
125
+ def get_text_converter(options):
126
+ """Decide on a text converter for prose."""
127
+ if 'format' in options:
128
+ if options['format'] == 'markdown':
129
+ if convert_markdown is None:
130
+ raise ValueError(
131
+ "Markdown conversion isn't available, "
132
+ "install the [markdown] extra."
133
+ )
134
+ return _conv_md
135
+
136
+ # No conversion needed.
137
+ return lambda s: s