utils.py 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. """Various utilities for parsing OpenAPI operations from docstrings and validating against
  2. the OpenAPI spec.
  3. """
  4. from __future__ import annotations
  5. import re
  6. COMPONENT_SUBSECTIONS = {
  7. 2: {
  8. "schema": "definitions",
  9. "response": "responses",
  10. "parameter": "parameters",
  11. "security_scheme": "securityDefinitions",
  12. },
  13. 3: {
  14. "schema": "schemas",
  15. "response": "responses",
  16. "parameter": "parameters",
  17. "header": "headers",
  18. "example": "examples",
  19. "security_scheme": "securitySchemes",
  20. },
  21. }
  22. def build_reference(
  23. component_type: str, openapi_major_version: int, component_name: str
  24. ) -> dict[str, str]:
  25. """Return path to reference
  26. :param str component_type: Component type (schema, parameter, response, security_scheme)
  27. :param int openapi_major_version: OpenAPI major version (2 or 3)
  28. :param str component_name: Name of component to reference
  29. """
  30. return {
  31. "$ref": "#/{}{}/{}".format(
  32. "components/" if openapi_major_version >= 3 else "",
  33. COMPONENT_SUBSECTIONS[openapi_major_version][component_type],
  34. component_name,
  35. )
  36. }
  37. # from django.contrib.admindocs.utils
  38. def trim_docstring(docstring: str) -> str:
  39. """Uniformly trims leading/trailing whitespace from docstrings.
  40. Based on http://www.python.org/peps/pep-0257.html#handling-docstring-indentation
  41. """
  42. if not docstring or not docstring.strip():
  43. return ""
  44. # Convert tabs to spaces and split into lines
  45. lines = docstring.expandtabs().splitlines()
  46. indent = min(len(line) - len(line.lstrip()) for line in lines if line.lstrip())
  47. trimmed = [lines[0].lstrip()] + [line[indent:].rstrip() for line in lines[1:]]
  48. return "\n".join(trimmed).strip()
  49. # from rest_framework.utils.formatting
  50. def dedent(content: str) -> str:
  51. """
  52. Remove leading indent from a block of text.
  53. Used when generating descriptions from docstrings.
  54. Note that python's `textwrap.dedent` doesn't quite cut it,
  55. as it fails to dedent multiline docstrings that include
  56. unindented text on the initial line.
  57. """
  58. whitespace_counts = [
  59. len(line) - len(line.lstrip(" "))
  60. for line in content.splitlines()[1:]
  61. if line.lstrip()
  62. ]
  63. # unindent the content if needed
  64. if whitespace_counts:
  65. whitespace_pattern = "^" + (" " * min(whitespace_counts))
  66. content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), "", content)
  67. return content.strip()
  68. # http://stackoverflow.com/a/8310229
  69. def deepupdate(original: dict, update: dict) -> dict:
  70. """Recursively update a dict.
  71. Subdict's won't be overwritten but also updated.
  72. """
  73. for key, value in original.items():
  74. if key not in update:
  75. update[key] = value
  76. elif isinstance(value, dict):
  77. deepupdate(value, update[key])
  78. return update