"""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