← back to Exo
python: add hermetic basedpyright typecheck to nix flake check
0b7d88b43be849c09db7b92f151edcbb2991e500 · 2026-02-13 19:23:13 +0000 · Jake Hillion
The existing CI typecheck job used `uv run basedpyright` which depends
on a non-hermetic uv sync step. This replaces it with a fully hermetic
typecheck as a Nix flake check using the uv2nix virtual environment.
Added a `typecheckVenv` with dev dependencies, a `linuxOverlay` to
ignore native shared library deps (NVIDIA, torch, triton, mlx) that
aren't needed at type-check time, and `passthru` preservation plus
`.pyi` stub copying on the `exo-pyo3-bindings` overlay so basedpyright
can resolve the Rust bindings types. Also guarded the `mlx` Nix build
override to macOS only since it requires Metal. Removed the old
non-hermetic `typecheck` CI job since `nix flake check` now covers it.
The hermetic check ensures type checking uses exactly the locked
dependency versions and catches type errors without requiring a
working uv/pip environment.
Test plan:
- CI (`nix flake check` runs on x86_64-linux, aarch64-linux, aarch64-darwin)
- Verified `nix build ".#checks.x86_64-linux.typecheck"` passes with 0 errors
Files touched
M .github/workflows/pipeline.ymlM python/parts.nix
Diff
commit 0b7d88b43be849c09db7b92f151edcbb2991e500
Author: Jake Hillion <jake@hillion.co.uk>
Date: Fri Feb 13 19:23:13 2026 +0000
python: add hermetic basedpyright typecheck to nix flake check
The existing CI typecheck job used `uv run basedpyright` which depends
on a non-hermetic uv sync step. This replaces it with a fully hermetic
typecheck as a Nix flake check using the uv2nix virtual environment.
Added a `typecheckVenv` with dev dependencies, a `linuxOverlay` to
ignore native shared library deps (NVIDIA, torch, triton, mlx) that
aren't needed at type-check time, and `passthru` preservation plus
`.pyi` stub copying on the `exo-pyo3-bindings` overlay so basedpyright
can resolve the Rust bindings types. Also guarded the `mlx` Nix build
override to macOS only since it requires Metal. Removed the old
non-hermetic `typecheck` CI job since `nix flake check` now covers it.
The hermetic check ensures type checking uses exactly the locked
dependency versions and catches type errors without requiring a
working uv/pip environment.
Test plan:
- CI (`nix flake check` runs on x86_64-linux, aarch64-linux, aarch64-darwin)
- Verified `nix build ".#checks.x86_64-linux.typecheck"` passes with 0 errors
---
.github/workflows/pipeline.yml | 27 ------------------------
python/parts.nix | 47 ++++++++++++++++++++++++++++++++++++++----
2 files changed, 43 insertions(+), 31 deletions(-)
diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml
index c2589453..8483b130 100644
--- a/.github/workflows/pipeline.yml
+++ b/.github/workflows/pipeline.yml
@@ -8,33 +8,6 @@ on:
- main
jobs:
- typecheck:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- lfs: false
-
- - uses: cachix/install-nix-action@v31
- with:
- nix_path: nixpkgs=channel:nixos-unstable
-
- - uses: cachix/cachix-action@v14
- name: Configure Cachix
- with:
- name: exo
- authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
-
- - name: Load nix develop environment
- run: nix run github:nicknovitski/nix-develop/v1
-
- - name: Sync dependencies
- run: uv sync --all-packages
-
- - name: Run type checker
- run: uv run basedpyright --project pyproject.toml
-
nix:
name: Build and check (${{ matrix.system }})
runs-on: ${{ matrix.runner }}
diff --git a/python/parts.nix b/python/parts.nix
index 1cf0f93d..46b4abdf 100644
--- a/python/parts.nix
+++ b/python/parts.nix
@@ -14,7 +14,9 @@
# Override overlay to inject Nix-built components
exoOverlay = final: prev: {
- # Replace workspace exo_pyo3_bindings with Nix-built wheel
+ # Replace workspace exo_pyo3_bindings with Nix-built wheel.
+ # Preserve passthru so mkVirtualEnv can resolve dependency groups.
+ # Copy .pyi stub + py.typed marker so basedpyright can find the types.
exo-pyo3-bindings = pkgs.stdenv.mkDerivation {
pname = "exo-pyo3-bindings";
version = "0.1.0";
@@ -22,6 +24,12 @@
# Install from pre-built wheel
nativeBuildInputs = [ final.pyprojectWheelHook ];
dontStrip = true;
+ passthru = prev.exo-pyo3-bindings.passthru or { };
+ postInstall = ''
+ local siteDir=$out/${final.python.sitePackages}/exo_pyo3_bindings
+ cp ${inputs.self}/rust/exo_pyo3_bindings/exo_pyo3_bindings.pyi $siteDir/
+ touch $siteDir/py.typed
+ '';
};
};
@@ -29,17 +37,32 @@
# Overlay to provide build systems and custom packages
buildSystemsOverlay = final: prev: {
- # Use our pure Nix-built MLX with Metal support
- mlx = self'.packages.mlx;
-
# mlx-lm is a git dependency that needs setuptools
mlx-lm = prev.mlx-lm.overrideAttrs (old: {
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [
final.setuptools
];
});
+ } // lib.optionalAttrs pkgs.stdenv.hostPlatform.isDarwin {
+ # Use our pure Nix-built MLX with Metal support (macOS only)
+ mlx = self'.packages.mlx;
};
+ # Additional overlay for Linux-specific fixes (type checking env).
+ # Native wheels have shared lib dependencies we don't need at type-check time.
+ linuxOverlay = final: prev:
+ let
+ ignoreMissing = drv: drv.overrideAttrs { autoPatchelfIgnoreMissingDeps = [ "*" ]; };
+ nvidiaPackages = lib.filterAttrs (name: _: lib.hasPrefix "nvidia-" name) prev;
+ in
+ lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux (
+ (lib.mapAttrs (_: ignoreMissing) nvidiaPackages) // {
+ mlx = ignoreMissing prev.mlx;
+ torch = ignoreMissing prev.torch;
+ triton = ignoreMissing prev.triton;
+ }
+ );
+
pythonSet = (pkgs.callPackage inputs.pyproject-nix.build.packages {
inherit python;
}).overrideScope (
@@ -48,6 +71,7 @@
overlay
exoOverlay
buildSystemsOverlay
+ linuxOverlay
]
);
exoVenv = pythonSet.mkVirtualEnv "exo-env" workspace.deps.default;
@@ -118,6 +142,21 @@
${pkgs.ruff}/bin/ruff check ${inputs.self}
touch $out
'';
+
+ # Hermetic basedpyright type checking
+ typecheck = pkgs.runCommand "typecheck"
+ {
+ nativeBuildInputs = [
+ testVenv
+ pkgs.basedpyright
+ ];
+ }
+ ''
+ cd ${inputs.self}
+ export HOME=$TMPDIR
+ basedpyright --pythonpath ${testVenv}/bin/python
+ touch $out
+ '';
};
};
}
← 1c3cc699 fix: add missing getModelFitStatus prop to Recent tab (#1470
·
back to Exo
·
Pass usage and generation stats through all adapters correct 36a7115b →