from __future__ import annotations import os import sys from dataclasses import dataclass from enum import Enum from importlib.metadata import Distribution, PackageNotFoundError, distribution from pathlib import Path from typing import Literal UvUpgradeStrategy = Literal["add", "lock"] class Installer(str, Enum): PIP = "pip" UV_PIP = "uv-pip" UV = "uv-project" UV_TOOL = "uv-tool" PIPX = "pipx" BREW = "brew" CONDA = "conda" MAMBA = "mamba" UNKNOWN = "unknown" @dataclass(frozen=True) class InstallerInfo: installer: Installer upgrade_cmd: str | None def _is_pipx_environment() -> bool: """Check whether the current environment is inside a pipx-managed venv.""" return "pipx/venvs" in sys.prefix or "pipx\\venvs" in sys.prefix def _is_uv_tool_environment() -> bool: """Check whether the current environment is inside a uv tool-managed venv.""" return "/uv/tools/" in sys.prefix or "\\uv\\tools\\" in sys.prefix def _detect_conda_variant() -> Installer: """Return MAMBA if the Mamba executable is configured, otherwise CONDA.""" return Installer.MAMBA if os.environ.get("MAMBA_EXE") else Installer.CONDA def _detect_conda_environment() -> Installer | None: """Detect whether the current environment is a Conda (or Mamba) environment. Checks in two ways: - CONDA_PREFIX env var being set (i.e. a conda env is activated) - sys.prefix path containing a known conda distribution directory name Returns the installer, or None if not a conda environment. """ conda_prefix = os.environ.get("CONDA_PREFIX", "") if conda_prefix and os.path.normcase(sys.prefix).startswith( os.path.normcase(conda_prefix) ): return _detect_conda_variant() prefix_lower = sys.prefix.lower() parts = prefix_lower.replace("\\", "/").split("/") conda_prefixes = ("conda", "miniconda", "miniforge", "mambaforge") if any(part.startswith(conda_prefixes) for part in parts): return _detect_conda_variant() return None def _is_brew_environment() -> bool: """Check whether the Python executable lives under a Homebrew prefix.""" exe = sys.executable.lower() return any( p in exe for p in ("/opt/homebrew/", "/usr/local/cellar/", "/home/linuxbrew/") ) def _get_installer_metadata(dist: Distribution) -> str | None: """Read the INSTALLER metadata file from a distribution, if present.""" if (value := dist.read_text("INSTALLER")) is None: return None value = value.strip().lower() return value if value else None def _has_uv_lock(max_depth: int = 3) -> bool: """Check whether a uv.lock file exists in a parent directory of sys.prefix.""" parent = Path(sys.prefix) for _ in range(max_depth): parent = parent.parent if (parent / "uv.lock").exists(): return True return False def _get_upgrade_cmd( installer: Installer, package_name: str, uv_upgrade_strategy: UvUpgradeStrategy = "add", ) -> str | None: """Return the shell command to upgrade a package for the given installer.""" if installer == Installer.UV: if uv_upgrade_strategy == "lock": return f"uv lock --upgrade-package {package_name}" return f"uv add {package_name} --upgrade-package {package_name}" commands: dict[Installer, str] = { Installer.PIP: f"pip install -U {package_name}", Installer.UV_PIP: f"uv pip install --upgrade {package_name}", Installer.UV_TOOL: f"uv tool upgrade {package_name}", Installer.PIPX: f"pipx upgrade {package_name}", Installer.BREW: f"brew upgrade {package_name}", Installer.CONDA: f"conda update {package_name}", Installer.MAMBA: f"mamba update {package_name}", } return commands.get(installer) def detect_installer( package_name: str, uv_upgrade_strategy: UvUpgradeStrategy = "add", ) -> InstallerInfo | None: """Detect which installer was used to install the given package. Returns None if the package is not installed. """ def _result(installer: Installer) -> InstallerInfo: return InstallerInfo( installer, _get_upgrade_cmd(installer, package_name, uv_upgrade_strategy), ) try: dist = distribution(package_name) except PackageNotFoundError: return None # Step 2: filesystem / environment checks if _is_pipx_environment(): return _result(Installer.PIPX) if _is_uv_tool_environment(): return _result(Installer.UV_TOOL) if conda_result := _detect_conda_environment(): return _result(conda_result) if _is_brew_environment(): return _result(Installer.BREW) # Step 3: INSTALLER metadata metadata_value = _get_installer_metadata(dist) if metadata_value == "uv": if _has_uv_lock(): return _result(Installer.UV) return _result(Installer.UV_PIP) if metadata_value == "pip": return _result(Installer.PIP) # Step 4: default return InstallerInfo(Installer.UNKNOWN, None)