Skip to content
Merged
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
89 changes: 67 additions & 22 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Release to PyPI
name: Release to PyPI and Publish Documentation

on:
workflow_run:
Expand All @@ -13,10 +13,15 @@ jobs:
runs-on: ubuntu-latest
outputs:
exists: ${{ steps.check-tag.outputs.exists }}
version: ${{ steps.get-version.outputs.version }}
steps:
- uses: actions/checkout@v6
- name: "Get CAPIO-CL version"
run: echo "PYCAPIO_VERSION=$(grep -E 'VERSION [0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt | awk '{print $2}')" >> $GITHUB_ENV
- name: "Get PYCAPIO version"
id: get-version
run: |
VERSION=$(grep -E 'VERSION [0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt | awk '{print $2}')
echo "PYCAPIO_VERSION=$VERSION" >> $GITHUB_ENV
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: "Check if tag exists"
id: check-tag
uses: mukunku/tag-exists-action@v1.7.0
Expand All @@ -26,7 +31,7 @@ jobs:
build-wheels:
name: Build wheels on ${{ matrix.os }}
needs: check-tag-existance
if: needs.check-version.outputs.tag_exists == 'false'
if: needs.check-tag-existance.outputs.exists == 'false'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
Expand All @@ -35,15 +40,12 @@ jobs:
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"

- name: Install cibuildwheel
run: python -m pip install --upgrade cibuildwheel

- name: Build wheels
env:
CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-* cp314-*"
Expand All @@ -54,11 +56,8 @@ jobs:
CIBW_BUILD_VERBOSITY: 1
CIBW_BEFORE_ALL_LINUX: >
dnf install -y openmpi-devel

CIBW_ENVIRONMENT_LINUX: 'PATH="/usr/lib64/openmpi/bin:$PATH"'

run: cibuildwheel --output-dir wheelhouse

- name: Upload wheel artifacts
uses: actions/upload-artifact@v4
with:
Expand All @@ -68,22 +67,19 @@ jobs:
build-sdist:
name: Build source distribution
needs: check-tag-existance
if: needs.check-version.outputs.tag_exists == 'false'
if: needs.check-tag-existance.outputs.exists == 'false'
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"

- name: Build sdist
run: |
python -m pip install --upgrade build
python -m build --sdist

- name: Upload sdist artifact
uses: actions/upload-artifact@v4
with:
Expand All @@ -93,40 +89,89 @@ jobs:
publish:
name: Release and Publish to PyPI
needs: [check-tag-existance, build-wheels, build-sdist]
if: needs.check-version.outputs.tag_exists == 'false'
if: needs.check-tag-existance.outputs.exists == 'false'
runs-on: ubuntu-latest
permissions:
contents: write # Required for creating GitHub Releases
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Download all wheels
uses: actions/download-artifact@v4
with:
pattern: wheels-*
merge-multiple: true
path: dist

- name: Download sdist
uses: actions/download-artifact@v4
with:
name: sdist
path: dist

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: "v${{ env.PYCAPIO_VERSION }}"
name: "v${{ env.PYCAPIO_VERSION }}"
tag_name: "v${{ needs.check-tag-existance.outputs.version }}"
name: "v${{ needs.check-tag-existance.outputs.version }}"
generate_release_notes: true
files: dist/*

- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_DEPLOY_KEY }}
run: |
python -m pip install --upgrade twine
twine check dist/*
twine upload dist/*
twine upload dist/*

build-docs:
name: Build documentation
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install system packages
run: |
sudo apt-get update
sudo apt-get install -y doxygen

- name: Install documentation dependencies
run: pip install -r docs/requirements.txt

- name: Build HTML
run: make -C docs html

- name: Upload documentation artifact
uses: actions/upload-artifact@v4
with:
name: documentation
path: docs/_build/html

deploy-docs:
name: Deploy documentation
needs: [ build-docs, publish ]
if: needs.check-tag-existance.outputs.exists == 'false'
runs-on: ubuntu-24.04
concurrency:
group: docs-deploy
cancel-in-progress: false
steps:
- name: Download documentation artifact
uses: actions/download-artifact@v4
with:
name: documentation
path: site

- name: "Deploy documentation page"
uses: appleboy/scp-action@v1.0.0
with:
host: capio.hpc4ai.it
username: capio-user
key: ${{ secrets.PYCAPIO_DOC_DEPLOY_KEY }}
rm: true
source: "site/*"
strip_components: 1
target: /mnt/services/capio/nginx/html/pycapio
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# PyCAPIO

[![codecov](https://codecov.io/gh/High-Performance-IO/PyCAPIO/graph/badge.svg?token=YOUR_TOKEN)](https://codecov.io/gh/OWNER/REPO)
[![CI Tests](https://github.com/High-Performance-IO/PyCAPIO/actions/workflows/ci_cd.yml/badge.svg)](https://github.com/High-Performance-IO/PyCAPIO/actions)

[![PyPI version](https://img.shields.io/pypi/v/pycapio.svg)](https://pypi.org/project/pycapio/)
[![Python Versions](https://img.shields.io/pypi/pyversions/pycapio.svg)](https://pypi.org/project/pycapio/)
[![codecov](https://img.shields.io/codecov/c/github/High-Performance-IO/PyCAPIO?logo=codecov)](https://codecov.io/gh/High-Performance-IO/PyCAPIO)
[![CI Tests](https://img.shields.io/github/actions/workflow/status/High-Performance-IO/PyCAPIO/ci_cd.yml?logo=githubactions&label=CI%20Tests)](https://github.com/High-Performance-IO/PyCAPIO/actions)
[![PyPI version](https://img.shields.io/pypi/v/pycapio.svg?logo=pypi)](https://pypi.org/project/pycapio/)
![Python](https://img.shields.io/badge/dynamic/regex?url=https%3A%2F%2Fraw.githubusercontent.com%2FHigh-Performance-IO%2FPyCAPIO%2Frefs%2Fheads%2Fmain%2Fpyproject.toml&search=requires-python%20%3D%20%22%28%5B%5E%22%5D%2B%29%22&replace=%241&label=Python&logo=python)

**PyCAPIO** brings transparent data streaming to file-based Python workflows, minimizing I/O bottlenecks without
requiring code modifications.
Expand Down
2 changes: 2 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_build
doxygen
43 changes: 43 additions & 0 deletions docs/Doxyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Doxyfile for PyCAPIO — tuned to feed Breathe (XML) and provide a standalone
# C++ HTML reference. Run from the docs/ directory: doxygen Doxyfile

PROJECT_NAME = "PyCAPIO"
PROJECT_BRIEF = "Native C++/pybind11 layer of PyCAPIO"
OUTPUT_DIRECTORY = doxygen

# Parse the public headers and their implementations.
INPUT = ../include ../src
FILE_PATTERNS = *.hpp *.h *.cpp
RECURSIVE = YES

# We document the public interface; private members are surfaced by Breathe
# only where requested.
EXTRACT_ALL = YES
EXTRACT_PRIVATE = NO
EXTRACT_STATIC = YES
HIDE_UNDOC_MEMBERS = NO

# Breathe consumes the XML output.
GENERATE_XML = YES
XML_OUTPUT = xml
XML_PROGRAMLISTING = YES

# A standalone HTML C++ reference is handy on its own; disable if not wanted.
GENERATE_HTML = YES
HTML_OUTPUT = html
GENERATE_LATEX = NO

# C++ niceties.
BUILTIN_STL_SUPPORT = YES
TEMPLATE_RELATIONS = YES
JAVADOC_AUTOBRIEF = YES
QT_AUTOBRIEF = YES
MARKDOWN_SUPPORT = YES
SORT_MEMBER_DOCS = NO

# Keep the log readable.
QUIET = YES
WARN_IF_UNDOCUMENTED = NO

# The CAPIO headers pulled in transitively are not part of our public API.
EXCLUDE_PATTERNS = */common/* */_libcapio_impl.hpp
25 changes: 25 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = _build

.PHONY: help doxygen html pdf latexpdf clean

help:
@echo "Targets: html, pdf (alias latexpdf), doxygen, clean"

# Generate the C++ XML/HTML used by Breathe.
doxygen:
@command -v doxygen >/dev/null 2>&1 || { echo "doxygen not found; C++ API pages will be empty"; exit 0; }
doxygen Doxyfile

html: doxygen
$(SPHINXBUILD) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html"
@echo "HTML written to $(BUILDDIR)/html/index.html"

latexpdf pdf: doxygen
$(SPHINXBUILD) -b latex "$(SOURCEDIR)" "$(BUILDDIR)/latex"
$(MAKE) -C "$(BUILDDIR)/latex" all-pdf
@echo "PDF written to $(BUILDDIR)/latex/pycapio.pdf"

clean:
rm -rf "$(BUILDDIR)" doxygen
48 changes: 48 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# PyCAPIO documentation

This directory contains the [Sphinx](https://www.sphinx-doc.org) documentation
for PyCAPIO. It documents both the Python API (via `autodoc`, reading the
docstrings in `pycapio/`) and the C++ layer (via [Doxygen](https://www.doxygen.nl)
+ [Breathe](https://breathe.readthedocs.io)), and can produce both an HTML site
and a PDF.

## Build locally

Install the documentation dependencies (a virtual environment is recommended):

```console
pip install -r docs/requirements.txt
```

For the C++ API pages you also need Doxygen, and for the PDF you need a LaTeX
toolchain:

```console
# Debian/Ubuntu
sudo apt-get install doxygen texlive-latex-recommended texlive-latex-extra latexmk
# macOS (Homebrew)
brew install doxygen # and a LaTeX distribution such as MacTeX for PDF
```

Then, from inside `docs/`:

```console
make html # HTML site -> docs/_build/html/index.html
make pdf # PDF -> docs/_build/latex/pycapio.pdf
make clean # remove generated output
```

`make` runs Doxygen first (when available) and then Sphinx. The Python pages
build even without Doxygen — only the C++ pages will be empty.

> **Note** — The docs build does **not** require compiling the native
> `pycapio._pycapio` extension. `conf.py` registers a lightweight stand-in for
> it so `autodoc` can import the pure-Python modules anywhere. The native
> classes are documented from the C++ sources instead.

## Writing docstrings

Python docstrings use the [Google style](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
parsed by `napoleon`. C++ entities use Doxygen comments (`/** ... */` or
`///`). Adding a docstring to a function or class is enough for it to appear in
the rendered API reference — no manual page edits required.
5 changes: 5 additions & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
sphinx>=7.2
furo>=2024.1.29
myst-parser>=2.0
breathe>=4.35
sphinx-autodoc-typehints>=2.0
3 changes: 3 additions & 0 deletions docs/source/_static/custom.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* Small tweaks on top of the Furo theme. */
.wy-table-responsive table td { white-space: normal; }
code.literal { font-size: 0.85em; }
12 changes: 12 additions & 0 deletions docs/source/api/internals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# `pycapio.internals` — low-level primitives

This subpackage re-exports the raw CAPIO building blocks from the native
extension. The page below lists what is available; the behaviour of each symbol
is documented from the C++ sources in {doc}`native`.

```{eval-rst}
.. automodule:: pycapio.internals
:members:
:undoc-members:
:no-index:
```
42 changes: 42 additions & 0 deletions docs/source/api/native.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Native API

Several names exported from {doc}`internals` (and re-exported at the top level)
are implemented in C++ and exposed to Python through pybind11 — the compiled
`pycapio._pycapio` extension. The table below maps each Python name to the C++
entity that implements it; follow the links for the full, source-generated
reference on the {doc}`../cpp/index` page.

| Python name | C++ entity | Reference |
| ------------------------ | --------------------------- | --------- |
| `PyCapioTextIOWrapper` | `IOWrapper<IOMode::Text>` | {cpp:class}`IOWrapper` |
| `PyCapioBinaryIOWrapper` | `IOWrapper<IOMode::Binary>` | {cpp:class}`IOWrapper` |
| `PyCapioPath` | `OsPath` | {cpp:class}`OsPath` |
| `DirEntry` | `CapioDirEntry` | {cpp:class}`CapioDirEntry` |
| `PyCapioScandirWrapper` | `ScandirIteratorWrapper` | {cpp:class}`ScandirIteratorWrapper` |
| `PyCAPIOException` | `PyCapioException` | {cpp:class}`PyCapioException` |

| Python function | C++ function | Reference |
| ------------------------------------- | --------------------- | --------- |
| `pycapio_init` | `libcapio_init` | {cpp:func}`libcapio_init` |
| `pycapio_teardown` | `libcapio_teardown` | {cpp:func}`libcapio_teardown` |
| `pycapio_open` | `libcapio_open` | {cpp:func}`libcapio_open` |
| `pycapio_mkdir` | `libcapio_mkdir` | {cpp:func}`libcapio_mkdir` |

## Behaviour summary

**IO wrappers.** `PyCapioTextIOWrapper` and `PyCapioBinaryIOWrapper` are the
`Text` and `Binary` instantiations of the same `IOWrapper` class template. They
implement the file-object protocol (`read`, `readline`, `readlines`, `write`,
`writelines`, `seek`, `flush`, `close`, iteration and context-manager support)
on top of a CAPIO file descriptor. Text wrappers return `str`; binary wrappers
return `bytes`.

**`PyCapioPath`.** A drop-in replacement for {mod}`os.path` whose queries are
answered by CAPIO for paths inside the CAPIO directory, installed in place of
`os.path` while a `CapioContext` is active.

**Directory scanning.** `PyCapioScandirWrapper` iterates a CAPIO directory and
yields `DirEntry` objects, mirroring {func}`os.scandir` and `os.DirEntry`.

The full rendered reference for all of these lives on the {doc}`../cpp/index`
page.
Loading
Loading