Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 34 additions & 8 deletions src/kimi_cli/web/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from typing import Any, cast
from urllib.parse import quote

import scalar_fastapi
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
Expand Down Expand Up @@ -39,11 +38,35 @@
logger.enable("kimi_cli")
logger.add(sys.stderr, level=_log_level)

# scalar-fastapi does not ship typing stubs.
get_scalar_api_reference = cast( # pyright: ignore[reportUnknownMemberType]
Callable[..., HTMLResponse],
scalar_fastapi.get_scalar_api_reference, # pyright: ignore[reportUnknownMemberType]
)

def _get_scalar_api_reference() -> Callable[..., HTMLResponse] | None:
"""Import scalar-fastapi lazily.

scalar-fastapi does not ship typing stubs, and importing it at module import
time makes the whole Web UI fail if that optional dependency is broken.

Keeping this as a best-effort import keeps `/healthz` and the Web UI working
even if the docs UI is unavailable.
"""

try:
import scalar_fastapi # type: ignore[import-not-found]
except Exception as e:
logger.warning(f"Failed to import scalar_fastapi; disabling /docs: {e}")
return None

return cast( # pyright: ignore[reportUnknownMemberType]
Callable[..., HTMLResponse],
scalar_fastapi.get_scalar_api_reference, # pyright: ignore[reportUnknownMemberType]
)


def _docs_unavailable_response() -> HTMLResponse:
return HTMLResponse(
"Docs UI is unavailable (failed to import scalar-fastapi).",
status_code=503,
)


# Constants
STATIC_DIR = Path(__file__).parent / "static"
Expand Down Expand Up @@ -228,9 +251,12 @@ async def lifespan(app: FastAPI):
@application.get("/scalar", include_in_schema=False)
@application.get("/docs", include_in_schema=False)
async def scalar_html() -> HTMLResponse: # pyright: ignore[reportUnusedFunction]
get_scalar_api_reference = _get_scalar_api_reference()
if get_scalar_api_reference is None:
return _docs_unavailable_response()

return get_scalar_api_reference(
openapi_url=application.openapi_url or "",
title=application.title,
openapi_url=application.openapi_url or "", title=application.title
)

@application.get("/healthz")
Expand Down
6 changes: 6 additions & 0 deletions tests/test_web_app_import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def test_create_app_imports() -> None:
# Import should not have side effects that crash on import.
from kimi_cli.web.app import create_app

app = create_app()
assert app is not None
Loading