from __future__ import annotations import importlib import os import subprocess import sys from typing import Any, Dict, Iterable, List, Optional, Tuple from SYS.logger import log from SYS.rich_display import stdout_console def _as_bool(value: Any, default: bool = False) -> bool: if value is None: return default if isinstance(value, bool): return value s = str(value).strip().lower() if s in {"1", "true", "yes", "on"}: return True if s in {"0", "false", "no", "off"}: return False return default def _is_pytest() -> bool: return bool(os.environ.get("PYTEST_CURRENT_TEST")) def _try_import(module: str) -> bool: try: importlib.import_module(module) return True except Exception: return False def florencevision_missing_modules() -> List[str]: missing: List[str] = [] # pillow is already in requirements, but keep the check for robustness. if not _try_import("transformers"): missing.append("transformers") if not _try_import("torch"): missing.append("torch") if not _try_import("PIL"): missing.append("pillow") # Florence-2 remote code frequently requires these extras. if not _try_import("einops"): missing.append("einops") if not _try_import("timm"): missing.append("timm") return missing def _pip_install(requirements: List[str]) -> Tuple[bool, str]: if not requirements: return True, "No requirements" cmd = [sys.executable, "-m", "pip", "install", "--upgrade", *requirements] try: proc = subprocess.run( cmd, check=False, capture_output=True, text=True, ) if proc.returncode == 0: importlib.invalidate_caches() return True, proc.stdout.strip() or "Installed" out = (proc.stdout or "") + "\n" + (proc.stderr or "") return False, out.strip() or f"pip exited with code {proc.returncode}" except Exception as exc: return False, str(exc) def maybe_auto_install_configured_tools(config: Dict[str, Any]) -> None: """Best-effort dependency auto-installer for configured tools. This is intentionally conservative: - Only acts when a tool block is enabled. - Skips under pytest. Current supported tool(s): florencevision """ if _is_pytest(): return tool_cfg = (config or {}).get("tool") if not isinstance(tool_cfg, dict): return fv = tool_cfg.get("florencevision") if isinstance(fv, dict) and _as_bool(fv.get("enabled"), False): auto_install = _as_bool(fv.get("auto_install"), True) if not auto_install: return missing = florencevision_missing_modules() if not missing: return names = ", ".join(missing) try: with stdout_console().status( f"Installing FlorenceVision dependencies: {names}", spinner="dots", ): ok, detail = _pip_install(missing) except Exception: log(f"[startup] FlorenceVision dependencies missing ({names}). Attempting auto-install...") ok, detail = _pip_install(missing) if ok: log("[startup] FlorenceVision dependency install OK") else: log(f"[startup] FlorenceVision dependency auto-install failed. {detail}") __all__ = ["maybe_auto_install_configured_tools", "florencevision_missing_modules"]