"""Installation à la demande d'un paquet optionnel, sans toucher au build du Space ni à l'en-tête des jobs. Ordre d'essai : `uv pip` (environnements créés par `uv run` dans les HF Jobs, qui n'ont pas pip), puis `python -m pip` (Space Docker ; repli automatique en installation --user si site-packages est en lecture seule). """ from __future__ import annotations import importlib import shutil import site import subprocess import sys from typing import Callable, Optional def pip_install(spec: str, *, extra_index: Optional[str] = None, only_binary: bool = False, no_deps: bool = False, log_fn: Callable[[str], None] = print, timeout: int = 900) -> bool: opts: list[str] = [] if extra_index: opts += ["--extra-index-url", extra_index] if only_binary: opts += ["--only-binary", ":all:"] if no_deps: opts.append("--no-deps") cmds = [] if shutil.which("uv"): cmds.append(["uv", "pip", "install", "--python", sys.executable, *opts, spec]) cmds.append([sys.executable, "-m", "pip", "install", "--quiet", *opts, spec]) for cmd in cmds: try: run = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) except Exception as exc: # noqa: BLE001 — outil absent ou délai dépassé : on essaie le suivant log_fn(f"installation de {spec} : {cmd[0]} indisponible ({exc})") continue if run.returncode == 0: try: site.addsitedir(site.getusersitepackages()) # cas de l'installation --user except Exception: # noqa: BLE001 pass importlib.invalidate_caches() return True log_fn(f"installation de {spec} refusée par {cmd[0]} : {(run.stderr or run.stdout)[-300:]}") return False