Skip to content

Type to search extensions

PyPilot

v1.4.0

Creates Python virtual environments on the interpreter version your dependencies actually support, and names the package that set the limit. It reads wheel tags and requires_python from PyPI, so a library with no wheel for your Python is caught before the install fails. Runs on uv by default, with a pip mode, and answers from package metadata rather than a language model.

Updated 14 hours ago
4 downloads 4 stars Repository ↗

PyPilot Logo

PyPilot for Zed

When you clone a Python repository and run an install, it often fails because a dependency lacks a prebuilt wheel for your interpreter. PyPilot calculates which Python versions the project's dependencies actually support, configures a virtual environment on that version, and identifies the specific package causing any constraint conflict.

Every resolution relies on PyPI package metadata and local lookup tables. The tool runs locally without API keys or external services.

Documentation: https://pypilot-dev.github.io/pypilot/, with the guide, the engine CLI, VS Code, Zed, the Python package, pixi and the reference.

Features marked (since 1.3.0) need engine 1.3.0 or newer.

The problem it solves

A package's requires_python field is often incomplete. For example, mediapipe 0.10.9 declares >=3.8, yet only ships wheels for cp39 through cp312. If you try installing it on Python 3.13, pip searches for an unavailable source distribution and terminates with a metadata error that never mentions Python version incompatibility.

PyPilot checks both signals. It takes requires_python as an initial range, then intersects that range with the interpreter versions that provide prebuilt wheels for your operating system and CPU architecture. After evaluating each dependency in the workspace, the resulting intersection represents the interpreters that can run the entire project. When this intersection is empty, PyPilot names the conflicting packages and shows their incompatible requirements.

Naming the two conflicting packages matters more than applying an automated workaround, because a dependency collision is where package manager error output is least helpful.

What else it catches

Outdated or removed APIs

Package metadata verifies whether a package installs, but cannot verify whether your code runs against that release. For example, mediapipe 0.10.35 installs cleanly on modern CPython releases and then crashes on module 'mediapipe' has no attribute 'solutions' because Google removed the legacy API. PyPilot inspects the installed package on disk, flagging references like mp.solutions in your editor while you write code instead of waiting for runtime failures.

GPU driver and CUDA alignment

For pip-installed frameworks, the host NVIDIA display driver determines compatibility rather than the system CUDA toolkit. Wheels package their own CUDA runtime, and display drivers remain backward compatible. PyPilot inspects nvidia-smi, maps the driver to its highest supported CUDA runtime, and selects the matching PyTorch index (such as cu124 instead of a mismatched default). Systems without a dedicated GPU use CPU wheels, while Apple Silicon devices receive MPS guidance.

Pinned version constraints

Pinned dependencies restrict the wheel search space. A declaration like mediapipe==0.10.14 is evaluated against wheels published for that specific release (which ended at Python 3.12), rather than the wider matrix of newer releases.

Jupyter notebooks

In VS Code, imports inside .ipynb cells get the same diagnostics and quick fixes as .py files; IPython magics and shell escapes (%pip, !ls) are ignored. A notebook can only run in an environment that has ipykernel, so in projects with notebooks PyPilot adds ipykernel>=6.31.0,<7 to .venv (ipykernel 7.x can make VS Code notebooks hang, and 7.0.0 cannot connect on Python 3.14) whenever it sets up the environment or installs a package (as a dev dependency in uv projects), and warns when it is missing.

Installing several packages at once

Select several unresolved imports and open the quick fixes: one action installs all of them in a single step, instead of one action per package. "Install all N missing packages in this file" (or "in this notebook") covers the rest of the document.

Environment management with uv and pip

PyPilot supports both uv and standard pip for managing virtual environments.

Project initialization and uv mode

When package_manager = "uv" (the default):

  • If the project directory lacks a pyproject.toml, PyPilot runs uv init --bare to create one, with .python-version set to the Python it chose and requires-python starting at the oldest Python every dependency supports (3.9 at the lowest). Nothing else is added to the folder.
  • When PyPilot builds .venv on a Python version, it pins the project to that version, so a later uv sync or uv add keeps the environment rather than rebuilding it on an older pin or a global uv python pin.
  • If a requirements.txt file exists during setup, PyPilot reads its dependencies and runs uv add to migrate them directly into the new pyproject.toml.
  • Running pypilot install <package> invokes uv add <package>, adding the dependency to the project manifest and syncing the environment.

