litellm/backend/main.py
Yassin Kortam 75038d5753 Merge remote-tracking branch 'origin/litellm_fix/auth-module' into litellm_admin_auth_v2
# Conflicts:
#	backend/main.py
#	litellm/proxy/auth_v2/resolvers.py
#	litellm/proxy/proxy_server.py
#	tests/test_litellm/proxy/auth_v2/test_resolver.py
#	tests/test_litellm/proxy/test_component_allowlists.py
2026-07-07 15:15:33 +03:00

67 lines
2.1 KiB
Python

"""UI backend entrypoint.
Reuses the existing FastAPI app from `litellm.proxy.proxy_server` and trims its
route table to just the management/admin surface used by the dashboard. Purely
additive — no existing module is modified.
Run with:
uvicorn backend.main:app --host 0.0.0.0 --port 4001
"""
from contextlib import asynccontextmanager
from fastapi.routing import Mount
# See gateway/main.py for why we assemble DATABASE_URL(s) here before
# importing proxy_server.
from litellm.proxy.db.db_url_settings import DatabaseURLSettings
DatabaseURLSettings.from_env().apply_to_env()
from litellm.proxy.proxy_server import app
from backend.routers import ADMIN_PREFIX
from backend.routes.allowlist import (
BACKEND_EXACT_PATHS,
BACKEND_MOUNT_PATHS,
BACKEND_PATH_PREFIXES,
)
_ADMIN_PREFIX = ADMIN_PREFIX + "/"
def _is_backend_route(route) -> bool:
"""Keep the route on the backend if it is an explicit admin route or its path
is in the (legacy, allowlisted) management surface.
The explicit ``backend.routers`` admin routes are the source of truth and are
always kept; the allowlist covers the remaining proxy management surface that
has not been migrated onto explicit routers yet.
"""
path = getattr(route, "path", None)
if path is None:
return False
if path == ADMIN_PREFIX or path.startswith(_ADMIN_PREFIX):
return True
if isinstance(route, Mount):
# The dashboard UI static mounts are served by the dedicated UI container.
# Only Mounts in the backend allowlist (e.g. swagger docs) remain on backend.
return path in BACKEND_MOUNT_PATHS
if path in BACKEND_EXACT_PATHS:
return True
return any(path.startswith(prefix) for prefix in BACKEND_PATH_PREFIXES)
# See gateway/main.py for why the trim runs inside the lifespan instead of at
# module scope.
_proxy_lifespan = app.router.lifespan_context
@asynccontextmanager
async def _backend_lifespan(app_):
async with _proxy_lifespan(app_):
app_.router.routes = [r for r in app_.router.routes if _is_backend_route(r)]
yield
app.router.lifespan_context = _backend_lifespan