Browse Source

🐛 Catch ImportError (not just ModuleNotFoundError) for optional orjson/ujson imports

When orjson or ujson is installed but its C extension fails to load
(e.g. ABI mismatch, platform issue), Python raises a plain ImportError,
not its subclass ModuleNotFoundError. The previous narrow catch let that
propagate, crashing `import fastapi.responses` even though the user may
not need those optional response classes.

Broadening to except ImportError keeps the correct fallback behaviour
(set the module to None) for both the absent-package and the
broken-install cases.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
pull/15709/head
nahcmon 1 month ago
parent
commit
7f0e16d681
  1. 4
      fastapi/responses.py
  2. 43
      tests/test_deprecated_responses.py

4
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]

43
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

Loading…
Cancel
Save