← eval8_submission_v1a

fastapi_14458

failed WRONG_FIX UNSUBMITTED wrong_fix_unsubmitted(timeout) · 26 tool calls · 0 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 (0)

#ToolArgumentsResult
No trace captured.

Patch

diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py
index af168a17..98d05013 100644
--- a/fastapi/dependencies/models.py
+++ b/fastapi/dependencies/models.py
@@ -111,7 +111,7 @@ class Dependant:
         ) or inspect.isgeneratorfunction(_unwrapped_call(self.call)):
             return True
         dunder_call = getattr(_impartial(self.call), "__call__", None)  # noqa: B004
-        if dunder_call is None:
+        if dunder_call is None or inspect.isclass(_impartial(self.call)):
             return False  # pragma: no cover
         if inspect.isgeneratorfunction(
             _impartial(dunder_call)

Test output

show
-none-any/starlette/routing.py:290: in handle
    await self.app(scope, receive, send)
fastapi/routing.py:119: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
fastapi/routing.py:105: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
fastapi/routing.py:375: in app
    solved_result = await solve_dependencies(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.requests.Request object at 0x7c41aa4236b0>
dependant = Dependant(path_params=[], query_params=[ModelField(field_info=Query(PydanticUndefined), name='value', mode='validation...oauth_scopes=None, parent_oauth_scopes=None, use_cache=True, path='/async-callable-dependency-class', scope='function')
body = None, background_tasks = None
response = <starlette.responses.Response object at 0x7c41aa3dcf50>
dependency_overrides_provider = <fastapi.applications.FastAPI object at 0x7c41ab142120>
dependency_cache = {}
async_exit_stack = <contextlib.AsyncExitStack object at 0x7c41aa3d5720>
embed_body_fields = False

    async def solve_dependencies(
        *,
        request: Union[Request, WebSocket],
        dependant: Dependant,
        body: Optional[Union[Dict[str, Any], FormData]] = None,
        background_tasks: Optional[StarletteBackgroundTasks] = None,
        response: Optional[Response] = None,
        dependency_overrides_provider: Optional[Any] = None,
        dependency_cache: Optional[Dict[DependencyCacheKey, Any]] = None,
        # TODO: remove this parameter later, no longer used, not removing it yet as some
        # people might be monkey patching this function (although that's not supported)
        async_exit_stack: AsyncExitStack,
        embed_body_fields: bool,
    ) -> SolvedDependency:
        request_astack = request.scope.get("fastapi_inner_astack")
        assert isinstance(request_astack, AsyncExitStack), (
            "fastapi_inner_astack not found in request scope"
        )
        function_astack = request.scope.get("fastapi_function_astack")
        assert isinstance(function_astack, AsyncExitStack), (
            "fastapi_function_astack not found in request scope"
        )
        values: Dict[str, Any] = {}
        errors: List[Any] = []
        if response is None:
            response = Response()
            del response.headers["content-length"]
            response.status_code = None  # type: ignore
        if dependency_cache is None:
            dependency_cache = {}
        for sub_dependant in dependant.dependencies:
            sub_dependant.call = cast(Callable[..., Any], sub_dependant.call)
            call = sub_dependant.call
            use_sub_dependant = sub_dependant
            if (
                dependency_overrides_provider
                and dependency_overrides_provider.dependency_overrides
            ):
                original_call = sub_dependant.call
                call = getattr(
                    dependency_overrides_provider, "dependency_overrides", {}
                ).get(original_call, original_call)
                use_path: str = sub_dependant.path  # type: ignore
                use_sub_dependant = get_dependant(
                    path=use_path,
                    call=call,
                    name=sub_dependant.name,
                    parent_oauth_scopes=sub_dependant.oauth_scopes,
                    scope=sub_dependant.scope,
                )
    
            solved_result = await solve_dependencies(
                request=request,
                dependant=use_sub_dependant,
                body=body,
                background_tasks=background_tasks,
                response=response,
                dependency_overrides_provider=dependency_overrides_provider,
                dependency_cache=dependency_cache,
                async_exit_stack=async_exit_stack,
                embed_body_fields=embed_body_fields,
            )
            background_tasks = solved_result.background_tasks
            if solved_result.errors:
                errors.extend(solved_result.errors)
                continue
            if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:
                solved = dependency_cache[sub_dependant.cache_key]
            elif (
                use_sub_dependant.is_gen_callable or use_sub_dependant.is_async_gen_callable
            ):
                use_astack = request_astack
                if sub_dependant.scope == "function":
                    use_astack = function_astack
                solved = await _solve_generator(
                    dependant=use_sub_dependant,
                    stack=use_astack,
                    sub_values=solved_result.values,
                )
            elif use_sub_dependant.is_coroutine_callable:
>               solved = await call(**solved_result.values)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: object AsyncCallableDependency can't be used in 'await' expression

fastapi/dependencies/utils.py:667: TypeError
=============================== 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
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 10 passed, 1 warning in 0.93s