Repository: tiangolo/fastapi

Issue:
Async class method dependency raises a ValueError
**Describe the bug**
If you use an async class method as a dependency, a `ValueError` is thrown. It doesn't happen for a non-async method.

Complete error: `ValueError: [KeyError(<class 'coroutine'>), TypeError("'coroutine' object is not iterable"), TypeError('vars() argument must have __dict__ attribute')]` (at `fastapi/encoders.py:106`)

**To Reproduce**

```py
from fastapi import Depends, FastAPI
from starlette.requests import Request

class DependencyClass:
    async def async_dep(self, request: Request):
        return True

    def sync_dep(self, request: Request):
        return True

app = FastAPI()
dependency = DependencyClass()

# Error
@app.get('/async-dep')
def async_dep(r=Depends(dependency.async_dep)):
    return r

# Everything is fine
@app.get('/sync-dep')
def sync_dep(r=Depends(dependency.sync_dep)):
    return r
```

**Expected behavior**
The async class method dependency should be called and its return value injected.

**Environment:**
 - OS: macOS
 - FastAPI Version: 0.42.0
- Python version: 3.7.2

**Additional context**
I believe the issue comes from here:

https://github.com/tiangolo/fastapi/blob/65536cbf63318d111bf608960378d651b6c1596a/fastapi/dependencies/utils.py#L353-L359

Indeed, `inspect.isfunction(call)` will return `False` in case of a class method. Hence, it is [sent to `run_in_threadpool`](https://github.com/tiangolo/fastapi/blob/65536cbf63318d111bf608960378d651b6c1596a/fastapi/dependencies/utils.py#L453-L456), which never awaits the coroutine, and we end up trying to serialize it instead of its result (hence the `ValueError`).

Changing the check by:

```py
if inspect.isfunction(call) or inspect.ismethod(call):
```

solves the issue. I can make a PR with the fix and unit tests if it helps.


Discussion:
not sure what the "right" behavior should be but in your `async_dep` (wich itself is not async) you return a coroutine r without awaiting it so it's kind of expected that it doesn't return it's value, is it ?

somehting like this "works" even if I fail to see the use case
```
import asyncio

import uvicorn
from fastapi import Depends, FastAPI
from starlette.requests import Request


class DependencyClass:
    async def async_dep(self, request: Request):
        await asyncio.sleep(0)
        return False

    def sync_dep(self, request: Request):
        return True


app = FastAPI()
dependency = DependencyClass()

# Error
@app.get('/async-dep')
async def authenticate(r=Depends(dependency.async_dep)):
    s = await r
    return s

# Everything is fine
@app.get('/sync-dep')
def authenticate(r=Depends(dependency.sync_dep)):
    return r


if __name__ == '__main__':
    uvicorn.run("679_async_dep_class:app", reload= True)
```

Well yes, for the sake of simplicity in the example, my async method don't do anything async, but still is a coroutine that needs to be awaited.

The use case is inspired from the async class dependencies, like `OAuth2PasswordBearer`:

https://github.com/tiangolo/fastapi/blob/65536cbf63318d111bf608960378d651b6c1596a/fastapi/security/oauth2.py#L138-L163

`__call__` *is* async here and, when you inject this dependency, we do get `param`, we don't need to `await` it in the controller function.

So, I think we should be able to do something similar with class methods. For example:

```py
class Authentication:
    def __init__(self, params):
        self.params = params

    async def get_user(self, request: Request) -> User:
        return await self._authenticate(request)

    async def get_active_user(self, request: Request) -> User:
        user = await self._authenticate(request)
        if not user.is_active:
            raise HTTPException()
        return user

    async def _authenticate(self, request: Request) -> User:
        # Do some authentication logic
        return user

authentication = Authentication(params)

@app.get('/active-user')
def active_user(user=Depends(authentication.get_active_user)):
    return user
```

This is useful to share common logic between all the dependencies while providing some specialized behaviour.

IMO, this is clearly a bug as it works flawlessly with async `__call__` and non-async methods.
makes lot of sense indeed, was stuck on your version before edit
This makes sense to me; @frankie567 I think it's worth a PR.

Resolving patch:
diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -351,7 +351,7 @@ def add_param_to_fields(*, field: Field, dependant: Dependant) -> None:
 
 
 def is_coroutine_callable(call: Callable) -> bool:
-    if inspect.isfunction(call):
+    if inspect.isroutine(call):
         return asyncio.iscoroutinefunction(call)
     if inspect.isclass(call):
         return False


Test patch:
diff --git a/tests/test_dependency_class.py b/tests/test_dependency_class.py
new file mode 100644
--- /dev/null
+++ b/tests/test_dependency_class.py
@@ -0,0 +1,70 @@
+import pytest
+from fastapi import Depends, FastAPI
+from starlette.testclient import TestClient
+
+app = FastAPI()
+
+
+class CallableDependency:
+    def __call__(self, value: str) -> str:
+        return value
+
+
+class AsyncCallableDependency:
+    async def __call__(self, value: str) -> str:
+        return value
+
+
+class MethodsDependency:
+    def synchronous(self, value: str) -> str:
+        return value
+
+    async def asynchronous(self, value: str) -> str:
+        return value
+
+
+callable_dependency = CallableDependency()
+async_callable_dependency = AsyncCallableDependency()
+methods_dependency = MethodsDependency()
+
+
+@app.get("/callable-dependency")
+async def get_callable_dependency(value: str = Depends(callable_dependency)):
+    return value
+
+
+@app.get("/async-callable-dependency")
+async def get_callable_dependency(value: str = Depends(async_callable_dependency)):
+    return value
+
+
+@app.get("/synchronous-method-dependency")
+async def get_synchronous_method_dependency(
+    value: str = Depends(methods_dependency.synchronous),
+):
+    return value
+
+
+@app.get("/asynchronous-method-dependency")
+async def get_asynchronous_method_dependency(
+    value: str = Depends(methods_dependency.asynchronous),
+):
+    return value
+
+
+client = TestClient(app)
+
+
+@pytest.mark.parametrize(
+    "route,value",
+    [
+        ("/callable-dependency", "callable-dependency"),
+        ("/async-callable-dependency", "async-callable-dependency"),
+        ("/synchronous-method-dependency", "synchronous-method-dependency"),
+        ("/asynchronous-method-dependency", "asynchronous-method-dependency"),
+    ],
+)
+def test_class_dependency(route, value):
+    response = client.get(route, params={"value": value})
+    assert response.status_code == 200
+    assert response.json() == value