fix: an install pins every installed package with a constraints file, so a new package's dependencies cannot move one that is already here; an unreadable package list installs nothing
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 5.5
parent
78a418812b
commit
599cb2c0c3
@@ -229,7 +229,7 @@ def test_a_missing_requirement_is_installed_by_its_whole_line_so_a_pin_or_url_su
|
||||
monkeypatch.setattr(update, "_installed_version", lambda n: "1.0" if n == "numpy" else None)
|
||||
ran = []
|
||||
monkeypatch.setattr(update.subprocess, "run", lambda cmd, **k: ran.append(cmd) or subprocess.CompletedProcess(cmd, 0, "", ""))
|
||||
monkeypatch.setattr(update, "_pip_freeze", lambda: {})
|
||||
monkeypatch.setattr(update, "_pip_freeze", lambda: {"numpy": "1.26.4"})
|
||||
assert update.install_requirements()["ok"]
|
||||
assert [c[-1] for c in ran] == ["zz_absent_pkg>=3.8,<3.9", "zz_model @ https://example.com/zz_model.whl"]
|
||||
|
||||
@@ -244,7 +244,7 @@ def test_one_package_that_cannot_install_does_not_stop_the_others(repo, monkeypa
|
||||
return subprocess.CompletedProcess(cmd, 1 if "zz_bad_url" in cmd[-1] else 0, "", "404 not found")
|
||||
|
||||
monkeypatch.setattr(update.subprocess, "run", fake_run)
|
||||
monkeypatch.setattr(update, "_pip_freeze", lambda: {})
|
||||
monkeypatch.setattr(update, "_pip_freeze", lambda: {"numpy": "1.26.4"})
|
||||
out = update.install_requirements()
|
||||
assert ran == ["zz_bad_url @ https://example.com/missing.whl", "zz_good_pkg"], "the second still installs"
|
||||
assert out["ok"] is False and "zz_bad_url" in out["detail"] and "404 not found" in out["detail"]
|
||||
@@ -269,6 +269,32 @@ def test_a_spacy_model_that_does_not_load_is_downloaded_on_the_switch(repo, monk
|
||||
ran = []
|
||||
monkeypatch.setattr(update, "_spacy_model_loads", lambda: bool(ran) and ran[-1] == ["spacy", "download", "en_core_web_sm"])
|
||||
monkeypatch.setattr(update.subprocess, "run", lambda cmd, **k: ran.append(cmd[-3:]) or subprocess.CompletedProcess(cmd, 0, "", ""))
|
||||
monkeypatch.setattr(update, "_pip_freeze", lambda: {})
|
||||
monkeypatch.setattr(update, "_pip_freeze", lambda: {"numpy": "1.26.4"})
|
||||
assert update.install_requirements()["ok"]
|
||||
assert ran[-1] == ["spacy", "download", "en_core_web_sm"]
|
||||
|
||||
|
||||
def test_an_install_pins_every_installed_package_so_nothing_already_here_moves(repo, monkeypatch):
|
||||
(repo / "requirements.txt").write_text("zz_new_pkg\n")
|
||||
monkeypatch.setattr(update, "_installed_version", lambda n: None)
|
||||
seen = {}
|
||||
|
||||
def fake_run(cmd, **k):
|
||||
if "-c" in cmd:
|
||||
seen["constraints"] = open(cmd[cmd.index("-c") + 1]).read()
|
||||
return subprocess.CompletedProcess(cmd, 0, "", "")
|
||||
|
||||
monkeypatch.setattr(update.subprocess, "run", fake_run)
|
||||
monkeypatch.setattr(update, "_pip_freeze", lambda: {"numpy": "1.26.4", "torch": "2.9.0"})
|
||||
assert update.install_requirements()["ok"]
|
||||
assert "numpy==1.26.4" in seen["constraints"] and "torch==2.9.0" in seen["constraints"]
|
||||
|
||||
|
||||
def test_an_unreadable_installed_list_installs_nothing(repo, monkeypatch):
|
||||
(repo / "requirements.txt").write_text("zz_new_pkg\n")
|
||||
monkeypatch.setattr(update, "_installed_version", lambda n: None)
|
||||
ran = []
|
||||
monkeypatch.setattr(update.subprocess, "run", lambda cmd, **k: ran.append(cmd) or subprocess.CompletedProcess(cmd, 0, "", ""))
|
||||
monkeypatch.setattr(update, "_pip_freeze", lambda: {})
|
||||
out = update.install_requirements()
|
||||
assert out["ok"] is False and ran == []
|
||||
|
||||
+20
-2
@@ -12,10 +12,12 @@ feature: no module can announce the thing that updates every module.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
@@ -426,9 +428,25 @@ def install_requirements(timeout: int = 900) -> dict:
|
||||
# stop every other one from installing too. Dependencies are resolved normally: a new package
|
||||
# with nothing under it is not installed, it is broken. The freeze diff says whether anything
|
||||
# already present moved as a result.
|
||||
if not before:
|
||||
return {"ran": False, "ok": False,
|
||||
"detail": "Could not read the installed packages, so nothing is installed (an install could move them). "
|
||||
f"Run it yourself: {sys.executable} -m pip install <the missing packages>"}
|
||||
# Constraints: every installed package stays at its version, so a new package's dependencies can be added
|
||||
# but never bump one that is already here. A dependency that needs a newer one fails, and says so.
|
||||
constraints = tempfile.NamedTemporaryFile("w", suffix=".txt", delete=False, encoding="utf-8")
|
||||
try:
|
||||
constraints.write("\n".join(f"{n}=={v}" for n, v in before.items()) + "\n")
|
||||
constraints.close()
|
||||
return _install_each(status, constraints.name, before, floor_note, timeout, model_broken)
|
||||
finally:
|
||||
os.unlink(constraints.name)
|
||||
|
||||
|
||||
def _install_each(status, constraints_path, before, floor_note, timeout, model_broken):
|
||||
failed, done = [], []
|
||||
for line in status.get("install", []):
|
||||
cmd = [sys.executable, "-m", "pip", "install", "--disable-pip-version-check", line]
|
||||
cmd = [sys.executable, "-m", "pip", "install", "--disable-pip-version-check", "-c", constraints_path, line]
|
||||
try:
|
||||
proc = subprocess.run(cmd, cwd=REPO_ROOT, capture_output=True, text=True, timeout=timeout)
|
||||
except subprocess.TimeoutExpired:
|
||||
@@ -442,7 +460,7 @@ def install_requirements(timeout: int = 900) -> dict:
|
||||
else:
|
||||
done.append(line)
|
||||
if model_broken:
|
||||
cmd = [sys.executable, "-m", "spacy", "download", "en_core_web_sm"]
|
||||
cmd = [sys.executable, "-m", "spacy", "download", "en_core_web_sm"] # the model is data, not a dependency: no constraints
|
||||
try:
|
||||
proc = subprocess.run(cmd, cwd=REPO_ROOT, capture_output=True, text=True, timeout=timeout)
|
||||
if proc.returncode != 0 or not _spacy_model_loads():
|
||||
|
||||
Reference in New Issue
Block a user