1

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.

feder
  • 1,849
  • 2
  • 25
  • 43

2 Answers2

2

Fastapi doesn't run because it cant be reached by python interpreter untill it complete your computations. You should start your web app independently of the main process, I strongly recommend you to use docker-compose. As fastapi recommends you, you should use Dramatiq or Celery for huge background tasks, or you can just run separate service in compose services, for example:

# background.py
if __name__ == '__main__':
    main()

# main.py
app = FastAPI()

docker-compose.yml:


services:
  web-app-interface:
    command: uvicorn main.main ...
  my-daemon:
    command: python background.py

You can make them communicate with a message broker, such as RabbitMQ etc. And never use multiprocessing with uvicorn, it can cause process leak, bcz uvicorn rules it's own workers.

Mastermind
  • 454
  • 3
  • 11
  • thanks and yes, that is how I would do it as well. But... I consume data from a kafka topics (in the background process) and then I would want to append the new events to a dataframe (it doesn't matter if the dataframe is lost upon restart). The fastapi REST calls should then return fractions of that dataframe. Thus, I cannot separate the applications. Do you understand the constraint? – feder Mar 10 '22 at 16:03
  • You can write result of background task to database and read it in your api endpoints. – Mastermind Mar 10 '22 at 17:52
  • I feel saving it to a database makes little sense since I consume messages, thus they are ALREADY persisted. Repersisting is of no value. The dashboard depends on these messages ( I want to push them with websockets to the dashboard). Thanks for sharing your thoughts. – feder Mar 15 '22 at 06:23
  • If you don't really need to save them, you can use any MQ, as I mentioned before. – Mastermind Mar 15 '22 at 15:45
  • Yegor, it makes very little sense to copy an incoming message of a kafka topic again to another MQ. I see no value in duplicating an action (since I know the kafka infrastructure is under my control). But be certain, you have my gratitude for sharing your ideas. Appreciated. – feder Mar 16 '22 at 06:48
1

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.

feder
  • 1,849
  • 2
  • 25
  • 43