"""FastAPI application factory.""" from __future__ import annotations import logging import os from pathlib import Path from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import HTMLResponse from uipath.dev.server import UiPathDeveloperServer logger = logging.getLogger(__name__) STATIC_DIR = Path(__file__).parent / "static" FRONTEND_DIR = Path(__file__).parent / "frontend" _FALLBACK_HTML = """\ UiPath Developer Server

UiPath Developer Server

The API is running. %(message)s

%(status_title)s

%(status_body)s

API docs: /docs  |  Entrypoints: /api/entrypoints

""" def _fallback_html() -> str: """Return an informative HTML page when the frontend is not available.""" if not FRONTEND_DIR.exists(): return _FALLBACK_HTML % dict( accent="#f59e0b", message="The frontend is not included in this installation.", status_title="No Frontend Source", status_body=( "The frontend source directory was not found. " "If you installed from PyPI, the pre-built static files should " "be included. Try reinstalling with " "pip install uipath-dev." ), ) return _FALLBACK_HTML % dict( accent="#ef4444", message="The frontend has not been built yet.", status_title="Build Required", status_body=( "Run the following commands to build the frontend:

" "cd src/uipath/dev/server/frontend
" "npm install
" "npm run build

" "Then restart the server. Check the server logs for build errors." ), ) def create_app(server: UiPathDeveloperServer) -> FastAPI: """Create a FastAPI application wired to the given server instance.""" app = FastAPI( title="UiPath Developer Server", description="Web API and WebSocket backend for the UiPath Developer Console", version="0.1.0", ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Store server reference on app state for route access app.state.server = server auth_enabled = os.environ.get("UIPATH_AUTH_ENABLED", "true").lower() not in ( "false", "0", "no", ) # Read user's pyproject.toml from CWD (once at startup) _user_project: dict[str, str | None] = { "project_name": None, "project_version": None, "project_authors": None, } _pyproject_path = Path.cwd() / "pyproject.toml" if _pyproject_path.is_file(): try: import tomllib with open(_pyproject_path, "rb") as f: _pydata = tomllib.load(f) _proj = _pydata.get("project", {}) _user_project["project_name"] = _proj.get("name") _user_project["project_version"] = _proj.get("version") _authors = _proj.get("authors") if _authors and isinstance(_authors, list) and len(_authors) > 0: _user_project["project_authors"] = ( _authors[0].get("name") if isinstance(_authors[0], dict) else str(_authors[0]) ) except Exception: pass # Config endpoint — tells the frontend which features are available @app.get("/api/config", include_in_schema=False) async def _config(): return {"auth_enabled": auth_enabled, **_user_project} # Register routes from uipath.dev.server.routes.entrypoints import router as entrypoints_router from uipath.dev.server.routes.graph import router as graph_router from uipath.dev.server.routes.reload import router as reload_router from uipath.dev.server.routes.runs import router as runs_router from uipath.dev.server.ws.handler import router as ws_router if auth_enabled: from uipath.dev.server.auth import restore_session from uipath.dev.server.routes.auth import router as auth_router app.include_router(auth_router, prefix="/api") restore_session() app.include_router(entrypoints_router, prefix="/api") app.include_router(runs_router, prefix="/api") app.include_router(graph_router, prefix="/api") app.include_router(reload_router, prefix="/api") app.include_router(ws_router) # Auto-build frontend if source is available and build is stale from uipath.dev.server.frontend_build import ensure_frontend_built frontend_ready = ensure_frontend_built() # Serve static frontend files if built, otherwise serve fallback page if frontend_ready and (STATIC_DIR / "index.html").exists(): from fastapi.staticfiles import StaticFiles app.mount("/", StaticFiles(directory=str(STATIC_DIR), html=True), name="static") else: fallback = _fallback_html() @app.get("/", response_class=HTMLResponse) async def _fallback_page(): return fallback return app