Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """Submission validator for AutoDataLab++. | |
| Two modes: | |
| Local mode (default): | |
| python validate_submission.py | |
| Runs file-presence checks, py_compile, ground-truth rebuild, | |
| `inference.py --oracle`, in-process FastAPI smoke, and openenv | |
| validate. Used pre-deploy. | |
| Live mode (--base-url): | |
| python validate_submission.py --base-url https://your-space.hf.space | |
| Hits the deployed HTTP endpoints (/, /health, /reset, /step, /state) | |
| to confirm the live deployment responds correctly. Used post-deploy. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import shutil | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| ROOT = Path(__file__).resolve().parent | |
| REQUIRED = [ | |
| ROOT / "openenv.yaml", | |
| ROOT / "pyproject.toml", | |
| ROOT / "server" / "__init__.py", | |
| ROOT / "server" / "app.py", | |
| ROOT / "uv.lock", | |
| ROOT / "inference.py", | |
| ] | |
| def run(cmd: list[str]) -> int: | |
| print("$", " ".join(cmd), flush=True) | |
| return subprocess.call(cmd, cwd=ROOT) | |
| def check_fastapi_routes_local() -> int: | |
| """Run smoke routes via in-process TestClient. Used in local mode.""" | |
| from fastapi.testclient import TestClient | |
| from server.app import app | |
| client = TestClient(app) | |
| checks = [ | |
| ("GET /", client.get("/")), | |
| ("GET /health", client.get("/health")), | |
| ] | |
| reset = client.post("/reset", json={"task": "easy_brief"}) | |
| checks.append(("POST /reset", reset)) | |
| if reset.status_code == 200: | |
| episode_id = reset.json()["episode_id"] | |
| checks.append(( | |
| "POST /step", | |
| client.post( | |
| "/step", | |
| json={ | |
| "episode_id": episode_id, | |
| "action": {"action_type": "consult", "expert_id": "analyst"}, | |
| }, | |
| ), | |
| )) | |
| checks.append(( | |
| "GET /state", | |
| client.get("/state", params={"episode_id": episode_id}), | |
| )) | |
| for label, response in checks: | |
| if response.status_code != 200: | |
| print(f"FAIL: {label} returned {response.status_code}", file=sys.stderr) | |
| return 1 | |
| print("ok: FastAPI smoke routes returned 200", flush=True) | |
| return 0 | |
| def check_fastapi_routes_live(base_url: str, timeout: float = 30.0) -> int: | |
| """Run smoke routes via real HTTP against a deployed base URL.""" | |
| import urllib.error | |
| import urllib.request | |
| import json as _json | |
| base = base_url.rstrip("/") | |
| def _get(path: str): | |
| req = urllib.request.Request(base + path, method="GET") | |
| return urllib.request.urlopen(req, timeout=timeout) | |
| def _post(path: str, payload: dict): | |
| body = _json.dumps(payload).encode("utf-8") | |
| req = urllib.request.Request( | |
| base + path, | |
| data=body, | |
| method="POST", | |
| headers={"Content-Type": "application/json"}, | |
| ) | |
| return urllib.request.urlopen(req, timeout=timeout) | |
| try: | |
| # GET / | |
| with _get("/") as r: | |
| if r.status != 200: | |
| print(f"FAIL: GET / returned {r.status}", file=sys.stderr) | |
| return 1 | |
| print("ok: GET / -> 200", flush=True) | |
| # GET /health | |
| with _get("/health") as r: | |
| if r.status != 200: | |
| print(f"FAIL: GET /health returned {r.status}", file=sys.stderr) | |
| return 1 | |
| print("ok: GET /health -> 200", flush=True) | |
| # POST /reset | |
| with _post("/reset", {"task": "easy_brief"}) as r: | |
| if r.status != 200: | |
| print(f"FAIL: POST /reset returned {r.status}", file=sys.stderr) | |
| return 1 | |
| reset_payload = _json.loads(r.read().decode("utf-8")) | |
| print("ok: POST /reset -> 200", flush=True) | |
| episode_id = reset_payload.get("episode_id") | |
| if not episode_id: | |
| print("FAIL: /reset response missing episode_id", file=sys.stderr) | |
| return 1 | |
| # POST /step | |
| with _post( | |
| "/step", | |
| { | |
| "episode_id": episode_id, | |
| "action": {"action_type": "consult", "expert_id": "analyst"}, | |
| }, | |
| ) as r: | |
| if r.status != 200: | |
| print(f"FAIL: POST /step returned {r.status}", file=sys.stderr) | |
| return 1 | |
| print("ok: POST /step -> 200", flush=True) | |
| # GET /state?episode_id=... | |
| with _get(f"/state?episode_id={episode_id}") as r: | |
| if r.status != 200: | |
| print(f"FAIL: GET /state returned {r.status}", file=sys.stderr) | |
| return 1 | |
| print("ok: GET /state -> 200", flush=True) | |
| print("ok: live HTTP smoke routes returned 200", flush=True) | |
| return 0 | |
| except urllib.error.HTTPError as e: | |
| print(f"FAIL: HTTPError {e.code} for {e.url}", file=sys.stderr) | |
| return 1 | |
| except urllib.error.URLError as e: | |
| print(f"FAIL: URLError {e.reason} (network or DNS)", file=sys.stderr) | |
| return 1 | |
| except Exception as e: | |
| print(f"FAIL: live HTTP smoke crashed: {e}", file=sys.stderr) | |
| return 1 | |
| def main() -> int: | |
| parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) | |
| parser.add_argument( | |
| "--base-url", | |
| default=None, | |
| help="If set, run live HTTP smoke checks against this URL instead of local checks.", | |
| ) | |
| args = parser.parse_args() | |
| # ----- LIVE MODE: only HTTP smoke against the deployed URL ----- | |
| if args.base_url: | |
| print(f"validate_submission: live mode against {args.base_url}", flush=True) | |
| return check_fastapi_routes_live(args.base_url) | |
| # ----- LOCAL MODE: full suite ----- | |
| for path in REQUIRED: | |
| if not path.exists(): | |
| print(f"missing required file: {path}", file=sys.stderr) | |
| return 1 | |
| code = run([sys.executable, "-m", "py_compile", str(ROOT / "inference.py")]) | |
| if code != 0: | |
| return code | |
| code = run([sys.executable, str(ROOT / "ceo_brief_env" / "tasks" / "_build_ground_truth.py")]) | |
| if code != 0: | |
| return code | |
| code = run([sys.executable, str(ROOT / "inference.py"), "--oracle"]) | |
| if code != 0: | |
| return code | |
| code = check_fastapi_routes_local() | |
| if code != 0: | |
| return code | |
| openenv = shutil.which("openenv") | |
| if openenv: | |
| code = run([openenv, "validate", "--verbose"]) | |
| if code != 0: | |
| return code | |
| print("validate_submission: local checks passed.", flush=True) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) |