← eval_submission_analyzer

fastapi_14458

resolved RESOLVED PASS · 12 tool calls · 160 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 (13)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rnE \"is_coroutine_callable|is_async_gen_callable|is_gen_callable\" fastapi/"
}
{"status": "ok", "stdout": "fastapi/dependencies/models.py:106: def is_gen_callable(self) -> bool:\nfastapi/dependencies/models.py:130: def is_async_gen_callabl
full
{"status": "ok", "stdout": "fastapi/dependencies/models.py:106:    def is_gen_callable(self) -> bool:\nfastapi/dependencies/models.py:130:    def is_async_gen_callable(self) -> bool:\nfastapi/dependencies/models.py:154:    def is_coroutine_callable(self) -> bool:\nfastapi/dependencies/models.py:186:        if self.is_gen_callable or self.is_async_gen_callable:\nfastapi/dependencies/utils.py:290:                (dependant.is_gen_callable or dependant.is_async_gen_callable)\nfastapi/dependencies/utils.py:571:    if dependant.is_async_gen_callable:\nfastapi/dependencies/utils.py:573:    elif dependant.is_gen_callable:\nfastapi/dependencies/utils.py:656:            use_sub_dependant.is_gen_callable or use_sub_dependant.is_async_gen_callable\nfastapi/dependencies/utils.py:666:        elif use_sub_dependant.is_coroutine_callable:\nfastapi/routing.py:305:    is_coroutine = dependant.is_coroutine_callable\n", "exit_code": 0}
2read_file
{
 "filepath": "fastapi/dependencies/models.py",
 "start_line": 100,
 "end_line": 160
}
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": " @cached_property\n def _security_dependencies(self) -> List[\"Dependant\"]:\n securi
full
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": "    @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            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
3read_file
{
 "end_line": 188,
 "filepath": "fastapi/dependencies/models.py",
 "start_line": 154
}
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": " def is_coroutine_callable(self) -> bool:\n if self.call is None:\n return False # pr
full
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": "    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\n    @cached_property\n    def computed_scope(self) -> Union[str, None]:\n        if self.scope:\n            return self.scope\n        if self.is_gen_callable or self.is_async_gen_callable:\n            return \"request\"\n        return None\n", "start_line": 154, "end_line": 188, "total_lines": 188, "is_truncated": false}
4write_file
{
 "content": "\nimport inspect\nfrom fastapi.dependencies.models import Dependant\n\nclass AsyncCall:\n    async def __call__(self):\n        return 1\n\nclass GenCall:\n    def __call__(self):\n        yield 1\n\nclass AsyncGenCall:\n    async def __call__(self):\n        yield 1\n\ndef test_regression():\n    # Test for is_coroutine_callable\n    dep_async = Dependant(call=AsyncCall)\n    print(f\"AsyncCall is_coroutine_callable: {dep_async.is_coroutine_callable}\")\n    assert dep_async.is_coroutine_callable is False, \"AsyncCall class should not be considered a coroutine callable\"\n\n    # Test for is_gen_callable\n    dep_gen = Dependant(call=GenCall)\n    print(f\"GenCall is_gen_callable: {dep_gen.is_gen_callable}\")\n    assert dep_gen.is_gen_callable is False, \"GenCall class should not be considered a generator callable\"\n\n    # Test for is_async_gen_callable\n    dep_async_gen = Dependant(call=AsyncGenCall)\n    print(f\"AsyncGenCall is_async_gen_callable: {dep_async_gen.is_async_gen_callable}\")\n    assert dep_async_gen.is_async_gen_callable is False, \"AsyncGenCall class should not be considered an async generator callable\"\n\n    # Test for instances (should still be True if they are coroutines/generators)\n    # Wait, the issue says \"if we use class (not instance) as dependency\"\n    # If we use an instance, it should still work.\n    \n    # For instances, we need to see how FastAPI handles them. \n    # Usually, if you pass an instance, self.call is that instance.\n    \n    # Let's check if an instance of AsyncCall is considered a coroutine callable.\n    # Actually, the current implementation checks __call__ of self.call.\n    # If self.call is an instance of AsyncCall, then self.call.__call__ is the method.\n    # The method is a coroutine function.\n    \n    # Let's test with instances.\n    instance_async = AsyncCall()\n    dep_instance_async = Dependant(call=instance_async)\n    print(f\"AsyncCall instance is_coroutine_callable: {dep_instance_async.is_coroutine_callable}\")\n    # This should probably be True because the instance is callable and its __call__ is a coroutine.\n    \n    instance_gen = GenCall()\n    dep_instance_gen = Dependant(call=instance_gen)\n    print(f\"GenCall instance is_gen_callable: {dep_instance_gen.is_gen_callable}\")\n    \n    instance_async_gen = AsyncGenCall()\n    dep_instance_async_gen = Dependant(call=instance_async_gen)\n    print(f\"AsyncGenCall instance is_async_gen_callable: {dep_instance_async_gen.is_async_gen_callable}\")\n\nif __name__ == \"__main__\":\n    try:\n        test_regression()\n        print(\"Tests passed!\")\n    except AssertionError as e:\n        print(f\"Test failed: {e}\")\n        exit(1)\n",
 "filepath": "/tmp/test_regression.py"
}
{"status": "ok", "filepath": "/tmp/test_regression.py", "size": 2624}
full
{"status": "ok", "filepath": "/tmp/test_regression.py", "size": 2624}
5run_command
{
 "command": "python3 /tmp/test_regression.py"
}
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 2", "details": {"stdout": "", "stderr": "python3: can't open f
full
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 2", "details": {"stdout": "", "stderr": "python3: can't open file '/tmp/swe_work/eval_submission_analyzer/fastapi_14458/a/tmp/test_regression.py': [Errno 2] No such file or directory\n", "exit_code": 2}}
6run_command
{
 "command": "python3 /workspace/tmp/test_regression.py"
}
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 2", "details": {"stdout": "", "stderr": "python3: can't open f
full
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 2", "details": {"stdout": "", "stderr": "python3: can't open file '/tmp/swe_work/eval_submission_analyzer/fastapi_14458/a/workspace/tmp/test_regression.py': [Errno 2] No such file or directory\n", "exit_code": 2}}
7run_command
{
 "command": "ls -R /workspace"
}
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 2", "details": {"stdout": "", "stderr": "ls: cannot access '/t
full
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 2", "details": {"stdout": "", "stderr": "ls: cannot access '/tmp/swe_work/eval_submission_analyzer/fastapi_14458/a/workspace': No such file or directory\n", "exit_code": 2}}
8run_command
{
 "command": "python3 -c \"\nimport inspect\nfrom fastapi.dependencies.models import Dependant\n\nclass AsyncCall:\n    async def __call__(self):\n        return 1\n\nclass GenCall:\n    def __call__(self):\n        yield 1\n\nclass AsyncGenCall:\n    async def __call__(self):\n        yield 1\n\ndef test():\n    # Test for is_coroutine_callable\n    dep_async = Dependant(call=AsyncCall)\n    print(f'AsyncCall is_coroutine_callable: {dep_async.is_coroutine_callable}')\n    \n    # Test for is_gen_callable\n    dep_gen = Dependant(call=GenCall)\n    print(f'GenCall is_gen_callable: {dep_gen.is_gen_callable}')\n    \n    # Test for is_async_gen_callable\n    dep_async_gen = Dependant(call=AsyncGenCall)\n    print(f'AsyncGenCall is_async_gen_callable: {dep_async_gen.is_async_gen_callable}')\n\n    # Test for instances\n    instance_async = AsyncCall()\n    dep_instance_async = Dependant(call=instance_async)\n    print(f'AsyncCall instance is_coroutine_callable: {dep_instance_async.is_coroutine_callable}')\n\n    instance_gen = GenCall()\n    dep_instance_gen = Dependant(call=instance_gen)\n    print(f'GenCall instance is_gen_callable: {dep_instance_gen.is_gen_callable}')\n\n    instance_async_gen = AsyncGenCall()\n    dep_instance_async_gen = Dependant(call=instance_async_gen)\n    print(f'AsyncGenCall instance is_async_gen_callable: {dep_instance_async_gen.is_async_gen_callable}')\n\ntest()\n\""
}
{"status": "ok", "stdout": "AsyncCall is_coroutine_callable: True\nGenCall is_gen_callable: True\nAsyncGenCall is_async_gen_callable: True\nAsyncCall instance i
full
{"status": "ok", "stdout": "AsyncCall is_coroutine_callable: True\nGenCall is_gen_callable: True\nAsyncGenCall is_async_gen_callable: True\nAsyncCall instance is_coroutine_callable: True\nGenCall instance is_gen_callable: True\nAsyncGenCall instance is_async_gen_callable: True\n", "exit_code": 0}
9edit_file
{
 "filepath": "fastapi/dependencies/models.py",
 "new_string": "    @cached_property\n    def is_gen_callable(self) -> bool:\n        if self.call is None:\n            return False  # pragma: no cover\n        if inspect.isclass(_impartial(self.call)):\n            return False\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",
 "old_string": "    @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"
}
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/models.py\n+++ b/fasta
full
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/models.py\n+++ b/fastapi/dependencies/models.py\n@@ -106,6 +106,8 @@\n     def is_gen_callable(self) -> bool:\n         if self.call is None:\n             return False  # pragma: no cover\n+        if inspect.isclass(_impartial(self.call)):\n+            return False\n         if inspect.isgeneratorfunction(\n             _impartial(self.call)\n         ) or inspect.isgeneratorfunction(_unwrapped_call(self.call)):\n", "is_truncated": false}
10edit_file
{
 "filepath": "fastapi/dependencies/models.py",
 "new_string": "    @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.isclass(_impartial(self.call)):\n            return False\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        return False",
 "old_string": "    @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        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        return False"
}
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/models.py\n+++ b/fasta
full
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/models.py\n+++ b/fastapi/dependencies/models.py\n@@ -132,6 +132,8 @@\n     def is_async_gen_callable(self) -> bool:\n         if self.call is None:\n             return False  # pragma: no cover\n+        if inspect.isclass(_impartial(self.call)):\n+            return False\n         if inspect.isasyncgenfunction(\n             _impartial(self.call)\n         ) or inspect.isasyncgenfunction(_unwrapped_call(self.call)):\n", "is_truncated": false}
11edit_file
{
 "filepath": "fastapi/dependencies/models.py",
 "new_string": "    @cached_property\n    def is_coroutine_callable(self) -> bool:\n        if self.call is None:\n            return False  # pragma: no cover\n        if inspect.isclass(_impartial(self.call)):\n            return False\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",
 "old_string": "    @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"
}
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/models.py\n+++ b/fasta
full
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/models.py\n+++ b/fastapi/dependencies/models.py\n@@ -158,6 +158,8 @@\n     def is_coroutine_callable(self) -> bool:\n         if self.call is None:\n             return False  # pragma: no cover\n+        if inspect.isclass(_impartial(self.call)):\n+            return False\n         if inspect.isroutine(_impartial(self.call)) and iscoroutinefunction(\n             _impartial(self.call)\n         ):\n", "is_truncated": false}
12run_command
{
 "command": "python3 -c \"\nimport inspect\nfrom fastapi.dependencies.models import Dependant\n\nclass AsyncCall:\n    async def __call__(self):\n        return 1\n\nclass GenCall:\n    def __call__(self):\n        yield 1\n\nclass AsyncGenCall:\n    async def __call__(self):\n        yield 1\n\ndef test():\n    # Test for is_coroutine_callable\n    dep_async = Dependant(call=AsyncCall)\n    print(f'AsyncCall is_coroutine_callable: {dep_async.is_coroutine_callable}')\n    assert dep_async.is_coroutine_callable is False\n    \n    # Test for is_gen_callable\n    dep_gen = Dependant(call=GenCall)\n    print(f'GenCall is_gen_callable: {dep_gen.is_gen_callable}')\n    assert dep_gen.is_gen_callable is False\n    \n    # Test for is_async_gen_callable\n    dep_async_gen = Dependant(call=AsyncGenCall)\n    print(f'AsyncGenCall is_async_gen_callable: {dep_async_gen.is_async_gen_callable}')\n    assert dep_async_gen.is_async_gen_callable is False\n\n    # Test for instances\n    instance_async = AsyncCall()\n    dep_instance_async = Dependant(call=instance_async)\n    print(f'AsyncCall instance is_coroutine_callable: {dep_instance_async.is_coroutine_callable}')\n    assert dep_instance_async.is_coroutine_callable is True\n\n    instance_gen = GenCall()\n    dep_instance_gen = Dependant(call=instance_gen)\n    print(f'GenCall instance is_gen_callable: {dep_instance_gen.is_gen_callable}')\n    assert dep_instance_gen.is_gen_callable is True\n\n    instance_async_gen = AsyncGenCall()\n    dep_instance_async_gen = Dependant(call=instance_async_gen)\n    print(f'AsyncGenCall instance is_async_gen_callable: {dep_instance_async_gen.is_async_gen_callable}')\n    assert dep_instance_async_gen.is_async_gen_callable is True\n\ntest()\n\""
}
{"status": "ok", "stdout": "AsyncCall is_coroutine_callable: False\nGenCall is_gen_callable: False\nAsyncGenCall is_async_gen_callable: False\nAsyncCall instanc
full
{"status": "ok", "stdout": "AsyncCall is_coroutine_callable: False\nGenCall is_gen_callable: False\nAsyncGenCall is_async_gen_callable: False\nAsyncCall instance is_coroutine_callable: True\nGenCall instance is_gen_callable: True\nAsyncGenCall instance is_async_gen_callable: True\n", "exit_code": 0}
13submit_patch
[]
{"status": "ok", "patch_size": 4164, "files_changed": 2}
full
{"status": "ok", "patch_size": 4164, "files_changed": 2}

Patch

diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py
index af168a17..62674f66 100644
--- a/fastapi/dependencies/models.py
+++ b/fastapi/dependencies/models.py
@@ -106,6 +106,8 @@ class Dependant:
     def is_gen_callable(self) -> bool:
         if self.call is None:
             return False  # pragma: no cover
+        if inspect.isclass(_impartial(self.call)):
+            return False
         if inspect.isgeneratorfunction(
             _impartial(self.call)
         ) or inspect.isgeneratorfunction(_unwrapped_call(self.call)):
@@ -130,6 +132,8 @@ class Dependant:
     def is_async_gen_callable(self) -> bool:
         if self.call is None:
             return False  # pragma: no cover
+        if inspect.isclass(_impartial(self.call)):
+            return False
         if inspect.isasyncgenfunction(
             _impartial(self.call)
         ) or inspect.isasyncgenfunction(_unwrapped_call(self.call)):
@@ -154,6 +158,8 @@ class Dependant:
     def is_coroutine_callable(self) -> bool:
         if self.call is None:
             return False  # pragma: no cover
+        if inspect.isclass(_impartial(self.call)):
+            return False
         if inspect.isroutine(_impartial(self.call)) and iscoroutinefunction(
             _impartial(self.call)
         ):
diff --git a/tmp/test_regression.py b/tmp/test_regression.py
new file mode 100644
index 00000000..25c6a359
--- /dev/null
+++ b/tmp/test_regression.py
@@ -0,0 +1,65 @@
+
+import inspect
+from fastapi.dependencies.models import Dependant
+
+class AsyncCall:
+    async def __call__(self):
+        return 1
+
+class GenCall:
+    def __call__(self):
+        yield 1
+
+class AsyncGenCall:
+    async def __call__(self):
+        yield 1
+
+def test_regression():
+    # Test for is_coroutine_callable
+    dep_async = Dependant(call=AsyncCall)
+    print(f"AsyncCall is_coroutine_callable: {dep_async.is_coroutine_callable}")
+    assert dep_async.is_coroutine_callable is False, "AsyncCall class should not be considered a coroutine callable"
+
+    # Test for is_gen_callable
+    dep_gen = Dependant(call=GenCall)
+    print(f"GenCall is_gen_callable: {dep_gen.is_gen_callable}")
+    assert dep_gen.is_gen_callable is False, "GenCall class should not be considered a generator callable"
+
+    # Test for is_async_gen_callable
+    dep_async_gen = Dependant(call=AsyncGenCall)
+    print(f"AsyncGenCall is_async_gen_callable: {dep_async_gen.is_async_gen_callable}")
+    assert dep_async_gen.is_async_gen_callable is False, "AsyncGenCall class should not be considered an async generator callable"
+
+    # Test for instances (should still be True if they are coroutines/generators)
+    # Wait, the issue says "if we use class (not instance) as dependency"
+    # If we use an instance, it should still work.
+    
+    # For instances, we need to see how FastAPI handles them. 
+    # Usually, if you pass an instance, self.call is that instance.
+    
+    # Let's check if an instance of AsyncCall is considered a coroutine callable.
+    # Actually, the current implementation checks __call__ of self.call.
+    # If self.call is an instance of AsyncCall, then self.call.__call__ is the method.
+    # The method is a coroutine function.
+    
+    # Let's test with instances.
+    instance_async = AsyncCall()
+    dep_instance_async = Dependant(call=instance_async)
+    print(f"AsyncCall instance is_coroutine_callable: {dep_instance_async.is_coroutine_callable}")
+    # This should probably be True because the instance is callable and its __call__ is a coroutine.
+    
+    instance_gen = GenCall()
+    dep_instance_gen = Dependant(call=instance_gen)
+    print(f"GenCall instance is_gen_callable: {dep_instance_gen.is_gen_callable}")
+    
+    instance_async_gen = AsyncGenCall()
+    dep_instance_async_gen = Dependant(call=instance_async_gen)
+    print(f"AsyncGenCall instance is_async_gen_callable: {dep_instance_async_gen.is_async_gen_callable}")
+
+if __name__ == "__main__":
+    try:
+        test_regression()
+        print("Tests passed!")
+    except AssertionError as e:
+        print(f"Test failed: {e}")
+        exit(1)

Test output

show
............                                                             [100%]
=============================== warnings summary ===============================
../../../../../../kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45
  /kaggle/tmp/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.75s