From 28bcc7acd7c555f8f855c66e670ff96143939bb9 Mon Sep 17 00:00:00 2001 From: agentson Date: Fri, 20 Feb 2026 09:28:23 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20uvicorn=20=EB=AF=B8=EC=84=A4=EC=B9=98=20?= =?UTF-8?q?=EC=8B=9C=20dashboard=20=EC=8B=A4=ED=8C=A8=EB=A5=BC=20=EB=8F=99?= =?UTF-8?q?=EA=B8=B0=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EA=B0=90=EC=A7=80?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EC=98=A4=ED=95=B4=20=EC=97=86=EB=8A=94=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EC=B6=9C=EB=A0=A5=20(#178)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 스레드 시작 전에 uvicorn import를 검증하도록 _start_dashboard_server 수정. uvicorn 미설치 시 "started" 로그 없이 즉시 WARNING 출력 후 None 반환. - 사전 import 검증으로 "started" → "failed" 오해 소지 있는 로그 쌍 제거 - uvicorn 미설치 시 명확한 경고 메시지 출력 - test_start_dashboard_server_returns_none_when_uvicorn_missing 테스트 추가 Co-Authored-By: Claude Sonnet 4.6 --- src/main.py | 12 ++++++++++-- tests/test_main.py | 23 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/main.py b/src/main.py index 222d4e8..1bd453a 100644 --- a/src/main.py +++ b/src/main.py @@ -1300,10 +1300,18 @@ def _start_dashboard_server(settings: Settings) -> threading.Thread | None: if not settings.DASHBOARD_ENABLED: return None + # Validate dependencies before spawning the thread so startup failures are + # reported synchronously (avoids the misleading "started" → "failed" log pair). + try: + import uvicorn # noqa: F401 + from src.dashboard import create_dashboard_app # noqa: F401 + except ImportError as exc: + logger.warning("Dashboard server unavailable (missing dependency): %s", exc) + return None + def _serve() -> None: try: import uvicorn - from src.dashboard import create_dashboard_app app = create_dashboard_app(settings.DB_PATH) @@ -1314,7 +1322,7 @@ def _start_dashboard_server(settings: Settings) -> threading.Thread | None: log_level="info", ) except Exception as exc: - logger.warning("Dashboard server failed to start: %s", exc) + logger.warning("Dashboard server stopped unexpectedly: %s", exc) thread = threading.Thread( target=_serve, diff --git a/tests/test_main.py b/tests/test_main.py index 0001412..59fe9fb 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -2194,6 +2194,29 @@ def test_start_dashboard_server_enabled_starts_thread() -> None: mock_thread.start.assert_called_once() +def test_start_dashboard_server_returns_none_when_uvicorn_missing() -> None: + """Returns None (no thread) and logs a warning when uvicorn is not installed.""" + settings = Settings( + KIS_APP_KEY="test_key", + KIS_APP_SECRET="test_secret", + KIS_ACCOUNT_NO="12345678-01", + GEMINI_API_KEY="test_gemini_key", + DASHBOARD_ENABLED=True, + ) + import builtins + real_import = builtins.__import__ + + def mock_import(name: str, *args: object, **kwargs: object) -> object: + if name == "uvicorn": + raise ImportError("No module named 'uvicorn'") + return real_import(name, *args, **kwargs) + + with patch("builtins.__import__", side_effect=mock_import): + thread = _start_dashboard_server(settings) + + assert thread is None + + # --------------------------------------------------------------------------- # market_outlook BUY confidence threshold tests (#173) # ---------------------------------------------------------------------------