"""Sandbox 临时 Python 包扫描与 best-effort 持久化。""" from __future__ import annotations import json import subprocess from datetime import datetime, timezone from typing import Optional from uuid import UUID def scan_container(container: str) -> Optional[dict]: try: scan = subprocess.run(["docker", "exec", "--user", "zcbot", "--workdir", "/sandbox", container, "python", "-I", "-S", "/sandbox/package_scan.py"], capture_output=True, text=True, timeout=30) if scan.returncode != 0: raise RuntimeError(scan.stderr.strip()) packages = json.loads(scan.stdout) if not isinstance(packages, list): raise ValueError("scanner output is not a package list") inspect = subprocess.run(["docker", "inspect", "--format={{.Image}}", container], capture_output=True, text=True, timeout=15) py = subprocess.run(["docker", "exec", "--workdir", "/sandbox", container, "python", "-I", "-S", "-c", "import sys;print('.'.join(map(str,sys.version_info[:3])))"], capture_output=True, text=True, timeout=15) if inspect.returncode != 0 or py.returncode != 0: raise RuntimeError("failed to inspect image or Python version") return {"packages": packages, "image_digest": inspect.stdout.strip(), "python_version": py.stdout.strip(), "total_installed_bytes": sum(int(x.get("installed_bytes") or 0) for x in packages)} except Exception as exc: print(f"[sandbox-package-scan] failed container={container}: {type(exc).__name__}: {exc}") return None def persist_scan(*, container_session_id: str, user_id: UUID, execution_kind: str, result: Optional[dict]) -> None: if not result or not result.get("packages"): return try: from core.storage import session_scope from core.storage.models import SandboxPackageScan with session_scope() as s: s.merge(SandboxPackageScan( container_session_id=container_session_id, user_id=user_id, execution_kind=execution_kind, image_digest=result["image_digest"], python_version=result["python_version"], packages=result["packages"], package_count=len(result["packages"]), total_installed_bytes=result["total_installed_bytes"], finished_at=datetime.now(timezone.utc), )) except Exception as exc: print(f"[sandbox-package-scan] persist failed session={container_session_id}: {type(exc).__name__}: {exc}") def scan_and_persist(container: str, user_id: UUID, kind: str, session_id: str) -> None: persist_scan(container_session_id=session_id, user_id=user_id, execution_kind=kind, result=scan_container(container))