How to Set Up CI/CD for Switchyard Projects: A Complete GitHub Actions Guide

Setting up CI/CD for Switchyard requires orchestrating Python linting and testing, Rust workspace builds, and Maturin wheel packaging in GitHub Actions to validate the hybrid codebase on every push.

Switchyard, NVIDIA's hybrid Python/Rust routing framework, maintains a native server core alongside Python bindings in crates/switchyard-py/. Because the project spans two languages and requires compiled artifacts, implementing robust CI/CD for Switchyard projects demands distinct validation stages for each stack. This guide walks through the exact GitHub Actions configuration used to lint Python code, test the Rust workspace, and publish distributable wheels.

Understanding the Dual-Stack Architecture

Switchyard is organized as a Rust workspace with PyO3 Python bindings. Key files defining the build and test surface include:

  • pyproject.toml: Declares Python dependencies, development groups, and build scripts using uv
  • Cargo.toml: Root workspace manifest aggregating all Rust crates under crates/*
  • crates/switchyard-py/: Houses the PyO3 integration layer that compiles into the Python wheel
  • tests/: Contains Python-level unit tests for the public API and CLI
  • mkdocs.yml: Configures documentation generation (optional CI step for docs validation)

Python Stack: Linting and Testing

The Python layer uses uv for dependency management and requires three validation steps before packaging.

Linting with Ruff and MyPy

Static analysis ensures code quality before runtime testing. The development dependency group includes both tools:


# Install dev dependencies including ruff and mypy

uv sync --group dev

# Lint Python source

uv run ruff check .

# Type-check the switchyard package

uv run mypy switchyard

Running the Python Test Suite

Execute pytest against the tests/ directory to validate the integration layer:

uv run pytest tests/ -v

Rust Stack: Workspace Testing

The native server and routing algorithms live in the Rust workspace defined by the root Cargo.toml. Testing requires the stable toolchain and workspace-level execution:


# Ensure stable Rust is available

rustup toolchain install stable

# Test all crates in the workspace

cargo test --workspace

This validates the core logic that the Python bindings expose.

Packaging Stack: Wheels and Distribution

Switchyard uses Maturin to build Python wheels embedding the compiled Rust binaries. This stage should only execute after both Python and Rust tests pass.

Building the Wheel

Generate a release-grade wheel for distribution:


# Build manylinux-compatible wheel (disable manylinux container for standard runners)

uv run maturin build --release --manylinux off

The output appears in target/wheels/.

Continuous Deployment Options

Extend the pipeline to publish artifacts:

  • PyPI: Use twine upload target/wheels/*.whl after building
  • Docker: Build and push an image containing the native server binary for containerized deployments

Orchestrating the Pipeline in GitHub Actions

Combine these stages into .github/workflows/ci.yml with strategic job dependencies to maximize parallelism while preventing broken code from reaching packaging:

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  lint-typecheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install uv
        run: curl -LsSf https://astral.sh/uv/install.sh | sh
      - name: Install dependencies
        run: uv sync --group dev
      - name: Run ruff
        run: uv run ruff check .
      - name: Run mypy
        run: uv run mypy switchyard

  python-tests:
    runs-on: ubuntu-latest
    needs: lint-typecheck
    steps:
      - uses: actions/checkout@v4
      - name: Install uv
        run: curl -LsSf https://astral.sh/uv/install.sh | sh
      - name: Sync dependencies
        run: uv sync --group dev
      - name: Run pytest
        run: uv run pytest tests/ -v

  rust-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Rust
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          override: true
      - name: Cargo test
        run: cargo test --workspace

  build-wheel:
    runs-on: ubuntu-latest
    needs: [python-tests, rust-tests]
    steps:
      - uses: actions/checkout@v4
      - name: Install uv and maturin
        run: |
          curl -LsSf https://astral.sh/uv/install.sh | sh
          uv pip install maturin
      - name: Build wheel
        run: uv run maturin build --release --manylinux off
      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: wheel
          path: target/wheels/*.whl

Pipeline Optimization Strategies

  • Parallel Execution: The lint-typecheck and rust-tests jobs run concurrently to minimize total CI time. Only build-wheel waits for verification.
  • Artifact Persistence: The actions/upload-artifact step preserves wheels for downstream deployment jobs or manual inspection.
  • Isolation: Each job runs on a fresh ubuntu-latest runner, eliminating hidden state between Python and Rust environments.

Summary

  • Switchyard requires CI/CD pipelines that handle both Python (pyproject.toml) and Rust (Cargo.toml) validation in parallel tracks
  • Use uv sync --group dev to install development dependencies including ruff, mypy, and pytest
  • Run Rust tests with cargo test --workspace from the repository root to validate the native core
  • Package the hybrid library using uv run maturin build --release --manylinux off to embed Rust binaries in Python wheels
  • Structure GitHub Actions with separate jobs for Python linting, Python testing, Rust testing, and wheel building to maximize reliability and CI performance

Frequently Asked Questions

Does Switchyard require specific uv configuration for CI?

No additional configuration is required beyond the standard uv sync --group dev command. The pyproject.toml in the repository root declares all development dependencies, including the test runner and build tools, which uv resolves automatically in the GitHub Actions runner.

How do I handle maturin builds in GitHub Actions?

Install maturin via uv pip install maturin after installing uv itself. Execute uv run maturin build --release --manylinux off to generate wheels compatible with the target environment. Always run this step after both Python and Rust tests complete to ensure packaging only occurs with verified code.

Can I run Rust and Python tests in parallel?

Yes. The recommended workflow places rust-tests and lint-typecheck in separate jobs without cross-dependencies, allowing GitHub Actions to execute them concurrently. Only the python-tests job depends on linting, and build-wheel depends on both test suites, creating an efficient dependency graph.

What triggers should I use for the Switchyard CI pipeline?

Configure the workflow to trigger on push events to the main branch and pull_request events targeting main. This ensures every proposed change and every merge undergoes full validation across both the Python integration layer and the native Rust core.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →