diff --git a/fastapi/responses.py b/fastapi/responses.py index 29df4b7a61..0c9caa4d04 100644 --- a/fastapi/responses.py +++ b/fastapi/responses.py @@ -26,13 +26,13 @@ class _OrjsonModule(Protocol): try: ujson = cast(_UjsonModule, importlib.import_module("ujson")) -except ModuleNotFoundError: # pragma: nocover +except ImportError: # pragma: nocover ujson = None # type: ignore[assignment] try: orjson = cast(_OrjsonModule, importlib.import_module("orjson")) -except ModuleNotFoundError: # pragma: nocover +except ImportError: # pragma: nocover orjson = None # type: ignore[assignment] diff --git a/tests/test_deprecated_responses.py b/tests/test_deprecated_responses.py index 8cbd9c11fe..795cb85a5c 100644 --- a/tests/test_deprecated_responses.py +++ b/tests/test_deprecated_responses.py @@ -1,3 +1,5 @@ +import subprocess +import sys import warnings import pytest @@ -77,3 +79,44 @@ def test_ujson_response_returns_correct_data(): def test_ujson_response_emits_deprecation_warning(): with pytest.warns(FastAPIDeprecationWarning, match="UJSONResponse is deprecated"): UJSONResponse(content={"hello": "world"}) + + +# Optional-import resilience + + +def test_responses_importable_when_orjson_raises_import_error(): + # A corrupt/broken orjson install raises ImportError (not ModuleNotFoundError) + # when its C extension fails to load. fastapi.responses must remain importable. + script = ( + "import importlib; _real = importlib.import_module\n" + "def _fake(name, package=None):\n" + " if name == 'orjson': raise ImportError('binary load failed')\n" + " return _real(name, package)\n" + "importlib.import_module = _fake\n" + "import fastapi.responses\n" + "assert fastapi.responses.orjson is None\n" + ) + result = subprocess.run( + [sys.executable, "-c", script], + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr + + +def test_responses_importable_when_ujson_raises_import_error(): + script = ( + "import importlib; _real = importlib.import_module\n" + "def _fake(name, package=None):\n" + " if name == 'ujson': raise ImportError('binary load failed')\n" + " return _real(name, package)\n" + "importlib.import_module = _fake\n" + "import fastapi.responses\n" + "assert fastapi.responses.ujson is None\n" + ) + result = subprocess.run( + [sys.executable, "-c", script], + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr