Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions openapidocs/common.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import base64
import copy
from abc import ABC, abstractmethod
from dataclasses import asdict, fields, is_dataclass
from dataclasses import asdict, dataclass, fields, is_dataclass
from datetime import date, datetime, time
from enum import Enum
from typing import Any, List, Tuple
from typing import Any, Callable, Iterable, cast
from uuid import UUID

import yaml
Expand All @@ -18,10 +18,12 @@ class Format(Enum):
JSON = "JSON"


@dataclass
class OpenAPIElement:
"""Base class for all OpenAPI Elements"""


@dataclass
class OpenAPIRoot(OpenAPIElement):
"""Base class for a root OpenAPI Documentation"""

Expand Down Expand Up @@ -67,8 +69,8 @@ def normalize_key(key: Any) -> str:
return "".join([first.lower(), *map(str.title, others)])


def normalize_dict_factory(items: List[Tuple[Any, Any]]) -> Any:
data = {}
def normalize_dict_factory(items: list[tuple[Any, Any]]) -> dict[str, Any]:
data: dict[str, Any] = {}
for key, value in items:
if value is None:
continue
Expand All @@ -87,8 +89,8 @@ def normalize_dict_factory(items: List[Tuple[Any, Any]]) -> Any:
return data


def regular_dict_factory(items: List[Tuple[Any, Any]]) -> Any:
data = {}
def regular_dict_factory(items: list[tuple[Any, Any]]) -> dict[Any, Any]:
data: dict[Any, Any] = {}
for key, value in items:
for handler in TYPES_HANDLERS:
value = handler.normalize(value)
Expand All @@ -100,11 +102,11 @@ def regular_dict_factory(items: List[Tuple[Any, Any]]) -> Any:
# replicates the asdict method from dataclasses module, to support
# bypassing "asdict" on child properties when they implement a `to_obj`
# method: some entities require a specific shape when represented
def _asdict_inner(obj, dict_factory):
def _asdict_inner(obj: Any, dict_factory: Callable[[Any], Any]) -> Any:
if hasattr(obj, "to_obj"):
return obj.to_obj()
if isinstance(obj, OpenAPIElement):
result = []
result: list[tuple[str, Any]] = []
for f in fields(obj):
value = _asdict_inner(getattr(obj, f.name), dict_factory)
result.append((f.name, value))
Expand All @@ -115,11 +117,13 @@ def _asdict_inner(obj, dict_factory):
if hasattr(obj, "dict") and callable(obj.dict):
# For Pydantic 1
return obj.dict()
if is_dataclass(obj):
if is_dataclass(obj) and not isinstance(obj, type):
return asdict(obj, dict_factory=regular_dict_factory)
elif isinstance(obj, (list, tuple)):
obj = cast(Iterable[Any], obj)
return type(obj)(_asdict_inner(v, dict_factory) for v in obj)
elif isinstance(obj, dict):
obj = cast(dict[Any, Any], obj)
return type(obj)(
(_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory))
for k, v in obj.items()
Expand All @@ -128,7 +132,7 @@ def _asdict_inner(obj, dict_factory):
return copy.deepcopy(obj)


def normalize_dict(obj):
def normalize_dict(obj: Any) -> Any:
if hasattr(obj, "dict") and callable(obj.dict):
return obj.dict()
if hasattr(obj, "to_obj"):
Expand Down
1 change: 0 additions & 1 deletion openapidocs/logs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import logging
import logging.handlers

from rich.logging import RichHandler

Expand Down
13 changes: 8 additions & 5 deletions openapidocs/mk/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""

from abc import ABC, abstractmethod
from typing import Any, cast


class DocumentsWriter(ABC):
Expand All @@ -14,13 +15,13 @@ class DocumentsWriter(ABC):
"""

@abstractmethod
def write(self, data, **kwargs) -> str:
def write(self, data: object, **kwargs: dict[str, Any]) -> str:
"""
Writes markdown.
"""


def is_reference(data) -> bool:
def is_reference(data: object) -> bool:
"""
Returns a value indicating whether the given dictionary represents
a reference.
Expand All @@ -32,7 +33,7 @@ def is_reference(data) -> bool:
return "$ref" in data


def is_object_schema(data) -> bool:
def is_object_schema(data: object) -> bool:
"""
Returns a value indicating whether the given schema dictionary represents
an object schema.
Expand All @@ -41,10 +42,11 @@ def is_object_schema(data) -> bool:
"""
if not isinstance(data, dict):
return False
data = cast(dict[str, object], data)
return data.get("type") == "object" and isinstance(data.get("properties"), dict)


def is_array_schema(data) -> bool:
def is_array_schema(data: object) -> bool:
"""
Returns a value indicating whether the given schema dictionary represents
an array schema.
Expand All @@ -53,10 +55,11 @@ def is_array_schema(data) -> bool:
"""
if not isinstance(data, dict):
return False
data = cast(dict[str, object], data)
return data.get("type") == "array" and isinstance(data.get("items"), dict)


