fix(lazy_deps): pip's file precedence, not its enumeration order

Gate review: pip ranks PIP_CONFIG_FILE highest (and skips the user tier when it exists), then
the venv site file, then user, then global; the list was built in enumeration order so the
user file beat both. Global tier now matches pip per platform (XDG_CONFIG_DIRS on Linux,
/Library/Application Support on macOS). configparser.Error only; the timeout hint no longer
repeats the manual-install command FeatureUnavailable already appends; the bridge runs only
when uv is the tier in use.
This commit is contained in:
kshitijk4poor
2026-09-20 16:24:41 +05:30
committed by kshitij
parent 30caab21fb
commit aa11bc3554
2 changed files with 38 additions and 40 deletions

View File

@@ -613,7 +613,7 @@ class TestPipConfIndexBridge:
"""Run _venv_pip_install against a stubbed uv (with *env* set) and return the env it was spawned with."""
conf = tmp_path / "pip.conf"
conf.write_text(f"[global]\nindex-url = {self.MIRROR}\n", encoding="utf-8")
# PIP_CONFIG_FILE keeps the host's own /etc, sys.prefix and ~ pip files out of the read.
# PIP_CONFIG_FILE is pip's highest file tier, so it wins over any host /etc file.
monkeypatch.setenv("PIP_CONFIG_FILE", str(conf))
monkeypatch.setattr(ld.Path, "home", staticmethod(lambda: tmp_path))
monkeypatch.setattr(ld.sys, "prefix", str(tmp_path / "venv"))
@@ -639,9 +639,11 @@ class TestPipConfIndexBridge:
env = self._run_with_fake_uv(monkeypatch, tmp_path)
assert env["UV_INDEX_URL"] == self.MIRROR
def test_pip_index_url_env_beats_pip_conf_and_explicit_uv_config_is_left_alone(self, monkeypatch, tmp_path):
monkeypatch.setenv("PIP_INDEX_URL", "https://env.example/simple")
assert self._run_with_fake_uv(monkeypatch, tmp_path)["UV_INDEX_URL"] == "https://env.example/simple"
def test_pip_index_url_env_beats_pip_conf(self, monkeypatch, tmp_path):
env = self._run_with_fake_uv(monkeypatch, tmp_path, PIP_INDEX_URL="https://env.example/simple")
assert env["UV_INDEX_URL"] == "https://env.example/simple"
def test_explicit_uv_index_knob_is_not_overridden(self, monkeypatch, tmp_path):
monkeypatch.delenv("PIP_INDEX_URL", raising=False)
env = self._run_with_fake_uv(monkeypatch, tmp_path, UV_DEFAULT_INDEX="https://custom.example/simple")
assert "UV_INDEX_URL" not in env, "an explicit uv index knob must not be overridden"
assert "UV_INDEX_URL" not in env

View File

