6

Why does adding async to Fastapi function gives me the "'coroutine' object is not iterable" error

I only get the error after I add the async keyword at the front of my function as follows, when I call the function/endpoint using Swagger UI:

@router.post("/create")
async def job_create_post_view(
    request: Request, 
    is_htmx=Depends(is_htmx), 
    db:Session=Depends(get_db),
    short_description: str = Form(default=None),
    long_description: str = Form(default=None),
   
   .....
    
    job_image:Optional[UploadFile]=File(...)
    ):
    

The error is as follows:

[TypeError("'coroutine' object is not iterable"), TypeError('vars() argument must have __dict__ attribute')]

I am trying to do something asynchronous inside the function:

contents = await job_image.read()

This is the stack trace:

Traceback (most recent call last):
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 366, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 75, in __call__
    return await self.app(scope, receive, send)
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\fastapi\applications.py", line 269, in __call__
    await super().__call__(scope, receive, send)
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\starlette\applications.py", line 124, in __call__
    await self.middleware_stack(scope, receive, send)
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\starlette\middleware\errors.py", line 184, in __call__
    raise exc
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\starlette\middleware\errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\starlette\middleware\cors.py", line 92, in __call__
    await self.simple_response(scope, receive, send, request_headers=headers)
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\starlette\middleware\cors.py", line 147, in simple_response
    await self.app(scope, receive, send)
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\starlette\middleware\authentication.py", line 48, in __call__
    await self.app(scope, receive, send)
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\starlette\exceptions.py", line 93, in __call__
    raise exc
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\starlette\exceptions.py", line 82, in __call__
    await self.app(scope, receive, sender)
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\fastapi\middleware\asyncexitstack.py", line 21, in __call__
    raise e
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\fastapi\middleware\asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\starlette\routing.py", line 670, in __call__
    await route.handle(scope, receive, send)
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\starlette\routing.py", line 266, in handle
    await self.app(scope, receive, send)
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\starlette\routing.py", line 65, in app
    response = await func(request)
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\fastapi\routing.py", line 235, in app
    response_data = await serialize_response(
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\fastapi\routing.py", line 149, in serialize_response
    return jsonable_encoder(response_content)
  File "D:\TEMP\job_search - revert\venv\lib\site-packages\fastapi\encoders.py", line 144, in jsonable_encoder
    raise ValueError(errors)
ValueError: [TypeError("'coroutine' object is not iterable"), TypeError('vars() argument must have __dict__ attribute')]
Julie An
  • 81
  • 1
  • 1
  • 5
  • How are you calling the function? What is the `@login_required` decorator? What is the stack trace to where the error gets thrown? – MatsLindh Jun 14 '22 at 06:45
  • You removed the `login_required` decorator from the source now, but that may very well be the cause of your error - are you still running the code with it? Do you have a small view function that shows the issue? – MatsLindh Jun 14 '22 at 07:53
  • Yes it worked after I removed the decorator. Am I not allowed to use a decorator with async functions? – Julie An Jun 14 '22 at 08:49
  • 2
    The [function that you return from your decorator will need to be a async function as well in that case](https://stackoverflow.com/questions/42043226/using-a-coroutine-as-decorator), so you have to at least make sure that everything gets handled correctly inside your decorator. However, usually you don't use decorators like that with FastAPI, but uses the `Depends` injection mechanism instead (also available as `Security` for things like handling the user being logged in, etc). – MatsLindh Jun 14 '22 at 09:04

3 Answers3

7

I had the same error.
I found out that the problem was from calling a async function without putting await before calling it.

The function which I was trying to call, was like something like this:

async def func1():
    ...

async def func2():
    x = func1()
    ...

So, the problem got resolved when I edited like this:

async def func1():
    ...

async def func2():
    x = await func1()
    ...
3

[TypeError("'coroutine' object is not iterable"), TypeError('vars() argument must have __dict__ attribute')] usually happened when the developer made a mistake inside the async function like this [Mistake]:

@app.get("/")
 async def read_root():
    return fetch_all_todos()

but the correct behavior for async is like this [Correct]:

@app.get("/")
async def read_root():
    response = await fetch_all_todos()
    return response

You need to put await in a variable and then return it.

Tawfeeq Amro
  • 605
  • 7
  • 15
0

I faced the issue while using Celery with Redis in FastAPI. This was the code where the main problem raised:

@router.post('/bg_test')
def bg_test(text: str):
    return create_task.delay(text=text)

As the create_task is a celery task, I can't return the result in the main router, rather I need to initiate the run and handle the rest in celery and return the task id here. Here is the working version:

@router.post('/bg_test')
def bg_test(text: str):
    task = create_task.delay(text=text)
    return task.id