Spaces:
Sleeping
Sleeping
| """FastAPI app + ``physix-server`` console-script entry point. | |
| Mounts the OpenEnv stateless endpoints (``/reset`` etc.) plus the bespoke | |
| ``/interactive/*`` router that maintains in-process sessions for browsers. | |
| CORS allows the Vite dev origin out of the box; override with the | |
| ``PHYSIX_CORS_ORIGINS`` env var (comma-separated, or ``*`` for any). | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import logging | |
| import os | |
| import uvicorn | |
| from fastapi import FastAPI, Request | |
| from fastapi.exceptions import RequestValidationError | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import JSONResponse | |
| from openenv.core.env_server import create_fastapi_app | |
| from starlette.exceptions import HTTPException as StarletteHTTPException | |
| from physix.models import PhysiXAction, PhysiXObservation | |
| from physix.server.environment import PhysiXEnvironment | |
| from physix.server.interactive import build_interactive_router | |
| _DEFAULT_CORS_ORIGINS = ( | |
| "http://localhost:5173", | |
| "http://127.0.0.1:5173", | |
| ) | |
| def build_app() -> FastAPI: | |
| app = create_fastapi_app( | |
| env=PhysiXEnvironment, | |
| action_cls=PhysiXAction, | |
| observation_cls=PhysiXObservation, | |
| ) | |
| _install_cors(app) | |
| _install_error_handlers(app) | |
| app.include_router(build_interactive_router()) | |
| return app | |
| def _install_cors(app: FastAPI) -> None: | |
| raw = os.environ.get("PHYSIX_CORS_ORIGINS", "") | |
| origins = ( | |
| [o.strip() for o in raw.split(",") if o.strip()] | |
| if raw | |
| else list(_DEFAULT_CORS_ORIGINS) | |
| ) | |
| allow_all = origins == ["*"] | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=origins if not allow_all else ["*"], | |
| allow_credentials=not allow_all, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| def _install_error_handlers(app: FastAPI) -> None: | |
| """Re-emit errors via JSONResponse so CORS headers are applied to 4xx/5xx.""" | |
| async def _http_exc(_request: Request, exc: StarletteHTTPException) -> JSONResponse: | |
| return JSONResponse( | |
| status_code=exc.status_code, | |
| content={"detail": exc.detail}, | |
| headers=exc.headers, | |
| ) | |
| async def _validation_exc( | |
| _request: Request, exc: RequestValidationError | |
| ) -> JSONResponse: | |
| return JSONResponse(status_code=422, content={"detail": exc.errors()}) | |
| async def _unhandled_exc(_request: Request, exc: Exception) -> JSONResponse: | |
| logging.getLogger(__name__).exception( | |
| "Unhandled exception in route handler" | |
| ) | |
| return JSONResponse( | |
| status_code=500, | |
| content={"detail": f"{type(exc).__name__}: {exc}"}, | |
| ) | |
| app: FastAPI = build_app() | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description="Run the PhysiX-Live env server.") | |
| parser.add_argument("--host", default=os.environ.get("PHYSIX_HOST", "0.0.0.0")) | |
| parser.add_argument("--port", type=int, default=int(os.environ.get("PORT", "8000"))) | |
| parser.add_argument( | |
| "--log-level", default=os.environ.get("PHYSIX_LOG_LEVEL", "info") | |
| ) | |
| parser.add_argument( | |
| "--reload", | |
| action="store_true", | |
| help="Auto-reload on source changes. Use during development only.", | |
| ) | |
| args = parser.parse_args() | |
| logging.basicConfig(level=args.log_level.upper()) | |
| uvicorn.run( | |
| "physix.server.app:app", | |
| host=args.host, | |
| port=args.port, | |
| log_level=args.log_level, | |
| reload=args.reload, | |
| ) | |
| if __name__ == "__main__": | |
| main() | |