Skip to content

Make uv optional#2325

Open
youdie006 wants to merge 1 commit into
pypa:masterfrom
youdie006:feat/optional-uv
Open

Make uv optional#2325
youdie006 wants to merge 1 commit into
pypa:masterfrom
youdie006:feat/optional-uv

Conversation

@youdie006

Copy link
Copy Markdown

Summary

Hatch required uv as a hard dependency, and several internal environments hardcoded "installer": "uv", so a clean Hatch install with uv removed broke (#2141).

This makes uv optional:

  • New default_installer() (src/hatch/env/internal/__init__.py): returns "uv" when uv is on PATH (shutil.which, matching the existing VirtualEnvironment.uv_path detection), else "pip". The internal envs (build, static-analysis, test, type-check, and the hatch-uv bootstrap env) use it instead of hardcoding "uv".
  • pyproject.toml: moved uv>=0.5.23 from required dependencies into a [uv] optional extra, so pip install hatch[uv] opts back in.

Verified end-to-end: with uv on PATH all internal envs resolve uv; with uv absent all resolve pip. No regression to the uv-present experience.

Tests

tests/env/internal/test_default_installer.py — 18 parametrized tests over the internal envs for both uv-present and uv-absent. ruff + mypy clean; history entry added.

A note for maintainers

The code-side fallback is the core fix and stands on its own. Moving uv to an optional extra is a packaging-default change — happy to adjust if you'd prefer keeping uv as a default-but-removable dependency, or a different extra name.

AI usage disclosure

Per the contributing guide: this PR was developed with AI assistance (Claude Code); the implementation and tests were substantially AI-assisted and reviewed before submission.

Closes #2141

Comment thread src/hatch/env/internal/__init__.py Outdated
# `uv` is an optional dependency, so the internal environments only default to it when it is
# actually available and otherwise fall back to `pip`. This mirrors the detection used by the
# virtual environment type (see `hatch.env.virtual.VirtualEnvironment.uv_path`).
return "uv" if shutil.which("uv") is not None else "pip"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not mirror what is in VirtualEnvironment.uv_path https://github.com/pypa/hatch/blob/master/src/hatch/env/virtual.py#L110 . The uv_path does augmentation with the scripts dir. That augmentation exists precisely because uv (installed as a Python package) lands in the install environment's scripts dir, which is not necessarily on the running process's PATH.

Another concern here is that this runs a full PATH scan on every config build.

Hatch required uv as a hard dependency and several internal environments
hardcoded "installer": "uv", so a clean install with uv removed broke. Add a
default_installer() helper that returns "uv" when uv is on PATH else "pip", use
it across the internal envs (build, static-analysis, test, type-check, and the
hatch-uv bootstrap), and move uv to a [uv] optional extra. uv stays the default
when present; pip is used when absent.
@youdie006

Copy link
Copy Markdown
Author

Thanks @cjames23 — both fixed:

  1. Detection now mirrors VirtualEnvironment.uv_path: a new uv_available() prepends sysconfig.get_path("scripts") to PATH before shutil.which("uv", path=...), so uv installed as a package in the scripts dir is found even when it's not on the bare PATH (added a test for exactly that scenario).
  2. The PATH scan no longer runs per config build — uv_available() is functools.cached (scanned once per process; tests clear the cache for independence).

which.assert_called_once_with("uv", path=f"{scripts_dir}{os.pathsep}/usr/bin")

def test_detects_uv_in_scripts_dir_not_on_bare_path(self, mocker, tmp_path):
# The exact scenario from the review: ``uv`` is installed as a package and lives in the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not reference PR reviews in comments

Comment on lines +14 to +22
# `uv` is an optional dependency, so detect whether it is actually installed before defaulting
# the internal environments to it. This mirrors the standalone detection used by the virtual
# environment type (see `hatch.env.virtual.VirtualEnvironment.uv_path`): when `uv` is installed
# as a Python package it lands in the install environment's scripts directory, which is not
# necessarily on the running process's `PATH`, so that directory is prepended before searching.
#
# The result is cached because `default_installer` is called by every internal environment config
# producer, and a fresh `PATH` scan on every config build is wasteful. Tests that toggle `uv`
# availability must clear this cache (`uv_available.cache_clear()`).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This level of verbosity of a comment is not needed here.

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.

Make uv optional

2 participants