essentials-openapi 1.2.1__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/CHANGELOG.md +3 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/PKG-INFO +31 -3
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/README.md +28 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/__init__.py +1 -1
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/commands/docs.py +15 -2
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/common.py +14 -10
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/logs.py +0 -1
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/common.py +8 -5
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/contents.py +11 -9
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/generate.py +10 -3
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/jinja.py +52 -20
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/md.py +3 -2
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/__init__.py +5 -1
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/utils/source.py +9 -4
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/utils/web.py +1 -1
- essentials_openapi-1.3.0/openapidocs/v2.py +362 -0
- essentials_openapi-1.3.0/openapidocs/v3.py +948 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/pyproject.toml +2 -2
- essentials_openapi-1.2.1/openapidocs/v2.py +0 -361
- essentials_openapi-1.2.1/openapidocs/v3.py +0 -947
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/.gitignore +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/LICENSE +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/commands/__init__.py +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/main.py +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/__init__.py +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/texts.py +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/examples.py +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/README.md +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/__init__.py +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/layout.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/partial/components-parameters.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/partial/components-responses.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/partial/components-schemas.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/partial/components-security-schemes.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/partial/content-examples.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/partial/external-docs.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/partial/info.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/partial/path-items.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/partial/request-auth.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/partial/request-body.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/partial/request-parameters.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/partial/request-responses.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/partial/schema-repr.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/partial/servers.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/partial/tags.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_markdown/partial/type.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/README.md +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/__init__.py +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/layout.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/partial/components-parameters.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/partial/components-responses.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/partial/components-schemas.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/partial/components-security-schemes.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/partial/content-examples.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/partial/external-docs.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/partial/info.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/partial/path-items.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/partial/request-auth.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/partial/request-body.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/partial/request-parameters.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/partial/request-responses.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/partial/schema-repr.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/partial/servers.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/partial/tags.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_mkdocs/partial/type.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_plantuml_api/README.md +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_plantuml_api/layout.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_plantuml_api/partial/schema-repr.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_plantuml_schemas/README.md +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_plantuml_schemas/layout.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/mk/v3/views_plantuml_schemas/partial/schema-repr.html +0 -0
- {essentials_openapi-1.2.1 → essentials_openapi-1.3.0}/openapidocs/utils/__init__.py +0 -0
|
@@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.3.0] - 2025-11-19
|
|
9
|
+
- Add support for passing custom Jinja2 templates as an argument, by @sindrehan.
|
|
10
|
+
|
|
8
11
|
## [1.2.1] - 2025-07-30
|
|
9
12
|
|
|
10
13
|
- Added support for using the current working directory (CWD) as an option when
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: essentials-openapi
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Classes to generate OpenAPI Documentation v3 and v2, in JSON and YAML.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Neoteroi/essentials-openapi
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/Neoteroi/essentials-openapi/issues
|
|
@@ -11,12 +11,12 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
-
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
20
|
Requires-Dist: essentials>=1.1.5
|
|
21
21
|
Requires-Dist: markupsafe>=3.0.0
|
|
22
22
|
Requires-Dist: pyyaml>=6
|
|
@@ -102,6 +102,34 @@ oad gen-docs -s source-openapi.json -d schemas.wsd --style "PLANTUML_API"
|
|
|
102
102
|
|
|
103
103
|
_Example of PlantUML diagram generated from path items._
|
|
104
104
|
|
|
105
|
+
#### Using custom templates
|
|
106
|
+
|
|
107
|
+
You can override the default templates by providing a custom templates directory:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
oad gen-docs -s source-openapi.json -d output.md -T ./my-templates/
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The custom templates directory should contain template files with the same names as the built-in templates. Any template file found in the custom directory will override the corresponding default template, while non-overridden templates will use the defaults. This follows the same pattern as [MkDocs template customization](https://www.mkdocs.org/user-guide/customizing-your-theme/#overriding-template-blocks).
|
|
114
|
+
|
|
115
|
+
**Important:** The custom templates directory must match the output style being rendered. Each style (MKDOCS, MARKDOWN, PLANTUML_SCHEMAS, PLANTUML_API) has its own template structure. You need to provide templates appropriate for the `--style` parameter you're using.
|
|
116
|
+
|
|
117
|
+
**Template structure:**
|
|
118
|
+
- `layout.html` - Main layout template
|
|
119
|
+
- `partial/` - Directory containing reusable template components
|
|
120
|
+
|
|
121
|
+
**Example custom template directory structure:**
|
|
122
|
+
```
|
|
123
|
+
my-templates/
|
|
124
|
+
├── layout.html # Overrides main layout
|
|
125
|
+
└── partial/
|
|
126
|
+
├── info.html # Overrides info section
|
|
127
|
+
└── path-items.html # Overrides path items section
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
All templates use [Jinja2](https://jinja.palletsprojects.com/) syntax and have access to the same filters, functions, and context variables as the built-in templates.
|
|
131
|
+
|
|
132
|
+
|
|
105
133
|
### Goals
|
|
106
134
|
|
|
107
135
|
* Provide an API to generate OpenAPI Documentation files.
|
|
@@ -73,6 +73,34 @@ oad gen-docs -s source-openapi.json -d schemas.wsd --style "PLANTUML_API"
|
|
|
73
73
|
|
|
74
74
|
_Example of PlantUML diagram generated from path items._
|
|
75
75
|
|
|
76
|
+
#### Using custom templates
|
|
77
|
+
|
|
78
|
+
You can override the default templates by providing a custom templates directory:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
oad gen-docs -s source-openapi.json -d output.md -T ./my-templates/
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The custom templates directory should contain template files with the same names as the built-in templates. Any template file found in the custom directory will override the corresponding default template, while non-overridden templates will use the defaults. This follows the same pattern as [MkDocs template customization](https://www.mkdocs.org/user-guide/customizing-your-theme/#overriding-template-blocks).
|
|
85
|
+
|
|
86
|
+
**Important:** The custom templates directory must match the output style being rendered. Each style (MKDOCS, MARKDOWN, PLANTUML_SCHEMAS, PLANTUML_API) has its own template structure. You need to provide templates appropriate for the `--style` parameter you're using.
|
|
87
|
+
|
|
88
|
+
**Template structure:**
|
|
89
|
+
- `layout.html` - Main layout template
|
|
90
|
+
- `partial/` - Directory containing reusable template components
|
|
91
|
+
|
|
92
|
+
**Example custom template directory structure:**
|
|
93
|
+
```
|
|
94
|
+
my-templates/
|
|
95
|
+
├── layout.html # Overrides main layout
|
|
96
|
+
└── partial/
|
|
97
|
+
├── info.html # Overrides info section
|
|
98
|
+
└── path-items.html # Overrides path items section
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
All templates use [Jinja2](https://jinja.palletsprojects.com/) syntax and have access to the same filters, functions, and context variables as the built-in templates.
|
|
102
|
+
|
|
103
|
+
|
|
76
104
|
### Goals
|
|
77
105
|
|
|
78
106
|
* Provide an API to generate OpenAPI Documentation files.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "1.
|
|
1
|
+
__version__ = "1.3.0"
|
|
2
2
|
VERSION = __version__
|
|
@@ -31,7 +31,20 @@ from openapidocs.mk.jinja import OutputStyle
|
|
|
31
31
|
default="MKDOCS",
|
|
32
32
|
show_default=True,
|
|
33
33
|
)
|
|
34
|
-
|
|
34
|
+
@click.option(
|
|
35
|
+
"-T",
|
|
36
|
+
"--templates",
|
|
37
|
+
help=(
|
|
38
|
+
"Path to a custom templates directory. "
|
|
39
|
+
"Templates in this directory will override default templates with matching names. "
|
|
40
|
+
"Unspecified templates will use defaults."
|
|
41
|
+
),
|
|
42
|
+
required=False,
|
|
43
|
+
default=None,
|
|
44
|
+
)
|
|
45
|
+
def generate_documents_command(
|
|
46
|
+
source: str, destination: str, style: Union[int, str], templates: Union[str, None]
|
|
47
|
+
):
|
|
35
48
|
"""
|
|
36
49
|
Generates other kinds of documents from source OpenAPI Documentation files.
|
|
37
50
|
|
|
@@ -48,7 +61,7 @@ def generate_documents_command(source: str, destination: str, style: Union[int,
|
|
|
48
61
|
https://github.com/Neoteroi/essentials-openapi
|
|
49
62
|
"""
|
|
50
63
|
try:
|
|
51
|
-
generate_document(source, destination, style)
|
|
64
|
+
generate_document(source, destination, style, templates)
|
|
52
65
|
except KeyboardInterrupt: # pragma: nocover
|
|
53
66
|
logger.info("User interrupted")
|
|
54
67
|
exit(1)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import copy
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from dataclasses import asdict, fields, is_dataclass
|
|
4
|
+
from dataclasses import asdict, dataclass, fields, is_dataclass
|
|
5
5
|
from datetime import date, datetime, time
|
|
6
6
|
from enum import Enum
|
|
7
|
-
from typing import Any,
|
|
7
|
+
from typing import Any, Callable, Iterable, cast
|
|
8
8
|
from uuid import UUID
|
|
9
9
|
|
|
10
10
|
import yaml
|
|
@@ -18,10 +18,12 @@ class Format(Enum):
|
|
|
18
18
|
JSON = "JSON"
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
@dataclass
|
|
21
22
|
class OpenAPIElement:
|
|
22
23
|
"""Base class for all OpenAPI Elements"""
|
|
23
24
|
|
|
24
25
|
|
|
26
|
+
@dataclass
|
|
25
27
|
class OpenAPIRoot(OpenAPIElement):
|
|
26
28
|
"""Base class for a root OpenAPI Documentation"""
|
|
27
29
|
|
|
@@ -67,8 +69,8 @@ def normalize_key(key: Any) -> str:
|
|
|
67
69
|
return "".join([first.lower(), *map(str.title, others)])
|
|
68
70
|
|
|
69
71
|
|
|
70
|
-
def normalize_dict_factory(items:
|
|
71
|
-
data = {}
|
|
72
|
+
def normalize_dict_factory(items: list[tuple[Any, Any]]) -> dict[str, Any]:
|
|
73
|
+
data: dict[str, Any] = {}
|
|
72
74
|
for key, value in items:
|
|
73
75
|
if value is None:
|
|
74
76
|
continue
|
|
@@ -87,8 +89,8 @@ def normalize_dict_factory(items: List[Tuple[Any, Any]]) -> Any:
|
|
|
87
89
|
return data
|
|
88
90
|
|
|
89
91
|
|
|
90
|
-
def regular_dict_factory(items:
|
|
91
|
-
data = {}
|
|
92
|
+
def regular_dict_factory(items: list[tuple[Any, Any]]) -> dict[Any, Any]:
|
|
93
|
+
data: dict[Any, Any] = {}
|
|
92
94
|
for key, value in items:
|
|
93
95
|
for handler in TYPES_HANDLERS:
|
|
94
96
|
value = handler.normalize(value)
|
|
@@ -100,11 +102,11 @@ def regular_dict_factory(items: List[Tuple[Any, Any]]) -> Any:
|
|
|
100
102
|
# replicates the asdict method from dataclasses module, to support
|
|
101
103
|
# bypassing "asdict" on child properties when they implement a `to_obj`
|
|
102
104
|
# method: some entities require a specific shape when represented
|
|
103
|
-
def _asdict_inner(obj, dict_factory):
|
|
105
|
+
def _asdict_inner(obj: Any, dict_factory: Callable[[Any], Any]) -> Any:
|
|
104
106
|
if hasattr(obj, "to_obj"):
|
|
105
107
|
return obj.to_obj()
|
|
106
108
|
if isinstance(obj, OpenAPIElement):
|
|
107
|
-
result = []
|
|
109
|
+
result: list[tuple[str, Any]] = []
|
|
108
110
|
for f in fields(obj):
|
|
109
111
|
value = _asdict_inner(getattr(obj, f.name), dict_factory)
|
|
110
112
|
result.append((f.name, value))
|
|
@@ -115,11 +117,13 @@ def _asdict_inner(obj, dict_factory):
|
|
|
115
117
|
if hasattr(obj, "dict") and callable(obj.dict):
|
|
116
118
|
# For Pydantic 1
|
|
117
119
|
return obj.dict()
|
|
118
|
-
if is_dataclass(obj):
|
|
120
|
+
if is_dataclass(obj) and not isinstance(obj, type):
|
|
119
121
|
return asdict(obj, dict_factory=regular_dict_factory)
|
|
120
122
|
elif isinstance(obj, (list, tuple)):
|
|
123
|
+
obj = cast(Iterable[Any], obj)
|
|
121
124
|
return type(obj)(_asdict_inner(v, dict_factory) for v in obj)
|
|
122
125
|
elif isinstance(obj, dict):
|
|
126
|
+
obj = cast(dict[Any, Any], obj)
|
|
123
127
|
return type(obj)(
|
|
124
128
|
(_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory))
|
|
125
129
|
for k, v in obj.items()
|
|
@@ -128,7 +132,7 @@ def _asdict_inner(obj, dict_factory):
|
|
|
128
132
|
return copy.deepcopy(obj)
|
|
129
133
|
|
|
130
134
|
|
|
131
|
-
def normalize_dict(obj):
|
|
135
|
+
def normalize_dict(obj: Any) -> Any:
|
|
132
136
|
if hasattr(obj, "dict") and callable(obj.dict):
|
|
133
137
|
return obj.dict()
|
|
134
138
|
if hasattr(obj, "to_obj"):
|
|
@@ -5,6 +5,7 @@ from source OAD files.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
|
+
from typing import Any, cast
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class DocumentsWriter(ABC):
|
|
@@ -14,13 +15,13 @@ class DocumentsWriter(ABC):
|
|
|
14
15
|
"""
|
|
15
16
|
|
|
16
17
|
@abstractmethod
|
|
17
|
-
def write(self, data, **kwargs) -> str:
|
|
18
|
+
def write(self, data: object, **kwargs: dict[str, Any]) -> str:
|
|
18
19
|
"""
|
|
19
20
|
Writes markdown.
|
|
20
21
|
"""
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
def is_reference(data) -> bool:
|
|
24
|
+
def is_reference(data: object) -> bool:
|
|
24
25
|
"""
|
|
25
26
|
Returns a value indicating whether the given dictionary represents
|
|
26
27
|
a reference.
|
|
@@ -32,7 +33,7 @@ def is_reference(data) -> bool:
|
|
|
32
33
|
return "$ref" in data
|
|
33
34
|
|
|
34
35
|
|
|
35
|
-
def is_object_schema(data) -> bool:
|
|
36
|
+
def is_object_schema(data: object) -> bool:
|
|
36
37
|
"""
|
|
37
38
|
Returns a value indicating whether the given schema dictionary represents
|
|
38
39
|
an object schema.
|
|
@@ -41,10 +42,11 @@ def is_object_schema(data) -> bool:
|
|
|
41
42
|
"""
|
|
42
43
|
if not isinstance(data, dict):
|
|
43
44
|
return False
|
|
45
|
+
data = cast(dict[str, object], data)
|
|
44
46
|
return data.get("type") == "object" and isinstance(data.get("properties"), dict)
|
|
45
47
|
|
|
46
48
|
|
|
47
|
-
def is_array_schema(data) -> bool:
|
|
49
|
+
def is_array_schema(data: object) -> bool:
|
|
48
50
|
"""
|
|
49
51
|
Returns a value indicating whether the given schema dictionary represents
|
|
50
52
|
an array schema.
|
|
@@ -53,10 +55,11 @@ def is_array_schema(data) -> bool:
|
|
|
53
55
|
"""
|
|
54
56
|
if not isinstance(data, dict):
|
|
55
57
|
return False
|
|
58
|
+
data = cast(dict[str, object], data)
|
|
56
59
|
return data.get("type") == "array" and isinstance(data.get("items"), dict)
|
|
57
60
|
|
|
58
61
|
|
|
59
|
-
def get_ref_type_name(reference) -> str:
|
|
62
|
+
def get_ref_type_name(reference: dict[str, str] | str) -> str:
|
|
60
63
|
"""
|
|
61
64
|
Returns the type name of a reference.
|
|
62
65
|
|
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
"""
|
|
2
2
|
This module contains classes to generate representations of content types by mime type.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
import os
|
|
5
6
|
from abc import ABC, abstractmethod
|
|
6
7
|
from datetime import datetime
|
|
7
8
|
from json import JSONEncoder
|
|
9
|
+
from typing import Any, Mapping, Sequence
|
|
8
10
|
from urllib.parse import urlencode
|
|
9
11
|
|
|
10
12
|
from essentials.json import FriendlyEncoder, dumps
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
class OADJSONEncoder(JSONEncoder):
|
|
14
|
-
def default(self,
|
|
16
|
+
def default(self, o: object) -> Any:
|
|
15
17
|
try:
|
|
16
|
-
return JSONEncoder.default(self,
|
|
18
|
+
return JSONEncoder.default(self, o)
|
|
17
19
|
except TypeError:
|
|
18
|
-
if isinstance(
|
|
20
|
+
if isinstance(o, datetime):
|
|
19
21
|
datetime_format = os.environ.get("OPENAPI_DATETIME_FORMAT")
|
|
20
22
|
if datetime_format:
|
|
21
|
-
return
|
|
23
|
+
return o.strftime(datetime_format)
|
|
22
24
|
else:
|
|
23
|
-
return
|
|
24
|
-
return FriendlyEncoder.default(self,
|
|
25
|
+
return o.isoformat()
|
|
26
|
+
return FriendlyEncoder.default(self, o) # type: ignore
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
class ContentWriter(ABC):
|
|
@@ -37,7 +39,7 @@ class ContentWriter(ABC):
|
|
|
37
39
|
"""
|
|
38
40
|
|
|
39
41
|
@abstractmethod
|
|
40
|
-
def write(self, value) -> str:
|
|
42
|
+
def write(self, value: Any) -> str:
|
|
41
43
|
"""
|
|
42
44
|
Writes markdown to represent a value in a certain type of content.
|
|
43
45
|
"""
|
|
@@ -47,7 +49,7 @@ class JSONContentWriter(ContentWriter):
|
|
|
47
49
|
def handle_content_type(self, content_type: str) -> bool:
|
|
48
50
|
return "json" in content_type.lower()
|
|
49
51
|
|
|
50
|
-
def write(self, value) -> str:
|
|
52
|
+
def write(self, value: object) -> str:
|
|
51
53
|
return dumps(value, indent=4, cls=OADJSONEncoder)
|
|
52
54
|
|
|
53
55
|
|
|
@@ -56,5 +58,5 @@ class FormContentWriter(ContentWriter):
|
|
|
56
58
|
# multipart/form-data. Otherwise, use application/x-www-form-urlencoded.
|
|
57
59
|
return "x-www-form-urlencoded" == content_type.lower()
|
|
58
60
|
|
|
59
|
-
def write(self, value) -> str:
|
|
61
|
+
def write(self, value: Mapping[Any, Any] | Sequence[tuple[Any, Any]]) -> str:
|
|
60
62
|
return urlencode(value)
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Optional
|
|
2
2
|
|
|
3
3
|
from openapidocs.mk.v3 import OpenAPIV3DocumentationHandler
|
|
4
4
|
from openapidocs.utils.source import read_from_source
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def generate_document(
|
|
7
|
+
def generate_document(
|
|
8
|
+
source: str,
|
|
9
|
+
destination: str,
|
|
10
|
+
style: int | str,
|
|
11
|
+
templates_path: Optional[str] = None,
|
|
12
|
+
):
|
|
8
13
|
# Note: if support for more kinds of OAD versions will be added, handle a version
|
|
9
14
|
# parameter in this function
|
|
10
15
|
|
|
11
16
|
data = read_from_source(source)
|
|
12
|
-
handler = OpenAPIV3DocumentationHandler(
|
|
17
|
+
handler = OpenAPIV3DocumentationHandler(
|
|
18
|
+
data, style=style, source=source, templates_path=templates_path
|
|
19
|
+
)
|
|
13
20
|
|
|
14
21
|
html = handler.write()
|
|
15
22
|
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
"""
|
|
2
2
|
This module provides a Jinja2 environment.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
import os
|
|
5
6
|
from enum import Enum
|
|
6
|
-
|
|
7
|
-
from
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from jinja2 import (
|
|
11
|
+
ChoiceLoader,
|
|
12
|
+
Environment,
|
|
13
|
+
FileSystemLoader,
|
|
14
|
+
PackageLoader,
|
|
15
|
+
Template,
|
|
16
|
+
select_autoescape,
|
|
17
|
+
)
|
|
8
18
|
|
|
9
19
|
from . import get_http_status_phrase, highlight_params, read_dict, sort_dict
|
|
10
20
|
from .common import DocumentsWriter, is_reference
|
|
@@ -62,29 +72,50 @@ class PackageLoadingError(ValueError):
|
|
|
62
72
|
|
|
63
73
|
|
|
64
74
|
def get_environment(
|
|
65
|
-
package_name: str,
|
|
75
|
+
package_name: str,
|
|
76
|
+
views_style: OutputStyle = OutputStyle.MKDOCS,
|
|
77
|
+
custom_templates_path: Optional[str] = None,
|
|
66
78
|
) -> Environment:
|
|
67
79
|
templates_folder = f"views_{views_style.name}".lower()
|
|
68
80
|
|
|
81
|
+
loaders = []
|
|
82
|
+
|
|
83
|
+
# If custom templates path is provided, validate and add FileSystemLoader first
|
|
84
|
+
if custom_templates_path:
|
|
85
|
+
custom_path = Path(custom_templates_path)
|
|
86
|
+
if not custom_path.exists():
|
|
87
|
+
raise ValueError(
|
|
88
|
+
f"Custom templates path does not exist: {custom_templates_path}"
|
|
89
|
+
)
|
|
90
|
+
if not custom_path.is_dir():
|
|
91
|
+
raise ValueError(
|
|
92
|
+
f"Custom templates path is not a directory: {custom_templates_path}"
|
|
93
|
+
)
|
|
94
|
+
loaders.append(FileSystemLoader(str(custom_path)))
|
|
95
|
+
|
|
96
|
+
# Always add the package loader as fallback
|
|
69
97
|
try:
|
|
70
|
-
|
|
98
|
+
loaders.append(PackageLoader(package_name, templates_folder))
|
|
71
99
|
except ValueError as package_loading_error: # pragma: no cover
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
100
|
+
if not custom_templates_path:
|
|
101
|
+
raise PackageLoadingError(
|
|
102
|
+
views_style, templates_folder
|
|
103
|
+
) from package_loading_error
|
|
104
|
+
|
|
105
|
+
loader = ChoiceLoader(loaders)
|
|
106
|
+
|
|
107
|
+
env = Environment(
|
|
108
|
+
loader=loader,
|
|
109
|
+
autoescape=select_autoescape(["html", "xml"])
|
|
110
|
+
if os.environ.get("SELECT_AUTOESCAPE") in {"YES", "Y", "1"}
|
|
111
|
+
else False,
|
|
112
|
+
auto_reload=True,
|
|
113
|
+
enable_async=False,
|
|
114
|
+
)
|
|
115
|
+
configure_filters(env)
|
|
116
|
+
configure_functions(env)
|
|
86
117
|
|
|
87
|
-
|
|
118
|
+
return env
|
|
88
119
|
|
|
89
120
|
|
|
90
121
|
class Jinja2DocumentsWriter(DocumentsWriter):
|
|
@@ -97,8 +128,9 @@ class Jinja2DocumentsWriter(DocumentsWriter):
|
|
|
97
128
|
self,
|
|
98
129
|
package_name: str,
|
|
99
130
|
views_style: OutputStyle = OutputStyle.MKDOCS,
|
|
131
|
+
custom_templates_path: Optional[str] = None,
|
|
100
132
|
) -> None:
|
|
101
|
-
self._env = get_environment(package_name, views_style)
|
|
133
|
+
self._env = get_environment(package_name, views_style, custom_templates_path)
|
|
102
134
|
|
|
103
135
|
@property
|
|
104
136
|
def env(self) -> Environment:
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
This module provides common functions to handle Markdown.
|
|
3
3
|
These functions apply to any kind of Markdown work.
|
|
4
4
|
"""
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
from typing import Iterable
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
def write_row(
|
|
9
10
|
row: Iterable[str],
|
|
10
|
-
columns_widths:
|
|
11
|
+
columns_widths: dict[int, int],
|
|
11
12
|
padding: int = 1,
|
|
12
13
|
indent: int = 0,
|
|
13
14
|
) -> str:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
This module provides functions to generate Markdown for OpenAPI Version 3.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
import copy
|
|
5
6
|
import os
|
|
6
7
|
import warnings
|
|
@@ -95,11 +96,14 @@ class OpenAPIV3DocumentationHandler:
|
|
|
95
96
|
writer: Optional[DocumentsWriter] = None,
|
|
96
97
|
style: Union[int, str] = 1,
|
|
97
98
|
source: str = "",
|
|
99
|
+
templates_path: Optional[str] = None,
|
|
98
100
|
) -> None:
|
|
99
101
|
self._source = source
|
|
100
102
|
self.texts = texts or EnglishTexts()
|
|
101
103
|
self._writer = writer or Jinja2DocumentsWriter(
|
|
102
|
-
__name__,
|
|
104
|
+
__name__,
|
|
105
|
+
views_style=style_from_value(style),
|
|
106
|
+
custom_templates_path=templates_path,
|
|
103
107
|
)
|
|
104
108
|
self.doc = self.normalize_data(copy.deepcopy(doc))
|
|
105
109
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"""
|
|
2
2
|
This module provides methods to obtain OpenAPI Documentation from file or web sources.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
import json
|
|
5
6
|
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
6
8
|
|
|
7
9
|
import yaml
|
|
8
10
|
|
|
@@ -11,7 +13,7 @@ from openapidocs.logs import logger
|
|
|
11
13
|
from .web import ensure_success, http_get
|
|
12
14
|
|
|
13
15
|
|
|
14
|
-
def read_from_json_file(file_path: Path):
|
|
16
|
+
def read_from_json_file(file_path: Path) -> dict[Any, Any] | list[Any]:
|
|
15
17
|
"""
|
|
16
18
|
Reads JSON from a given file by path.
|
|
17
19
|
"""
|
|
@@ -19,7 +21,7 @@ def read_from_json_file(file_path: Path):
|
|
|
19
21
|
return json.loads(source_file.read())
|
|
20
22
|
|
|
21
23
|
|
|
22
|
-
def read_from_yaml_file(file_path: Path):
|
|
24
|
+
def read_from_yaml_file(file_path: Path) -> dict[Any, Any] | list[Any]:
|
|
23
25
|
"""
|
|
24
26
|
Reads YAML from a given file by path.
|
|
25
27
|
"""
|
|
@@ -32,7 +34,7 @@ class SourceError(Exception):
|
|
|
32
34
|
super().__init__(message)
|
|
33
35
|
|
|
34
36
|
|
|
35
|
-
def read_from_url(url: str):
|
|
37
|
+
def read_from_url(url: str) -> dict[Any, Any] | list[Any]:
|
|
36
38
|
"""
|
|
37
39
|
Tries to read OpenAPI Documentation from the given source URL.
|
|
38
40
|
This method will try to fetch JSON or YAML from the given source, in case of
|
|
@@ -63,7 +65,10 @@ def read_from_url(url: str):
|
|
|
63
65
|
)
|
|
64
66
|
|
|
65
67
|
|
|
66
|
-
def read_from_source(
|
|
68
|
+
def read_from_source(
|
|
69
|
+
source: str,
|
|
70
|
+
cwd: Path | None = None,
|
|
71
|
+
) -> dict[Any, Any] | list[Any]:
|
|
67
72
|
"""
|
|
68
73
|
Tries to read a JSON or YAML file from a given source.
|
|
69
74
|
The source can be a path to a file, or a URL.
|
|
@@ -6,7 +6,7 @@ http_client = httpx.Client(verify=False, timeout=20)
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class FailedRequestError(Exception):
|
|
9
|
-
def __init__(self, message) -> None:
|
|
9
|
+
def __init__(self, message: str) -> None:
|
|
10
10
|
super().__init__(
|
|
11
11
|
f"Failed request: {message}. "
|
|
12
12
|
"Inspect the inner exception (__context__) for more information."
|