multilingual-chatbot / setup /fix_install.py
momenalhamza's picture
Deploy chatbot: code + RAG + Qwen (3 BERT classifiers loaded from HF Hub)
469ef7f verified
"""Diagnose and repair the import failures left by setup/install.py.
Failures from the previous run:
1. seqeval -> build-time error (setuptools_scm 10.x bug: vcs_versioning)
2. gradio -> imports installed package but fails on import (likely starlette 1.0)
3. fastapi -> same root cause as gradio (starlette 1.0 broke compat)
4. wandb -> optional; protobuf 7 may be the cause
Strategy:
Step 1: print the FULL import traceback for each failing package so we know
exactly what is wrong (not just the exception class name).
Step 2: apply targeted fixes:
- seqeval: install setuptools_scm<10 first, then seqeval
- fastapi/gradio: pin starlette<1.0 + known-good versions
- wandb (optional): pin protobuf<6 + reinstall
Step 3: re-verify all four imports.
Step 4: rewrite requirements.txt with the now-working versions.
"""
from __future__ import annotations
import importlib
import importlib.metadata as md
import subprocess
import sys
import traceback
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent.parent
REQ_FILE = PROJECT_ROOT / "requirements.txt"
# Packages we know failed
FAILING = ["seqeval", "gradio", "fastapi", "wandb"]
PIP_BASE = [sys.executable, "-m", "pip", "install",
"--user", "--break-system-packages", "--upgrade"]
# ---------------------------------------------------------------------------
# Step 1: diagnose
# ---------------------------------------------------------------------------
def diagnose(name: str) -> tuple[bool, str]:
"""Return (import_ok, traceback_or_version)."""
# Force fresh import (in case a previous attempt cached a partial module)
sys.modules.pop(name, None)
try:
mod = importlib.import_module(name)
ver = getattr(mod, "__version__", "?")
try:
ver = md.version(name)
except Exception: # noqa: BLE001
pass
return True, ver
except Exception:
return False, traceback.format_exc()
def print_diagnosis() -> dict[str, tuple[bool, str]]:
"""Run diagnose() on each failing package and print the result."""
print("=" * 72)
print("STEP 1: Diagnose failing imports")
print("=" * 72)
results: dict[str, tuple[bool, str]] = {}
for name in FAILING:
ok, info = diagnose(name)
results[name] = (ok, info)
print(f"\n--- {name} ---")
if ok:
print(f" Already importable. Version: {info}")
else:
# Print only the last few lines of the traceback (the cause)
lines = info.strip().splitlines()
tail = lines[-12:] if len(lines) > 12 else lines
for ln in tail:
print(f" {ln}")
return results
# ---------------------------------------------------------------------------
# Step 2: fixes
# ---------------------------------------------------------------------------
def run_pip(args: list[str], label: str) -> bool:
"""Run a pip install command and stream its tail to stdout."""
cmd = PIP_BASE + args
print(f"\n>>> {label}")
print(f" {' '.join(cmd)}")
proc = subprocess.run(cmd, capture_output=True, text=True, check=False)
log = (proc.stdout or "") + (proc.stderr or "")
tail = "\n".join(log.strip().splitlines()[-15:])
print(tail)
return proc.returncode == 0
def fix_seqeval() -> bool:
"""seqeval fails to build because setuptools_scm 10.x removed an internal
module. Pin setuptools_scm<10 and reinstall.
"""
print("\n" + "=" * 72)
print("FIX: seqeval (pin setuptools_scm<10)")
print("=" * 72)
ok1 = run_pip(["setuptools_scm<10"], "downgrade setuptools_scm")
ok2 = run_pip(["--no-cache-dir", "seqeval==1.2.2"], "install seqeval")
return ok1 and ok2
def fix_starlette_stack() -> bool:
"""gradio 6.14 + fastapi 0.136 + starlette 1.0.0 ended up incompatible.
Reinstall a known-good stack: starlette<1.0, fastapi<0.115, gradio 4.44.1.
gradio 4.44.1 is the last well-tested 4.x; pinning it removes the bleeding
edge surface while keeping all features we use (Blocks, Chatbot, etc.).
"""
print("\n" + "=" * 72)
print("FIX: gradio + fastapi (pin to stable stack)")
print("=" * 72)
# First uninstall the bad versions to avoid resolver footguns
uninstall = [sys.executable, "-m", "pip", "uninstall", "-y",
"--break-system-packages",
"starlette", "fastapi", "gradio", "gradio-client",
"hf-gradio", "safehttpx"]
print(f"\n>>> uninstall conflicting packages")
print(f" {' '.join(uninstall)}")
proc = subprocess.run(uninstall, capture_output=True, text=True, check=False)
print("\n".join((proc.stdout or "").strip().splitlines()[-10:]))
# Then reinstall a coherent set
pkgs = [
"starlette<1.0",
"fastapi>=0.110,<0.115",
"gradio==4.44.1",
]
return run_pip(pkgs, "install pinned starlette/fastapi/gradio")
def fix_wandb() -> bool:
"""wandb 0.26 fails to import (likely protobuf 7 vs <5 mismatch).
Pin protobuf<6 and reinstall wandb. Optional — failure is non-blocking.
"""
print("\n" + "=" * 72)
print("FIX: wandb (optional — pin protobuf<6)")
print("=" * 72)
ok1 = run_pip(["protobuf<6"], "downgrade protobuf")
ok2 = run_pip(["wandb>=0.16,<0.20"], "install older wandb")
return ok1 and ok2
# ---------------------------------------------------------------------------
# Step 3: verify + rewrite requirements
# ---------------------------------------------------------------------------
def verify_all() -> dict[str, tuple[bool, str]]:
"""Re-run diagnose() after fixes."""
print("\n" + "=" * 72)
print("STEP 3: Re-verify imports")
print("=" * 72)
results: dict[str, tuple[bool, str]] = {}
for name in FAILING:
ok, info = diagnose(name)
results[name] = (ok, info)
if ok:
print(f" ✓ {name:<20s} {info}")
else:
print(f" ✗ {name:<20s} STILL FAILING")
return results
def update_requirements(post: dict[str, tuple[bool, str]]) -> None:
"""Append a 'fixes' section to requirements.txt with the now-working pins."""
if not REQ_FILE.exists():
print("\n(no requirements.txt to update)")
return
extra = ["", "# --- post-fix pins (added by setup/fix_install.py) ---"]
for name, (ok, info) in post.items():
if ok:
extra.append(f"{name}=={info}")
else:
extra.append(f"# {name} (STILL FAILING: {info.splitlines()[-1] if info else 'unknown'})")
REQ_FILE.write_text(REQ_FILE.read_text() + "\n".join(extra) + "\n")
print(f"\nrequirements.txt updated -> {REQ_FILE}")
def main() -> int:
"""Orchestrate diagnose -> fix -> verify."""
print("Multilingual Chatbot — Repair failed installs")
print(f"Python: {sys.version.split()[0]}\n")
pre = print_diagnosis()
# Apply fixes only for the things actually broken
if not pre["seqeval"][0]:
fix_seqeval()
if not pre["gradio"][0] or not pre["fastapi"][0]:
fix_starlette_stack()
if not pre["wandb"][0]:
fix_wandb()
post = verify_all()
update_requirements(post)
# Decide return code: only required ones (seqeval, gradio, fastapi) block
blocking = [n for n in ("seqeval", "gradio", "fastapi") if not post[n][0]]
if blocking:
print(f"\nStill broken (BLOCKING): {blocking}")
print("Paste the diagnostic output above so we can refine the fix.")
return 1
print("\nAll required packages now import cleanly. Safe to continue to Phase 2.")
if not post["wandb"][0]:
print("(wandb is still broken but is optional — we'll proceed without it.)")
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except KeyboardInterrupt:
print("\nAborted by user.")
sys.exit(130)