Search code examples
pythoncronfastapicoroutineschedule

When I am using repeat_at from fastapi_utilities it says "expected coroutine, got None"


I am writing a fastapi app that want to schedule in minutes and hourly, but when I am running this it says:

\base_events.py", line 436, in create_task
    task = tasks.Task(coro, loop=self, name=name, context=context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: a coroutine was expected, got None

My code:

import requests
from fastapi_utilities import repeat_at
from fastapi_offline import FastAPIOffline
import asyncio

app = FastAPIOffline()

task = None

@repeat_at(cron="*/1 * * * *")  # every 1 minute
async def call_api():
    global task
    url = "http://sssssss/both/execute_query/4"
    try:
        loop = asyncio.get_event_loop()
        response = await loop.run_in_executor(None, lambda: requests.get(url))
        response_json = response.json()
        print(response_json)
    except requests.exceptions.RequestException as e:
        print(f"Error fetching data: {e}")

    if task is None:
        while True:
            #print("Running task...")
            await asyncio.sleep(0)  # This is a checkpoint where the task can be cancelled
            break
    return response_json

@app.get("/trigger-call-api")
async def trigger_call_api():
    global task
    if task is None:
        print('*************', call_api(), call_api)
        task = asyncio.create_task(call_api())
        return {"message": "call_api triggered"}
    else:
        return {"message": "call_api is already running"}
        

@app.get("/stop-call-api")
async def stop_call_api():
    global task
    if task is not None:
        #print('me@',dir(task))
        task.cancel()
        task = None
        #print('meXC',task, type(task), task.cancel(), task.cancelled(), task.cancelling())
    else:
        return {"message": "call_api is not running"}


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="localhost", port=8001)

I'm figuring out that call_api is None and trying to fix it, but no effort becomes.


Solution

  • According to the source code of the @repeat_at decorator, it does not support that an async def function is called directly after it has been decorated, because it returns a normal def function (which will start the original function as a background task and return None, as you have identified).

    You can work around this by adding a wrapper around call_api which you decorate with @repeat_at, so that you can keep using the undecorated call_api normally.

    @repeat_at(cron="*/1 * * * *")  # every 1 minute
    async def call_api_repeat():
        await call_api()
    
    async def call_api():
        global task
        url = "http://sssssss/both/execute_query/4"
        try:
            loop = asyncio.get_event_loop()
            response = await loop.run_in_executor(None, lambda: requests.get(url))
            response_json = response.json()
            print(response_json)
        except requests.exceptions.RequestException as e:
            print(f"Error fetching data: {e}")
    
        if task is None:
            while True:
                #print("Running task...")
                await asyncio.sleep(0)  # This is a checkpoint where the task can be cancelled
                break
        return response_json