""" check_install_packages.py Simple script that hardcodes the project's required packages and ensures they are installed before running the server or tests. It will install missing packages (or upgrade if below required version) by invoking pip via the running Python interpreter. Usage: python check_install_packages.py This script is intentionally conservative: it uses the distribution/package names from `requirements.txt` and calls pip to install the exact spec (e.g. "requests>=2.31.0"). It prints a concise summary at the end. """ from __future__ import annotations import sys import subprocess import importlib import importlib.metadata import re from typing import List, Tuple, Optional # Hardcoded package requirements (from requirements.txt) REQUIRED_PACKAGES: List[str] = [ "fastmcp>=0.1.0", "requests>=2.31.0", "pandas>=2.0.0", "selenium>=4.15.0", "beautifulsoup4>=4.12.0", "pydantic>=2.0.0", "email-validator>=2.0.0", "aiohttp>=3.8.0", "webdriver-manager>=4.0.0", "playwright", "openai" ] def parse_spec(spec: str) -> Tuple[str, Optional[str]]: """Return (name, min_version) for a spec like 'pkg>=1.2.3'.""" if ">=" in spec: name, ver = spec.split(">=", 1) return name.strip(), ver.strip() return spec.strip(), None def version_tuple(v: str) -> List[int]: parts = re.split(r"[^0-9]+", v) nums = [] for p in parts: if p == "": continue try: nums.append(int(p)) except ValueError: # Non-numeric part, stop parsing further break return nums def is_version_sufficient(installed: str, required: str) -> bool: if not installed: return False try: i_parts = version_tuple(installed) r_parts = version_tuple(required) # Compare element-wise for a, b in zip(i_parts, r_parts): if a > b: return True if a < b: return False # If equal up to the length of required, installed is sufficient if it's at least as long return len(i_parts) >= len(r_parts) except Exception: return False def install_package(spec: str) -> Tuple[bool, str]: """Install a package spec using pip. Streams output live and returns (success, output).""" # Use -v for verbose pip output; capture stdout/stderr while streaming to console cmd = [sys.executable, "-m", "pip", "install", "-v", spec] try: proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) output_lines = [] if proc.stdout: for line in proc.stdout: output_lines.append(line) # print each pip line as it arrives for visibility try: print(line.rstrip()) except Exception: # fallback: print raw line print(line) proc.wait() out = "".join(output_lines) success = proc.returncode == 0 return success, out except Exception as e: return False, str(e) def main(): results = [] print("Checking required packages...\n") for spec in REQUIRED_PACKAGES: name, min_ver = parse_spec(spec) installed_ver = None try: installed_ver = importlib.metadata.version(name) except importlib.metadata.PackageNotFoundError: installed_ver = None except Exception: # Some packages may have different distribution vs import names; try best-effort installed_ver = None needs_install = False action = "present" if installed_ver is None: needs_install = True action = "install" elif min_ver is not None: if not is_version_sufficient(installed_ver, min_ver): needs_install = True action = f"upgrade (installed {installed_ver} < required {min_ver})" if needs_install: print(f"-> {name}: {action} via pip ({spec})") success, output = install_package(spec) # Attempt to read installed version after install installed_after = None if success: try: installed_after = importlib.metadata.version(name) except Exception: installed_after = None if installed_after: print(f" -> {name} now installed as: {installed_after}") results.append({ "name": name, "spec": spec, "installed_version_before": installed_ver, "installed_version_after": installed_after, "action": action, "success": success, "output": output }) else: print(f"-> {name}: OK (installed: {installed_ver})") results.append({ "name": name, "spec": spec, "installed_version_before": installed_ver, "action": "none", "success": True, "output": "" }) # Summary print("\nSummary:") installed_count = sum(1 for r in results if r["success"] and r["action"] == "none") installed_or_fixed = sum(1 for r in results if r["success"] and r["action"] != "none") failed = [r for r in results if not r["success"]] print(f" Packages already OK: {installed_count}") print(f" Packages installed/upgraded by this script: {installed_or_fixed}") print(f" Packages failed to install: {len(failed)}") if failed: for f in failed: print(f" - {f['name']}: attempted '{f['spec']}' -> error (see details below)") # If any failed, print their outputs for debugging if failed: print("\nFailed install details:\n") for f in failed: print("-----") print(f"Package: {f['name']}") print(f"Spec: {f['spec']}") print(f"Installed before: {f.get('installed_version_before')}") print(f"Installed after: {f.get('installed_version_after')}") print("Output:") print(f.get("output", "(no output)")) print("\nDone.") # Exit with non-zero code if any install failed if failed: sys.exit(2) if __name__ == "__main__": main()