def get_ref_type_name(reference) -> str:
def get_ref_type_name(reference: dict[str, str] | str) -> str:
"""
Returns the type name of a reference.

Expand Down
20 changes: 11 additions & 9 deletions openapidocs/mk/contents.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
"""
This module contains classes to generate representations of content types by mime type.
"""

import os
from abc import ABC, abstractmethod
from datetime import datetime
from json import JSONEncoder
from typing import Any, Mapping, Sequence
from urllib.parse import urlencode

from essentials.json import FriendlyEncoder, dumps


class OADJSONEncoder(JSONEncoder):
def default(self, obj):
def default(self, o: object) -> Any:
try:
return JSONEncoder.default(self, obj)
return JSONEncoder.default(self, o)
except TypeError:
if isinstance(obj, datetime):
if isinstance(o, datetime):
datetime_format = os.environ.get("OPENAPI_DATETIME_FORMAT")
if datetime_format:
return obj.strftime(datetime_format)
return o.strftime(datetime_format)
else:
return obj.isoformat()
return FriendlyEncoder.default(self, obj) # type: ignore
return o.isoformat()
return FriendlyEncoder.default(self, o) # type: ignore


class ContentWriter(ABC):
Expand All @@ -37,7 +39,7 @@ def handle_content_type(self, content_type: str) -> bool:
"""

@abstractmethod
def write(self, value) -> str:
def write(self, value: Any) -> str:
"""
Writes markdown to represent a value in a certain type of content.
"""
Expand All @@ -47,7 +49,7 @@ class JSONContentWriter(ContentWriter):
def handle_content_type(self, content_type: str) -> bool:
return "json" in content_type.lower()

def write(self, value) -> str:
def write(self, value: object) -> str:
return dumps(value, indent=4, cls=OADJSONEncoder)


Expand All @@ -56,5 +58,5 @@ def handle_content_type(self, content_type: str) -> bool:
# multipart/form-data. Otherwise, use application/x-www-form-urlencoded.
return "x-www-form-urlencoded" == content_type.lower()

def write(self, value) -> str:
def write(self, value: Mapping[Any, Any] | Sequence[tuple[Any, Any]]) -> str:
return urlencode(value)
4 changes: 1 addition & 3 deletions openapidocs/mk/generate.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
from typing import Union

from openapidocs.mk.v3 import OpenAPIV3DocumentationHandler
from openapidocs.utils.source import read_from_source


def generate_document(source: str, destination: str, style: Union[int, str]):
def generate_document(source: str, destination: str, style: int | str):
# Note: if support for more kinds of OAD versions will be added, handle a version
# parameter in this function

Expand Down
5 changes: 3 additions & 2 deletions openapidocs/mk/md.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
This module provides common functions to handle Markdown.
These functions apply to any kind of Markdown work.
"""
from typing import Dict, Iterable

from typing import Iterable


def write_row(
row: Iterable[str],
columns_widths: Dict[int, int],
columns_widths: dict[int, int],
padding: int = 1,
indent: int = 0,
) -> str:
Expand Down
13 changes: 9 additions & 4 deletions openapidocs/utils/source.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""
This module provides methods to obtain OpenAPI Documentation from file or web sources.
"""

import json
from pathlib import Path
from typing import Any

import yaml

Expand All @@ -11,15 +13,15 @@
from .web import ensure_success, http_get


def read_from_json_file(file_path: Path):
def read_from_json_file(file_path: Path) -> dict[Any, Any] | list[Any]:
"""
Reads JSON from a given file by path.
"""
with open(file_path, "rt", encoding="utf-8") as source_file:
return json.loads(source_file.read())


def read_from_yaml_file(file_path: Path):
def read_from_yaml_file(file_path: Path) -> dict[Any, Any] | list[Any]:
"""
Reads YAML from a given file by path.
"""
Expand All @@ -32,7 +34,7 @@ def __init__(self, message: str) -> None:
super().__init__(message)


def read_from_url(url: str):
def read_from_url(url: str) -> dict[Any, Any] | list[Any]:
"""
Tries to read OpenAPI Documentation from the given source URL.
This method will try to fetch JSON or YAML from the given source, in case of
Expand Down Expand Up @@ -63,7 +65,10 @@ def read_from_url(url: str):
)


def read_from_source(source: str, cwd: Path = None):
def read_from_source(
source: str,
cwd: Path | None = None,
) -> dict[Any, Any] | list[Any]:
"""
Tries to read a JSON or YAML file from a given source.
The source can be a path to a file, or a URL.
Expand Down
2 changes: 1 addition & 1 deletion openapidocs/utils/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


class FailedRequestError(Exception):
def __init__(self, message) -> None:
def __init__(self, message: str) -> None:
super().__init__(
f"Failed request: {message}. "
"Inspect the inner exception (__context__) for more information."
Expand Down
Loading