Source code for openapi_diagram.utils

"""Module containing utility functions."""

from __future__ import annotations

import json
from contextlib import contextmanager
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import TYPE_CHECKING
from typing import Literal

import yaml

if TYPE_CHECKING:
    from collections.abc import Generator

Json = dict[str | Literal["anyOf", "type"], "Json"] | list["Json"] | str | bool


[docs] class UnsopportFileTypeError(ValueError): """Error raised when a file type isn't supported."""
[docs] def convert_3_dot_1_to_3_dot_0(json: dict[str, Json]): # noqa: C901, DOC101, DOC109, DOC103 """Attempt to convert version 3.1.0 of some openAPI json into 3.0.3. Ref.: https://github.com/tiangolo/fastapi/discussions/9789#discussioncomment-8629746 Usage: >>> from pprint import pprint >>> json = { ... "some_irrelevant_keys": {...}, ... "nested_dict": {"nested_key": {"anyOf": [{"type": "string"}, {"type": "null"}]}}, ... "examples": [{...}, {...}] ... } >>> convert_3_dot_1_to_3_dot_0(json) >>> pprint(json) {'example': {Ellipsis}, 'nested_dict': {'nested_key': {'anyOf': [{'type': 'string'}], 'nullable': True}}, 'openapi': '3.0.3', 'some_irrelevant_keys': {Ellipsis}} """ json["openapi"] = "3.0.3" def inner(yaml_dict: Json): if isinstance(yaml_dict, dict): if "anyOf" in yaml_dict and isinstance((anyOf := yaml_dict["anyOf"]), list): # noqa: N806 for i, item in enumerate(anyOf): if isinstance(item, dict) and item.get("type") == "null": anyOf.pop(i) yaml_dict["nullable"] = True if "examples" in yaml_dict: examples = yaml_dict["examples"] del yaml_dict["examples"] if isinstance(examples, list) and len(examples): yaml_dict["example"] = examples[0] for value in yaml_dict.values(): inner(value) elif isinstance(yaml_dict, list): for item in yaml_dict: inner(item) inner(json)
[docs] @contextmanager def openapi_3_dot_1_compat(spec_file: Path) -> Generator[Path, None, None]: """Context manager to downgrade openapi 3.1 specs to 3.0 specs. Parameters ---------- spec_file : Path Path to original spec file. Yields ------ Path Path to temporary compat file. Raises ------ ValueError If file format is not supported. """ if spec_file.suffix == ".json": spec_data = json.loads(spec_file.read_text()) elif spec_file.suffix in (".yaml", ".yml"): spec_data = yaml.safe_load(spec_file.read_text()) else: msg = f"File type: *{spec_file.suffix} is not supported." raise UnsopportFileTypeError(msg) with TemporaryDirectory() as tmp_dir: tmp_file = Path(tmp_dir) / "openapi_spec.json" if spec_data["openapi"].startswith("3.1"): convert_3_dot_1_to_3_dot_0(spec_data) tmp_file.write_text(json.dumps(spec_data)) yield tmp_file