Skip to content

Commit d61c9cc

Browse files
committed
generate SwaggerUI docs for models
1 parent 9921775 commit d61c9cc

File tree

11 files changed

+203
-153
lines changed

11 files changed

+203
-153
lines changed

.github/workflows/docs.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
env:
77
UV_PYTHON_PREFERENCE: only-system
88
UV_NO_SYNC: 1
9+
SWAGGER_UI_RELEASE: "5.30.3"
910

1011
jobs:
1112
run:
@@ -28,7 +29,17 @@ jobs:
2829
run: uv sync
2930
- name: doc8 style checks
3031
run: uv run doc8 docs/
31-
- name: Generate documentation
32-
run: uv run make -C docs html
32+
33+
# Generate Swagger UI docs
34+
- name: Fetch Swagger UI
35+
run: wget https://github.com/swagger-api/swagger-ui/archive/refs/tags/v${SWAGGER_UI_RELEASE}.tar.gz
36+
- name: Extract Swagger UI
37+
run: tar xf v${SWAGGER_UI_RELEASE}.tar.gz -C docs/_static/ --strip=1 swagger-ui-${SWAGGER_UI_RELEASE}/dist/ --transform s/dist/swagger-ui/
38+
- name: Generate schema
39+
run: uv run generate-schema.py
40+
41+
# Finally, generate Sphinx docs
3342
- name: Spelling
3443
run: uv run make -C docs spelling
44+
- name: Generate documentation
45+
run: uv run make -C docs html

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ tests/data/docs/_build/
1212

