pdf_oxide 0.3.23

The fastest Rust PDF library with text extraction: 0.8ms mean, 100% pass rate on 3,830 PDFs. 5× faster than pdf_extract, 17× faster than oxidize_pdf. Extract, create, and edit PDFs.
Documentation
name: Python Bindings CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]
  release:
    types: [ published ]

env:
  CARGO_TERM_COLOR: always

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.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14']


    steps:
      - uses: actions/checkout@v6

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v6
        with:
          python-version: ${{ matrix.python-version }}

      - name: Set up Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Cache Cargo registry
        uses: actions/cache@v5
        with:
          path: ~/.cargo/registry
          key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}

      - name: Cache Cargo index
        uses: actions/cache@v5
        with:
          path: ~/.cargo/git
          key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}

      - name: Cache Cargo build
        uses: actions/cache@v5
        with:
          path: target
          key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}

      - name: Install uv
        uses: astral-sh/setup-uv@v7

      - 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 --out dist

      - name: Install built wheel
        shell: bash
        run: |
          wheel=$(ls dist/*.whl)
          uv run pip install "$wheel"

      - name: Run Rust tests with Python feature
        # Skip on macOS due to extension-module linking restrictions
        # Use --lib --tests to run unit and integration tests (both under tests/)
        # This skips doctests which are validated in main CI
        if: runner.os != 'macOS'
        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@v6

      - name: Set up Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy

      - name: Check formatting
        run: cargo fmt --check

      - name: Run Clippy with Python feature
        run: cargo clippy --features python --all-targets -- -D warnings

      - name: Install uv
        uses: astral-sh/setup-uv@v7

      - name: Pin Python version
        run: uv python pin 3.11

      - name: Install dependencies
        run: uv sync --group dev

      - name: Generate .pyi from Rust
        run: uvx rylai -o python/pdf_oxide/

      - name: Run Ruff
        run: uv run ruff check . --fix

      - name: Run ty check
        run: uv run ty check .

  # 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@v6
      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: '3.11'
      - name: Build wheels
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          manylinux: ${{ matrix.manylinux }}
          args: --release --features python --out dist
      - name: Upload wheels as artifacts
        uses: actions/upload-artifact@v7
        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@v6
      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: '3.11'
      - name: Build wheels
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          args: --release --features python --out dist
      - name: Upload wheels as artifacts
        uses: actions/upload-artifact@v7
        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@v6
      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: '3.11'
          architecture: x64
      - name: Build wheels
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          args: --release --features python --out dist
      - name: Upload wheels as artifacts
        uses: actions/upload-artifact@v7
        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@v6
      - name: Build sdist
        uses: PyO3/maturin-action@v1
        with:
          command: sdist
          args: --out dist
      - name: Upload sdist as artifact
        uses: actions/upload-artifact@v7
        with:
          name: sdist
          path: dist/*.tar.gz

  # Publish to PyPI on release
  publish:
    name: Publish to PyPI
    needs: [test, lint, build-wheels-linux, build-wheels-macos, build-wheels-windows, build-sdist]
    runs-on: ubuntu-latest
    if: github.event_name == 'release' && github.event.action == 'published'

    steps:
      - uses: actions/download-artifact@v8
        with:
          pattern: wheels-*
          path: dist
          merge-multiple: true

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

      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          password: ${{ secrets.PYPI_API_TOKEN }}
          skip-existing: true

  # Generate documentation
  docs:
    name: Generate Python documentation
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v6

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: '3.11'

      - name: Set up Rust
        uses: dtolnay/rust-toolchain@stable

      - 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@v7
        with:
          name: python-docs
          path: docs-python/