diff --git a/.github/workflows/python-docs-nightly.yaml b/.github/workflows/python-docs-nightly.yaml new file mode 100644 index 000000000..8cff9ef5b --- /dev/null +++ b/.github/workflows/python-docs-nightly.yaml @@ -0,0 +1,58 @@ +name: Python API Docs (Nightly) + +on: + schedule: + # Run at 3 AM UTC every day + - cron: '0 3 * * *' + # Allow manual triggers for testing + workflow_dispatch: + +concurrency: + group: python-docs-${{ github.ref }} + cancel-in-progress: true + +jobs: + generate-docs: + name: Generate Python API Documentation + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install uv + uses: astral-sh/setup-uv@v5 + + - name: Install dependencies + run: | + uv sync --all-extras + + - name: Generate documentation + run: | + make python-docs + + - name: Upload documentation artifact + uses: actions/upload-artifact@v4 + with: + name: python-api-docs + path: docs/python + retention-days: 90 + + - name: Add summary to job + run: | + echo "## Python API Documentation Generated" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "📚 Documentation has been generated for the following packages:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- pyln.client - Client library and plugin library" >> $GITHUB_STEP_SUMMARY + echo "- pyln.proto - Lightning Network protocol implementation" >> $GITHUB_STEP_SUMMARY + echo "- pyln.grpc - gRPC protocol definitions" >> $GITHUB_STEP_SUMMARY + echo "- pyln.testing - Testing utilities" >> $GITHUB_STEP_SUMMARY + echo "- pyln.spec.bolt7 - BOLT #7 specification implementation" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Download the artifact to view the complete API documentation." >> $GITHUB_STEP_SUMMARY diff --git a/.gitignore b/.gitignore index a16087153..c54cf7795 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ coverage # Coverage profiling data files *.profraw *.profdata +# Generated Python API documentation +docs/python ccan/config.h __pycache__ config.vars diff --git a/Makefile b/Makefile index e1a5e05a9..3c7d7831c 100644 --- a/Makefile +++ b/Makefile @@ -689,6 +689,15 @@ coverage-clang-clean: .PHONY: coverage-clang-collect coverage-clang-report coverage-clang coverage-clang-clean +# Python API documentation targets +python-docs: + @./contrib/api/generate-python-docs.py + +python-docs-clean: + rm -rf docs/python + +.PHONY: python-docs python-docs-clean + # We make libwallycore.la a dependency, so that it gets built normally, without ncc. # Ncc can't handle the libwally source code (yet). ncc: ${TARGET_DIR}/libwally-core-build/src/libwallycore.la diff --git a/contrib/api/generate-python-docs.py b/contrib/api/generate-python-docs.py new file mode 100755 index 000000000..bfbf66496 --- /dev/null +++ b/contrib/api/generate-python-docs.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +""" +Generate Python API documentation for all workspace packages using pdoc3. + +This script generates HTML documentation for all Python packages in the +Core Lightning workspace and creates an index page linking to all of them. +""" + +import os +import subprocess +import sys +from datetime import datetime +from pathlib import Path + +# Define packages to document (module name -> source directory) +# Only includes packages that are in the workspace and can be imported +PACKAGES = { + "pyln.client": "contrib/pyln-client", + "pyln.proto": "contrib/pyln-proto", + "pyln.grpc": "contrib/pyln-grpc-proto", + "pyln.testing": "contrib/pyln-testing", + "pyln.spec.bolt7": "contrib/pyln-spec/bolt7", +} + +INDEX_HTML_TEMPLATE = """ + + + Core Lightning Python Packages Documentation + + + +

Core Lightning Python Packages Documentation

+

This page provides links to the API documentation for all Python packages in the Core Lightning workspace.

+ +
+
+

pyln.client

+

Client library and plugin library for Core Lightning

+
+ +
+

pyln.proto

+

Lightning Network protocol implementation

+
+ +
+

pyln.grpc

+

gRPC protocol definitions for Core Lightning

+
+ +
+

pyln.testing

+

Testing utilities for Core Lightning

+
+ +
+

pyln.spec.bolt7

+

BOLT #7 specification implementation

+
+
+ +

Generated on {timestamp}

