Coverage for src/lpip/pipxutils.py: 64%
23 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-17 20:20 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-17 20:20 +0000
1"""Locate and invoke the pipx executable (ADR-0024). No pipx Python library."""
3from __future__ import annotations
5import os
6import shutil
7import subprocess
8from collections.abc import Mapping
9from pathlib import Path
11from lpip.errors import PipxNotFoundError
14def find_pipx_executable(*, environ: Mapping[str, str] | None = None) -> Path | None:
15 env = environ if environ is not None else os.environ
16 found = shutil.which("pipx", path=env.get("PATH"))
17 return Path(found) if found else None
20def require_pipx(*, environ: Mapping[str, str] | None = None) -> Path:
21 found = find_pipx_executable(environ=environ)
22 if found is None:
23 raise PipxNotFoundError()
24 return found
27def pipx_install_argv(pipx: str, requirement: str) -> list[str]:
28 """Replace an existing pipx app of this name; do not call pipx upgrade."""
29 return [pipx, "install", "--force", requirement]
32def format_pipx_install_command(pipx: str, requirement: str) -> str:
33 return f'{pipx} install --force "{requirement}"'
36def run_pipx_install(
37 pipx: str,
38 requirement: str,
39 *,
40 environ: Mapping[str, str] | None = None,
41) -> int:
42 completed = subprocess.run( # nosec B603
43 pipx_install_argv(pipx, requirement),
44 env=dict(environ) if environ is not None else None,
45 )
46 return completed.returncode