Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python | |
| """ | |
| Pre-submission validation script for HF Spaces deployment. | |
| Checks: | |
| ✓ Environment variables are set | |
| ✓ Required files exist | |
| ✓ inference.py format compliance | |
| ✓ Docker build (optional) | |
| ✓ Inference script execution | |
| ✓ stdout format compliance | |
| Run: | |
| python validate.py | |
| python validate.py --skip-inference # Skip inference execution | |
| python validate.py --docker # Also validate Docker build | |
| """ | |
| import os | |
| import sys | |
| import subprocess | |
| import json | |
| import re | |
| from pathlib import Path | |
| from typing import List, Tuple | |
| class Colors: | |
| GREEN = "\033[92m" | |
| RED = "\033[91m" | |
| YELLOW = "\033[93m" | |
| BLUE = "\033[94m" | |
| RESET = "\033[0m" | |
| def print_check(name: str, passed: bool, msg: str = ""): | |
| status = f"{Colors.GREEN}✓{Colors.RESET}" if passed else f"{Colors.RED}✗{Colors.RESET}" | |
| print(f" {status} {name}") | |
| if msg: | |
| print(f" {msg}") | |
| def check_environment_variables() -> bool: | |
| """Check mandatory environment variables""" | |
| print(f"\n{Colors.BLUE}1. Environment Variables{Colors.RESET}") | |
| required_vars = { | |
| "API_BASE_URL": "LLM API endpoint", | |
| "MODEL_NAME": "Model identifier", | |
| "HF_TOKEN": "Hugging Face token", | |
| } | |
| all_ok = True | |
| for var, desc in required_vars.items(): | |
| value = os.getenv(var) | |
| passed = bool(value) | |
| print_check(f"{var}", passed, desc if not passed else f"Set: {value[:50]}...") | |
| all_ok = all_ok and passed | |
| return all_ok | |
| def check_required_files() -> bool: | |
| """Check required project files""" | |
| print(f"\n{Colors.BLUE}2. Required Files{Colors.RESET}") | |
| required_files = [ | |
| ("inference.py", "Inference script"), | |
| ("requirements.txt", "Python dependencies"), | |
| ("openenv.yaml", "OpenEnv specification"), | |
| ("Dockerfile", "Container definition"), | |
| (".env.example", "Environment template"), | |
| ] | |
| all_ok = True | |
| for filename, desc in required_files: | |
| passed = Path(filename).exists() | |
| print_check(filename, passed, desc) | |
| all_ok = all_ok and passed | |
| return all_ok | |
| def check_inference_compliance() -> bool: | |
| """Check inference.py OpenEnv compliance""" | |
| print(f"\n{Colors.BLUE}3. Inference Script Compliance{Colors.RESET}") | |
| checks = [] | |
| # Read inference.py | |
| try: | |
| with open("inference.py") as f: | |
| content = f.read() | |
| # Check 1: Imports | |
| has_openai = "from openai import OpenAI" in content or "import openai" in content | |
| checks.append(("OpenAI imports", has_openai)) | |
| # Check 2: env variables | |
| has_api_base = "API_BASE_URL" in content | |
| checks.append(("API_BASE_URL config", has_api_base)) | |
| has_model = "MODEL_NAME" in content | |
| checks.append(("MODEL_NAME config", has_model)) | |
| has_hf_token = "HF_TOKEN" in content | |
| checks.append(("HF_TOKEN config", has_hf_token)) | |
| # Check 3: Logging format | |
| has_start_log = '[START]' in content and 'task=' in content | |
| checks.append(("[START] logging", has_start_log)) | |
| has_step_log = '[STEP]' in content and 'step=' in content | |
| checks.append(("[STEP] logging", has_step_log)) | |
| has_end_log = '[END]' in content and 'success=' in content | |
| checks.append(("[END] logging", has_end_log)) | |
| # Check 4: Main function | |
| has_main = "if __name__" in content and "main()" in content | |
| checks.append(("Main entry point", has_main)) | |
| # Check 5: Async support | |
| has_async = "asyncio" in content or "async def" in content | |
| checks.append(("Async support", has_async)) | |
| except Exception as e: | |
| print_check("Read inference.py", False, str(e)) | |
| return False | |
| all_ok = True | |
| for check_name, passed in checks: | |
| print_check(check_name, passed) | |
| all_ok = all_ok and passed | |
| return all_ok | |
| def check_requirements() -> bool: | |
| """Check requirements.txt""" | |
| print(f"\n{Colors.BLUE}4. Python Dependencies{Colors.RESET}") | |
| required_packages = [ | |
| "openai", | |
| "numpy", | |
| "opencv-python", | |
| ] | |
| try: | |
| with open("requirements.txt") as f: | |
| content = f.read().lower() | |
| all_ok = True | |
| for pkg in required_packages: | |
| found = pkg.lower() in content | |
| print_check(pkg, found) | |
| all_ok = all_ok and found | |
| return all_ok | |
| except Exception as e: | |
| print_check("Read requirements.txt", False, str(e)) | |
| return False | |
| def check_openenv_yaml() -> bool: | |
| """Check openenv.yaml structure""" | |
| print(f"\n{Colors.BLUE}5. OpenEnv YAML Specification{Colors.RESET}") | |
| checks = [] | |
| try: | |
| with open("openenv.yaml") as f: | |
| content = f.read() | |
| # Check basic structure | |
| has_name = "name:" in content | |
| checks.append(("Name field", has_name)) | |
| has_endpoints = "endpoints:" in content | |
| checks.append(("Endpoints section", has_endpoints)) | |
| has_reset = "reset:" in content | |
| checks.append(("Reset endpoint", has_reset)) | |
| has_step = "step:" in content | |
| checks.append(("Step endpoint", has_step)) | |
| has_tasks = "tasks:" in content | |
| checks.append(("Tasks section", has_tasks)) | |
| has_env_vars = "environment_variables:" in content | |
| checks.append(("Environment variables", has_env_vars)) | |
| has_validation = "validation:" in content | |
| checks.append(("Validation rules", has_validation)) | |
| except Exception as e: | |
| print_check("Read openenv.yaml", False, str(e)) | |
| return False | |
| all_ok = True | |
| for check_name, passed in checks: | |
| print_check(check_name, passed) | |
| all_ok = all_ok and passed | |
| return all_ok | |
| def validate_stdout_format(output: str) -> Tuple[bool, List[str]]: | |
| """Validate stdout format compliance""" | |
| errors = [] | |
| lines = output.strip().split("\n") | |
| # Must have START, STEP(s), and END | |
| has_start = any(line.startswith("[START]") for line in lines) | |
| has_step = any(line.startswith("[STEP]") for line in lines) | |
| has_end = any(line.startswith("[END]") for line in lines) | |
| if not has_start: | |
| errors.append("Missing [START] line") | |
| if not has_step: | |
| errors.append("Missing [STEP] lines") | |
| if not has_end: | |
| errors.append("Missing [END] line") | |
| # Validate line formats | |
| for line in lines: | |
| if line.startswith("[START]"): | |
| if not re.search(r"task=\w+", line): | |
| errors.append(f"[START] missing task field: {line}") | |
| if not re.search(r"env=\w+", line): | |
| errors.append(f"[START] missing env field: {line}") | |
| if not re.search(r"model=", line): | |
| errors.append(f"[START] missing model field: {line}") | |
| elif line.startswith("[STEP]"): | |
| if not re.search(r"step=\d+", line): | |
| errors.append(f"[STEP] missing step field: {line}") | |
| if not re.search(r"reward=[\d.]+", line): | |
| errors.append(f"[STEP] missing reward field: {line}") | |
| if not re.search(r"done=(true|false)", line): | |
| errors.append(f"[STEP] missing done field: {line}") | |
| elif line.startswith("[END]"): | |
| if not re.search(r"success=(true|false)", line): | |
| errors.append(f"[END] missing success field: {line}") | |
| if not re.search(r"steps=\d+", line): | |
| errors.append(f"[END] missing steps field: {line}") | |
| if not re.search(r"score=[\d.]+", line): | |
| errors.append(f"[END] missing score field: {line}") | |
| return len(errors) == 0, errors | |
| def check_docker_build() -> bool: | |
| """Check if Dockerfile builds""" | |
| print(f"\n{Colors.BLUE}6. Docker Build{Colors.RESET}") | |
| if not Path("Dockerfile").exists(): | |
| print_check("Dockerfile exists", False) | |
| return False | |
| try: | |
| print(" Building Docker image (this may take a few minutes)...") | |
| result = subprocess.run( | |
| ["docker", "build", "-t", "autonomous-traffic-control:test", "."], | |
| capture_output=True, | |
| text=True, | |
| timeout=600, | |
| ) | |
| passed = result.returncode == 0 | |
| print_check("Docker build", passed) | |
| if not passed: | |
| print(f" Error: {result.stderr[:200]}") | |
| return passed | |
| except FileNotFoundError: | |
| print(f" {Colors.YELLOW}⚠ Docker not found - skipping{Colors.RESET}") | |
| return True | |
| except Exception as e: | |
| print_check("Docker build", False, str(e)) | |
| return False | |
| def check_inference_execution() -> bool: | |
| """Run inference and check output format""" | |
| print(f"\n{Colors.BLUE}7. Inference Execution{Colors.RESET}") | |
| if not Path("inference.py").exists(): | |
| print_check("inference.py", False) | |
| return False | |
| try: | |
| print(" Running inference script (timeout: 30s)...") | |
| env = os.environ.copy() | |
| if not env.get("API_BASE_URL"): | |
| print(f" {Colors.YELLOW}⚠ API_BASE_URL not set - using default{Colors.RESET}") | |
| if not env.get("HF_TOKEN"): | |
| print(f" {Colors.YELLOW}⚠ HF_TOKEN not set - validation will fail{Colors.RESET}") | |
| result = subprocess.run( | |
| ["python", "inference.py"], | |
| capture_output=True, | |
| text=True, | |
| timeout=30, | |
| env=env, | |
| ) | |
| output = result.stdout + result.stderr | |
| # Check execution | |
| execution_ok = result.returncode == 0 | |
| print_check("Script execution", execution_ok) | |
| # Check stdout format | |
| format_ok, errors = validate_stdout_format(output) | |
| print_check("Stdout format", format_ok) | |
| if errors: | |
| for error in errors[:3]: | |
| print(f" {Colors.RED}Error: {error}{Colors.RESET}") | |
| # Show sample output | |
| if "[START]" in output: | |
| start_line = [l for l in output.split("\n") if "[START]" in l][0] | |
| print(f" Sample: {start_line[:80]}...") | |
| return execution_ok and format_ok | |
| except subprocess.TimeoutExpired: | |
| print_check("Script execution", False, "Timeout (>30s)") | |
| return False | |
| except Exception as e: | |
| print_check("Script execution", False, str(e)) | |
| return False | |
| def main(): | |
| """Run all validation checks""" | |
| import argparse | |
| parser = argparse.ArgumentParser( | |
| description="Validate autonomous-traffic-control for HF Spaces deployment" | |
| ) | |
| parser.add_argument( | |
| "--skip-inference", | |
| action="store_true", | |
| help="Skip inference execution check", | |
| ) | |
| parser.add_argument( | |
| "--docker", | |
| action="store_true", | |
| help="Also validate Docker build", | |
| ) | |
| args = parser.parse_args() | |
| print(f"\n{Colors.BLUE}{'='*60}") | |
| print("Autonomous Traffic Control - Pre-submission Validation") | |
| print(f"{'='*60}{Colors.RESET}\n") | |
| results = { | |
| "Environment Variables": check_environment_variables(), | |
| "Required Files": check_required_files(), | |
| "Inference Compliance": check_inference_compliance(), | |
| "Python Dependencies": check_requirements(), | |
| "OpenEnv YAML": check_openenv_yaml(), | |
| } | |
| if args.docker: | |
| results["Docker Build"] = check_docker_build() | |
| if not args.skip_inference: | |
| results["Inference Execution"] = check_inference_execution() | |
| # Summary | |
| print(f"\n{Colors.BLUE}{'='*60}") | |
| print("Validation Summary") | |
| print(f"{'='*60}{Colors.RESET}\n") | |
| for check_name, passed in results.items(): | |
| status = f"{Colors.GREEN}PASS{Colors.RESET}" if passed else f"{Colors.RED}FAIL{Colors.RESET}" | |
| print(f" {check_name:<30} {status}") | |
| all_passed = all(results.values()) | |
| print(f"\n{Colors.BLUE}{'='*60}{Colors.RESET}\n") | |
| if all_passed: | |
| print(f"{Colors.GREEN}✓ All checks passed!{Colors.RESET}") | |
| print(" Your project is ready for HF Spaces submission.") | |
| return 0 | |
| else: | |
| print(f"{Colors.RED}✗ Some checks failed.{Colors.RESET}") | |
| print(" Please fix the issues above before submitting.") | |
| return 1 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |