Spaces:
Running
Running
| # Copyright (c) Meta Platforms, Inc. and affiliates. | |
| # All rights reserved. | |
| # | |
| # This source code is licensed under the BSD-style license found in the | |
| # LICENSE file in the root directory of this source tree. | |
| """ | |
| Validation utilities for multi-mode deployment readiness. | |
| This module provides functions to check if environments are properly | |
| configured for multi-mode deployment (Docker, direct Python, notebooks, clusters). | |
| """ | |
| import subprocess | |
| import tomllib | |
| from pathlib import Path | |
| def validate_multi_mode_deployment(env_path: Path) -> tuple[bool, list[str]]: | |
| """ | |
| Validate that an environment is ready for multi-mode deployment. | |
| Checks: | |
| 1. pyproject.toml exists | |
| 2. uv.lock exists and is up-to-date | |
| 3. pyproject.toml has [project.scripts] with server entry point | |
| 4. server/app.py has a main() function | |
| 5. Required dependencies are present | |
| Returns: | |
| Tuple of (is_valid, list of issues found) | |
| """ | |
| issues = [] | |
| # Check pyproject.toml exists | |
| pyproject_path = env_path / "pyproject.toml" | |
| if not pyproject_path.exists(): | |
| issues.append("Missing pyproject.toml") | |
| return False, issues | |
| # Check uv.lock exists | |
| lockfile_path = env_path / "uv.lock" | |
| if not lockfile_path.exists(): | |
| issues.append("Missing uv.lock - run 'uv lock' to generate it") | |
| else: | |
| # Check if uv.lock is up-to-date (optional, can be expensive) | |
| # We can add a check using `uv lock --check` if needed | |
| try: | |
| result = subprocess.run( | |
| ["uv", "lock", "--check", "--directory", str(env_path)], | |
| capture_output=True, | |
| text=True, | |
| timeout=5, | |
| ) | |
| if result.returncode != 0: | |
| issues.append( | |
| "uv.lock is out of date with pyproject.toml - run 'uv lock' to update" | |
| ) | |
| except (subprocess.TimeoutExpired, FileNotFoundError): | |
| # If uv is not available or times out, skip this check | |
| pass | |
| # Parse pyproject.toml | |
| try: | |
| with open(pyproject_path, "rb") as f: | |
| pyproject = tomllib.load(f) | |
| except Exception as e: | |
| issues.append(f"Failed to parse pyproject.toml: {e}") | |
| return False, issues | |
| # Check [project.scripts] section | |
| scripts = pyproject.get("project", {}).get("scripts", {}) | |
| if "server" not in scripts: | |
| issues.append("Missing [project.scripts] server entry point") | |
| # Check server entry point format | |
| server_entry = scripts.get("server", "") | |
| if server_entry and ":main" not in server_entry: | |
| issues.append( | |
| f"Server entry point should reference main function, got: {server_entry}" | |
| ) | |
| # Check required dependencies | |
| deps = [dep.lower() for dep in pyproject.get("project", {}).get("dependencies", [])] | |
| has_openenv = any( | |
| dep.startswith("openenv") and not dep.startswith("openenv-core") for dep in deps | |
| ) | |
| has_legacy_core = any(dep.startswith("openenv-core") for dep in deps) | |
| if not (has_openenv or has_legacy_core): | |
| issues.append("Missing required dependency: openenv>=0.2.0") | |
| elif has_legacy_core and not has_openenv: | |
| issues.append( | |
| "Dependency on openenv-core is deprecated; use openenv>=0.2.0 instead" | |
| ) | |
| # Check server/app.py exists | |
| server_app = env_path / "server" / "app.py" | |
| if not server_app.exists(): | |
| issues.append("Missing server/app.py") | |
| else: | |
| # Check for main() function (flexible - with or without parameters) | |
| app_content = server_app.read_text(encoding="utf-8") | |
| if "def main(" not in app_content: | |
| issues.append("server/app.py missing main() function") | |
| # Check if main() is callable | |
| if "__name__" not in app_content or "main()" not in app_content: | |
| issues.append( | |
| "server/app.py main() function not callable (missing if __name__ == '__main__')" | |
| ) | |
| return len(issues) == 0, issues | |
| def get_deployment_modes(env_path: Path) -> dict[str, bool]: | |
| """ | |
| Check which deployment modes are supported by the environment. | |
| Returns: | |
| Dictionary with deployment mode names and whether they're supported | |
| """ | |
| modes = { | |
| "docker": False, | |
| "openenv_serve": False, | |
| "uv_run": False, | |
| "python_module": False, | |
| } | |
| # Check Docker | |
| dockerfile = env_path / "server" / "Dockerfile" | |
| modes["docker"] = dockerfile.exists() | |
| # Check multi-mode deployment readiness | |
| is_valid, _ = validate_multi_mode_deployment(env_path) | |
| if is_valid: | |
| modes["openenv_serve"] = True | |
| modes["uv_run"] = True | |
| modes["python_module"] = True | |
| return modes | |
| def format_validation_report(env_name: str, is_valid: bool, issues: list[str]) -> str: | |
| """ | |
| Format a validation report for display. | |
| Returns: | |
| Formatted report string | |
| """ | |
| if is_valid: | |
| return f"[OK] {env_name}: Ready for multi-mode deployment" | |
| report = [f"[FAIL] {env_name}: Not ready for multi-mode deployment", ""] | |
| report.append("Issues found:") | |
| for issue in issues: | |
| report.append(f" - {issue}") | |
| return "\n".join(report) | |