Skip to content

Add optional uv backend (opt-in via --use-uv / config)#454

Open
gilgamezh wants to merge 9 commits into
masterfrom
uv-backend
Open

Add optional uv backend (opt-in via --use-uv / config)#454
gilgamezh wants to merge 9 commits into
masterfrom
uv-backend

Conversation

@gilgamezh

@gilgamezh gilgamezh commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

What

Adds uv as an opt-in backend for venv creation and dependency installs. By default fades keeps using venv/pip; uv is used only when explicitly requested. Everything else (dependency parsing, venv indexing/cache, GC, --where/--rm, REPL/autoimport, config files) is unchanged.

Closes #451.

Enabling uv

  • --use-uv — use uv for this run.
  • --uv-path PATH — point at a specific uv executable (implies --use-uv); otherwise uv is looked up in PATH and validated. If uv is requested but not found, fades errors out.
  • use_uv=true in any fades config file (/etc/fades/fades.ini, the per-user fades.ini, or a repo-local ./.fades.ini) enables it permanently; a CLI flag still wins.

Design notes

  • Opt-in, not auto-detect (per discussion in Proposal: support uv as an optional backend for venv creation & installs #451): uv produces different venv contents than pip, so fades' behaviour shouldn't change merely because uv is installed.
  • uv has no Python API (Non-CLI usage of uv? (From existing Rust code, or possibly from Python) astral-sh/uv#6080 closed without one), so it's invoked via subprocess like the existing PipManager. fades does not depend on the uv PyPI package (it runs against the system Python); it uses a uv binary on PATH.
  • UvManager mirrors PipManager's install/get_version, dispatched from envbuilder.create_venv. Backend resolution + option validation live in main._resolve_uv_backend (unit-tested).
  • uv venv --seed so the venv ships pip (and setuptools, per Python version) like the classic backend.
  • When uv is used: the PyPI availability pre-check is skipped (uv resolves directly); --pip-options/--venv-options are errors (use --uv-pip-options); --freeze uses plain pip freeze on the seeded venv.

Tests / CI

  • Unit tests for _resolve_uv_backend (enabled/disabled, --uv-path, not-found, option conflicts), UvManager, envbuilder dispatch, get_uv_exe.
  • Integration jobs (archlinux/fedora/native-windows) exercise: default = pip, --use-uv = uv, conflict + bogus-path errors, and --freeze on a uv venv. Windows confirms the uv path end-to-end.

🤖 Generated with Claude Code

When a `uv` binary is found in PATH, fades now uses it as a faster backend
to create the virtualenv (`uv venv`) and install dependencies (`uv pip
install`), instead of stdlib `venv` + `pip`. Detection is via
`helpers.get_uv_exe()` (shutil.which); fades does not depend on the `uv`
PyPI package, since it runs against the system Python.

The new `UvManager` mirrors `PipManager`'s interface (install/get_version/
freeze) so it plugs into the existing dispatch in `envbuilder.create_venv`.
A `--no-uv` flag forces the classic pip backend even when uv is available.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gilgamezh

gilgamezh commented Jun 20, 2026

Copy link
Copy Markdown
Contributor Author

Benchmark: uv vs pip

Wall time for fades -d <pkg> -x <run-something-in-the-venv>, comparing the default (uv) backend against --no-uv (pip).

django (single dependency)

Scenario Backend Wall time
Fresh venv (create + install) pip (--no-uv) 3.43s
Fresh venv (create + install) uv 0.39s
Existing/cached venv (no install) either ~0.16s

8.7× faster on the create+install path.

jupyterlab (~60 transitive deps)

Scenario Backend Wall time
Fresh, cold package cache (incl. downloads) pip (--no-uv) 16.63s
Fresh, cold package cache (incl. downloads) uv 12.70s
Fresh, warm package cache (already downloaded) pip (--no-uv) 13.97s
Fresh, warm package cache (already downloaded) uv 1.62s
Existing/cached venv (no install) either 0.92s
  • Cold cache (first-ever install): ≈ 1.3×. On a first install both backends spend most of the time downloading the same ~60 wheels over the network, which dominates and masks the backend difference.
  • Warm cache (steady state, packages already fetched once): 13.97s → 1.62s ≈ 8.6× faster — this isolates uv's actual resolve+install speed and matches the django ratio.

Takeaways

  • uv is ~8–9× faster on the install itself; the small cold-cache jupyterlab number is just because raw download bandwidth (identical for both backends) dominates the very first run.
  • The cached-venv path is unchanged regardless of backend — no install happens, fades just reuses the venv.

Single run per row, warm/cold as noted; numbers will vary with network and machine.

gilgamezh and others added 5 commits June 20, 2026 13:08
- freeze: drop `uv pip freeze --quiet`, which empties the whole output on some
  uv versions (producing a 0-byte freeze file); filter uv's info line instead,
  mirroring get_version.
- freeze: pick the backend from the venv's `pip_installed` metadata, not the
  current run's flags. A uv-built venv has no pip, and the cache key doesn't
  include the backend, so it can be reused under --no-uv / when uv is gone;
  PipManager.freeze would then crash on a missing pip.
- uv `--python`: probe for `python` / `python.exe` so the uv backend works on
  Windows (uv treats --python as a literal path, no PATHEXT resolution).
- main: fall back to the pip backend when `--venv-options` contains flags
  `uv venv` doesn't accept (e.g. --symlinks), keeping documented usage working.
- CI: make the --no-uv check also assert fades' own exit status (a crash could
  silently pass the negated grep), and add a --freeze regression step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Install uv in the native-windows job and add the uv auto-detect, --no-uv, and
