Skip to content

Commit 58e984e

Browse files
crandmcktmathern
andauthored
docs: Add sphinx API docs (#164)
* Initial try for API docs generated by Sphinx * Add .gitignore to _build dir * Remove _build dir * Add generated doc files to .gitignore * Fix .gitignore * Remove non-API docs from Sphinx docs * Remove docs/_static/.gitkeep * Move API docs to api-docs directory, update code comments and enable markdown code fences, move info on API doc generation to proj. contrib. * Address Tania comments * Add publish workflow * Fix workflow per Tania comments * Update wf with install dev deps and remove nx command * Remove push to main as wf trigger, change pages to github-pages * Change path where docs are built * docs: Debug workflow (#169) * fix: Bump version to c2pa C ffi v0.65.0 (#167) * chore: Bump version number (#168) Prepare release for v0.23.0. * fix: Tag on release * fix: On release only * fix: Safer context handling in streams (#170) * fix: 1 * fix: Update c2pa.py code * fix: Renamings * fix: Format * fix: FInal formatting fixes --------- Co-authored-by: tmathern <[email protected]> Co-authored-by: Tania Mathern <[email protected]>
1 parent d3bb12c commit 58e984e

File tree

12 files changed

+334
-34
lines changed

12 files changed

+334
-34
lines changed

.github/workflows/publish-docs.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Build and publish docs
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: read
11+
pages: write
12+
id-token: write
13+
14+
concurrency:
15+
group: 'github-pages'
16+
cancel-in-progress: false
17+
18+
jobs:
19+
deploy:
20+
environment:
21+
name: github-pages
22+
url: ${{ steps.deployment.outputs.page_url }}
23+
runs-on: ubuntu-latest
24+
steps:
25+
- uses: actions/checkout@v4
26+
with:
27+
filter: tree:0
28+
fetch-depth: 0
29+
30+
- name: Setup Pages
31+
uses: actions/configure-pages@v5
32+
33+
- name: Set up Python
34+
uses: actions/setup-python@v5
35+
with:
36+
python-version: "3.10"
37+
cache: "pip"
38+
39+
- name: Install dev deps
40+
run: python3 -m pip install -r requirements-dev.txt
41+
42+
- name: Build docs
43+
run: python3 scripts/generate_api_docs.py
44+
45+
- name: Upload artifact
46+
uses: actions/upload-pages-artifact@v3
47+
with:
48+
path: 'api-docs/_build/html/'
49+
50+
- name: Deploy to GitHub Pages
51+
id: deployment
52+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ wheels/
4141
.installed.cfg
4242
*.egg
4343

44+
# Sphinx docs
45+
api-docs/api
46+
api-docs/_build
47+
api-docs/_static
48+
api-docs/_preprocessed
49+
4450
# PyInstaller
4551
# Usually these files are written by a Python script from a template
4652
# before PyInstaller builds the exe, so as to inject date/other infos into it.

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,8 @@ format:
102102

103103
# Downloads the required native artifacts for the specified version
104104
download-native-artifacts:
105-
python3 scripts/download_artifacts.py $(C2PA_VERSION)
105+
python3 scripts/download_artifacts.py $(C2PA_VERSION)
106+
107+
# Build API documentation with Sphinx
108+
docs:
109+
python3 scripts/generate_api_docs.py

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ See the [`examples` directory](https://github.com/contentauth/c2pa-python/tree/m
3636
- `examples/sign.py` shows how to sign and verify an asset with a C2PA manifest.
3737
- `examples/training.py` demonstrates how to add a "Do Not Train" assertion to an asset and verify it.
3838

39+
## API reference documentation
40+
41+
See [the section in Contributing to the project](https://github.com/contentauth/c2pa-python/blob/main/docs/project-contributions.md#api-reference-documentation).
42+
3943
## Contributing
4044

4145
Contributions are welcome! For more information, see [Contributing to the project](https://github.com/contentauth/c2pa-python/blob/main/docs/project-contributions.md).

api-docs/conf.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import os
2+
from pathlib import Path
3+
4+
# -- Project information -----------------------------------------------------
5+
6+
project = "c2pa-python"
7+
author = "Content Authenticity Initiative (CAI)"
8+
9+
# -- General configuration ---------------------------------------------------
10+
11+
extensions = [
12+
"myst_parser",
13+
"autoapi.extension",
14+
"sphinx.ext.napoleon",
15+
]
16+
17+
myst_enable_extensions = [
18+
"colon_fence",
19+
"deflist",
20+
"fieldlist",
21+
"strikethrough",
22+
"tasklist",
23+
"attrs_block",
24+
"attrs_inline",
25+
]
26+
27+
templates_path = ["_templates"]
28+
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
29+
30+
# -- AutoAPI configuration ---------------------------------------------------
31+
32+
project_root = Path(__file__).resolve().parents[1]
33+
autoapi_type = "python"
34+
# Allow overriding the source path used by AutoAPI (for preprocessing)
35+
autoapi_dirs = [
36+
os.environ.get(
37+
"C2PA_DOCS_SRC",
38+
str(project_root / "src" / "c2pa"),
39+
)
40+
]
41+
autoapi_root = "api"
42+
autoapi_keep_files = True
43+
autoapi_add_toctree_entry = True
44+
autoapi_options = [
45+
"members",
46+
"undoc-members",
47+
"show-inheritance",
48+
"show-module-summary",
49+
"imported-members",
50+
]
51+
52+
# -- Options for HTML output -------------------------------------------------
53+
54+
html_theme = "furo"
55+
html_static_path = ["_static"]
56+
57+
# Avoid executing package imports during docs build
58+
autodoc_typehints = "description"
59+
60+
# Napoleon (Google/Numpy docstring support)
61+
napoleon_google_docstring = True
62+
napoleon_numpy_docstring = False
63+
64+

api-docs/index.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.. c2pa-python documentation master file
2+
3+
Welcome to c2pa-python's documentation!
4+
=======================================
5+
6+
.. toctree::
7+
:maxdepth: 2
8+
:caption: Contents:
9+
10+
api/index
11+
12+

docs/project-contributions.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,28 @@ To rebuild and test, enter these commands:
127127
make build-python
128128
make test
129129
```
130+
131+
## API reference documentation
132+
133+
We use Sphinx autodoc to generate API docs.
134+
135+
Install development dependencies:
136+
137+
```
138+
cd c2pa-python
139+
python3 -m pip install -r requirements-dev.txt
140+
```
141+
142+
Build docs by entering `make -C docs` or:
143+
144+
```
145+
python3 scripts/generate_api_docs.py
146+
```
147+
148+
View the output by loading `api-docs/build/html/index.html` in a web browser.
149+
150+
This uses `sphinx-autoapi` to parse `src/c2pa` directly, avoiding imports of native libs.
151+
- Entry script: `scripts/generate_api_docs.py`
152+
- Config: `api-docs/conf.py`; index: `api-docs/index.rst`
153+
154+
Sphinx config is in `api-docs/conf.py`, which uses `index.rst`.

requirements-dev.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,10 @@ autopep8==2.0.4 # For automatic code formatting
1616
flake8==7.3.0
1717

1818
# Test dependencies (for callback signers)
19-
cryptography==45.0.6
19+
cryptography==45.0.6
20+
21+
# Documentation
22+
Sphinx>=7.3.0
23+
sphinx-autoapi>=3.0.0
24+
myst-parser>=2.0.0
25+
furo>=2024.0.0

scripts/generate_api_docs.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Generate API documentation using Sphinx + AutoAPI.
4+
5+
This script builds HTML docs into docs/_build/html.
6+
It avoids importing the package by relying on sphinx-autoapi
7+
to parse source files directly.
8+
"""
9+
10+
import shutil
11+
import os
12+
import sys
13+
from pathlib import Path
14+
import importlib
15+
16+
17+
def ensure_tools_available() -> None:
18+
try:
19+
importlib.import_module("sphinx")
20+
importlib.import_module("autoapi")
21+
importlib.import_module("myst_parser")
22+
except Exception as exc:
23+
root = Path(__file__).resolve().parents[1]
24+
req = root / "requirements-dev.txt"
25+
print(
26+
"Missing documentation dependencies. "
27+
f"Install with: python3 -m pip install -r {req}",
28+
file=sys.stderr,
29+
)
30+
raise SystemExit(1) from exc
31+
32+
33+
def build_docs() -> None:
34+
root = Path(__file__).resolve().parents[1]
35+
docs_dir = root / "api-docs"
36+
build_dir = docs_dir / "_build" / "html"
37+
api_dir = docs_dir / "api"
38+
39+
# Preprocess sources: convert Markdown code fences in docstrings to reST
40+
src_pkg_dir = root / "src" / "c2pa"
41+
pre_dir = docs_dir / "_preprocessed"
42+
pre_pkg_dir = pre_dir / "c2pa"
43+
if pre_dir.exists():
44+
shutil.rmtree(pre_dir)
45+
pre_pkg_dir.mkdir(parents=True, exist_ok=True)
46+
47+
def convert_fences_to_rst(text: str) -> str:
48+
lines = text.splitlines()
49+
out: list[str] = []
50+
i = 0
51+
while i < len(lines):
52+
line = lines[i]
53+
stripped = line.lstrip()
54+
indent = line[: len(line) - len(stripped)]
55+
if stripped.startswith("```"):
56+
fence = stripped
57+
lang = fence[3:].strip() or "text"
58+
# Start directive
59+
out.append(f"{indent}.. code-block:: {lang}")
60+
out.append("")
61+
i += 1
62+
# Emit indented code until closing fence
63+
while i < len(lines):
64+
l2 = lines[i]
65+
if l2.lstrip().startswith("```"):
66+
i += 1
67+
break
68+
out.append(f"{indent} {l2}")
69+
i += 1
70+
continue
71+
out.append(line)
72+
i += 1
73+
return "\n".join(out) + ("\n" if text.endswith("\n") else "")
74+
75+
for src_path in src_pkg_dir.rglob("*.py"):
76+
rel = src_path.relative_to(src_pkg_dir)
77+
dest = pre_pkg_dir / rel
78+
dest.parent.mkdir(parents=True, exist_ok=True)
79+
content = src_path.read_text(encoding="utf-8")
80+
dest.write_text(convert_fences_to_rst(content), encoding="utf-8")
81+
82+
# Point AutoAPI to preprocessed sources
83+
os.environ["C2PA_DOCS_SRC"] = str(pre_pkg_dir)
84+
85+
# Clean AutoAPI output to avoid stale pages
86+
if api_dir.exists():
87+
shutil.rmtree(api_dir)
88+
89+
build_dir.mkdir(parents=True, exist_ok=True)
90+
91+
try:
92+
sphinx_build_mod = importlib.import_module("sphinx.cmd.build")
93+
sphinx_main = getattr(sphinx_build_mod, "main")
94+
code = sphinx_main([
95+
"-b",
96+
"html",
97+
str(docs_dir),
98+
str(build_dir),
99+
])
100+
if code != 0:
101+
raise SystemExit(code)
102+
except Exception:
103+
# Fallback to subprocess if needed
104+
import subprocess
105+
106+
cmd = [
107+
sys.executable,
108+
"-m",
109+
"sphinx",
110+
"-b",
111+
"html",
112+
str(docs_dir),
113+
str(build_dir),
114+
]
115+
subprocess.run(cmd, check=True)
116+
117+
print(f"API docs generated at: {build_dir}")
118+
119+
120+
if __name__ == "__main__":
121+
ensure_tools_available()
122+
build_docs()
123+
124+

src/c2pa/build.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
import os
1515
import sys
16-
import requests
16+
import requests # type: ignore
1717
from pathlib import Path
1818
import zipfile
1919
import io
@@ -53,8 +53,7 @@ def download_artifact(url: str, platform_name: str) -> None:
5353
# Extract all files to the platform directory
5454
zip_ref.extractall(platform_dir)
5555

56-
print(f"Successfully downloaded and extracted artifacts for {
57-
platform_name}")
56+
print(f"Successfully downloaded and extracted artifacts for {platform_name}")
5857

5958

6059
def download_artifacts() -> None:
@@ -95,7 +94,7 @@ def download_artifacts() -> None:
9594
def inject_version():
9695
"""Inject the version from pyproject.toml
9796
into src/c2pa/__init__.py as __version__."""
98-
import toml
97+
import toml # type: ignore
9998
pyproject_path = os.path.abspath(
10099
os.path.join(
101100
os.path.dirname(__file__),

0 commit comments

Comments
 (0)