From 96f745e9ff74495b0cfefef5c299817be782b222 Mon Sep 17 00:00:00 2001 From: songzhijun Date: Fri, 6 Feb 2026 09:47:02 +0800 Subject: [PATCH] fix(web): lazy-load scalar-fastapi docs UI Avoid importing scalar-fastapi at module import time so the Web UI can still start when the docs UI dependency is unavailable. Add a smoke test asserting create_app() can be imported and constructed. --- src/kimi_cli/web/app.py | 42 +++++++++++++++++++++++++++++------- tests/test_web_app_import.py | 6 ++++++ 2 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 tests/test_web_app_import.py diff --git a/src/kimi_cli/web/app.py b/src/kimi_cli/web/app.py index 9f6c3b6a0..feb749a50 100644 --- a/src/kimi_cli/web/app.py +++ b/src/kimi_cli/web/app.py @@ -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 @@ -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" @@ -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") diff --git a/tests/test_web_app_import.py b/tests/test_web_app_import.py new file mode 100644 index 000000000..3133e80d6 --- /dev/null +++ b/tests/test_web_app_import.py @@ -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