Skip to content

Commit ecc0db1

Browse files
authored
FEAT: generate API with simple configuration value (#1)
* MAINT remove redundant Markdown newlines
1 parent 27dd3a7 commit ecc0db1

File tree

8 files changed

+159
-12
lines changed

8 files changed

+159
-12
lines changed

.cspell.json

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
{
22
"version": "0.2",
3-
"enableFiletypes": [
4-
"git-commit",
5-
"github-actions-workflow",
6-
"julia",
7-
"jupyter"
8-
],
93
"flagWords": [
104
"analyse",
115
"colour",
@@ -20,6 +14,7 @@
2014
"transisions"
2115
],
2216
"ignorePaths": [
17+
"**/*.rst_t",
2318
"**/.cspell.json",
2419
".editorconfig",
2520
".gitignore",
@@ -30,6 +25,8 @@
3025
],
3126
"language": "en-US",
3227
"words": [
28+
"ampform",
29+
"apidoc",
3330
"autoapi",
3431
"autodoc",
3532
"ComPWA",
@@ -46,6 +43,14 @@
4643
"refspecific",
4744
"reftarget",
4845
"reftype",
49-
"rtfd"
46+
"rtfd",
47+
"srcdir",
48+
"templatedir"
49+
],
50+
"enableFiletypes": [
51+
"git-commit",
52+
"github-actions-workflow",
53+
"julia",
54+
"jupyter"
5055
]
5156
}

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ repos:
4545
- id: blacken-docs
4646

4747
- repo: https://github.com/ComPWA/repo-maintenance
48-
rev: 0.1.6
48+
rev: 0.1.7
4949
hooks:
5050
- id: check-dev-files
5151
args:

README.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ Just install through [PyPI](https://pypi.org) with `pip`:
2222
pip install sphinx-api-relink
2323
```
2424

25-
Next, in your
26-
[Sphinx configuration file](https://www.sphinx-doc.org/en/master/usage/configuration.html)
27-
(`conf.py`), add `"sphinx_api_relink"` to your `extensions`:
25+
Next, in your [Sphinx configuration file](https://www.sphinx-doc.org/en/master/usage/configuration.html) (`conf.py`), add `"sphinx_api_relink"` to your `extensions`:
2826

2927
```python
3028
extensions = [
@@ -50,3 +48,24 @@ api_target_types: dict[str, str] = {
5048
"RangeDefinition": "obj",
5149
}
5250
```
51+
52+
## Generate API
53+
54+
To generate the API for [`sphinx.ext.autodoc`](https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html), add this to your `conf.py`:
55+
56+
```python
57+
api_package_path = "../src/my_package" # relative to conf.py
58+
```
59+
60+
The API is generated with the same style used by the ComPWA repositories (see e.g. [ampform.rtfd.io/en/stable/api/ampform.html](https://ampform.readthedocs.io/en/stable/api/ampform.html)). To use the default template, set:
61+
62+
```python
63+
generate_apidoc_use_compwa_template = False
64+
```
65+
66+
Other configuration values (with their defaults):
67+
68+
```python
69+
generate_apidoc_directory = "api"
70+
generate_apidoc_excludes = ["version.py"]
71+
```

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,10 @@ license-files = ["LICENSE"]
7474
package-dir = {"" = "src"}
7575

7676
[tool.setuptools.package-data]
77-
"*" = ["py.typed"]
77+
"*" = [
78+
"py.typed",
79+
"templates/*.rst_t",
80+
]
7881

7982
[tool.setuptools.packages.find]
8083
namespaces = false

src/sphinx_api_relink/__init__.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
from __future__ import annotations
44

5+
import shutil
6+
from pathlib import Path
57
from typing import TYPE_CHECKING, Any
68

79
import sphinx.domains.python
810
from docutils import nodes
911
from sphinx.addnodes import pending_xref, pending_xref_condition
1012
from sphinx.domains.python import parse_reftarget
13+
from sphinx.ext.apidoc import main as sphinx_apidoc
1114

1215
if TYPE_CHECKING:
1316
from sphinx.application import Sphinx
@@ -17,13 +20,62 @@
1720
def setup(app: Sphinx) -> dict[str, Any]:
1821
app.add_config_value("api_target_substitutions", default={}, rebuild="env")
1922
app.add_config_value("api_target_types", default={}, rebuild="env")
23+
app.add_config_value("generate_apidoc_directory", default="api", rebuild="env")
24+
app.add_config_value("generate_apidoc_excludes", default=None, rebuild="env")
25+
app.add_config_value("generate_apidoc_package_path", default=None, rebuild="env")
26+
app.add_config_value("generate_apidoc_use_compwa_template", True, rebuild="env")
27+
app.connect("config-inited", generate_apidoc)
2028
app.connect("config-inited", replace_type_to_xref)
2129
return {
2230
"parallel_read_safe": True,
2331
"parallel_write_safe": True,
2432
}
2533

2634

35+
def generate_apidoc(app: Sphinx, _: BuildEnvironment) -> None:
36+
config_key = "generate_apidoc_package_path"
37+
package_path: str | None = getattr(app.config, config_key, None)
38+
if package_path is None:
39+
return
40+
abs_package_path = Path(app.srcdir) / package_path
41+
apidoc_dir = Path(app.srcdir) / app.config.generate_apidoc_directory
42+
_run_sphinx_apidoc(
43+
abs_package_path,
44+
apidoc_dir,
45+
excludes=app.config.generate_apidoc_excludes,
46+
use_compwa_template=app.config.generate_apidoc_use_compwa_template,
47+
)
48+
49+
50+
def _run_sphinx_apidoc(
51+
package_path: Path,
52+
apidoc_dir: str = "api",
53+
excludes: list[str] | None = None,
54+
use_compwa_template: bool = True,
55+
) -> None:
56+
if not package_path.exists():
57+
msg = f"Package under {package_path} does not exist"
58+
raise FileNotFoundError(msg)
59+
shutil.rmtree(apidoc_dir, ignore_errors=True)
60+
args: list[str] = [str(package_path)]
61+
if excludes is None:
62+
excludes = []
63+
version_file = "version.py"
64+
if (package_path / version_file).exists():
65+
excludes.append(version_file)
66+
for file in excludes:
67+
excluded_path = package_path / file
68+
if not excluded_path.exists():
69+
msg = f"Excluded file {excluded_path} does not exist"
70+
raise FileNotFoundError(msg)
71+
args.append(str(package_path / file))
72+
args.extend(f"-o {apidoc_dir} --force --no-toc --separate".split())
73+
if use_compwa_template:
74+
template_dir = Path(__file__).parent / "templates"
75+
args.extend(f"--templatedir {template_dir}".split())
76+
sphinx_apidoc(args)
77+
78+
2779
def replace_type_to_xref(app: Sphinx, _: BuildEnvironment) -> None:
2880
target_substitutions = _get_target_substitutions(app)
2981
ref_targets = {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{%- if show_headings and not separate %}
2+
{{ basename.split(".")[-1] | e | heading }}
3+
4+
.. code-block:: python
5+
6+
import {{ basename }}
7+
8+
{% endif -%}
9+
.. automodule:: {{ qualname }}
10+
{%- for option in automodule_options %}
11+
:{{ option }}:
12+
{%- endfor %}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{%- macro automodule(modname, options) -%}
2+
.. automodule:: {{ modname }}
3+
{%- for option in options %}
4+
:{{ option }}:
5+
{%- endfor %}
6+
{%- endmacro %}
7+
8+
{%- macro toctree(docnames) -%}
9+
.. toctree::
10+
{% for docname in docnames %}
11+
{{ docname }}
12+
{%- endfor %}
13+
{%- endmacro %}
14+
15+
{{ pkgname.split(".")[-1] | e | heading }}
16+
17+
.. code-block:: python
18+
19+
import {{ pkgname }}
20+
21+
{%- if modulefirst and not is_namespace %}
22+
{{ automodule(pkgname, automodule_options) }}
23+
{% endif %}
24+
25+
{%- if not modulefirst and not is_namespace %}
26+
27+
{{ automodule(pkgname, automodule_options) }}
28+
{% endif %}
29+
30+
{%- if submodules or subpackages %}
31+
.. rubric:: Submodules and Subpackages
32+
{% endif %}
33+
34+
{%- if subpackages %}
35+
36+
{{ toctree(subpackages) }}
37+
{% endif %}
38+
{%- if submodules %}
39+
{% if separatemodules %}
40+
{{ toctree(submodules) }}
41+
{%- else %}
42+
{%- for submodule in submodules %}
43+
{% if show_headings %}
44+
{{- [submodule, "module"] | join(" ") | e | heading(2) }}
45+
{% endif %}
46+
{{ automodule(submodule, automodule_options) }}
47+
{% endfor %}
48+
{%- endif %}
49+
{% endif %}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{{ header | heading }}
2+
3+
.. toctree::
4+
:maxdepth: {{ maxdepth }}
5+
{% for docname in docnames %}
6+
{{ docname }}
7+
{%- endfor %}

0 commit comments

Comments
 (0)