+ + +""" + + +def generate_docs(output_dir: Path, repo_root: Path): + """Generate documentation for all packages.""" + print(f"Generating Python documentation for all workspace packages...") + print(f"Output directory: {output_dir}") + + # Clean and create output directory + if output_dir.exists(): + import shutil + shutil.rmtree(output_dir) + output_dir.mkdir(parents=True) + + # Change to repo root for imports to work correctly + os.chdir(repo_root) + + # Generate documentation for each package + for package, source_dir in PACKAGES.items(): + print(f"Generating docs for {package} (from {source_dir})...") + + try: + # Use pdoc3 to generate HTML documentation + subprocess.run( + [ + "uv", "run", "pdoc3", + "--html", + "--output-dir", str(output_dir), + "--force", + package + ], + check=True, + cwd=repo_root, + ) + except subprocess.CalledProcessError as e: + print(f"Warning: Failed to generate docs for {package}, skipping...") + print(f"Error: {e}") + continue + + # Create index.html + index_path = output_dir / "index.html" + timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC") + index_path.write_text(INDEX_HTML_TEMPLATE.format(timestamp=timestamp)) + + print("\nDocumentation generated successfully!") + print(f"Open {output_dir}/index.html in your browser to view the documentation.") + + +def main(): + """Main entry point.""" + # Determine paths + script_dir = Path(__file__).parent.resolve() + repo_root = script_dir.parent.parent + + # Default output directory + output_dir = repo_root / "docs" / "python" + + # Allow override via command line argument + if len(sys.argv) > 1: + output_dir = Path(sys.argv[1]) + + generate_docs(output_dir, repo_root) + + +if __name__ == "__main__": + main() diff --git a/contrib/pyln-client/pyln/client/version.py b/contrib/pyln-client/pyln/client/version.py index 01345c82e..600979394 100644 --- a/contrib/pyln-client/pyln/client/version.py +++ b/contrib/pyln-client/pyln/client/version.py @@ -75,4 +75,4 @@ class NodeVersion: return False -__all__ = [NodeVersion] +__all__ = ["NodeVersion"] diff --git a/pyproject.toml b/pyproject.toml index 128efa96d..9838713c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ package-mode = false [dependency-groups] dev = [ # Test dependencies and inherited dependencies belong here - "crc32c>=2.2.post0", # Belongs to lnprototest + "crc32c>=2.2.post0", # Belongs to lnprototest "pytest>=8.0.0", "pytest-xdist>=3.6.0", "pytest-test-groups>=1.2.0", @@ -35,6 +35,7 @@ dev = [ "flask-socketio>=5", "tqdm", "pytest-benchmark", + "pdoc3>=0.11.6", ] [project.optional-dependencies] diff --git a/uv.lock b/uv.lock index e15d8113f..cb3018fe4 100644 --- a/uv.lock +++ b/uv.lock @@ -457,6 +457,7 @@ dev = [ { name = "crc32c" }, { name = "flake8" }, { name = "flask-socketio" }, + { name = "pdoc3" }, { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest-benchmark" }, @@ -491,6 +492,7 @@ dev = [ { name = "crc32c", specifier = ">=2.2.post0" }, { name = "flake8", specifier = ">=7.0" }, { name = "flask-socketio", specifier = ">=5" }, + { name = "pdoc3", specifier = ">=0.11.6" }, { name = "pytest", specifier = ">=8.0.0" }, { name = "pytest-benchmark" }, { name = "pytest-custom-exit-code", specifier = "==0.3.0" }, @@ -1061,6 +1063,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, ] +[[package]] +name = "markdown" +version = "3.9" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, +] + +[[package]] +name = "markdown" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, +] + [[package]] name = "markupsafe" version = "3.0.3" @@ -1197,6 +1226,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] +[[package]] +name = "pdoc3" +version = "0.11.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/f0/07d8b771b99c16a06741cd7b2639494a15357df819ecf899c33b87db6257/pdoc3-0.11.6.tar.gz", hash = "sha256:1ea5e84b87a754d191fb64bf5e517ca6c50d0d84a614c1efecf6b46d290ae387", size = 177107, upload-time = "2025-03-20T22:53:53.099Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/98/629f269c2bd91bdcac147aad5cf51ceb645c0196e23a41ee3c051125190f/pdoc3-0.11.6-py3-none-any.whl", hash = "sha256:8b72723767bd48d899812d2aec8375fc1c3476e179455db0b4575e6dccb44b93", size = 255188, upload-time = "2025-03-20T22:53:51.671Z" }, +] + [[package]] name = "pluggy" version = "1.6.0"