Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
52 changes: 52 additions & 0 deletions .github/workflows/publish-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Build and publish docs

on:
release:
types: [published]

workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: 'github-pages'
cancel-in-progress: false

jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
filter: tree:0
fetch-depth: 0

- name: Setup Pages
uses: actions/configure-pages@v5

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"
cache: "pip"

- name: Install dev deps
run: python3 -m pip install -r requirements-dev.txt

- name: Build docs
run: python3 scripts/generate_api_docs.py

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: 'api-docs/_build/html/'

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
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