diff --git a/fastapi_utils/cbv.py b/fastapi_utils/cbv.py index 231325c..3ce7b87 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 b5b9f64..1e9b88a 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: