Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ wheels/
.installed.cfg
*.egg

# Sphinx docs
api-docs/api
api-docs/_build
api-docs/_static
api-docs/_preprocessed

# PyInstaller
# Usually these files are written by a Python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,8 @@ format:

# Downloads the required native artifacts for the specified version
download-native-artifacts:
python3 scripts/download_artifacts.py $(C2PA_VERSION)
python3 scripts/download_artifacts.py $(C2PA_VERSION)

# Build API documentation with Sphinx
docs:
python3 scripts/generate_api_docs.py
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ See the [`examples` directory](https://github.com/contentauth/c2pa-python/tree/m
- `examples/sign.py` shows how to sign and verify an asset with a C2PA manifest.
- `examples/training.py` demonstrates how to add a "Do Not Train" assertion to an asset and verify it.

## API reference documentation

See [the section in Contributing to the project](https://github.com/contentauth/c2pa-python/blob/main/docs/project-contributions.md#api-reference-documentation).

## Contributing

Contributions are welcome! For more information, see [Contributing to the project](https://github.com/contentauth/c2pa-python/blob/main/docs/project-contributions.md).
Expand Down
64 changes: 64 additions & 0 deletions api-docs/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import os
from pathlib import Path

# -- Project information -----------------------------------------------------

project = "c2pa-python"
author = "Content Authenticity Initiative (CAI)"

# -- General configuration ---------------------------------------------------

extensions = [
"myst_parser",
"autoapi.extension",
"sphinx.ext.napoleon",
]

myst_enable_extensions = [
"colon_fence",
"deflist",
"fieldlist",
"strikethrough",
"tasklist",
"attrs_block",
"attrs_inline",
]

templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]

# -- AutoAPI configuration ---------------------------------------------------

project_root = Path(__file__).resolve().parents[1]
autoapi_type = "python"
# Allow overriding the source path used by AutoAPI (for preprocessing)
autoapi_dirs = [
os.environ.get(
"C2PA_DOCS_SRC",
str(project_root / "src" / "c2pa"),
)
]
autoapi_root = "api"
autoapi_keep_files = True
autoapi_add_toctree_entry = True
autoapi_options = [
"members",
"undoc-members",
"show-inheritance",
"show-module-summary",
"imported-members",
]

# -- Options for HTML output -------------------------------------------------

html_theme = "furo"
html_static_path = ["_static"]

# Avoid executing package imports during docs build
autodoc_typehints = "description"

# Napoleon (Google/Numpy docstring support)
napoleon_google_docstring = True
napoleon_numpy_docstring = False


12 changes: 12 additions & 0 deletions api-docs/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.. c2pa-python documentation master file

Welcome to c2pa-python's documentation!
=======================================

.. toctree::
:maxdepth: 2
:caption: Contents:

api/index


25 changes: 25 additions & 0 deletions docs/project-contributions.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,28 @@ To rebuild and test, enter these commands:
make build-python
make test
```

## API reference documentation

We use Sphinx autodoc to generate API docs.

Install development dependencies:

```
cd c2pa-python
python3 -m pip install -r requirements-dev.txt
```

Build docs by entering `make -C docs` or:

```
python3 scripts/generate_api_docs.py
```

View the output by loading `api-docs/build/html/index.html` in a web browser.

This uses `sphinx-autoapi` to parse `src/c2pa` directly, avoiding imports of native libs.
- Entry script: `scripts/generate_api_docs.py`
- Config: `api-docs/conf.py`; index: `api-docs/index.rst`

Sphinx config is in `api-docs/conf.py`, which uses `index.rst`.
8 changes: 7 additions & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,10 @@ autopep8==2.0.4 # For automatic code formatting
flake8==7.3.0

# Test dependencies (for callback signers)
cryptography==45.0.6
cryptography==45.0.6

# Documentation
Sphinx>=7.3.0
sphinx-autoapi>=3.0.0
myst-parser>=2.0.0
furo>=2024.0.0
124 changes: 124 additions & 0 deletions scripts/generate_api_docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""
Generate API documentation using Sphinx + AutoAPI.

This script builds HTML docs into docs/_build/html.
It avoids importing the package by relying on sphinx-autoapi
to parse source files directly.
"""

import shutil
import os
import sys
from pathlib import Path
import importlib


def ensure_tools_available() -> None:
try:
importlib.import_module("sphinx")
importlib.import_module("autoapi")
importlib.import_module("myst_parser")
except Exception as exc:
root = Path(__file__).resolve().parents[1]
req = root / "requirements-dev.txt"
print(
"Missing documentation dependencies. "
f"Install with: python3 -m pip install -r {req}",
file=sys.stderr,
)
raise SystemExit(1) from exc


def build_docs() -> None:
root = Path(__file__).resolve().parents[1]
docs_dir = root / "api-docs"
build_dir = docs_dir / "_build" / "html"
api_dir = docs_dir / "api"

# Preprocess sources: convert Markdown code fences in docstrings to reST
src_pkg_dir = root / "src" / "c2pa"
pre_dir = docs_dir / "_preprocessed"
pre_pkg_dir = pre_dir / "c2pa"
if pre_dir.exists():
shutil.rmtree(pre_dir)
pre_pkg_dir.mkdir(parents=True, exist_ok=True)

def convert_fences_to_rst(text: str) -> str:
lines = text.splitlines()
out: list[str] = []
i = 0
while i < len(lines):
line = lines[i]
stripped = line.lstrip()
indent = line[: len(line) - len(stripped)]
if stripped.startswith("```"):
fence = stripped
lang = fence[3:].strip() or "text"
# Start directive
out.append(f"{indent}.. code-block:: {lang}")
out.append("")
i += 1
# Emit indented code until closing fence
while i < len(lines):
l2 = lines[i]
if l2.lstrip().startswith("```"):
i += 1
break
out.append(f"{indent} {l2}")
i += 1
continue
out.append(line)
i += 1
return "\n".join(out) + ("\n" if text.endswith("\n") else "")

for src_path in src_pkg_dir.rglob("*.py"):
rel = src_path.relative_to(src_pkg_dir)
dest = pre_pkg_dir / rel
dest.parent.mkdir(parents=True, exist_ok=True)
content = src_path.read_text(encoding="utf-8")
dest.write_text(convert_fences_to_rst(content), encoding="utf-8")

# Point AutoAPI to preprocessed sources
os.environ["C2PA_DOCS_SRC"] = str(pre_pkg_dir)

# Clean AutoAPI output to avoid stale pages
if api_dir.exists():
shutil.rmtree(api_dir)

build_dir.mkdir(parents=True, exist_ok=True)

try:
sphinx_build_mod = importlib.import_module("sphinx.cmd.build")
sphinx_main = getattr(sphinx_build_mod, "main")
code = sphinx_main([
"-b",
"html",
str(docs_dir),
str(build_dir),
])
if code != 0:
raise SystemExit(code)
except Exception:
# Fallback to subprocess if needed
import subprocess

cmd = [
sys.executable,
"-m",
"sphinx",
"-b",
"html",
str(docs_dir),
str(build_dir),
]
subprocess.run(cmd, check=True)

print(f"API docs generated at: {build_dir}")


if __name__ == "__main__":
ensure_tools_available()
build_docs()


7 changes: 3 additions & 4 deletions src/c2pa/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

import os
import sys
import requests
import requests # type: ignore
from pathlib import Path
import zipfile
import io
Expand Down Expand Up @@ -53,8 +53,7 @@ def download_artifact(url: str, platform_name: str) -> None:
# Extract all files to the platform directory
zip_ref.extractall(platform_dir)

print(f"Successfully downloaded and extracted artifacts for {
platform_name}")
print(f"Successfully downloaded and extracted artifacts for {platform_name}")


def download_artifacts() -> None:
Expand Down Expand Up @@ -95,7 +94,7 @@ def download_artifacts() -> None:
def inject_version():
"""Inject the version from pyproject.toml
into src/c2pa/__init__.py as __version__."""
import toml
import toml # type: ignore
pyproject_path = os.path.abspath(
os.path.join(
os.path.dirname(__file__),
Expand Down
Loading
Loading