import subprocess import sys from pathlib import Path class DependencyManager: def __init__(self, requirements_path: Path) -> None: self.requirements_path = requirements_path @staticmethod def _normalize_requirement_name(requirement_line: str) -> str: line = requirement_line.split(";", 1)[0].strip() for sep in ["==", ">=", "<=", "!=", "~=", ">", "<"]: if sep in line: line = line.split(sep, 1)[0].strip() break if "[" in line: line = line.split("[", 1)[0].strip() return line def parse_requirements(self) -> list[str]: if not self.requirements_path.exists(): return [] requirements: list[str] = [] for raw in self.requirements_path.read_text(encoding="utf-8").splitlines(): line = raw.strip() if not line or line.startswith("#"): continue requirements.append(line) return requirements def _is_installed(self, requirement: str) -> bool: package_name = self._normalize_requirement_name(requirement) if not package_name: return True result = subprocess.run( [sys.executable, "-m", "pip", "show", package_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False, ) return result.returncode == 0 def install_missing(self) -> list[str]: requirements = self.parse_requirements() missing = [req for req in requirements if not self._is_installed(req)] if missing: subprocess.check_call([sys.executable, "-m", "pip", "install", *missing]) return missing