| |
| """ |
| Pre-Deployment Validation Script |
| Run this before deploying to HuggingFace Spaces to ensure everything is ready. |
| """ |
|
|
| import os |
| import sys |
| import yaml |
| import json |
| from pathlib import Path |
|
|
| def check_file_exists(filepath, description): |
| """Check if a required file exists.""" |
| if Path(filepath).exists(): |
| print(f" ✓ {description}: {filepath}") |
| return True |
| else: |
| print(f" ✗ {description} MISSING: {filepath}") |
| return False |
|
|
| def validate_openenv_yaml(): |
| """Validate openenv.yaml structure.""" |
| print("\n" + "="*60) |
| print("Validating openenv.yaml") |
| print("="*60) |
| |
| if not Path("openenv.yaml").exists(): |
| print(" ✗ openenv.yaml not found!") |
| return False |
| |
| with open("openenv.yaml", "r") as f: |
| config = yaml.safe_load(f) |
| |
| required = ["spec_version", "name", "type", "runtime", "app", "port", "tasks", "action_schema", "observation_schema"] |
| all_ok = True |
| |
| for field in required: |
| if field in config: |
| print(f" ✓ {field}: present") |
| else: |
| print(f" ✗ {field}: MISSING") |
| all_ok = False |
| |
| |
| if "tasks" in config: |
| task_count = len(config["tasks"]) |
| if task_count >= 3: |
| print(f" ✓ Tasks: {task_count} defined (>= 3 required)") |
| else: |
| print(f" ✗ Tasks: only {task_count} defined (need >= 3)") |
| all_ok = False |
| |
| return all_ok |
|
|
| def validate_dockerfile(): |
| """Validate Dockerfile.""" |
| print("\n" + "="*60) |
| print("Validating Dockerfile") |
| print("="*60) |
| |
| if not Path("Dockerfile").exists(): |
| print(" ✗ Dockerfile not found!") |
| return False |
| |
| with open("Dockerfile", "r") as f: |
| content = f.read() |
| |
| checks = [ |
| ("EXPOSE 7860", "Port 7860 exposed"), |
| ("HEALTHCHECK", "Health check configured"), |
| ("CMD", "CMD instruction present"), |
| ("uvicorn", "Uvicorn server command"), |
| ] |
| |
| all_ok = True |
| for pattern, description in checks: |
| if pattern in content: |
| print(f" ✓ {description}") |
| else: |
| print(f" ✗ {description} MISSING") |
| all_ok = False |
| |
| return all_ok |
|
|
| def validate_project_structure(): |
| """Validate project structure.""" |
| print("\n" + "="*60) |
| print("Validating Project Structure") |
| print("="*60) |
| |
| required_files = [ |
| ("pyproject.toml", "Project configuration"), |
| ("README.md", "Documentation"), |
| ("inference.py", "Inference script"), |
| ("models.py", "Data models"), |
| ("client.py", "Client implementation"), |
| ("server/app.py", "FastAPI application"), |
| ("server/my_env_environment.py", "Environment implementation"), |
| ("server/tasks.py", "Task definitions"), |
| ("graders/__init__.py", "Graders module"), |
| ] |
| |
| all_ok = True |
| for filepath, description in required_files: |
| if not check_file_exists(filepath, description): |
| all_ok = False |
| |
| return all_ok |
|
|
| def validate_environment_interface(): |
| """Validate environment implements required interface.""" |
| print("\n" + "="*60) |
| print("Validating Environment Interface") |
| print("="*60) |
| |
| try: |
| from server.my_env_environment import QuantumCircuitEnvironment |
| from models import QuantumAction, QuantumObservation, ActionType, GateType |
| |
| env = QuantumCircuitEnvironment(seed=42) |
| |
| |
| obs = env.reset(config={"task_id": "easy"}) |
| assert isinstance(obs, QuantumObservation), "reset() must return QuantumObservation" |
| print(" ✓ reset(config) returns QuantumObservation") |
| |
| |
| action = QuantumAction(action_type=ActionType.ADD, gate=GateType.H, qubits=[0]) |
| obs = env.step(action) |
| assert isinstance(obs, QuantumObservation), "step() must return QuantumObservation" |
| assert hasattr(obs, 'reward'), "Observation must have reward" |
| assert hasattr(obs, 'done'), "Observation must have done" |
| print(" ✓ step(action) returns QuantumObservation with reward and done") |
| |
| |
| state = env.state |
| assert state is not None, "state property must return State" |
| print(" ✓ state property returns State") |
| |
| |
| assert 0.0 <= obs.score <= 1.0, f"Score out of range: {obs.score}" |
| print(f" ✓ Score in valid range: {obs.score:.4f}") |
| |
| return True |
| |
| except Exception as e: |
| print(f" ✗ Environment validation failed: {e}") |
| return False |
|
|
| def validate_tasks(): |
| """Validate all tasks are accessible.""" |
| print("\n" + "="*60) |
| print("Validating Tasks") |
| print("="*60) |
| |
| try: |
| from server.my_env_environment import QuantumCircuitEnvironment |
| |
| env = QuantumCircuitEnvironment(seed=42) |
| tasks = ["easy", "medium", "hard", "efficient", "noisy", "budget", "approx"] |
| |
| for task_id in tasks: |
| obs = env.reset(config={"task_id": task_id}) |
| assert obs.task_id == task_id, f"Task ID mismatch: {obs.task_id} != {task_id}" |
| print(f" ✓ Task '{task_id}': accessible") |
| |
| print(f" ✓ All {len(tasks)} tasks validated") |
| return True |
| |
| except Exception as e: |
| print(f" ✗ Task validation failed: {e}") |
| return False |
|
|
| def validate_inference_script(): |
| """Validate inference script structure.""" |
| print("\n" + "="*60) |
| print("Validating Inference Script") |
| print("="*60) |
| |
| if not Path("inference.py").exists(): |
| print(" ✗ inference.py not found!") |
| return False |
| |
| with open("inference.py", "r") as f: |
| content = f.read() |
| |
| checks = [ |
| ("from openai import OpenAI", "OpenAI client import"), |
| ("API_BASE_URL", "API_BASE_URL variable"), |
| ("MODEL_NAME", "MODEL_NAME variable"), |
| ("HF_TOKEN", "HF_TOKEN variable"), |
| ("[START]", "START log format"), |
| ("[STEP]", "STEP log format"), |
| ("[END]", "END log format"), |
| ("async def main", "Async main function"), |
| ] |
| |
| all_ok = True |
| for pattern, description in checks: |
| if pattern in content: |
| print(f" ✓ {description}") |
| else: |
| print(f" ✗ {description} MISSING") |
| all_ok = False |
| |
| return all_ok |
|
|
| def main(): |
| """Run all validation checks.""" |
| print("\n" + "="*60) |
| print("PRE-DEPLOYMENT VALIDATION") |
| print("="*60) |
| |
| checks = [ |
| ("Project Structure", validate_project_structure), |
| ("openenv.yaml", validate_openenv_yaml), |
| ("Dockerfile", validate_dockerfile), |
| ("Environment Interface", validate_environment_interface), |
| ("Tasks", validate_tasks), |
| ("Inference Script", validate_inference_script), |
| ] |
| |
| results = [] |
| for name, check_func in checks: |
| try: |
| result = check_func() |
| results.append((name, result)) |
| except Exception as e: |
| print(f"\n ✗ {name} validation failed with exception: {e}") |
| results.append((name, False)) |
| |
| |
| print("\n" + "="*60) |
| print("VALIDATION SUMMARY") |
| print("="*60) |
| |
| passed = sum(1 for _, r in results if r) |
| total = len(results) |
| |
| for name, result in results: |
| status = "✓ PASS" if result else "✗ FAIL" |
| print(f" {status}: {name}") |
| |
| print(f"\nTotal: {passed}/{total} checks passed") |
| |
| if passed == total: |
| print("\n" + "="*60) |
| print("🎉 READY FOR DEPLOYMENT!") |
| print("="*60) |
| print("\nNext steps:") |
| print(" 1. Build Docker image: docker build -t quantum-circuit-opt:latest .") |
| print(" 2. Test locally: docker run -p 7860:7860 quantum-circuit-opt:latest") |
| print(" 3. Deploy to HF Spaces: openenv push") |
| print("\nSee QUICK_DEPLOY.md for detailed instructions.") |
| return 0 |
| else: |
| print("\n" + "="*60) |
| print(f"❌ {total - passed} check(s) failed") |
| print("="*60) |
| print("\nPlease fix the issues above before deploying.") |
| return 1 |
|
|
| if __name__ == "__main__": |
| sys.exit(main()) |
|
|