From 7437e73508bd9217a62b38e5e54ad5d62b0cc1e1 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Mon, 11 May 2026 14:27:54 +0000 Subject: [PATCH] test_5106_windows_gpu_detection_mock: don't shadow real httpx This file's name sorts before every other file in studio/backend/tests/ (starts with the digit '5'), so pytest collects it first. The previous ``sys.modules.setdefault("httpx", _httpx_stub)`` ran before any other test imported real httpx, which meant the stub permanently shadowed the real module for the rest of the collection. Tests that did ``from httpx import HTTPError, Response`` (test_anthropic_messages, test_browse_folders_route, test_training_*, etc) then failed at collection with ``ImportError: cannot import name 'HTTPError'`` because the stub did not define those names. The existing test_llama_cpp_windows_nvidia_path.py did not trigger the same issue because it sorts after test_a* / test_b* / etc, by which point the real httpx has already been imported and setdefault is a no-op. Switch the stub installation to ``importlib.util.find_spec(name) is None`` so we only fall back to the stub when the real module truly is not installed. Backend CI installs httpx, structlog, and the studio/backend/loggers package is reachable via the sys.path augmentation a few lines above, so on CI all three find_spec calls succeed and no stubs are installed at all. Also add HTTPError and Response to the stub module for the offline case, so anyone running this test outside CI with httpx absent still gets a stub that satisfies the broader test suite's imports. Refs #5106 --- .../test_5106_windows_gpu_detection_mock.py | 89 ++++++++++++------- 1 file changed, 58 insertions(+), 31 deletions(-) diff --git a/studio/backend/tests/test_5106_windows_gpu_detection_mock.py b/studio/backend/tests/test_5106_windows_gpu_detection_mock.py index 8362a23158..27fd6a3fa4 100644 --- a/studio/backend/tests/test_5106_windows_gpu_detection_mock.py +++ b/studio/backend/tests/test_5106_windows_gpu_detection_mock.py @@ -48,41 +48,68 @@ _BACKEND_DIR = str(Path(__file__).resolve().parent.parent) if _BACKEND_DIR not in sys.path: sys.path.insert(0, _BACKEND_DIR) -# Stub heavy deps the rest of the studio backend pulls in so this -# test can run on the same matrix as test_llama_cpp_windows_nvidia_path. -_loggers_stub = _types.ModuleType("loggers") -_loggers_stub.get_logger = lambda name: __import__("logging").getLogger(name) -sys.modules.setdefault("loggers", _loggers_stub) -sys.modules.setdefault("structlog", _types.ModuleType("structlog")) - -_httpx_stub = _types.ModuleType("httpx") -for _exc_name in ( - "ConnectError", - "TimeoutException", - "ReadTimeout", - "ReadError", - "RemoteProtocolError", - "CloseError", -): - setattr(_httpx_stub, _exc_name, type(_exc_name, (Exception,), {})) +# Stub heavy deps the rest of the studio backend pulls in IFF they +# are not installed in this environment. ``sys.modules.setdefault`` +# alone is not enough: pytest collects test files alphabetically, so +# this file (``test_5106_*``) is imported before any other test +# imports real ``httpx`` / ``structlog`` / ``loggers``. Unconditionally +# installing a stub there would shadow the real httpx for every +# subsequent test in the directory (test_anthropic_messages.py, +# test_training_*, etc) and break their `from httpx import HTTPError, +# Response` imports. Guard each stub with ``find_spec`` so we only +# fall back to the stub when the real module truly is missing. +import importlib.util as _importlib_util # noqa: E402 -class _FakeTimeout: - def __init__(self, *a, **kw): - pass +def _maybe_stub(name: str, builder): + if _importlib_util.find_spec(name) is None: + sys.modules[name] = builder() -_httpx_stub.Timeout = _FakeTimeout -_httpx_stub.Client = type( - "Client", - (), - { - "__init__": lambda self, **kw: None, - "__enter__": lambda self: self, - "__exit__": lambda self, *a: None, - }, -) -sys.modules.setdefault("httpx", _httpx_stub) +def _build_loggers_stub(): + m = _types.ModuleType("loggers") + m.get_logger = lambda name: __import__("logging").getLogger(name) + return m + + +def _build_structlog_stub(): + return _types.ModuleType("structlog") + + +def _build_httpx_stub(): + m = _types.ModuleType("httpx") + for _exc_name in ( + "ConnectError", + "TimeoutException", + "ReadTimeout", + "ReadError", + "RemoteProtocolError", + "CloseError", + "HTTPError", + ): + setattr(m, _exc_name, type(_exc_name, (Exception,), {})) + m.Response = type("Response", (), {}) + + class _FakeTimeout: + def __init__(self, *a, **kw): + pass + + m.Timeout = _FakeTimeout + m.Client = type( + "Client", + (), + { + "__init__": lambda self, **kw: None, + "__enter__": lambda self: self, + "__exit__": lambda self, *a: None, + }, + ) + return m + + +_maybe_stub("loggers", _build_loggers_stub) +_maybe_stub("structlog", _build_structlog_stub) +_maybe_stub("httpx", _build_httpx_stub) from core.inference.llama_cpp import LlamaCppBackend # noqa: E402