-
-
Notifications
You must be signed in to change notification settings - Fork 171
/
utils.py
172 lines (139 loc) · 5.03 KB
/
utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
"""Various utilities for parsing OpenAPI operations from docstrings and validating against
the OpenAPI spec.
"""
import re
import json
from distutils import version
from apispec import exceptions
COMPONENT_SUBSECTIONS = {
2: {
"schema": "definitions",
"response": "responses",
"parameter": "parameters",
"security_scheme": "securityDefinitions",
},
3: {
"schema": "schemas",
"response": "responses",
"parameter": "parameters",
"example": "examples",
"security_scheme": "securitySchemes",
},
}
def build_reference(component_type, openapi_major_version, component_name):
"""Return path to reference
:param str component_type: Component type (schema, parameter, response, security_scheme)
:param int openapi_major_version: OpenAPI major version (2 or 3)
:param str component_name: Name of component to reference
"""
return {
"$ref": "#/{}{}/{}".format(
"components/" if openapi_major_version >= 3 else "",
COMPONENT_SUBSECTIONS[openapi_major_version][component_type],
component_name,
)
}
def validate_spec(spec):
"""Validate the output of an :class:`APISpec` object against the
OpenAPI specification.
Note: Requires installing apispec with the ``[validation]`` extras.
::
pip install 'apispec[validation]'
:raise: apispec.exceptions.OpenAPIError if validation fails.
"""
try:
import prance
except ImportError as error: # re-raise with a more verbose message
exc_class = type(error)
raise exc_class(
"validate_spec requires prance to be installed. "
"You can install all validation requirements using:\n"
" pip install 'apispec[validation]'"
)
parser_kwargs = {}
if spec.openapi_version.version[0] == 3:
parser_kwargs["backend"] = "openapi-spec-validator"
try:
prance.BaseParser(spec_string=json.dumps(spec.to_dict()), **parser_kwargs)
except prance.ValidationError as err:
raise exceptions.OpenAPIError(*err.args)
else:
return True
class OpenAPIVersion(version.LooseVersion):
"""OpenAPI version
:param str|OpenAPIVersion openapi_version: OpenAPI version
Parses an OpenAPI version expressed as string. Provides shortcut to digits
(major, minor, patch).
Example: ::
ver = OpenAPIVersion('3.0.2')
assert ver.major == 3
assert ver.minor == 0
assert ver.patch == 1
assert ver.vstring == '3.0.2'
assert str(ver) == '3.0.2'
"""
MIN_INCLUSIVE_VERSION = version.LooseVersion("2.0")
MAX_EXCLUSIVE_VERSION = version.LooseVersion("4.0")
def __init__(self, openapi_version):
if isinstance(openapi_version, version.LooseVersion):
openapi_version = openapi_version.vstring
if (
not self.MIN_INCLUSIVE_VERSION
<= openapi_version
< self.MAX_EXCLUSIVE_VERSION
):
raise exceptions.APISpecError(
f"Not a valid OpenAPI version number: {openapi_version}"
)
super().__init__(openapi_version)
@property
def major(self):
return self.version[0]
@property
def minor(self):
return self.version[1]
@property
def patch(self):
return self.version[2]
# from django.contrib.admindocs.utils
def trim_docstring(docstring):
"""Uniformly trims leading/trailing whitespace from docstrings.
Based on http://www.python.org/peps/pep-0257.html#handling-docstring-indentation
"""
if not docstring or not docstring.strip():
return ""
# Convert tabs to spaces and split into lines
lines = docstring.expandtabs().splitlines()
indent = min(len(line) - len(line.lstrip()) for line in lines if line.lstrip())
trimmed = [lines[0].lstrip()] + [line[indent:].rstrip() for line in lines[1:]]
return "\n".join(trimmed).strip()
# from rest_framework.utils.formatting
def dedent(content):
"""
Remove leading indent from a block of text.
Used when generating descriptions from docstrings.
Note that python's `textwrap.dedent` doesn't quite cut it,
as it fails to dedent multiline docstrings that include
unindented text on the initial line.
"""
whitespace_counts = [
len(line) - len(line.lstrip(" "))
for line in content.splitlines()[1:]
if line.lstrip()
]
# unindent the content if needed
if whitespace_counts:
whitespace_pattern = "^" + (" " * min(whitespace_counts))
content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), "", content)
return content.strip()
# http://stackoverflow.com/a/8310229
def deepupdate(original, update):
"""Recursively update a dict.
Subdict's won't be overwritten but also updated.
"""
for key, value in original.items():
if key not in update:
update[key] = value
elif isinstance(value, dict):
deepupdate(value, update[key])
return update