Spaces:
Sleeping
Sleeping
| """FastAPI factory for the SQLDrift environment. | |
| ``create_app()`` returns a fully-wired FastAPI app exposing the | |
| stateless HTTP routes (``/health``, ``/schema``, ``/reset``, ``/step``) | |
| and the stateful ``/ws`` WebSocket session. Stateful multi-step | |
| episodes must go through ``/ws``; each HTTP ``/step`` spawns a | |
| fresh env instance that is ``close()``-d in ``finally`` (one env per request). | |
| ``main()`` runs the server with Uvicorn — exported as the | |
| ``[project.scripts] sql-drift-server`` entry point. | |
| """ | |
| from __future__ import annotations | |
| from typing import Any | |
| from uuid import uuid4 | |
| from openenv.core.env_server.http_server import create_app as _openenv_create_app | |
| from models import SqlDriftAction, SqlDriftObservation | |
| from skill_library import DEFAULT_STORE_DIR, Store, cleanup_stale_session_dirs | |
| from . import settings | |
| from .sql_drift_env_environment import SqlDriftEnvironment | |
| ENV_NAME = "sql_drift_env" | |
| DEFAULT_MAX_CONCURRENT_ENVS = settings.MAX_CONCURRENT_ENVS | |
| _SESSION_STORE_ROOT = DEFAULT_STORE_DIR / "sessions" | |
| # Purge stale session directories left by previous server runs before | |
| # accepting any traffic. Failures are non-fatal. | |
| _startup_removed = cleanup_stale_session_dirs( | |
| _SESSION_STORE_ROOT, settings.SKILL_STORE_SESSION_TTL_HOURS | |
| ) | |
| if _startup_removed: | |
| import logging as _logging | |
| _logging.getLogger("sql_drift_env.app.server.app").info( | |
| "startup: removed %d stale session skill-store dirs from %s", | |
| _startup_removed, | |
| _SESSION_STORE_ROOT, | |
| ) | |
| def _create_server_environment() -> SqlDriftEnvironment: | |
| """Build one server-managed env with its own on-disk skill library. | |
| ``cleanup_on_close=True`` ensures the session directory is deleted when | |
| the WebSocket session ends, preventing unbounded on-disk session growth. | |
| """ | |
| session_dir = _SESSION_STORE_ROOT / uuid4().hex | |
| return SqlDriftEnvironment( | |
| skill_store=Store(directory=session_dir), | |
| cleanup_on_close=True, | |
| ) | |
| def create_app(max_concurrent_envs: int | None = None) -> Any: | |
| """Build the FastAPI app bound to a fresh-env factory per session.""" | |
| if max_concurrent_envs is None: | |
| max_concurrent_envs = DEFAULT_MAX_CONCURRENT_ENVS | |
| return _openenv_create_app( | |
| env=_create_server_environment, | |
| action_cls=SqlDriftAction, | |
| observation_cls=SqlDriftObservation, | |
| env_name=ENV_NAME, | |
| max_concurrent_envs=max_concurrent_envs, | |
| ) | |
| def main(host: str = settings.SERVER_HOST, port: int = settings.SERVER_PORT) -> None: | |
| """Uvicorn entry point — matches the [project.scripts] wiring.""" | |
| import uvicorn | |
| uvicorn.run(create_app(), host=host, port=port) | |
| # Module-level app instance for uvicorn's ``module:attr`` syntax | |
| # (``uvicorn server.app:app``) and the ``openenv.yaml`` ``app:`` field. | |
| # Built at import time; safe because the OpenEnv factory only stores the | |
| # environment factory and instantiates per request / session. | |
| app = create_app() | |
| __all__ = ["ENV_NAME", "app", "create_app", "main"] | |
| if __name__ == "__main__": | |
| main() | |