Pip fallback and freeze synchronization

When uv is unavailable on the system or package_manager = "pip" is explicitly configured:

  • PyPilot creates the virtual environment using Python's standard venv module.
  • Dependencies are installed using pip install.
  • After each installation or dependency modification, PyPilot runs pip freeze > requirements.txt to update the file with pinned versions. This keeps your dependencies documented and reproducible even without a lockfile manager.

pixi

pixi installs packages from conda-forge and PyPI into a per-project environment, with a lock file. Guide: pixi and PyPilot.

Why pixi helps

pip and uv install Python packages. A dependency without a wheel for your platform or Python is built from source, and that build often needs things outside Python that pip and uv cannot install:

  • System libraries such as GDAL, PROJ, GEOS or libGL. conda-forge ships them as packages next to the Python bindings, at matching versions.
  • Compilers. conda-forge has prebuilt binaries of packages that have no wheel for your platform or Python, so no C, C++ or Fortran toolchain is needed.
  • The CUDA toolkit. pip wheels carry only the CUDA runtime; code with .cu sources needs nvcc, which conda-forge provides per project (cuda-nvcc).
  • More than Python. One manifest covers Python together with R, C++ or Rust.
  • One lock file for every OS. pixi.lock pins native dependencies too, for every platform listed in the manifest.
  • Several environments (for example default and cuda) in one project.

pixi projects

