From e12962b059b103150f9b774e0a9db56718688f98 Mon Sep 17 00:00:00 2001 From: dataflow-solutions-sk Date: Mon, 17 Aug 2026 17:27:28 +0200 Subject: [PATCH] Fix ModuleNotFoundError when importing fastapi_utils without typing-inspect (#318) cbv.py unconditionally imported typing_inspect.is_classvar for Pydantic 2, even though typing-inspect is an optional dependency. Replace it with an equivalent stdlib typing.get_origin check so importing fastapi_utils no longer requires the optional 'all' extra. Adds a regression test that reloads fastapi_utils.cbv with typing_inspect blocked in sys.modules to ensure the import stays optional. --- fastapi_utils/cbv.py | 11 ++++++++++- tests/test_cbv.py | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/fastapi_utils/cbv.py b/fastapi_utils/cbv.py index 231325c5..3ce7b875 100644 --- a/fastapi_utils/cbv.py +++ b/fastapi_utils/cbv.py @@ -1,4 +1,5 @@ import inspect +import typing from typing import ( Any, Callable, @@ -18,7 +19,15 @@ PYDANTIC_VERSION = pydantic.VERSION if PYDANTIC_VERSION[0] == "2": - from typing_inspect import is_classvar + # Implemented with the stdlib `typing` module rather than the optional + # `typing-inspect` dependency, so that simply importing `fastapi_utils` + # doesn't require an extra to be installed. This covers both the bare + # `ClassVar` and the subscripted `ClassVar[...]` forms, matching the + # behavior of `typing_inspect.is_classvar` for the annotations `cbv` + # encounters here. + def is_classvar(hint: Any) -> bool: + return hint is typing.ClassVar or typing.get_origin(hint) is typing.ClassVar + else: from pydantic.typing import is_classvar # type: ignore[no-redef] diff --git a/tests/test_cbv.py b/tests/test_cbv.py index b5b9f646..1e9b88af 100644 --- a/tests/test_cbv.py +++ b/tests/test_cbv.py @@ -1,14 +1,36 @@ from __future__ import annotations +import importlib +import sys from typing import Any, ClassVar, Optional import pytest from fastapi import APIRouter, Depends, Request from starlette.testclient import TestClient +import fastapi_utils.cbv from fastapi_utils.cbv import cbv +def test_cbv_importable_without_typing_inspect(monkeypatch: pytest.MonkeyPatch) -> None: + """`typing-inspect` is an optional dependency (only pulled in by the `all` extra), so + importing `fastapi_utils.cbv` must not require it to be installed. See #318. + + Setting `sys.modules["typing_inspect"] = None` makes the import machinery raise + `ImportError` for any `import typing_inspect` / `from typing_inspect import ...` + statement, simulating an environment where the package isn't installed. + """ + monkeypatch.setitem(sys.modules, "typing_inspect", None) + + try: + reloaded = importlib.reload(fastapi_utils.cbv) + assert reloaded.cbv is not None + finally: + # Restore the real module state for any tests that run afterwards. + monkeypatch.undo() + importlib.reload(fastapi_utils.cbv) + + class TestCBV: @pytest.fixture(autouse=True) def router(self) -> APIRouter: