robin-sparkless 4.0.0

PySpark-like DataFrame API in Rust on Polars; no JVM.
Documentation
name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  # --- Rust ---
  format:
    name: Format
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt
      - name: Cache cargo
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-cargo-
      - name: Check format
        run: cargo fmt --check

  clippy:
    name: Clippy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy
      - name: Cache cargo
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-cargo-
      - name: Clippy
        run: cargo clippy --workspace --all-features --all-targets -- -D warnings

  audit:
    name: Audit
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
      - name: Install cargo-audit
        run: cargo install cargo-audit
      - name: Cache cargo
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-cargo-
      - name: Audit
        run: cargo audit

  deny:
    name: Deny
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
      - name: Install cargo-deny
        run: cargo install cargo-deny
      - name: Cache cargo
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-cargo-
      - name: Deny
        run: cargo deny check advisories bans sources

  build:
    name: Build test binaries
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
      - name: Cache cargo
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-cargo-
      - name: Build test binaries
        run: cargo build --workspace --all-features --tests
        env:
          CARGO_BUILD_JOBS: 1

  test:
    name: Rust tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
      - name: Cache cargo
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-cargo-
      - name: Run Rust tests
        run: cargo test --workspace --all-features
        timeout-minutes: 25

  publish:
    name: Build docs and publish to crates.io
    runs-on: ubuntu-latest
    needs: [format, clippy, audit, deny, build, test]
    steps:
      - uses: actions/checkout@v4
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
      - name: Cache cargo
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-cargo-
      - name: Build docs
        run: cargo doc --workspace --no-deps
      - name: "Publish to crates.io (dependency order: spark-sql-parser, core, polars, main)"
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
        run: |
          set +e
          cargo publish -p spark-sql-parser --token "$CARGO_REGISTRY_TOKEN"
          spark_exit=$?
          set -e
          if [ "$spark_exit" -eq 101 ]; then
            echo "spark-sql-parser version already on crates.io, skipping"
          elif [ "$spark_exit" -ne 0 ]; then
            exit "$spark_exit"
          fi
          cargo publish -p robin-sparkless-core --token "$CARGO_REGISTRY_TOKEN"
          cargo publish -p robin-sparkless-polars --token "$CARGO_REGISTRY_TOKEN"
          cargo publish -p robin-sparkless --token "$CARGO_REGISTRY_TOKEN"

  # --- Python (lint + tests, then PyPI publish for 3.8+) ---
  python-lint:
    name: Python (mypy)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.10"
      - name: Install mypy
        run: pip install mypy
      - name: Mypy
        run: cd python && mypy sparkless

  python-tests:
    name: Python tests - ${{ matrix.os }} py${{ matrix.python-version }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
    defaults:
      run:
        shell: bash
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy, rustfmt
      - name: Cache cargo
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-py-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-cargo-py-
      - name: Create venv
        run: python -m venv .venv
      - name: Set Python path
        run: |
          if [ "$RUNNER_OS" = "Windows" ]; then
            echo "PY=.venv/Scripts/python" >> $GITHUB_ENV
          else
            echo "PY=.venv/bin/python" >> $GITHUB_ENV
          fi
      - name: Install maturin and test deps
        run: $PY -m pip install maturin && $PY -m pip install pytest pytest-xdist hypothesis pytest-timeout
      - name: Build and install extension
        run: cd python && $PY -m maturin develop --release
        env:
          VIRTUAL_ENV: ${{ github.workspace }}/.venv
      - name: Smoke test
        run: $PY -c "from sparkless.sql import SparkSession; from sparkless.sql.functions import col; s = SparkSession.builder.app_name('test').get_or_create(); df = s.create_dataframe([(1, 2, 'a')], ['x', 'y', 'z']); assert df.count() == 1; print('sparkless OK')"
      - name: Run Python tests (same subset as CI)
        run: $PY -m pytest tests -m "not delta and not integration" -v --tb=short --timeout 90
        timeout-minutes: 20

  # PyPI: build and publish wheels (Python 3.8+), after all checks pass
  publish-pypi-manylinux-x86_64:
    name: PyPI manylinux x86_64 + sdist
    runs-on: ubuntu-latest
    needs: [format, clippy, audit, deny, build, test, python-lint, python-tests]
    steps:
      - uses: actions/checkout@v4
      - name: Publish to PyPI (maturin)
        uses: messense/maturin-action@v1
        env:
          MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
        with:
          working-directory: python
          target: x86_64-unknown-linux-gnu
          command: publish
          args: -i 3.8 --zig --skip-existing
          manylinux: "2_17"
          container: off

  publish-pypi-manylinux-aarch64:
    name: PyPI manylinux aarch64
    runs-on: ubuntu-24.04-arm
    needs: [format, clippy, audit, deny, build, test, python-lint, python-tests]
    steps:
      - uses: actions/checkout@v4
      - name: Publish to PyPI (maturin)
        uses: messense/maturin-action@v1
        env:
          MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
        with:
          working-directory: python
          target: aarch64-unknown-linux-gnu
          command: publish
          args: -i 3.8 --zig --skip-existing
          manylinux: "2_28"
          container: off

  publish-pypi-mac:
    name: PyPI macOS ${{ matrix.target }}
    runs-on: macos-latest
    needs: [format, clippy, audit, deny, build, test, python-lint, python-tests]
    strategy:
      fail-fast: false
      matrix:
        target: [x86_64-apple-darwin, aarch64-apple-darwin]
    steps:
      - uses: actions/checkout@v4
      - name: Publish to PyPI (maturin)
        uses: messense/maturin-action@v1
        env:
          MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
        with:
          working-directory: python
          target: ${{ matrix.target }}
          command: publish
          args: -i 3.8 --skip-existing

  publish-pypi-musllinux-x86_64:
    name: PyPI musllinux x86_64
    runs-on: ubuntu-latest
    needs: [format, clippy, audit, deny, build, test, python-lint, python-tests]
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python 3.8
        uses: actions/setup-python@v5
        with:
          python-version: "3.8"
      - name: Publish to PyPI (maturin)
        uses: messense/maturin-action@v1
        env:
          MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
        with:
          working-directory: python
          target: x86_64-unknown-linux-musl
          command: publish
          args: -i 3.8 --zig --compatibility musllinux_1_2 --skip-existing

  publish-pypi-musllinux-aarch64:
    name: PyPI musllinux aarch64
    runs-on: ubuntu-24.04-arm
    needs: [format, clippy, audit, deny, build, test, python-lint, python-tests]
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python 3.8
        uses: actions/setup-python@v5
        with:
          python-version: "3.8"
      - name: Publish to PyPI (maturin)
        uses: messense/maturin-action@v1
        env:
          MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
        with:
          working-directory: python
          target: aarch64-unknown-linux-musl
          command: publish
          args: -i 3.8 --zig --compatibility musllinux_1_2 --skip-existing

  publish-pypi-windows-x86_64:
    name: PyPI Windows x86_64
    runs-on: windows-latest
    needs: [format, clippy, audit, deny, build, test, python-lint, python-tests]
    steps:
      - uses: actions/checkout@v4
      - name: Publish to PyPI (maturin)
        uses: messense/maturin-action@v1
        env:
          MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
        with:
          working-directory: python
          target: x86_64-pc-windows-msvc
          command: publish
          args: -i 3.8 --skip-existing

  publish-pypi-windows-arm64:
    name: PyPI Windows aarch64
    runs-on: windows-11-arm
    needs: [format, clippy, audit, deny, build, test, python-lint, python-tests]
    steps:
      - uses: actions/checkout@v4
      - name: Publish to PyPI (maturin)
        uses: messense/maturin-action@v1
        env:
          MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
        with:
          working-directory: python
          target: aarch64-pc-windows-msvc
          command: publish
          args: -i 3.8 --skip-existing