Projects managed by pixi (a pixi.toml, or [tool.pixi.workspace] in pyproject.toml) are detected automatically, whatever package_manager says. PyPilot then works through pixi and never creates a .venv, runs uv or writes pixi.lock:

  • The environment is pixi's: .pixi/envs/default, or another one chosen with pixi_environment in pypilot.toml, --pixi-env <name> for one run, or pypilot.pixiEnvironment in VS Code (since 1.3.0). Its Python version, installed packages and GPU builds are read from there, so import diagnostics and findings are accurate.
  • Setup runs pixi install; installing a package runs pixi add --pypi <package>; moving to another Python runs pixi add python=X.Y.
  • PyPI dependencies are checked against the environment's Python. When one has no wheel for it, PyPilot names the package and the fix (pixi add python=3.11), and turns pixi's own install errors into the same plain explanation.
  • The recommended Python respects the manifest's python spec, and doctor lists conda and PyPI dependencies separately.
  • (since 1.3.0) Doctor lists every environment with its features, Python and dependencies, and reports environments on different Pythons, a pixi.lock that no longer matches the manifest (checked with pixi lock --check --dry-run, which writes nothing), and PyPI dependencies with no wheel for one of the manifest's platforms, which would break pixi lock on every machine.
  • If pixi is not installed, PyPilot says so and changes nothing. (since 1.3.0) It offers to install it (pypilot install-pixi, or the editor's "Install pixi" button), using pixi's official installer.

When PyPilot suggests pixi (since 1.3.0)

In a pip or uv project, PyPilot recognises walls pip and uv cannot climb and conda-forge can, and raises a finding that suggests pixi:

  • a dependency with no installable wheel here whose build needs native libraries that conda-forge packages (about 190 packages, from GDAL, cartopy and fiona to dlib and faiss);
  • a failed pip or uv build whose output names a missing header, *-config tool or library (gdal.h, say);
  • CUDA sources (.cu, .cuh) with no nvcc on PATH, CUDA_HOME or CUDA_PATH;
  • a repository that mixes Python with R, Rust or CMake/Meson C++ (Info only).

It only suggests: nothing is converted until you run the migration. pypilot check <package> shows the conda-forge alternative when a package fails. Details: the advisory.

A requirements.txt with numpy, requests and gdal on Windows:

$ pypilot doctor
...
── Findings ─────────────────────────────────────────────
  [WARN ] GDAL 3.13.3 needs native libraries pip cannot install
          GDAL 3.13.3 has no wheel for Windows x64, so pip and uv build it from source. GDAL's Python bindings must be compiled against a system GDAL of exactly the same version (gdal-config), which pip cannot install. pixi installs gdal 3.13.3 from conda-forge with its native libraries (libgdal, proj, geos), built for win-64 and Python 3.15. Run `pypilot migrate-to-pixi` to set the project up with pixi.

$ pypilot migrate-to-pixi
Dry run: nothing was written.

--- /dev/null
+++ b/pixi.toml
@@ -0,0 +1,13 @@
+# Written by `pypilot migrate-to-pixi` from requirements.txt (left untouched).
+# Review it, then run `pixi install`.
+
+[workspace]
+name = "geo"
+channels = ["conda-forge"]
+platforms = ["win-64"]
+
+[dependencies]
+python = "3.15.*"
+numpy = "*"
+requests = "*"
+gdal = "*"

Would write ...\geo\pixi.toml: python 3.15.*, 3 conda-forge and 0 PyPI dependencies.

Next: pypilot migrate-to-pixi --write

Moving a project to pixi (since 1.3.0)

pypilot migrate-to-pixi previews the pixi.toml it would write for a pip or uv project (requirements.txt and [project.dependencies]); --write creates it, and --write --install then runs pixi install. pypilot migrate-conda --to pixi does the same from an environment.yml (channels, conda-only packages such as gdal or cudatoolkit, and the pip: section as [pypi-dependencies]). Step by step: Migrating to pixi.

  • It writes a separate pixi.toml and never edits or deletes pyproject.toml, requirements.txt or environment.yml, so CI and pip keep working. pixi prefers pixi.toml when both exist.
  • Each PyPI dependency packaged on conda-forge (per prefix.dev's PyPI-to-conda mapping, cached for data_refresh_days) and built for every target platform goes to [dependencies] under its conda name (torch becomes pytorch); the rest go to [pypi-dependencies], with the reason as a comment. Version specifiers carry over only when conda can express them exactly; otherwise the dependency stays on PyPI. Offline, everything stays on PyPI.
  • python is the version PyPilot recommends, within what the conda-forge builds support. Platforms default to the current one; add more with --platform linux-64 (repeatable).
  • Environment markers, editable installs and -r includes are listed in the file as comments to add by hand.
  • package_manager = "pixi" makes setup write the pixi.toml the same way on a project without a pixi manifest, then run pixi install.
  • In Zed, the advisory's notification has an Initialize pixi project button: it says what would be written and asks first (Write and install, Write pixi.toml or Cancel). The tasks pypilot: preview migration to pixi (pixi.toml) and pypilot: migrate to pixi (write pixi.toml and install) do the same. In VS Code, use PyPilot: Initialize pixi project and PyPilot: Install pixi.

Installation

Install PyPilot from Zed's extension panel. When you open a Python folder, the extension automatically inspects the environment and opens a notification if action is required.

Local development build

To test the extension locally:

  1. Build and install the helper binary with cargo install --path helper.
  2. In Zed, open the command palette and run zed: install dev extension, selecting the extension directory.

The extension checks PATH first, running your local binary instead of fetching a release from GitHub.

Architecture

PyPilot splits work between two components:

  • A WebAssembly shim in extension/ that runs inside Zed. It identifies the host platform, downloads the precompiled helper binary for that architecture, and registers it with Zed as an LSP server.
  • A native Rust binary in helper/ that runs all analysis, environment orchestration, dependency parsing, and LSP communication.

Because the editor extension contains no business logic, changes in Zed extension APIs do not affect environment resolution or compatibility checking.

Repository layout

pypilot/
├── extension/           Zed WebAssembly shim (extension.toml and src/lib.rs)
├── helper/              Native helper binary
│   ├── src/
│   │   ├── main.rs      CLI command dispatch
│   │   ├── cli/         Subcommand entry points
│   │   ├── lsp/         Language server protocol implementation
│   │   ├── core/        Virtual environment management, uv and pip drivers, solver
│   │   ├── pypi/        Package metadata parser and local cache
│   │   └── matrix/      Driver and framework compatibility tables
│   ├── data/            Bundled JSON tables for NVIDIA and frameworks
│   └── tests/           Fixtures and offline test suites
├── tasks/               Zed task templates
└── .github/workflows/   CI and release automation workflows

Commands

Zed extensions execute external actions through tasks. To use PyPilot commands inside Zed, copy tasks/windows.json (Windows) or tasks/macos-linux.json (macOS, Linux) into .zed/tasks.json within your workspace or into your global Zed tasks configuration, then run them with task: spawn. The tasks use a pypilot on your PATH when there is one, and otherwise the copy the extension downloaded, so they work without installing anything else.

The helper binary also runs directly in any shell:

pypilot doctor            Inspect the environment and print a status report
                          (--fix applies only the fixes the findings call for, like
                          the editor's Fix environment)
pypilot setup             Configure the virtual environment and install dependencies
pypilot check <package>   Check whether a package runs on the current Python version
                          (accepts a requirement, e.g. mediapipe==0.10.14)
pypilot explain [pkg]     Show why: requires_python vs the wheels per platform for a
                          package, or what blocks each Python in the project (--json)
pypilot install <pkg>     Install a package and update the project manifest
pypilot fix python        Rebuild the virtual environment on a compatible Python version
pypilot fix cuda          Re-pin torch and tensorflow to the system driver build
pypilot fix requirements  Fix obvious mistakes in requirements.txt and add undeclared
                          imports (yaml to PyYAML, sklearn to scikit-learn)
pypilot update-data       Update the bundled driver and framework data tables
                          (--check: compare with main without writing; exit 0 current,
                          1 stale, 2 network error)
pypilot migrate-conda     Convert environment.yml into pyproject.toml
                          (--to pixi: into pixi.toml; a preview unless --write)
pypilot migrate-to-pixi   Preview a pixi.toml for this project (--write, --install,
                          --platform <p>)
pypilot install-pixi      Install pixi with its official installer
pypilot logs              Print the local log's path (--tail [N]: the last N entries)
pypilot completions <sh>  Print a completion script (bash, zsh, fish, powershell)
pypilot version           Engine version, commit, target and data tables
pypilot lsp               Start the language server (launched automatically by Zed)

explain, migrate-to-pixi, install-pixi, logs, completions and version, doctor --fix, update-data --check, migrate-conda --to pixi and the options below are new in 1.3.0. fix requirements and the targeted doctor --fix are new in 1.4.0.

  • --json on every mode but lsp: one stable JSON document on stdout, for scripts and bug reports. Fields are only ever added, never renamed or removed; they are listed in USAGE.md.
  • --markdown on doctor and check: the editor's report, ready to paste into an issue.
  • --dry-run on setup, install, fix python, fix cuda and doctor --fix: prints every command it would run and every file it would change, with the reason, and changes nothing. The plan comes from the same code path as the real run.
  • explain shows why a package or the project is limited to the Pythons it is.
  • logs: every CLI command and editor action appends one JSON line to a local log (rotated at 1 MB, secrets masked, nothing sent anywhere; PYPILOT_NO_LOG=1 turns it off).
  • completions, for example source <(pypilot completions bash).

More in USAGE.md and the command reference.

Running pypilot doctor or pypilot setup from a Zed task also writes a request file to the cache directory. The running language server reads this file and presents the results as interactive editor notifications.

What doctor --fix does

Since 1.4.0 doctor --fix fixes what is wrong and nothing else. It does not rebuild the environment for every problem.

  • A healthy project is left alone.
  • Each finding gets its own fix: uv sync --locked for drift or a missing package, uv lock for a stale lock, a reinstall of one half-installed package, a repair of a damaged venv on the Python it had, a higher requires-python, or an edit to one requirement.
  • The Python moves only when a dependency forces it, and the output names that dependency. An existing .python-version pin wins otherwise. The Python a fix picks is written to .python-version.
  • Every file PyPilot edits is named. An edit that leaves the project unresolvable is rolled back.
  • Conflicts are resolved before anything changes: an unsatisfiable pin gets a relaxed pin that uv has checked, a typo gets "did you mean", a version that does not exist gets the nearest releases, and a package with no build for this OS gets an environment marker.
  • Known breakages come from known_breakages.json, a data table refreshed like the others: compiled packages that break under numpy 2, uncapped dependencies that later removed an API, packages that shadow each other, yanked pins, stdlib modules removed in newer Pythons, and removed APIs your own code uses (reported by file and line, with the replacement). PyPilot never edits your source code.
  • If requires-python can never be met (>=3.16), it is reported as an error and nothing is built.
  • pixi, conda environment.yml, Poetry and venv/ or env/ projects are fixed with their own tools.

--dry-run lists exactly what the real run does. See What doctor --fix fixes for the full table. In a test of 54 deliberately broken environments, 45 got the right targeted fix, none got a wrong one and none were missed.

Configuration

PyPilot stores default configuration in your platform config directory. Projects can override these options by creating a .zed/pypilot.toml or pypilot.toml file in the workspace root:

package_manager    = "uv"             # "auto", "uv", "uv-pip", "pip" or "pixi". Pip mode does not invoke uv.
notifications      = "problems-only"  # "all", "problems-only", or "off"
auto_check_on_open = true
data_refresh_days  = 7                # Set to 0 to disable network updates.
pixi_environment   = "default"        # pixi projects: which .pixi/envs/<name> (since 1.3.0)
diagnostics_scope  = "openFiles"      # or "workspace" (since 1.3.0)
  • package_manager: Selects how PyPilot manages the environment. auto (the default) uses uv when it is installed and pip otherwise; uv is a uv project (pyproject.toml + uv.lock); uv-pip is uv's pip interface on requirements.txt; pip is python -m venv + pip; pixi manages the project with pixi. In pip mode, PyPilot cannot download new Python interpreter versions automatically; if an incompatible version is detected, it informs you which Python version to install manually. A pixi project (a pixi.toml or [tool.pixi.workspace]) is always managed with pixi, whatever this says; with pixi on a project without a pixi manifest, setup first writes pixi.toml from the project's dependencies (as pypilot migrate-to-pixi --write does), then runs pixi install.
  • notifications: Controls alert frequency. problems-only warns about errors and version mismatches. all includes informational alerts like selected CUDA builds. off suppresses editor notifications. Diagnostics and code actions in active editor buffers remain enabled unless set to off.
  • data_refresh_days: The refresh interval for the bundled driver and framework matrices. PyPilot ships with embedded JSON tables for offline use. If a background check fails, it falls back to the embedded data.
  • pixi_environment: The pixi environment PyPilot treats as the project's (--pixi-env <name> overrides it for one run). Ignored outside pixi projects.
  • diagnostics_scope: openFiles checks imports in open files only. workspace also scans the project's .py files and notebooks in the background (skipping virtual environments, build directories and whatever .gitignore excludes), so problems show up for files you have not opened.

All settings: configuration reference.

Other editors

pypilot lsp is a plain LSP server over stdio. helper/src/lsp/PROTOCOL.md documents everything an editor integration needs (settings, commands, messages, diagnostics, code actions), with snippets for Neovim, Helix and Emacs.

Release integrity

Since engine 1.3.0, each release carries SHA256SUMS for its four archives, a CycloneDX SBOM of the engine (pypilot-helper.cdx.json) and GitHub build-provenance attestations, so a download can be checked with sha256sum -c SHA256SUMS --ignore-missing or gh attestation verify <file> -R pypilot-dev/pypilot. The VS Code extension and the pypilot-cli package pin one engine release and its hashes and refuse a download that does not match. See Security & release integrity.

Building from source

cargo test  -p pypilot-helper
cargo build -p pypilot-helper --release

rustup target add wasm32-wasip2
cargo build -p pypilot-zed --target wasm32-wasip2 --release

Tests run offline against recorded PyPI fixtures. The platform is always passed in, never read from the host, so Linux (x64, arm64, glibc and musl), macOS (x64, arm64) and Windows (x64, arm64) are all checked on any machine; helper/tests/platforms.rs covers the cases where they differ.

python scripts/check_data.py reports whether the bundled tables in helper/data/ have fallen behind PyPI, the PyTorch wheel indexes and NVIDIA's driver table. Run it before changing code and see AGENTS.md for how to update them.

License

PyPilot uses two licenses corresponding to its components:

  • helper/ is licensed under AGPL-3.0-or-later. See helper/LICENSE.
  • extension/ is licensed under Apache-2.0. See LICENSE. This complies with the Zed extension registry requirements for extension frontends.