Skip to content

Commit

Permalink
FEAT: generate API with simple configuration value (#1)
Browse files Browse the repository at this point in the history
* MAINT remove redundant Markdown newlines
  • Loading branch information
redeboer authored Dec 4, 2023
1 parent 27dd3a7 commit ecc0db1
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 12 deletions.
19 changes: 12 additions & 7 deletions .cspell.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
{
"version": "0.2",
"enableFiletypes": [
"git-commit",
"github-actions-workflow",
"julia",
"jupyter"
],
"flagWords": [
"analyse",
"colour",
Expand All @@ -20,6 +14,7 @@
"transisions"
],
"ignorePaths": [
"**/*.rst_t",
"**/.cspell.json",
".editorconfig",
".gitignore",
Expand All @@ -30,6 +25,8 @@
],
"language": "en-US",
"words": [
"ampform",
"apidoc",
"autoapi",
"autodoc",
"ComPWA",
Expand All @@ -46,6 +43,14 @@
"refspecific",
"reftarget",
"reftype",
"rtfd"
"rtfd",
"srcdir",
"templatedir"
],
"enableFiletypes": [
"git-commit",
"github-actions-workflow",
"julia",
"jupyter"
]
}
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ repos:
- id: blacken-docs

- repo: https://github.com/ComPWA/repo-maintenance
rev: 0.1.6
rev: 0.1.7
hooks:
- id: check-dev-files
args:
Expand Down
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ Just install through [PyPI](https://pypi.org) with `pip`:
pip install sphinx-api-relink
```

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`:
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`:

```python
extensions = [
Expand All @@ -50,3 +48,24 @@ api_target_types: dict[str, str] = {
"RangeDefinition": "obj",
}
```

## Generate API

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`:

```python
api_package_path = "../src/my_package" # relative to conf.py
```

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:

```python
generate_apidoc_use_compwa_template = False
```

Other configuration values (with their defaults):

```python
generate_apidoc_directory = "api"
generate_apidoc_excludes = ["version.py"]
```
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ license-files = ["LICENSE"]
package-dir = {"" = "src"}

[tool.setuptools.package-data]
"*" = ["py.typed"]
"*" = [
"py.typed",
"templates/*.rst_t",
]

[tool.setuptools.packages.find]
namespaces = false
Expand Down
52 changes: 52 additions & 0 deletions src/sphinx_api_relink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

from __future__ import annotations

import shutil
from pathlib import Path
from typing import TYPE_CHECKING, Any

import sphinx.domains.python
from docutils import nodes
from sphinx.addnodes import pending_xref, pending_xref_condition
from sphinx.domains.python import parse_reftarget
from sphinx.ext.apidoc import main as sphinx_apidoc

if TYPE_CHECKING:
from sphinx.application import Sphinx
Expand All @@ -17,13 +20,62 @@
def setup(app: Sphinx) -> dict[str, Any]:
app.add_config_value("api_target_substitutions", default={}, rebuild="env")
app.add_config_value("api_target_types", default={}, rebuild="env")
app.add_config_value("generate_apidoc_directory", default="api", rebuild="env")
app.add_config_value("generate_apidoc_excludes", default=None, rebuild="env")
app.add_config_value("generate_apidoc_package_path", default=None, rebuild="env")
app.add_config_value("generate_apidoc_use_compwa_template", True, rebuild="env")
app.connect("config-inited", generate_apidoc)
app.connect("config-inited", replace_type_to_xref)
return {
"parallel_read_safe": True,
"parallel_write_safe": True,
}


def generate_apidoc(app: Sphinx, _: BuildEnvironment) -> None:
config_key = "generate_apidoc_package_path"
package_path: str | None = getattr(app.config, config_key, None)
if package_path is None:
return
abs_package_path = Path(app.srcdir) / package_path
apidoc_dir = Path(app.srcdir) / app.config.generate_apidoc_directory
_run_sphinx_apidoc(
abs_package_path,
apidoc_dir,
excludes=app.config.generate_apidoc_excludes,
use_compwa_template=app.config.generate_apidoc_use_compwa_template,
)


def _run_sphinx_apidoc(
package_path: Path,
apidoc_dir: str = "api",
excludes: list[str] | None = None,
use_compwa_template: bool = True,
) -> None:
if not package_path.exists():
msg = f"Package under {package_path} does not exist"
raise FileNotFoundError(msg)
shutil.rmtree(apidoc_dir, ignore_errors=True)
args: list[str] = [str(package_path)]
if excludes is None:
excludes = []
version_file = "version.py"
if (package_path / version_file).exists():
excludes.append(version_file)
for file in excludes:
excluded_path = package_path / file
if not excluded_path.exists():
msg = f"Excluded file {excluded_path} does not exist"
raise FileNotFoundError(msg)
args.append(str(package_path / file))
args.extend(f"-o {apidoc_dir} --force --no-toc --separate".split())
if use_compwa_template:
template_dir = Path(__file__).parent / "templates"
args.extend(f"--templatedir {template_dir}".split())
sphinx_apidoc(args)


def replace_type_to_xref(app: Sphinx, _: BuildEnvironment) -> None:
target_substitutions = _get_target_substitutions(app)
ref_targets = {
Expand Down
12 changes: 12 additions & 0 deletions src/sphinx_api_relink/templates/module.rst_t
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{%- if show_headings and not separate %}
{{ basename.split(".")[-1] | e | heading }}

.. code-block:: python

import {{ basename }}

{% endif -%}
.. automodule:: {{ qualname }}
{%- for option in automodule_options %}
:{{ option }}:
{%- endfor %}
49 changes: 49 additions & 0 deletions src/sphinx_api_relink/templates/package.rst_t
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{%- macro automodule(modname, options) -%}
.. automodule:: {{ modname }}
{%- for option in options %}
:{{ option }}:
{%- endfor %}
{%- endmacro %}

{%- macro toctree(docnames) -%}
.. toctree::
{% for docname in docnames %}
{{ docname }}
{%- endfor %}
{%- endmacro %}

{{ pkgname.split(".")[-1] | e | heading }}

.. code-block:: python

import {{ pkgname }}

{%- if modulefirst and not is_namespace %}
{{ automodule(pkgname, automodule_options) }}
{% endif %}

{%- if not modulefirst and not is_namespace %}

{{ automodule(pkgname, automodule_options) }}
{% endif %}

{%- if submodules or subpackages %}
.. rubric:: Submodules and Subpackages
{% endif %}

{%- if subpackages %}

{{ toctree(subpackages) }}
{% endif %}
{%- if submodules %}
{% if separatemodules %}
{{ toctree(submodules) }}
{%- else %}
{%- for submodule in submodules %}
{% if show_headings %}
{{- [submodule, "module"] | join(" ") | e | heading(2) }}
{% endif %}
{{ automodule(submodule, automodule_options) }}
{% endfor %}
{%- endif %}
{% endif %}
7 changes: 7 additions & 0 deletions src/sphinx_api_relink/templates/toc.rst_t
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{{ header | heading }}

.. toctree::
:maxdepth: {{ maxdepth }}
{% for docname in docnames %}
{{ docname }}
{%- endfor %}

0 comments on commit ecc0db1

Please sign in to comment.