mkdocs-owl-api 0.1.0__py3-none-any.whl
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.
- mkdocs_owl_api/__init__.py +1 -0
- mkdocs_owl_api/loader.py +223 -0
- mkdocs_owl_api/plugin.py +94 -0
- mkdocs_owl_api/render/__init__.py +0 -0
- mkdocs_owl_api/render/asyncapi.py +570 -0
- mkdocs_owl_api/render/common.py +612 -0
- mkdocs_owl_api/render/openapi.py +293 -0
- mkdocs_owl_api/static/techdocs-owl-api.css +109 -0
- mkdocs_owl_api-0.1.0.dist-info/METADATA +157 -0
- mkdocs_owl_api-0.1.0.dist-info/RECORD +13 -0
- mkdocs_owl_api-0.1.0.dist-info/WHEEL +4 -0
- mkdocs_owl_api-0.1.0.dist-info/entry_points.txt +2 -0
- mkdocs_owl_api-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OpenAPI 3.x page renderer.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .common import (
|
|
10
|
+
_anchor,
|
|
11
|
+
_build_description_block,
|
|
12
|
+
_demote_headings,
|
|
13
|
+
_format_type,
|
|
14
|
+
_heading,
|
|
15
|
+
_md_to_html,
|
|
16
|
+
_pill,
|
|
17
|
+
_ref_link,
|
|
18
|
+
_render_downloads_table,
|
|
19
|
+
_render_schema,
|
|
20
|
+
_render_security_inline,
|
|
21
|
+
_schema_depth,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
_HTTP_METHODS = ("get", "post", "put", "delete", "patch", "head", "options", "trace")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _openapi_method_pill(method: str) -> str:
|
|
28
|
+
return _pill(method.upper(), kind=f"http-{method}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _openapi_render_parameters(params: list[dict[str, Any]]) -> str:
|
|
32
|
+
if not params:
|
|
33
|
+
return ""
|
|
34
|
+
parts: list[str] = [
|
|
35
|
+
'<table>',
|
|
36
|
+
'<thead>',
|
|
37
|
+
'<tr><th>Name</th><th>In</th><th>Type</th><th>Description</th></tr>',
|
|
38
|
+
'</thead>',
|
|
39
|
+
'<tbody>',
|
|
40
|
+
]
|
|
41
|
+
for p in params:
|
|
42
|
+
if not isinstance(p, dict):
|
|
43
|
+
continue
|
|
44
|
+
name = p.get("name", "")
|
|
45
|
+
in_ = p.get("in", "")
|
|
46
|
+
schema = p.get("schema") or {}
|
|
47
|
+
type_str = _format_type(schema)
|
|
48
|
+
|
|
49
|
+
name_md = f"`{name}`"
|
|
50
|
+
pills: list[str] = []
|
|
51
|
+
if p.get("required"):
|
|
52
|
+
pills.append(_pill("required", kind="required"))
|
|
53
|
+
if p.get("deprecated") or schema.get("deprecated"):
|
|
54
|
+
pills.append(_pill("deprecated", kind="deprecated"))
|
|
55
|
+
if pills:
|
|
56
|
+
name_md += "<br>" + " ".join(pills)
|
|
57
|
+
name_html = _md_to_html(name_md, inline=True)
|
|
58
|
+
|
|
59
|
+
type_html = _md_to_html(type_str, inline=True)
|
|
60
|
+
|
|
61
|
+
merged = dict(schema)
|
|
62
|
+
pdesc = (p.get("description") or "").strip()
|
|
63
|
+
if pdesc:
|
|
64
|
+
merged["description"] = pdesc
|
|
65
|
+
if p.get("example") is not None and "example" not in merged:
|
|
66
|
+
merged["example"] = p["example"]
|
|
67
|
+
desc_block = _build_description_block(merged)
|
|
68
|
+
desc_html = _md_to_html(desc_block) if desc_block else "—"
|
|
69
|
+
|
|
70
|
+
in_html = _md_to_html(f"`{in_}`", inline=True)
|
|
71
|
+
parts.append(f"<tr><td>{name_html}</td><td>{in_html}</td><td>{type_html}</td><td>{desc_html}</td></tr>")
|
|
72
|
+
parts.append('</tbody>')
|
|
73
|
+
parts.append('</table>')
|
|
74
|
+
parts.append("")
|
|
75
|
+
return "\n".join(parts)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _openapi_render_request_body(rb: dict[str, Any], spec: dict[str, Any], *, hide_internal: bool) -> str:
|
|
79
|
+
if not rb:
|
|
80
|
+
return ""
|
|
81
|
+
parts: list[str] = []
|
|
82
|
+
desc = (rb.get("description") or "").strip()
|
|
83
|
+
if desc:
|
|
84
|
+
parts.append(desc)
|
|
85
|
+
parts.append("")
|
|
86
|
+
content = rb.get("content") or {}
|
|
87
|
+
for media_type, media_obj in content.items():
|
|
88
|
+
parts.append(f"*Content type:* {_pill(media_type, kind='contenttype')}")
|
|
89
|
+
parts.append("")
|
|
90
|
+
schema = (media_obj or {}).get("schema") or {}
|
|
91
|
+
if "$ref" in schema:
|
|
92
|
+
parts.append(f"*Schema:* {_ref_link(schema['$ref'])}")
|
|
93
|
+
parts.append("")
|
|
94
|
+
elif schema.get("type"):
|
|
95
|
+
parts.append(f"*Schema:* {_format_type(schema)}")
|
|
96
|
+
parts.append("")
|
|
97
|
+
return "\n".join(parts)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _openapi_render_responses(responses: dict[str, Any], spec: dict[str, Any]) -> str:
|
|
101
|
+
if not responses:
|
|
102
|
+
return ""
|
|
103
|
+
parts: list[str] = [
|
|
104
|
+
"**Responses**",
|
|
105
|
+
"",
|
|
106
|
+
'<table>',
|
|
107
|
+
'<thead>',
|
|
108
|
+
'<tr><th>Status</th><th>Description</th><th>Schema</th></tr>',
|
|
109
|
+
'</thead>',
|
|
110
|
+
'<tbody>',
|
|
111
|
+
]
|
|
112
|
+
for code, resp in responses.items():
|
|
113
|
+
if not isinstance(resp, dict):
|
|
114
|
+
continue
|
|
115
|
+
desc = (resp.get("description") or "").strip()
|
|
116
|
+
desc_html = _md_to_html(desc, inline=True) if desc else "—"
|
|
117
|
+
schema_html = "—"
|
|
118
|
+
content = resp.get("content") or {}
|
|
119
|
+
bits: list[tuple[str, str]] = []
|
|
120
|
+
for mt, media_obj in content.items():
|
|
121
|
+
schema = (media_obj or {}).get("schema") or {}
|
|
122
|
+
if "$ref" in schema:
|
|
123
|
+
bits.append((mt, _ref_link(schema["$ref"])))
|
|
124
|
+
elif schema.get("type"):
|
|
125
|
+
bits.append((mt, _format_type(schema)))
|
|
126
|
+
else:
|
|
127
|
+
bits.append((mt, "`object`"))
|
|
128
|
+
if len(bits) == 1:
|
|
129
|
+
schema_html = _md_to_html(bits[0][1], inline=True)
|
|
130
|
+
elif bits:
|
|
131
|
+
schema_html = _md_to_html(
|
|
132
|
+
"<br>".join(f"`{mt}`: {sch}" for mt, sch in bits), inline=True,
|
|
133
|
+
)
|
|
134
|
+
code_html = _md_to_html(f"`{code}`", inline=True)
|
|
135
|
+
parts.append(f"<tr><td>{code_html}</td><td>{desc_html}</td><td>{schema_html}</td></tr>")
|
|
136
|
+
parts.append('</tbody>')
|
|
137
|
+
parts.append('</table>')
|
|
138
|
+
parts.append("")
|
|
139
|
+
return "\n".join(parts)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _render_openapi_page(
|
|
143
|
+
spec: dict[str, Any],
|
|
144
|
+
opts: dict[str, Any],
|
|
145
|
+
*,
|
|
146
|
+
spec_url: str = "",
|
|
147
|
+
attachments: list[dict[str, Any]] | None = None,
|
|
148
|
+
) -> str:
|
|
149
|
+
info = spec.get("info") or {}
|
|
150
|
+
title = (opts.get("title") or info.get("title") or "API Reference").strip()
|
|
151
|
+
intro = (opts.get("intro") or "").strip()
|
|
152
|
+
hide_version = bool(opts.get("hide_version"))
|
|
153
|
+
hide_internal = bool(opts.get("hide_internal"))
|
|
154
|
+
hide_download = bool(opts.get("hide_download_link"))
|
|
155
|
+
|
|
156
|
+
parts: list[str] = [f"# {title}", ""]
|
|
157
|
+
|
|
158
|
+
if intro:
|
|
159
|
+
parts.append(intro)
|
|
160
|
+
parts.append("")
|
|
161
|
+
|
|
162
|
+
version = (info.get("version") or "").strip()
|
|
163
|
+
if version and not hide_version:
|
|
164
|
+
parts.append(f"**Version:** `{version}`")
|
|
165
|
+
parts.append("")
|
|
166
|
+
|
|
167
|
+
downloads = _render_downloads_table(spec_url, attachments or [], hide_download=hide_download)
|
|
168
|
+
if downloads:
|
|
169
|
+
parts.append(downloads)
|
|
170
|
+
|
|
171
|
+
description = (info.get("description") or "").strip()
|
|
172
|
+
if description:
|
|
173
|
+
parts.append(_demote_headings(description))
|
|
174
|
+
parts.append("")
|
|
175
|
+
|
|
176
|
+
servers = spec.get("servers")
|
|
177
|
+
if isinstance(servers, list) and servers:
|
|
178
|
+
parts.append("## Servers")
|
|
179
|
+
parts.append("")
|
|
180
|
+
for srv in servers:
|
|
181
|
+
if not isinstance(srv, dict):
|
|
182
|
+
continue
|
|
183
|
+
url = srv.get("url", "")
|
|
184
|
+
desc = (srv.get("description") or "").strip()
|
|
185
|
+
parts.append(f"- `{url}`" + (f" — {desc}" if desc else ""))
|
|
186
|
+
variables = srv.get("variables")
|
|
187
|
+
if isinstance(variables, dict) and variables:
|
|
188
|
+
for vname, v in variables.items():
|
|
189
|
+
if not isinstance(v, dict):
|
|
190
|
+
continue
|
|
191
|
+
bits: list[str] = []
|
|
192
|
+
if v.get("default") is not None:
|
|
193
|
+
bits.append(f"default `{v['default']}`")
|
|
194
|
+
enum = v.get("enum")
|
|
195
|
+
if isinstance(enum, list) and enum:
|
|
196
|
+
bits.append("one of " + ", ".join(f"`{e}`" for e in enum))
|
|
197
|
+
vdesc = (v.get("description") or "").strip()
|
|
198
|
+
if vdesc:
|
|
199
|
+
bits.append(vdesc)
|
|
200
|
+
suffix = (" — " + "; ".join(bits)) if bits else ""
|
|
201
|
+
parts.append(f" - `{{{vname}}}`{suffix}")
|
|
202
|
+
parts.append("")
|
|
203
|
+
|
|
204
|
+
paths = spec.get("paths") or {}
|
|
205
|
+
if paths:
|
|
206
|
+
tag_descriptions: dict[str, str] = {}
|
|
207
|
+
for t in (spec.get("tags") or []):
|
|
208
|
+
if isinstance(t, dict) and t.get("name"):
|
|
209
|
+
tag_descriptions[t["name"]] = (t.get("description") or "").strip()
|
|
210
|
+
|
|
211
|
+
_DEFAULT_TAG = "Endpoints"
|
|
212
|
+
grouped: dict[str, list[tuple[str, str, dict, list]]] = {}
|
|
213
|
+
for path, path_obj in paths.items():
|
|
214
|
+
if not isinstance(path_obj, dict):
|
|
215
|
+
continue
|
|
216
|
+
path_params = path_obj.get("parameters") or []
|
|
217
|
+
for method in _HTTP_METHODS:
|
|
218
|
+
op = path_obj.get(method)
|
|
219
|
+
if not isinstance(op, dict):
|
|
220
|
+
continue
|
|
221
|
+
tags = op.get("tags") or [_DEFAULT_TAG]
|
|
222
|
+
for tag in tags:
|
|
223
|
+
grouped.setdefault(tag, []).append((path, method, op, path_params))
|
|
224
|
+
|
|
225
|
+
for tag_name, ops in grouped.items():
|
|
226
|
+
parts.append(_heading(2, tag_name, anchor=_anchor("tag", tag_name)))
|
|
227
|
+
parts.append("")
|
|
228
|
+
tag_desc = tag_descriptions.get(tag_name, "")
|
|
229
|
+
if tag_desc:
|
|
230
|
+
parts.append(_demote_headings(tag_desc))
|
|
231
|
+
parts.append("")
|
|
232
|
+
|
|
233
|
+
for path, method, op, path_params in ops:
|
|
234
|
+
summary = (op.get("summary") or "").strip()
|
|
235
|
+
heading_text = summary or f"`{path}`"
|
|
236
|
+
anchor = _anchor("endpoints", f"{tag_name}-{method}-{path}")
|
|
237
|
+
parts.append(_heading(3, heading_text, anchor=anchor))
|
|
238
|
+
parts.append("")
|
|
239
|
+
method_line = f"{_openapi_method_pill(method)} `{path}`"
|
|
240
|
+
if op.get("deprecated"):
|
|
241
|
+
method_line += " " + _pill("deprecated", kind="deprecated")
|
|
242
|
+
parts.append(method_line)
|
|
243
|
+
parts.append("")
|
|
244
|
+
|
|
245
|
+
desc = (op.get("description") or "").strip()
|
|
246
|
+
if desc:
|
|
247
|
+
parts.append(_demote_headings(desc))
|
|
248
|
+
parts.append("")
|
|
249
|
+
|
|
250
|
+
op_params = op.get("parameters") or []
|
|
251
|
+
all_params = path_params + op_params
|
|
252
|
+
if all_params:
|
|
253
|
+
parts.append("**Parameters**")
|
|
254
|
+
parts.append("")
|
|
255
|
+
parts.append(_openapi_render_parameters(all_params))
|
|
256
|
+
|
|
257
|
+
rb = op.get("requestBody") or {}
|
|
258
|
+
if rb:
|
|
259
|
+
parts.append("**Request body**")
|
|
260
|
+
parts.append("")
|
|
261
|
+
parts.append(_openapi_render_request_body(rb, spec, hide_internal=hide_internal))
|
|
262
|
+
|
|
263
|
+
responses = op.get("responses") or {}
|
|
264
|
+
if responses:
|
|
265
|
+
parts.append(_openapi_render_responses(responses, spec))
|
|
266
|
+
|
|
267
|
+
security = op.get("security")
|
|
268
|
+
if isinstance(security, list) and security:
|
|
269
|
+
blocks = [b for b in (_render_security_inline(spec, e) for e in security) if b]
|
|
270
|
+
if blocks:
|
|
271
|
+
parts.append("**Security**")
|
|
272
|
+
parts.append("")
|
|
273
|
+
for b in blocks:
|
|
274
|
+
parts.append(b)
|
|
275
|
+
parts.append("")
|
|
276
|
+
|
|
277
|
+
schemas = (spec.get("components") or {}).get("schemas") or {}
|
|
278
|
+
if schemas:
|
|
279
|
+
max_depth = _schema_depth(opts)
|
|
280
|
+
parts.append("## Schemas")
|
|
281
|
+
parts.append("")
|
|
282
|
+
for sname, sch in schemas.items():
|
|
283
|
+
if not isinstance(sch, dict):
|
|
284
|
+
continue
|
|
285
|
+
parts.append(_heading(3, sname, anchor=_anchor("schemas", sname)))
|
|
286
|
+
parts.append("")
|
|
287
|
+
parts.append(_render_schema(sch, hide_internal=hide_internal, max_depth=max_depth))
|
|
288
|
+
parts.append("")
|
|
289
|
+
|
|
290
|
+
while parts and parts[-1] in ("", "---"):
|
|
291
|
+
parts.pop()
|
|
292
|
+
|
|
293
|
+
return "\n".join(parts)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
.techdocs-owl-api-pill {
|
|
2
|
+
display: inline-block;
|
|
3
|
+
padding: 0.05em 0.55em;
|
|
4
|
+
margin: 0 0.15em 0.15em 0;
|
|
5
|
+
border-radius: 999px;
|
|
6
|
+
background: var(--md-default-fg-color--lightest);
|
|
7
|
+
color: var(--md-default-fg-color);
|
|
8
|
+
font-size: 0.78em;
|
|
9
|
+
font-family: var(--md-code-font-family, monospace);
|
|
10
|
+
font-weight: 500;
|
|
11
|
+
line-height: 1.5;
|
|
12
|
+
white-space: nowrap;
|
|
13
|
+
vertical-align: 0.05em;
|
|
14
|
+
cursor: default;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.techdocs-owl-api-pill--action-send {
|
|
18
|
+
background: hsla(200, 80%, 55%, 0.12);
|
|
19
|
+
color: hsl(200, 80%, 55%);
|
|
20
|
+
border: 1px solid hsl(200, 80%, 55%);
|
|
21
|
+
}
|
|
22
|
+
.techdocs-owl-api-pill--action-receive {
|
|
23
|
+
background: hsla(160, 60%, 45%, 0.12);
|
|
24
|
+
color: hsl(160, 60%, 45%);
|
|
25
|
+
border: 1px solid hsl(160, 60%, 45%);
|
|
26
|
+
}
|
|
27
|
+
.techdocs-owl-api-pill--action-publish {
|
|
28
|
+
background: hsla(200, 80%, 55%, 0.12);
|
|
29
|
+
color: hsl(200, 80%, 55%);
|
|
30
|
+
border: 1px solid hsl(200, 80%, 55%);
|
|
31
|
+
}
|
|
32
|
+
.techdocs-owl-api-pill--action-subscribe {
|
|
33
|
+
background: hsla(160, 60%, 45%, 0.12);
|
|
34
|
+
color: hsl(160, 60%, 45%);
|
|
35
|
+
border: 1px solid hsl(160, 60%, 45%);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.techdocs-owl-api-pill--required {
|
|
39
|
+
background: hsla(350, 80%, 55%, 0.12);
|
|
40
|
+
color: hsl(350, 80%, 55%);
|
|
41
|
+
border: 1px solid hsl(350, 80%, 55%);
|
|
42
|
+
}
|
|
43
|
+
.techdocs-owl-api-pill--internal {
|
|
44
|
+
background: hsla(40, 90%, 50%, 0.12);
|
|
45
|
+
color: hsl(40, 90%, 50%);
|
|
46
|
+
border: 1px solid hsl(40, 90%, 50%);
|
|
47
|
+
}
|
|
48
|
+
.techdocs-owl-api-pill--deprecated {
|
|
49
|
+
background: hsla(0, 0%, 55%, 0.12);
|
|
50
|
+
color: hsl(0, 0%, 55%);
|
|
51
|
+
border: 1px solid hsl(0, 0%, 55%);
|
|
52
|
+
text-decoration: line-through;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.techdocs-owl-api-pill--protocol {
|
|
56
|
+
background: hsla(270, 60%, 60%, 0.12);
|
|
57
|
+
color: hsl(270, 60%, 60%);
|
|
58
|
+
border: 1px solid hsl(270, 60%, 60%);
|
|
59
|
+
}
|
|
60
|
+
.techdocs-owl-api-pill--contenttype {
|
|
61
|
+
background: hsla(30, 80%, 55%, 0.12);
|
|
62
|
+
color: hsl(30, 80%, 55%);
|
|
63
|
+
border: 1px solid hsl(30, 80%, 55%);
|
|
64
|
+
}
|
|
65
|
+
.techdocs-owl-api-pill--scheme {
|
|
66
|
+
background: hsla(180, 60%, 45%, 0.12);
|
|
67
|
+
color: hsl(180, 60%, 45%);
|
|
68
|
+
border: 1px solid hsl(180, 60%, 45%);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.techdocs-owl-api-pill--tag {
|
|
72
|
+
background: hsla(220, 50%, 55%, 0.12);
|
|
73
|
+
color: hsl(220, 50%, 55%);
|
|
74
|
+
border: 1px solid hsl(220, 50%, 55%);
|
|
75
|
+
cursor: help;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.techdocs-owl-api-pill--http-get {
|
|
79
|
+
background: hsla(140, 60%, 45%, 0.12);
|
|
80
|
+
color: hsl(140, 60%, 45%);
|
|
81
|
+
border: 1px solid hsl(140, 60%, 45%);
|
|
82
|
+
}
|
|
83
|
+
.techdocs-owl-api-pill--http-post {
|
|
84
|
+
background: hsla(210, 70%, 55%, 0.12);
|
|
85
|
+
color: hsl(210, 70%, 55%);
|
|
86
|
+
border: 1px solid hsl(210, 70%, 55%);
|
|
87
|
+
}
|
|
88
|
+
.techdocs-owl-api-pill--http-put {
|
|
89
|
+
background: hsla(30, 80%, 55%, 0.12);
|
|
90
|
+
color: hsl(30, 80%, 55%);
|
|
91
|
+
border: 1px solid hsl(30, 80%, 55%);
|
|
92
|
+
}
|
|
93
|
+
.techdocs-owl-api-pill--http-delete {
|
|
94
|
+
background: hsla(0, 70%, 55%, 0.12);
|
|
95
|
+
color: hsl(0, 70%, 55%);
|
|
96
|
+
border: 1px solid hsl(0, 70%, 55%);
|
|
97
|
+
}
|
|
98
|
+
.techdocs-owl-api-pill--http-patch {
|
|
99
|
+
background: hsla(280, 60%, 55%, 0.12);
|
|
100
|
+
color: hsl(280, 60%, 55%);
|
|
101
|
+
border: 1px solid hsl(280, 60%, 55%);
|
|
102
|
+
}
|
|
103
|
+
.techdocs-owl-api-pill--http-head,
|
|
104
|
+
.techdocs-owl-api-pill--http-options,
|
|
105
|
+
.techdocs-owl-api-pill--http-trace {
|
|
106
|
+
background: hsla(0, 0%, 55%, 0.12);
|
|
107
|
+
color: hsl(0, 0%, 55%);
|
|
108
|
+
border: 1px solid hsl(0, 0%, 55%);
|
|
109
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mkdocs-owl-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MkDocs plugin from TechDocs Owl, renders AsyncAPI and OpenAPI specs into reference pages
|
|
5
|
+
Project-URL: Homepage, https://github.com/techdocs-owl/mkdocs-owl-api
|
|
6
|
+
Project-URL: Repository, https://github.com/techdocs-owl/mkdocs-owl-api
|
|
7
|
+
Project-URL: Issues, https://github.com/techdocs-owl/mkdocs-owl-api/issues
|
|
8
|
+
Author-email: Dima Patserkovskyi <dmtwngtech@pm.me>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: api-reference,asyncapi,documentation,mkdocs,mkdocs-plugin,openapi
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Framework :: MkDocs
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Documentation
|
|
23
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Requires-Dist: markdown>=3.4
|
|
26
|
+
Requires-Dist: mkdocs>=1.5
|
|
27
|
+
Requires-Dist: pyyaml>=6.0
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# techdocs-owl-api
|
|
31
|
+
|
|
32
|
+
MkDocs plugin that renders AsyncAPI and OpenAPI specs into reference pages
|
|
33
|
+
at build time.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install mkdocs-owl-api
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
# mkdocs.yml
|
|
43
|
+
plugins:
|
|
44
|
+
- search
|
|
45
|
+
- owl-api
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Add a page with a `techdocs-owl-asyncapi:` or `techdocs-owl-openapi:`
|
|
49
|
+
frontmatter key and an empty body — the plugin fills it in at build time.
|
|
50
|
+
|
|
51
|
+
```markdown
|
|
52
|
+
---
|
|
53
|
+
techdocs-owl-asyncapi: ../specs/asyncapi.yml
|
|
54
|
+
---
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Supports AsyncAPI 2.x/3.0 and OpenAPI 3.x (YAML or JSON), local paths or
|
|
58
|
+
HTTP(S) URLs, recursive `$ref` resolution, and a bundled stylesheet
|
|
59
|
+
(auto-injected, no `extra_css` setup needed).
|
|
60
|
+
|
|
61
|
+
## Site-wide defaults
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
plugins:
|
|
65
|
+
- owl-api:
|
|
66
|
+
schema_depth: 3
|
|
67
|
+
hide_internal: false
|
|
68
|
+
hide_bindings: false
|
|
69
|
+
hide_traits: false
|
|
70
|
+
hide_security: false
|
|
71
|
+
hide_version: false
|
|
72
|
+
hide_download_link: false
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Page frontmatter overrides these per-page.
|
|
76
|
+
|
|
77
|
+
## AsyncAPI pages
|
|
78
|
+
|
|
79
|
+
```markdown
|
|
80
|
+
---
|
|
81
|
+
techdocs-owl-asyncapi:
|
|
82
|
+
spec: https://schema.example.com/my-service-asyncapi
|
|
83
|
+
title: My Service Events
|
|
84
|
+
intro: One-paragraph intro shown above the spec body.
|
|
85
|
+
schema_depth: 3
|
|
86
|
+
---
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
| Key | Type | Default | Effect |
|
|
90
|
+
|---|---|---|---|
|
|
91
|
+
| `spec` | string | — | **Required.** Path or URL to the spec file. |
|
|
92
|
+
| `title` | string | `info.title` | Page H1. |
|
|
93
|
+
| `intro` | markdown | — | Shown between the title and metadata block. |
|
|
94
|
+
| `hide_version` | bool | `false` | Hide the version line. |
|
|
95
|
+
| `hide_internal` | bool | `false` | Drop properties marked `x-internal-only: true`. |
|
|
96
|
+
| `hide_bindings` | bool | `false` | Skip bindings on servers/channels/operations/messages. |
|
|
97
|
+
| `hide_traits` | bool | `false` | Skip trait sections and references. |
|
|
98
|
+
| `hide_security` | bool | `false` | Skip security admonitions. |
|
|
99
|
+
| `hide_download_link` | bool | `false` | Hide the spec download link. |
|
|
100
|
+
| `schema_depth` | int | `3` | Depth of dot-path flattening for nested object properties. |
|
|
101
|
+
| `attachments` | list | — | Extra files to copy and list in the downloads table. |
|
|
102
|
+
|
|
103
|
+
Renders, in order: info, Servers, Operations (operation-centric across
|
|
104
|
+
both AsyncAPI versions), Messages, Schemas, Parameters, Traits — sections
|
|
105
|
+
absent from the spec are skipped.
|
|
106
|
+
|
|
107
|
+
## OpenAPI pages
|
|
108
|
+
|
|
109
|
+
```markdown
|
|
110
|
+
---
|
|
111
|
+
techdocs-owl-openapi: https://petstore3.swagger.io/api/v3/openapi.json
|
|
112
|
+
---
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
| Key | Type | Default | Effect |
|
|
116
|
+
|---|---|---|---|
|
|
117
|
+
| `spec` | string | — | **Required.** Path or URL to the spec file. |
|
|
118
|
+
| `title` | string | `info.title` | Page H1. |
|
|
119
|
+
| `intro` | markdown | — | Shown between the title and metadata block. |
|
|
120
|
+
| `hide_version` | bool | `false` | Hide the version line. |
|
|
121
|
+
| `hide_internal` | bool | `false` | Drop `x-internal-only` properties. |
|
|
122
|
+
| `hide_download_link` | bool | `false` | Hide the spec download link. |
|
|
123
|
+
| `schema_depth` | int | `3` | Depth of dot-path flattening for nested object properties. |
|
|
124
|
+
| `attachments` | list | — | Extra files to copy and list in the downloads table. |
|
|
125
|
+
|
|
126
|
+
Renders: info, Servers, endpoints grouped by tag (parameters, request
|
|
127
|
+
body, responses, security), Schemas.
|
|
128
|
+
|
|
129
|
+
## Downloads & attachments
|
|
130
|
+
|
|
131
|
+
The resolved spec (external `$ref`s inlined) is written to
|
|
132
|
+
`assets/techdocs-owl-api/<page-slug>.json` and linked from a Downloads
|
|
133
|
+
table. Add extra files (e.g. `.proto` schemas) with `attachments`:
|
|
134
|
+
|
|
135
|
+
```markdown
|
|
136
|
+
---
|
|
137
|
+
techdocs-owl-asyncapi:
|
|
138
|
+
spec: ../specs/asyncapi.yml
|
|
139
|
+
attachments:
|
|
140
|
+
- path: ../schemas/customer.proto
|
|
141
|
+
title: Customer Protobuf Schema
|
|
142
|
+
- ../schemas/order.proto # shorthand: path only, title = filename
|
|
143
|
+
---
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Error handling
|
|
147
|
+
|
|
148
|
+
Errors (missing/unreadable spec, network failures, parse errors, bad
|
|
149
|
+
frontmatter) render as a `!!! danger` admonition instead of failing the
|
|
150
|
+
build.
|
|
151
|
+
|
|
152
|
+
## Development
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
uv sync
|
|
156
|
+
uv run pytest
|
|
157
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
mkdocs_owl_api/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
2
|
+
mkdocs_owl_api/loader.py,sha256=Q6imBzhVS-drOG1mBIsW0QA_ZRLkKEG5OSQW_kYuOx4,7782
|
|
3
|
+
mkdocs_owl_api/plugin.py,sha256=ikgqN-zL6DMdaPIQ657qlH490ujeB9zQsTTAfc0v18o,3374
|
|
4
|
+
mkdocs_owl_api/render/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
mkdocs_owl_api/render/asyncapi.py,sha256=vF13OxbkMUjSHqNFrIyh7zEgXLZHEN8QfrIotJBlOfk,17788
|
|
6
|
+
mkdocs_owl_api/render/common.py,sha256=mH9tNRghEH3RzW99UndbRVpbuun8-RhpO-5fps01zpI,18771
|
|
7
|
+
mkdocs_owl_api/render/openapi.py,sha256=VH9y-lb9V5rN7G9KcmVF2gLM-FouEoKVuJYfoMWitDI,10720
|
|
8
|
+
mkdocs_owl_api/static/techdocs-owl-api.css,sha256=DEZ4Id-kfBcC6yFCjRxupVZsa9decjoS2GyIsvsNXA0,3025
|
|
9
|
+
mkdocs_owl_api-0.1.0.dist-info/METADATA,sha256=JatLxKyfeCXF4Qqtzew78MUxc4d50qEorNcVJioEYcg,4994
|
|
10
|
+
mkdocs_owl_api-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
11
|
+
mkdocs_owl_api-0.1.0.dist-info/entry_points.txt,sha256=QqZ4GwAVPpKIzpUBqT4-ByK6xWlbSkZXimeJO1z9z-Q,62
|
|
12
|
+
mkdocs_owl_api-0.1.0.dist-info/licenses/LICENSE,sha256=rX9oWAHzz3SB6uV6FA9MTTwQ5iggOo9A5WpiskJFGUM,1069
|
|
13
|
+
mkdocs_owl_api-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TechDocs Owl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|