--freeze checks there. This confirms the uv path works on Windows, where the
venv interpreter is Scripts\python.exe (regression coverage for the uv
--python resolution). The grep-based steps run under bash (Git Bash), since
the Windows runner defaults to PowerShell.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pip-installed uv wasn't reliably on PATH for fades' shutil.which on the
Windows runner, so the auto-detect step couldn't confirm the uv backend. Use
the official astral-sh/setup-uv action to guarantee uv on PATH, and capture +
print fades output so failures in the uv path are diagnosable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Windows interpreter path was interpolated raw into the bash steps, where
unquoted backslashes were stripped (command not found). Single-quote it and
convert backslashes to forward slashes (accepted by Git Bash) so the uv path
is actually exercised on Windows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gilgamezh gilgamezh requested a review from facundobatista June 20, 2026 11:53
uv seeds nothing by default, so uv-built venvs lacked pip and setuptools that
the classic 'python -m venv' backend provides, breaking packages that expect
them present. Pass --seed so the venv ships pip (+ setuptools, per the Python
version, matching stdlib venv) at negligible cost.

Note: this does not restore pkg_resources for packages like azure-cli, since
uv seeds a recent setuptools (>=81) that removed it -- the same is true for the
pip backend on Python 3.12+. Documented the 'setuptools<81' workaround.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gilgamezh

Copy link
Copy Markdown
Contributor Author

uv venvs now seed pip/setuptools (uv venv --seed) + a note on pkg_resources

While testing azure-cli we hit ModuleNotFoundError: No module named 'pkg_resources' on the uv backend, which surfaced two separate things:

1. uv venvs were missing pip/setuptools (fixed)

uv venv seeds nothing by default, whereas the classic python -m venv backend ships pip (and, on Python ≤ 3.11, setuptools). So uv-built venvs silently lacked tooling that some packages expect to be present. fades now runs uv venv --seed, restoring that parity:

  • Python 3.12+: seeds pip (matches stdlib venv, which dropped setuptools seeding in 3.12).
  • Python ≤ 3.11: seeds pip + setuptools.

Cost is negligible (~6 ms, warm cache).

2. pkg_resources / azure-cli (not fixable transparently — documented)

azure-cli imports the long-deprecated pkg_resources, which setuptools removed in >= 81. --seed installs a recent setuptools, so it does not bring pkg_resources back. Importantly, this is not uv-specific — the pip backend fails the same way on Python 3.12+ (no setuptools seeded at all). It only "works" on the classic backend on Python ≤ 3.11 because that seeds an old bundled setuptools that still ships pkg_resources.

fades shouldn't force-pin an old setuptools, so the workaround is documented in the README — request an older setuptools explicitly:

fades -d azure-cli -d "setuptools<81" -x az --help

(or --no-uv on a Python old enough to seed an old setuptools). Verified this restores pkg_resources (setuptools 80.10.2) through the uv backend.

Implemented in 9ab2790.

Per discussion on #451, uv is no longer auto-detected/used just because it's on
PATH (it produces different venvs than pip, so behaviour shouldn't change based
on an external binary's presence). uv is now explicit:

- `--use-uv` enables it; `--uv-path PATH` points at a specific uv (implies
  --use-uv); without a path uv is looked up in PATH and validated. If uv is
  requested but not found, fades errors out.
- `use_uv=true` in any fades config file enables it permanently (machine/user/
  repo `.fades.ini`); a CLI flag still wins.
- `--pip-options`/`--venv-options` together with --use-uv is an error; new
  `--uv-pip-options` passes options to `uv pip install`.
- the PyPI availability pre-check is skipped under uv (uv resolves directly).
- `--freeze` uses plain pip freeze (uv venvs are seeded with pip), dropping the
  uv-specific freeze code.

Detection/resolution lives in main._resolve_uv_backend (unit-tested); README
and man page document the opt-in model and how to enable it always via config.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gilgamezh gilgamezh changed the title Add optional uv backend for venv creation and dependency install Add optional uv backend (opt-in via --use-uv / config) Jun 20, 2026
@gilgamezh

Copy link
Copy Markdown
Contributor Author

Switched uv from default to opt-in

Following the discussion in #451 (thanks @facundobatista), uv is no longer auto-detected/used when present. It's now explicit:

  • --use-uv (or --uv-path) per run, or use_uv=true in a config file (machine/user/repo .fades.ini) to enable it always — CLI overrides config.
  • requesting uv when it isn't installed is an error (no silent fallback).
  • --pip-options/--venv-options are rejected with uv; use the new --uv-pip-options for uv pip install passthrough.
  • the PyPI pre-check is skipped under uv; --freeze uses standard pip-freeze output (venvs are seeded with pip).

The FADES_USE_UV env var idea was dropped in favor of config files, which already support per-repo/per-machine scoping plus CLI override. The README documents a one-liner to flip use_uv=true into your fades.ini.

All green on Linux/macOS/Windows.

Wrap get_confdir() in Path() so the one-liner works whether get_confdir
returns a str (older fades) or a Path, and add -W ignore to silence the
pkg_resources deprecation warning emitted by older installed versions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: support uv as an optional backend for venv creation & installs

1 participant