"""Post-apply verification: wheelhouse, offline deps, smoke import.""" from __future__ import annotations import hashlib import os import subprocess import sys from pathlib import Path from typing import List, Optional, Tuple from install_deps import UpdateDepsError, install_requirements, missing_wheelhouse_packages def requirements_changed(old_path: Path, new_path: Path) -> bool: if not old_path.is_file() or not new_path.is_file(): return True return hashlib.sha256(old_path.read_bytes()).digest() != hashlib.sha256(new_path.read_bytes()).digest() def wheelhouse_covers_requirements(req_path: Path, wheels_dir: Path) -> Tuple[bool, List[str]]: missing = missing_wheelhouse_packages(req_path, wheels_dir) return (len(missing) == 0, missing) def install_requirements_from_package(root: str | Path) -> None: os.environ["WESP_OTA_UPDATE"] = "1" try: install_requirements(root, offline_only=True) except UpdateDepsError: raise finally: os.environ.pop("WESP_OTA_UPDATE", None) def run_smoke_test(root: str | Path, python_exe: Optional[str] = None, timeout: int = 30) -> Tuple[bool, str]: exe = python_exe or sys.executable base = Path(root).resolve() env = os.environ.copy() env["PYTHONPATH"] = str(base) cmd = [ exe, "-c", "import sys; sys.path.insert(0, %r); from wsgi import app; assert app is not None" % str(base), ] try: proc = subprocess.run( cmd, cwd=str(base), env=env, capture_output=True, text=True, timeout=timeout, ) except subprocess.TimeoutExpired: return False, "Smoke-test: timeout" if proc.returncode != 0: err = (proc.stderr or proc.stdout or "").strip()[:300] return False, err or f"Smoke-test exit {proc.returncode}" return True, "OK"