@@ -11,6 +11,7 @@ package can only add modules, never shadow core; PyPI-by-name specs only (``_spe
from __future__ import annotations
import configparser
import contextlib
import logging
import os
@@ -423,10 +424,10 @@ def _core_constraints_file() -> Optional[Path]:
def _pip_config_candidates(env: dict[str, str]) -> list[Path]:
"""pip's config files, lowest precedence first (mirrors ``pip._internal.configuration``
``get_configuration_files``): ``PIP_CONFIG_FILE``, then global, the venv's ``sys.prefix``
site file, then user (legacy ``~/.pip`` before the new location, so the new one wins).
``PIP_CONFIG_FILE=os.devnull`` disables every file, as in pip."""
"""pip's config files, lowest precedence first, as ``pip._internal.configuration`` ranks them:
global, then user (skipped entirely when ``PIP_CONFIG_FILE`` names an existing file), then the
venv's ``sys.prefix`` site file, then ``PIP_CONFIG_FILE`` itself on top. ``RawConfigParser.read``
applies them in order, so the last file wins. ``PIP_CONFIG_FILE=os.devnull`` disables all of them."""
explicit = env.get("PIP_CONFIG_FILE", "")
if explicit == os.devnull:
return []
@@ -435,14 +436,20 @@ def _pip_config_candidates(env: dict[str, str]) -> list[Path]:
name = "pip.ini"
global_files = [Path(env.get("ProgramData") or r"C:\ProgramData") / "pip" / name]
user_files = [home / "pip" / name, Path(env.get("APPDATA") or home / "AppData" / "Roaming") / "pip" / name]
elif sys.platform == "darwin":
name = "pip.conf"
global_files = [Path("/Library/Application Support/pip") / name]
app_support = home / "Library" / "Application Support" / "pip"
user_files = [home / ".pip" / name, (app_support if app_support.is_dir() else home / ".config" / "pip") / name]
else:
name = "pip.conf"
global_files = [Path("/etc") / name, Path("/etc/pip") / name]
xdg = Path(env.get("XDG_CONFIG_HOME") or home / ".config")
user_files = [home / ".pip" / name, xdg / "pip" / name]
if sys.platform == "darwin" and (home / "Library" / "Application Support" / "pip").is_dir():
user_files.append(home / "Library" / "Application Support" / "pip" / name)
return ([Path(explicit)] if explicit else []) + global_files + [Path(sys.prefix) / name] + user_files
xdg_dirs = (env.get("XDG_CONFIG_DIRS") or "/etc/xdg").split(os.pathsep)
global_files = [Path(d) / "pip" / name for d in xdg_dirs if d] + [Path("/etc") / name]
user_files = [home / ".pip" / name, Path(env.get("XDG_CONFIG_HOME") or home / ".config") / "pip" / name]
explicit_files = [Path(explicit)] if explicit else []
if explicit_files and explicit_files[0].is_file():
user_files = []
return global_files + user_files + [Path(sys.prefix) / name] + explicit_files
def _pip_conf_index_url(env: dict[str, str]) -> Optional[str]:
@@ -452,17 +459,14 @@ def _pip_conf_index_url(env: dict[str, str]) -> Optional[str]:
mirrored (common behind restricted networks) watches every lazy install
hit the default pypi.org and time out (#95608).
"""
# Raw: pip does not interpolate, and mirror URLs carry percent-encoded credentials.
parser = configparser.RawConfigParser()
try:
import configparser
# Raw: pip does not interpolate, and mirror URLs carry percent-encoded credentials.
parser = configparser.RawConfigParser()
found = parser.read(str(p) for p in _pip_config_candidates(env))
if not found or not parser.has_section("global"):
parser.read(str(p) for p in _pip_config_candidates(env))
if not parser.has_section("global"):
return None
value = parser.get("global", "index-url", fallback="").strip()
return value or None
except Exception as e: # noqa: BLE001 (config reading is best-effort)
return parser.get("global", "index-url", fallback="").strip() or None
except configparser.Error as e:
logger.debug("Could not read pip.conf for index-url: %s", e)
return None
@@ -584,21 +588,16 @@ def _venv_pip_install(specs: tuple[str, ...], *, timeout: int = 300, constraint_
from tools.environments.local import hermes_subprocess_env
uv_env = hermes_subprocess_env(inherit_credentials=False)
uv_env["VIRTUAL_ENV"] = str(Path(sys.executable).parent.parent)
# uv never reads pip.conf, so a mirrored-pip user would see every
# lazy install stall against the default pypi.org for the full
# timeout (#95608). Bridge pip's index-url unless uv is configured
# explicitly (any of its index knobs). PIP_INDEX_URL follows pip's own
# precedence (env var beats pip.conf).
if not any(uv_env.get(k) for k in ("UV_INDEX_URL", "UV_DEFAULT_INDEX", "UV_INDEX")):
pip_index_url = (uv_env.get("PIP_INDEX_URL") or "").strip() or (
_pip_conf_index_url(uv_env)
)
if pip_index_url:
uv_env["UV_INDEX_URL"] = pip_index_url
# Tier 1: uv. --compile-bytecode because uv writes no __pycache__ by default, so the first
# import would recompile the backend AND its transitives (_warm_installed_bytecode is the
# belt-and-braces pass for the spec's own roots on any tier).
if uv_bin := _uv_binary():
# Bridge pip's index unless any uv index knob is set; PIP_INDEX_URL beats pip.conf, as in
# pip (see _pip_conf_index_url for why uv needs this at all).
if not any(uv_env.get(k) for k in ("UV_INDEX_URL", "UV_DEFAULT_INDEX", "UV_INDEX")):
pip_index_url = (uv_env.get("PIP_INDEX_URL") or "").strip() or _pip_conf_index_url(uv_env)
if pip_index_url:
uv_env["UV_INDEX_URL"] = pip_index_url
try:
r = _run_installer([uv_bin, "pip", "install", "--compile-bytecode", *extra_args, *specs], timeout=timeout, env=uv_env)
if r.returncode != 0:
@@ -612,11 +611,8 @@ def _venv_pip_install(specs: tuple[str, ...], *, timeout: int = 300, constraint_
# no feedback, then silence. The failure string flows into
# FeatureUnavailable, which callers surface as warnings.
hint = (
f"uv pip install timed out after {timeout}s: {e}. "
"If your network needs a package mirror, set index-url in "
"pip.conf (bridged to uv automatically) or UV_INDEX_URL "
f"directly; or install manually with: "
f"{sys.executable} -m pip install {' '.join(specs)}"
f"uv pip install timed out after {timeout}s. If your network needs a package "
"mirror, set index-url in pip.conf (bridged to uv automatically) or UV_INDEX_URL."
)
return _InstallResult(False, "", hint)
except FileNotFoundError as e: # uv vanished between lookup and spawn; it never evaluated the requirements