"""Various utilities for parsing OpenAPI operations from docstrings and validating against
the OpenAPI spec.
"""
from __future__ import annotations
import re
COMPONENT_SUBSECTIONS = {
2: {
"schema": "definitions",
"response": "responses",
"parameter": "parameters",
"security_scheme": "securityDefinitions",
},
3: {
"schema": "schemas",
"response": "responses",
"parameter": "parameters",
"header": "headers",
"example": "examples",
"security_scheme": "securitySchemes",
},
}
[docs]
def build_reference(
component_type: str, openapi_major_version: int, component_name: str
) -> dict[str, str]:
"""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,
)
}
# from django.contrib.admindocs.utils
[docs]
def trim_docstring(docstring: str) -> str:
"""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
[docs]
def dedent(content: str) -> str:
"""
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
[docs]
def deepupdate(original: dict, update: dict) -> dict:
"""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