1313
# Documentation
1414
/docs/_build/
15+
/docs/_static/swagger-ui/
16+
/*.tar.gz
1517

1618
# Coverage
1719
/.coverage

docs/_static/.gitkeep

Whitespace-only changes.

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,4 @@
3737
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
3838

3939
# html_theme = "alabaster"
40-
# html_static_path = ["_static"]
40+
html_static_path = ["_static"]

docs/dev.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
###########
2+
Development
3+
###########
4+
5+
***************
6+
Swagger UI docs
7+
***************
8+
9+
To generate the Swagger UI documentation locally
10+
11+
* Download the `latest release <https://github.com/swagger-api/swagger-ui/releases>`_.
12+
* Extract the `dist` folder:
13+
14+
.. code-block:: console
15+
16+
tar xf v5.30.3.tar.gz -C docs/_static/ --strip=1 swagger-ui-5.30.3/dist/ --transform s/dist/swagger-ui/
17+
18+
* Generate schema:
19+

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ documentation for details.
1818
/quickstart
1919
/how-tos
2020
/reference
21+
/dev
2122

docs/reference.rst

Lines changed: 8 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -2,124 +2,15 @@
22
Reference
33
#########
44

5-
Each tutorial file can contain two elements:
5+
For a complete model reference, please see the `Swagger UI documentation <_static/swagger-ui/index.html>`_.
66

7-
parts (required, type: :ref:`Parts <reference_parts>`)
8-
The individual parts of the tutorial. See :ref:`Parts <reference_parts>` for more information.
9-
configuration (optional, type: :ref:`Parts <reference_configuration>`)
10-
Global/initial configuration for the tutorial. See :ref:`Configuration <reference_configuration>` for
11-
more information.
7+
For a quick overview, each tutorial file can contain two elements:
128

13-
.. _reference_parts:
9+
parts (required)
10+
The individual parts of the tutorial. A part can have multiple types, either:
1411

15-
*****
16-
parts
17-
*****
12+
* ``CommandsPartModel`` for a list of commands to execute.
13+
* ``FilePartModel`` for a file to create.
1814

19-
Parts is a list of individual "chapters" of your tutorial. For example, a chapter might be a set of commands
20-
to install a software, or a configuration file that the user is instructed to create.
21-
22-
23-
file parts
24-
==========
25-
26-
File parts are intended to create a file when running a tutorial, and to e.g. instruct the user to create that
27-
file when rendering the tutorial in documentation. A full example:
28-
29-
.. literalinclude:: /tutorials/file-full-example/tutorial.yaml
30-
:language: yaml
31-
32-
The above tutorial will render a file block when rendered:
33-
34-
.. structured-tutorial:: file-full-example/tutorial.yaml
35-
36-
.. structured-tutorial-part::
37-
38-
The configuration options are:
39-
40-
parts[n].contents (optional, str)
41-
The contents of the file. Mutually exclusive to ``source``.
42-
parts[n].source (optional, str)
43-
Path of the original file. Mutually exclusive to ``contents``.
44-
parts[n].destination (optional, str)
45-
Where to create the new file.
46-
parts[n].template (optional, bool)
47-
Default: ``True``
48-
49-
Set to ``False`` if this file should not be rendered as a template. This can be used to copy binary
50-
files and cannot be read as text files, or if you want the destination file to be a template.
51-
parts[n].doc (optional, type: :ref:`parts[n]doc <reference_parts_n_doc>`)
52-
See :ref:`parts[n]doc <reference_parts_n_doc>` for details.
53-
parts[n].run
54-
No options are supported thus far.
55-
56-
.. _reference_parts_n_doc:
57-
58-
parts[n].doc
59-
------------
60-
61-
language (optional, str)
62-
Language used for syntax highlighting.
63-
ignore_spelling (optional, bool)
64-
Default: ``False``
65-
66-
Set to ``True`` to wrap the `caption` option in a ``:spelling:ignore:`` directive for
67-
`sphinxcontrib-spelling <https://github.com/sphinx-contrib/spelling>`_.
68-
69-
In addition, any option of the `code-block
70-
<https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-code-block>`_ directive
71-
are also supported.
72-
73-
commands parts
74-
==============
75-
76-
.. _reference_configuration:
77-
78-
*************
79-
configuration
80-
*************
81-
82-
configuration.doc
83-
=================
84-
85-
configuration.doc.context (dict)
86-
Default: ``{"doc": True, "run": False, "cwd": "~", ...}``
87-
88-
The initial context for rendering template strings. Additional variables in the context are used to
89-
influence the appearance of the prompt in console blocks:
90-
91-
user
92-
Default: ``"user"``
93-
host
94-
Default: ``"host"``
95-
cwd
96-
Default: ``"~"``
97-
prompt_template
98-
Default: ``"{{ user }}@{{ host }}:{{ cwd }}{% if user == 'root' %}#{% else %}${% endif %} "``
99-
100-
configuration.run
101-
=================
102-
103-
configuration.run.context (dict)
104-
Default: ``{"doc": True, "run": False, "cwd": Path.cwd()}``
105-
106-
The initial context for rendering template strings.
107-
108-
configuration.run.git_export (optional, bool)
109-
Default: ``False``
110-
111-
If set to ``True``, perform a git export before running the tutorial. The current working directory will
112-
change to the git export. After the tutorial, the export directory will be removed. This setting
113-
overrides ``temporary_directory``.
114-
115-
If set, the ``cwd`` context variable will point to the temporary directory where the git repository was
116-
exported to. The original/initial working directory is available as ``orig_cwd``.
117-
118-
configuration.run.temporary_directory (optional, bool)
119-
Default: ``False``
120-
121-
If set to ``True``, switch to an empty, temporary directory before running the tutorial. The empty
122-
directory will be removed once the tutorial is completed.
123-
124-
If set, the ``cwd`` context variable will point to the temporary directory. The original/initial working
125-
directory is available as ``orig_cwd``.
15+
configuration (optional)
16+
Global/initial configuration for the tutorial. This is represented by the ``ConfigurationModel``.

docs/tutorials/simple-file/tutorial.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ parts:
1515
- commands:
1616
- command: python -m json.tool --compact {{ file_path }}
1717
doc:
18-
output: "{{ file_contents }}"
18+
output: "{{ file_contents }}"

generate-schema.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""Script to generate an OpenAPI schema."""
2+
3+
import argparse
4+
import json
5+
from pathlib import Path
6+
from typing import Any
7+
8+
import jinja2
9+
from pydantic import BaseModel
10+
11+
from structured_tutorials.models import TutorialModel
12+
13+
parser = argparse.ArgumentParser()
14+
parser.add_argument("--swagger-path", type=Path, default=Path.cwd() / "docs" / "_static" / "swagger-ui")
15+
parser.add_argument(
16+
"-o", "--output", action="store_true", default=False, help="Write schema to stdout as well."
17+
)
18+
args = parser.parse_args()
19+
20+
swagger_initializer = """window.onload = function() {
21+
//<editor-fold desc="Changeable Configuration Block">
22+
23+
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
24+
window.ui = SwaggerUIBundle({
25+
spec: {{ spec }},
26+
dom_id: '#swagger-ui',
27+
deepLinking: true,
28+
presets: [
29+
SwaggerUIBundle.presets.apis,
30+
SwaggerUIStandalonePreset
31+
],
32+
plugins: [
33+
SwaggerUIBundle.plugins.DownloadUrl
34+
],
35+
layout: "StandaloneLayout"
36+
});
37+
38+
//</editor-fold>
39+
}"""
40+
41+
42+
def generate_openapi_schema(
43+
model: type[BaseModel], title: str = "Structured Tutorials model reference", version: str = "1.0.0"
44+
) -> dict[str, Any]:
45+
"""Generate the OpenAPI schema for the given model."""
46+
# Generate Pydantic v2 JSON schema
47+
schema = model.model_json_schema(ref_template="#/components/schemas/{model}")
48+
schemas = schema.get("$defs", {}).copy()
49+
schemas[model.__name__] = schema
50+
51+
# Minimal OpenAPI 3.0.0 document with only components
52+
openapi = {
53+
"openapi": "3.0.0",
54+
"info": {"title": title, "version": version},
55+
"paths": {}, # no API paths
56+
"components": {"schemas": schemas},
57+
}
58+
return openapi
59+
60+
61+
openapi = generate_openapi_schema(TutorialModel)
62+
63+
env = jinja2.Environment()
64+
template = env.from_string(swagger_initializer)
65+
swagger_js = template.render(spec=json.dumps(openapi, indent=4, sort_keys=True))
66+
with open(args.swagger_path / "swagger-initializer.js", "w") as fh:
67+
fh.write(swagger_js)
68+
69+
if args.output:
70+
print(json.dumps(openapi, indent=4, sort_keys=True))

0 commit comments

Comments
 (0)