Search code examples
pythonfastapiuvicorn

how to design a fastapi app with independent background computation?


I've created a python main application main.py, which I invoke with uvicorn main.main --reload. Which of course runs the following code...

if __name__ == '__main__':
    main()

That part of the application runs constantly, reads data an processes it until the application is aborted manually. I use asyncio to run coroutines.

Task

I would like to build a small html dashboard on it, which can display the data that is constantly computed.

Question

How can I run these background calculations of main.py and still implement a dashboard/website with fastapi and jinja2?

  • What is the best practice/architecture to structure the files: the background and fastapi app code? e.g. Is there a initial startup function in fastapi where I could invoke the background computation in a coroutine or the other way around?
  • How would you invoke the application according to your recommendation?

What I have achieved so far

I can run the main application without any fastapi code. And I can run the dashboard without the background tasks. Both work fine independently. But fastapi does not run, when I add its code to the main application with the background computation. (How could it?!? I can only invoke either the main application or the fastapi app.)

Any architectural concepts are appreciated. Thank you.


Solution

  • A good approch is to use the on_event decorator with startup. The only thing to remain is to use asyncio.create_task to invoke the background task. As long as you don't await it, it will not block and thus fastapi/uvicorn can continue to serve any http request.

    my_service = MyService()
    
    @app.on_event('startup')
    async def service_tasks_startup():
        """Start all the non-blocking service tasks, which run in the background."""
        asyncio.create_task(my_service.start_processing_data())
    

    Also, with this said, any request can consume the data of this background service.

    @app.get("/")
    def root():
        return my_service.value
    

    Think of MyService as any class of your liking. Kafka consumption, computations, etc. Of course, the value is just an example attribute of the Class MyService.