Release v0.3.78 — 164 issues fixed: audit scope, contributor fixes, and the regression battery against v0.3.77 #2743
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Python Bindings CI | |
| on: | |
| push: | |
| branches: [ main, develop ] | |
| paths-ignore: | |
| - 'CHANGELOG.md' | |
| - '**/*.md' | |
| - 'docs/**' | |
| - 'LICENSE*' | |
| - '.gitignore' | |
| pull_request: | |
| branches: [ main, develop ] | |
| release: | |
| types: [ published ] | |
| # Cancel any in-progress run on the same branch when a new one starts. | |
| # Don't cancel release runs — they must always complete. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name != 'release' }} | |
| env: | |
| CARGO_TERM_COLOR: always | |
| # Least-privilege default token (OpenSSF Scorecard Token-Permissions). Jobs | |
| # opt into wider scopes only where they actually need them. | |
| permissions: | |
| contents: read | |
| jobs: | |
| # Test job - builds extension and runs tests | |
| test: | |
| name: Test Python ${{ matrix.python-version }} on ${{ matrix.os }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Free disk space (Ubuntu) | |
| if: runner.os == 'Linux' | |
| uses: ./.github/actions/free-disk-space | |
| - name: Set up Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Set up Rust | |
| uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 | |
| with: | |
| cache: false | |
| rustflags: '' | |
| - name: Cache Cargo registry | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ~/.cargo/registry | |
| key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} | |
| - name: Cache Cargo index | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ~/.cargo/git | |
| key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} | |
| - name: Cache Cargo build | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: target | |
| key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 | |
| - name: Create virtual environment with uv | |
| run: uv venv | |
| - name: Install maturin | |
| run: uv pip install maturin pytest | |
| - name: Build Python wheel | |
| run: uv run maturin build --release --features python,ocr,barcodes --out dist | |
| env: | |
| CARGO_INCREMENTAL: "0" | |
| - name: Install built wheel | |
| shell: bash | |
| run: | | |
| wheel=$(ls dist/*.whl) | |
| uv run pip install "$wheel" | |
| - name: Generate stubs and verify symbol parity | |
| # Regenerate .pyi stub (using rylai.toml features, i.e. --features python) | |
| # then confirm every stub symbol exists in the installed wheel (issue #464). | |
| # Uses the test wheel (python,ocr,barcodes) which is a superset of the | |
| # release wheel; any stub symbol absent here is certainly absent at release. | |
| run: | | |
| uvx rylai -o python/pdf_oxide/ | |
| uv run python scripts/check_stub_parity.py python/pdf_oxide/pdf_oxide.pyi | |
| - name: Run Rust tests with Python feature | |
| # Skip on macOS due to extension-module linking restrictions. | |
| # Skip on Linux: main CI runs full cargo test on Linux; running it | |
| # here too exhausts the ~14 GB runner disk after the maturin release | |
| # build (release + debug build artifacts exceed available space). | |
| # CARGO_BUILD_JOBS=2 caps concurrent link-worker spawns so the | |
| # parallel-link burst stays under the hosted-runner 7 GB RAM | |
| # ceiling — fixes the rust-lld SIGBUS flake tracked in #399. | |
| if: runner.os == 'Windows' | |
| env: | |
| CARGO_BUILD_JOBS: "2" | |
| CARGO_INCREMENTAL: "0" | |
| run: cargo test --lib --tests --features python | |
| - name: Run Python tests | |
| run: uv run pytest tests/test_python.py -v | |
| # Lint job - check code quality | |
| lint: | |
| name: Lint and Format Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Set up Rust | |
| uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 | |
| with: | |
| cache: false | |
| rustflags: '' | |
| components: rustfmt, clippy | |
| - name: Check formatting | |
| run: cargo fmt --check | |
| - name: Run Clippy with Python feature | |
| run: cargo clippy --features python --all-targets --workspace -- -D warnings | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 | |
| - name: Pin Python version | |
| run: uv python pin 3.11 | |
| - name: Install dependencies | |
| run: uv sync --group dev | |
| - name: Generate .pyi from Rust | |
| # rylai's default emits PEP-585 / PEP-604 syntax with | |
| # `from __future__ import annotations` at the top; runtime | |
| # support for Python 3.8/3.9 is unaffected because .pyi is a | |
| # type-checker artifact, not a runtime module. Issue #462. | |
| run: uvx rylai -o python/pdf_oxide/ | |
| # ruff runs without --fix in CI (CI verifies; developers auto-fix locally). | |
| - name: Ruff lint | |
| run: uv run ruff check . --output-format=github | |
| - name: Ruff format --check | |
| run: uv run ruff format --check . | |
| # Project's existing type checker | |
| - name: Run ty check | |
| run: uv run ty check . | |
| # basedpyright — stricter fork of pyright that catches issues ty misses. | |
| # Hard gate now that #44 is closed (0 errors, 0 warnings on a clean tree). | |
| - name: basedpyright (type check) | |
| run: | | |
| uv pip install basedpyright | |
| uv run basedpyright python/pdf_oxide | |
| # pip-audit the dependency graph (uv-locked), excluding the | |
| # current project itself — pdf-oxide is not on PyPI until the | |
| # release job publishes it, so `--strict` on the live venv would | |
| # flag our own package as "not found". Export deps-only and audit | |
| # from requirements. `--no-deps` is required because uv-exported | |
| # requirements are not hashed; the export already contains the | |
| # full resolved dependency set. | |
| - name: pip-audit (project deps) | |
| run: | | |
| uv export --no-emit-project --format requirements-txt --no-hashes > /tmp/pip-audit-requirements.txt | |
| uv pip install pip-audit | |
| uv run pip-audit --strict --disable-pip --no-deps -r /tmp/pip-audit-requirements.txt | |
| # Build Linux wheels (manylinux + musllinux, x86_64 + aarch64) | |
| # Uses PyO3/maturin-action, which handles cross-compilation via Docker + zig. | |
| # Lowered glibc floor to 2_28 (RHEL 8 / Ubuntu 20.04 / Debian 11 / Amazon Linux 2023) | |
| # from the previous 2_34 (Ubuntu 22.04+) for broader CI/base-image coverage. See #284. | |
| build-wheels-linux: | |
| name: Build wheels (linux ${{ matrix.target }} ${{ matrix.manylinux }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: x86_64 | |
| manylinux: manylinux_2_28 | |
| - target: aarch64 | |
| manylinux: manylinux_2_28 | |
| - target: x86_64 | |
| manylinux: musllinux_1_2 | |
| - target: aarch64 | |
| manylinux: musllinux_1_2 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Set up Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: '3.11' | |
| - name: Build wheels | |
| uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 | |
| with: | |
| target: ${{ matrix.target }} | |
| manylinux: ${{ matrix.manylinux }} | |
| args: --release --features python,ocr,barcodes --out dist | |
| - name: Upload wheels as artifacts | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: wheels-linux-${{ matrix.target }}-${{ matrix.manylinux }} | |
| path: dist/*.whl | |
| # Build macOS wheels (x86_64 + arm64) | |
| build-wheels-macos: | |
| name: Build wheels (macos ${{ matrix.target }}) | |
| runs-on: macos-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: [x86_64, aarch64] | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Set up Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: '3.11' | |
| - name: Build wheels | |
| uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 | |
| with: | |
| target: ${{ matrix.target }} | |
| args: --release --features python,ocr,barcodes --out dist | |
| - name: Upload wheels as artifacts | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: wheels-macos-${{ matrix.target }} | |
| path: dist/*.whl | |
| # Build Windows wheels (x64 + arm64) | |
| build-wheels-windows: | |
| name: Build wheels (windows ${{ matrix.target }}) | |
| runs-on: windows-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: [x64, aarch64] | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Set up Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: '3.11' | |
| architecture: x64 | |
| - name: Build wheels | |
| uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 | |
| with: | |
| target: ${{ matrix.target }} | |
| args: --release --features python,ocr,barcodes --out dist | |
| - name: Upload wheels as artifacts | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: wheels-windows-${{ matrix.target }} | |
| path: dist/*.whl | |
| # Build source distribution (universal fallback for any Rust-capable platform) | |
| build-sdist: | |
| name: Build source distribution | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Build sdist | |
| uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 | |
| with: | |
| command: sdist | |
| args: --out dist | |
| - name: Upload sdist as artifact | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: sdist | |
| path: dist/*.tar.gz | |
| # Docker smoke tests — install the manylinux / musllinux wheel inside the | |
| # actual target distro containers so we catch libc symbol issues (e.g. | |
| # __memcmpeq@GLIBC_2.35 on Amazon Linux 2023) before publishing. | |
| # Runs only when Linux wheels are available (push/PR to main/develop + release). | |
| docker-smoke-test: | |
| name: Docker smoke (${{ matrix.distro }}) | |
| runs-on: ubuntu-latest | |
| needs: [build-wheels-linux] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - distro: amazonlinux-2023 | |
| image: amazonlinux:2023 | |
| artifact: wheels-linux-x86_64-manylinux_2_28 | |
| # ubuntu-20.04 (focal) dropped 2026-05-05: standard LTS support | |
| # ended 2025-04-30 and Canonical's archive.ubuntu.com / | |
| # security.ubuntu.com mirrors for focal stopped serving (port 80 | |
| # connection timeouts), while old-releases.ubuntu.com hasn't | |
| # received focal yet (ESM at esm.ubuntu.com is paywalled). | |
| # `apt-get update` fails before pip even runs. debian-11 below | |
| # covers the same glibc 2.31 floor. | |
| - distro: debian-11 | |
| image: debian:11-slim | |
| artifact: wheels-linux-x86_64-manylinux_2_28 | |
| - distro: alpine-3 | |
| image: alpine:3.19 | |
| artifact: wheels-linux-x86_64-musllinux_1_2 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Download wheel | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: ${{ matrix.artifact }} | |
| path: dist/ | |
| - name: Smoke test in ${{ matrix.distro }} | |
| run: | | |
| docker run --rm \ | |
| -v "${{ github.workspace }}/dist:/wheels:ro" \ | |
| -v "${{ github.workspace }}/tests/fixtures:/fixtures:ro" \ | |
| -v "${{ github.workspace }}/tests/smoke_docker.py:/smoke_docker.py:ro" \ | |
| "${{ matrix.image }}" \ | |
| sh -c ' | |
| set -e | |
| OS_ID=$(. /etc/os-release 2>/dev/null && echo "$ID" || echo unknown) | |
| case "$OS_ID" in | |
| amzn) | |
| dnf install -y python3-pip > /dev/null 2>&1 | |
| ;; | |
| ubuntu|debian) | |
| # Debian 11 left LTS on 2026-08-31. deb.debian.org keeps | |
| # serving bullseye but its last bullseye-security Release | |
| # file carries a Valid-Until of 2026-09-07, after which | |
| # apt-get update refuses the repository (exit 100) and the | |
| # smoke test dies before pip runs. Read bullseye from | |
| # archive.debian.org, which has no security suite and whose | |
| # Release files are past their validity window by design. | |
| CODENAME=$(. /etc/os-release && echo "$VERSION_CODENAME") | |
| APT_OPTS="-o Acquire::Retries=3" | |
| if [ "$CODENAME" = bullseye ]; then | |
| printf "deb http://archive.debian.org/debian bullseye main\n" > /etc/apt/sources.list | |
| rm -f /etc/apt/sources.list.d/* | |
| APT_OPTS="$APT_OPTS -o Acquire::Check-Valid-Until=false" | |
| fi | |
| apt-get $APT_OPTS update -qq | |
| DEBIAN_FRONTEND=noninteractive apt-get $APT_OPTS install -qq -y python3-pip python3-venv | |
| ;; | |
| alpine) | |
| apk add --no-cache python3 py3-pip > /dev/null 2>&1 | |
| ;; | |
| esac | |
| python3 -m venv /venv | |
| . /venv/bin/activate | |
| pip install --quiet --upgrade pip | |
| pip install --quiet /wheels/*.whl | |
| python3 /smoke_docker.py | |
| ' | |
| publish: | |
| name: Publish to PyPI | |
| needs: [test, lint, build-wheels-linux, build-wheels-macos, build-wheels-windows, build-sdist, docker-smoke-test] | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'release' && github.event.action == 'published' | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| pattern: wheels-* | |
| path: dist | |
| merge-multiple: true | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: sdist | |
| path: dist | |
| - name: Publish to PyPI | |
| uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 | |
| with: | |
| password: ${{ secrets.PYPI_API_TOKEN }} | |
| skip-existing: true | |
| # Generate documentation | |
| docs: | |
| name: Generate Python documentation | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Set up Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: '3.11' | |
| - name: Set up Rust | |
| uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 | |
| with: | |
| cache: false | |
| rustflags: '' | |
| - name: Install dependencies | |
| run: | | |
| pip install maturin pdoc3 | |
| - name: Build and install wheel | |
| shell: bash | |
| run: | | |
| maturin build --release --features python --out dist | |
| wheel=$(ls dist/*.whl) | |
| pip install "$wheel" | |
| - name: Generate docs | |
| run: pdoc --html --output-dir docs-python pdf_oxide | |
| - name: Upload docs | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: python-docs | |
| path: docs-python/ |