You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

190 lines
6.5 KiB

"""
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"
]
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()