← pod_r3_v1g

fastapi_14458

resolved RESOLVED PASS · 17 tool calls · 175 s · fastapi/fastapi

Task input

🐛 Fix using class (not instance) dependency that has `__call__` method

As reported in https://github.com/fastapi/fastapi/discussions/14452, FastAPI 0.123.6 introduced a regression - if we use class (not instance) as dependency and this class has `__call__` method that is coroutine or generator, it is mistakenly treated as coroutine\generator (actually class is not coroutine\generator, instance of this class is).


So, to fix this we need to check if the callable is class before inspecting its `dunder_call` in `is_coroutine_callable`, `is_async_gen_callable`, and `is_gen_callable`.

Tool calls (18)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rn \"is_coroutine_callable\" ."
}
{"status": "ok", "stdout": "./fastapi/dependencies/models.py:154: def is_coroutine_callable(self) -> bool:\n./fastapi/dependencies/utils.py:666: elif use_sub_de
full
{"status": "ok", "stdout": "./fastapi/dependencies/models.py:154:    def is_coroutine_callable(self) -> bool:\n./fastapi/dependencies/utils.py:666:        elif use_sub_dependant.is_coroutine_callable:\n./fastapi/routing.py:305:    is_coroutine = dependant.is_coroutine_callable\n", "stderr": "", "exit_code": 0}
2run_command
{
 "command": "sed -n '150,180p' fastapi/dependencies/models.py"
}
{"status": "ok", "stdout": " return True\n return False\n\n @cached_property\n def is_coroutine_callable(self) -> bool:\n if self.call is None:\n return False #
full
{"status": "ok", "stdout": "            return True\n        return False\n\n    @cached_property\n    def is_coroutine_callable(self) -> bool:\n        if self.call is None:\n            return False  # pragma: no cover\n        if inspect.isroutine(_impartial(self.call)) and iscoroutinefunction(\n            _impartial(self.call)\n        ):\n            return True\n        if inspect.isroutine(_unwrapped_call(self.call)) and iscoroutinefunction(\n            _unwrapped_call(self.call)\n        ):\n            return True\n        dunder_call = getattr(_impartial(self.call), \"__call__\", None)  # noqa: B004\n        if dunder_call is None:\n            return False  # pragma: no cover\n        if iscoroutinefunction(_impartial(dunder_call)) or iscoroutinefunction(\n            _unwrapped_call(dunder_call)\n        ):\n            return True\n        dunder_unwrapped_call = getattr(_unwrapped_call(self.call), \"__call__\", None)  # noqa: B004\n        if dunder_unwrapped_call is None:\n            return False  # pragma: no cover\n        if iscoroutinefunction(\n            _impartial(dunder_unwrapped_call)\n        ) or iscoroutinefunction(_unwrapped_call(dunder_unwrapped_call)):\n            return True\n        # if inspect.isclass(self.call): False, covered by default return\n        return False\n", "stderr": "", "exit_code": 0}
3run_command
{
 "command": "grep -n \"is_async_gen_callable\" fastapi/dependencies/models.py"
}
{"status": "ok", "stdout": "130: def is_async_gen_callable(self) -> bool:\n186: if self.is_gen_callable or self.is_async_gen_callable:\n", "stderr": "", "exit_c
full
{"status": "ok", "stdout": "130:    def is_async_gen_callable(self) -> bool:\n186:        if self.is_gen_callable or self.is_async_gen_callable:\n", "stderr": "", "exit_code": 0}
4run_command
{
 "command": "grep -n \"is_gen_callable\" fastapi/dependencies/models.py"
}
{"status": "ok", "stdout": "106: def is_gen_callable(self) -> bool:\n186: if self.is_gen_callable or self.is_async_gen_callable:\n", "stderr": "", "exit_code":
full
{"status": "ok", "stdout": "106:    def is_gen_callable(self) -> bool:\n186:        if self.is_gen_callable or self.is_async_gen_callable:\n", "stderr": "", "exit_code": 0}
5run_command
{
 "command": "sed -n '100,135p' fastapi/dependencies/models.py"
}
{"status": "ok", "stdout": " @cached_property\n def _security_dependencies(self) -> List[\"Dependant\"]:\n security_deps = [dep for dep in self.dependencies if
full
{"status": "ok", "stdout": "    @cached_property\n    def _security_dependencies(self) -> List[\"Dependant\"]:\n        security_deps = [dep for dep in self.dependencies if dep._is_security_scheme]\n        return security_deps\n\n    @cached_property\n    def is_gen_callable(self) -> bool:\n        if self.call is None:\n            return False  # pragma: no cover\n        if inspect.isgeneratorfunction(\n            _impartial(self.call)\n        ) or inspect.isgeneratorfunction(_unwrapped_call(self.call)):\n            return True\n        dunder_call = getattr(_impartial(self.call), \"__call__\", None)  # noqa: B004\n        if dunder_call is None:\n            return False  # pragma: no cover\n        if inspect.isgeneratorfunction(\n            _impartial(dunder_call)\n        ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_call)):\n            return True\n        dunder_unwrapped_call = getattr(_unwrapped_call(self.call), \"__call__\", None)  # noqa: B004\n        if dunder_unwrapped_call is None:\n            return False  # pragma: no cover\n        if inspect.isgeneratorfunction(\n            _impartial(dunder_unwrapped_call)\n        ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_unwrapped_call)):\n            return True\n        return False\n\n    @cached_property\n    def is_async_gen_callable(self) -> bool:\n        if self.call is None:\n            return False  # pragma: no cover\n        if inspect.isasyncgenfunction(\n            _impartial(self.call)\n        ) or inspect.isasyncgenfunction(_unwrapped_call(self.call)):\n", "stderr": "", "exit_code": 0}
6run_command
{
 "command": "sed -n '130,150p' fastapi/dependencies/models.py"
}
{"status": "ok", "stdout": " def is_async_gen_callable(self) -> bool:\n if self.call is None:\n return False # pragma: no cover\n if inspect.isasyncgenfunction(
full
{"status": "ok", "stdout": "    def is_async_gen_callable(self) -> bool:\n        if self.call is None:\n            return False  # pragma: no cover\n        if inspect.isasyncgenfunction(\n            _impartial(self.call)\n        ) or inspect.isasyncgenfunction(_unwrapped_call(self.call)):\n            return True\n        dunder_call = getattr(_impartial(self.call), \"__call__\", None)  # noqa: B004\n        if dunder_call is None:\n            return False  # pragma: no cover\n        if inspect.isasyncgenfunction(\n            _impartial(dunder_call)\n        ) or inspect.isasyncgenfunction(_unwrapped_call(dunder_call)):\n            return True\n        dunder_unwrapped_call = getattr(_unwrapped_call(self.call), \"__call__\", None)  # noqa: B004\n        if dunder_unwrapped_call is None:\n            return False  # pragma: no cover\n        if inspect.isasyncgenfunction(\n            _impartial(dunder_unwrapped_call)\n        ) or inspect.isasyncgenfunction(_unwrapped_call(dunder_unwrapped_call)):\n            return True\n", "stderr": "", "exit_code": 0}
7-8 ×2run_command
{
 "command": "python3 - <<'PY'\nimport inspect\nfrom fastapi.dependencies.models import Dependant\n\nclass MyCallable:\n    def __call__(self):\n        yield 1\n\ndef test_gen_callable():\n    # Class as dependency\n    dep_class = Dependant(call=MyCallable)\n    print(f\"Class as dep is_gen_callable: {dep_class.is_gen_callable}\")\n    \n    # Instance as dependency\n    dep_inst = Dependant(call=MyCallable())\n    print(f\"Instance as dep is_gen_callable: {dep_inst.is_gen_callable}\")\n\nasync def my_async_call():\n    pass\n\nclass MyAsyncCallable:\n    async def __call__(self):\n        pass\n\ndef test_coroutine_callable():\n    # Class as dependency\n    dep_class = Dependant(call=MyAsyncCallable)\n    print(f\"Class as dep is_coroutine_callable: {dep_class.is_coroutine_callable}\")\n    \n    # Instance as dependency\n    dep_inst = Dependant(call=MyAsyncCallable())\n    print(f\"Instance as dep is_coroutine_callable: {dep_inst.is_coroutine_callable}\")\n\ntest_gen_callable()\ntest_coroutine_callable()\nPY\n"
}
{"status": "ok", "stdout": "Class as dep is_gen_callable: True\nInstance as dep is_gen_callable: True\nClass as dep is_coroutine_callable: True\nInstance as dep
full
{"status": "ok", "stdout": "Class as dep is_gen_callable: True\nInstance as dep is_gen_callable: True\nClass as dep is_coroutine_callable: True\nInstance as dep is_coroutine_callable: True\n", "stderr": "", "exit_code": 0}
9run_command
{
 "command": "python3 - <<'PY'\nimport inspect\nfrom fastapi.dependencies.models import Dependant\n\nclass MyCallable:\n    def __call__(self):\n        yield 1\n\ndef test_gen_callable():\n    # Class as dependency\n    dep_class = Dependant(call=MyCallable)\n    print(f\"Class as dep is_gen_callable: {dep_class.is_gen_callable}\")\n    \n    # Instance as dependency\n    dep_inst = Dependant(call=MyCallable())\n    print(f\"Instance as dep is_gen_callable: {dep_inst.is_gen_callable}\")\n\nclass MyAsyncCallable:\n    async def __call__(self):\n        pass\n\ndef test_coroutine_callable():\n    # Class as dependency\n    dep_class = Dependant(call=MyAsyncCallable)\n    print(f\"Class as dep is_coroutine_callable: {dep_class.is_coroutine_callable}\")\n    \n    # Instance as dependency\n    dep_inst = Dependant(call=MyAsyncCallable())\n    print(f\"Instance as dep is_coroutine_callable: {dep_inst.is_coroutine_callable}\")\n\ntest_gen_callable()\ntest_coroutine_callable()\nPY\n"
}
{"status": "ok", "stdout": "Class as dep is_gen_callable: True\nInstance as dep is_gen_callable: True\nClass as dep is_coroutine_callable: True\nInstance as dep
full
{"status": "ok", "stdout": "Class as dep is_gen_callable: True\nInstance as dep is_gen_callable: True\nClass as dep is_coroutine_callable: True\nInstance as dep is_coroutine_callable: True\n", "stderr": "", "exit_code": 0}
10run_command
{
 "command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/dependencies/models.py\")\ns = p.read_text()\n\n# Fix is_gen_callable\nold_gen = \"\"\"        dunder_call = getattr(_impartial(self.call), \"__call__\", None)  # noqa: B004\n        if dunder_call is None:\n            return False  # pragma: no cover\n        if inspect.isgeneratorfunction(\n            _impartial(dunder_call)\n        ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_call)):\n            return True\"\"\"\nnew_gen = \"\"\"        if inspect.isclass(_impartial(self.call)):\n            return False\n        dunder_call = getattr(_impartial(self.call), \"__call__\", None)  # noqa: B004\n        if dunder_call is None:\n            return False  # pragma: no cover\n        if inspect.isgeneratorfunction(\n            _impartial(dunder_call)\n        ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_call)):\n            return True\"\"\"\n\n# Fix is_async_gen_callable\nold_async_gen = \"\"\"        dunder_call = getattr(_impartial(self.call), \"__call__\", None)  # noqa: B004\n        if dunder_call is None:\n            return False  # pragma: no cover\n        if inspect.isasyncgenfunction(\n            _impartial(dunder_call)\n        ) or inspect.isasyncgenfunction(_unwrapped_call(dunder_call)):\n            return True\"\"\"\nnew_async_gen = \"\"\"        if inspect.isclass(_impartial(self.call)):\n            return False\n        dunder_call = getattr(_impartial(self.call), \"__call__\", None)  # noqa: B004\n        if dunder_call is None:\n            return False  # pragma: no cover\n        if inspect.isasyncgenfunction(\n            _impartial(dunder_call)\n        ) or inspect.isasyncgenfunction(_unwrapped_call(dunder_call)):\n            return True\"\"\"\n\n# Fix is_coroutine_callable\nold_coro = \"\"\"        dunder_call = getattr(_impartial(self.call), \"__call__\", None)  # noqa: B004\n        if dunder_call is None:\n            return False  # pragma: no cover\n        if iscoroutinefunction(_impartial(dunder_call)) or iscoroutinefunction(\n            _unwrapped_call(dunder_call)\n        ):\n            return True\"\"\"\nnew_coro = \"\"\"        if inspect.isclass(_impartial(self.call)):\n            return False\n        dunder_call = getattr(_impartial(self.call), \"__call__\", None)  # noqa: B004\n        if dunder_call is None:\n            return False  # pragma: no cover\n        if iscoroutinefunction(_impartial(dunder_call)) or iscoroutinefunction(\n            _unwrapped_call(dunder_call)\n        ):\n            return True\"\"\"\n\ns = s.replace(old_gen, new_gen)\ns = s.replace(old_async_gen, new_async_gen)\ns = s.replace(old_coro, new_coro)\np.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
11run_command
{
 "command": "python3 - <<'PY'\nimport inspect\nfrom fastapi.dependencies.models import Dependant\n\nclass MyCallable:\n    def __call__(self):\n        yield 1\n\ndef test_gen_callable():\n    # Class as dependency\n    dep_class = Dependant(call=MyCallable)\n    print(f\"Class as dep is_gen_callable: {dep_class.is_gen_callable}\")\n    \n    # Instance as dependency\n    dep_inst = Dependant(call=MyCallable())\n    print(f\"Instance as dep is_gen_callable: {dep_inst.is_gen_callable}\")\n\nclass MyAsyncCallable:\n    async def __call__(self):\n        pass\n\ndef test_coroutine_callable():\n    # Class as dependency\n    dep_class = Dependant(call=MyAsyncCallable)\n    print(f\"Class as dep is_coroutine_callable: {dep_class.is_coroutine_callable}\")\n    \n    # Instance as dependency\n    dep_inst = Dependant(call=MyAsyncCallable())\n    print(f\"Instance as dep is_coroutine_callable: {dep_inst.is_coroutine_callable}\")\n\ntest_gen_callable()\ntest_coroutine_callable()\nPY\n"
}
{"status": "ok", "stdout": "Class as dep is_gen_callable: False\nInstance as dep is_gen_callable: True\nClass as dep is_coroutine_callable: False\nInstance as d
full
{"status": "ok", "stdout": "Class as dep is_gen_callable: False\nInstance as dep is_gen_callable: True\nClass as dep is_coroutine_callable: False\nInstance as dep is_coroutine_callable: True\n", "stderr": "", "exit_code": 0}
12run_command
{
 "command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/dependencies/models.py\")\ns = p.read_text()\n\n# We need to check for classes before checking dunder_call for both impartial and unwrapped.\n# The previous attempt only checked _impartial(self.call).\n\n# Let's refine the replacement.\n# In is_gen_callable:\n# The current code (after my first attempt) is:\n#         if inspect.isclass(_impartial(self.call)):\n#             return False\n#         dunder_call = getattr(_impartial(self.call), \"__call__\", None)  # noqa: B004\n#         ...\n#         dunder_unwrapped_call = getattr(_unwrapped_call(self.call), \"__call__\", None)  # noqa: B004\n#         ...\n\n# Wait, if it's a class, we should not check dunder_call at all.\n# But we also have dunder_unwrapped_call.\n\n# Let's look at the code again.\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
13run_command
{
 "command": "sed -n '100,185p' fastapi/dependencies/models.py"
}
{"status": "ok", "stdout": " @cached_property\n def _security_dependencies(self) -> List[\"Dependant\"]:\n security_deps = [dep for dep in self.dependencies if
full
{"status": "ok", "stdout": "    @cached_property\n    def _security_dependencies(self) -> List[\"Dependant\"]:\n        security_deps = [dep for dep in self.dependencies if dep._is_security_scheme]\n        return security_deps\n\n    @cached_property\n    def is_gen_callable(self) -> bool:\n        if self.call is None:\n            return False  # pragma: no cover\n        if inspect.isgeneratorfunction(\n            _impartial(self.call)\n        ) or inspect.isgeneratorfunction(_unwrapped_call(self.call)):\n            return True\n        if inspect.isclass(_impartial(self.call)):\n            return False\n        dunder_call = getattr(_impartial(self.call), \"__call__\", None)  # noqa: B004\n        if dunder_call is None:\n            return False  # pragma: no cover\n        if inspect.isgeneratorfunction(\n            _impartial(dunder_call)\n        ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_call)):\n            return True\n        dunder_unwrapped_call = getattr(_unwrapped_call(self.call), \"__call__\", None)  # noqa: B004\n        if dunder_unwrapped_call is None:\n            return False  # pragma: no cover\n        if inspect.isgeneratorfunction(\n            _impartial(dunder_unwrapped_call)\n        ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_unwrapped_call)):\n            return True\n        return False\n\n    @cached_property\n    def is_async_gen_callable(self) -> bool:\n        if self.call is None:\n            return False  # pragma: no cover\n        if inspect.isasyncgenfunction(\n            _impartial(self.call)\n        ) or inspect.isasyncgenfunction(_unwrapped_call(self.call)):\n            return True\n        if inspect.isclass(_impartial(self.call)):\n            return False\n        dunder_call = getattr(_impartial(self.call), \"__call__\", None)  # noqa: B004\n        if dunder_call is None:\n            return False  # pragma: no cover\n        if inspect.isasyncgenfunction(\n            _impartial(du
14run_command
{
 "command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/dependencies/models.py\")\ns = p.read_text()\n\n# We should also check if _unwrapped_call(self.call) is a class.\n# Let's replace the class check to cover both.\n\n# In is_gen_callable:\nold_gen_class_check = \"        if inspect.isclass(_impartial(self.call)):\\n            return False\"\nnew_gen_class_check = \"        if inspect.isclass(_impartial(self.call)) or inspect.isclass(_unwrapped_call(self.call)):\\n            return False\"\n\n# In is_async_gen_callable:\nold_async_gen_class_check = \"        if inspect.isclass(_impartial(self.call)):\\n            return False\"\nnew_async_gen_class_check = \"        if inspect.isclass(_impartial(self.call)) or inspect.isclass(_unwrapped_call(self.call)):\\n            return False\"\n\n# In is_coroutine_callable:\nold_coro_class_check = \"        if inspect.isclass(_impartial(self.call)):\\n            return False\"\nnew_coro_class_check = \"        if inspect.isclass(_impartial(self.call)) or inspect.isclass(_unwrapped_call(self.call)):\\n            return False\"\n\ns = s.replace(old_gen_class_check, new_gen_class_check)\ns = s.replace(old_async_gen_class_check, new_async_gen_class_check)\ns = s.replace(old_coro_class_check, new_coro_class_check)\np.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
15run_command
{
 "command": "python3 - <<'PY'\nimport inspect\nfrom fastapi.dependencies.models import Dependant\n\nclass MyCallable:\n    def __call__(self):\n        yield 1\n\ndef test_gen_callable():\n    # Class as dependency\n    dep_class = Dependant(call=MyCallable)\n    print(f\"Class as dep is_gen_callable: {dep_class.is_gen_callable}\")\n    \n    # Instance as dependency\n    dep_inst = Dependant(call=MyCallable())\n    print(f\"Instance as dep is_gen_callable: {dep_inst.is_gen_callable}\")\n\nclass MyAsyncCallable:\n    async def __call__(self):\n        pass\n\ndef test_coroutine_callable():\n    # Class as dependency\n    dep_class = Dependant(call=MyAsyncCallable)\n    print(f\"Class as dep is_coroutine_callable: {dep_class.is_coroutine_callable}\")\n    \n    # Instance as dependency\n    dep_inst = Dependant(call=MyAsyncCallable())\n    print(f\"Instance as dep is_coroutine_callable: {dep_inst.is_coroutine_callable}\")\n\ntest_gen_callable()\ntest_coroutine_callable()\nPY\n"
}
{"status": "ok", "stdout": "Class as dep is_gen_callable: False\nInstance as dep is_gen_callable: True\nClass as dep is_coroutine_callable: False\nInstance as d
full
{"status": "ok", "stdout": "Class as dep is_gen_callable: False\nInstance as dep is_gen_callable: True\nClass as dep is_coroutine_callable: False\nInstance as dep is_coroutine_callable: True\n", "stderr": "", "exit_code": 0}
16run_command
{
 "command": "git diff"
}
{"status": "ok", "stdout": "diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py\nindex af168a17..24c100f9 100644\n--- a/fastapi/depende
full
{"status": "ok", "stdout": "diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py\nindex af168a17..24c100f9 100644\n--- a/fastapi/dependencies/models.py\n+++ b/fastapi/dependencies/models.py\n@@ -110,6 +110,8 @@ class Dependant:\n             _impartial(self.call)\n         ) or inspect.isgeneratorfunction(_unwrapped_call(self.call)):\n             return True\n+        if inspect.isclass(_impartial(self.call)) or inspect.isclass(_unwrapped_call(self.call)):\n+            return False\n         dunder_call = getattr(_impartial(self.call), \"__call__\", None)  # noqa: B004\n         if dunder_call is None:\n             return False  # pragma: no cover\n@@ -134,6 +136,8 @@ class Dependant:\n             _impartial(self.call)\n         ) or inspect.isasyncgenfunction(_unwrapped_call(self.call)):\n             return True\n+        if inspect.isclass(_impartial(self.call)) or inspect.isclass(_unwrapped_call(self.call)):\n+            return False\n         dunder_call = getattr(_impartial(self.call), \"__call__\", None)  # noqa: B004\n         if dunder_call is None:\n             return False  # pragma: no cover\n@@ -162,6 +166,8 @@ class Dependant:\n             _unwrapped_call(self.call)\n         ):\n             return True\n+        if inspect.isclass(_impartial(self.call)) or inspect.isclass(_unwrapped_call(self.call)):\n+            return False\n         dunder_call = getattr(_impartial(self.call), \"__call__\", None)  # noqa: B004\n         if dunder_call is None:\n             return False  # pragma: no cover\n", "stderr": "", "exit_code": 0}
17run_command
{
 "command": "submit_patch"
}
{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/bas
full
{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/bash: submit_patch: command not found\n", "exit_code": 127}}
18submit_patch
{}
{"status": "ok", "patch_size": 1502, "files_changed": 1}
full
{"status": "ok", "patch_size": 1502, "files_changed": 1}

Patch

diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py
index af168a17..24c100f9 100644
--- a/fastapi/dependencies/models.py
+++ b/fastapi/dependencies/models.py
@@ -110,6 +110,8 @@ class Dependant:
             _impartial(self.call)
         ) or inspect.isgeneratorfunction(_unwrapped_call(self.call)):
             return True
+        if inspect.isclass(_impartial(self.call)) or inspect.isclass(_unwrapped_call(self.call)):
+            return False
         dunder_call = getattr(_impartial(self.call), "__call__", None)  # noqa: B004
         if dunder_call is None:
             return False  # pragma: no cover
@@ -134,6 +136,8 @@ class Dependant:
             _impartial(self.call)
         ) or inspect.isasyncgenfunction(_unwrapped_call(self.call)):
             return True
+        if inspect.isclass(_impartial(self.call)) or inspect.isclass(_unwrapped_call(self.call)):
+            return False
         dunder_call = getattr(_impartial(self.call), "__call__", None)  # noqa: B004
         if dunder_call is None:
             return False  # pragma: no cover
@@ -162,6 +166,8 @@ class Dependant:
             _unwrapped_call(self.call)
         ):
             return True
+        if inspect.isclass(_impartial(self.call)) or inspect.isclass(_unwrapped_call(self.call)):
+            return False
         dunder_call = getattr(_impartial(self.call), "__call__", None)  # noqa: B004
         if dunder_call is None:
             return False  # pragma: no cover

Test output

show
............                                                             [100%]
=============================== warnings summary ===============================
../../../../../../../Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45
  /Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45: DeprecationWarning: The anyio.abc.BlockingPortal alias is deprecated, use anyio.from_thread.BlockingPortal instead.
    _PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]]

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
12 passed, 1 warning in 0.47s