Search code examples
python-trio

Combining trio and flask


I'm trying to make an HTTP API that can create and destroy concurrent tasks that open TCP connections to remote servers streaming ~15-second data. I'll have to figure out how to handle the data later. For now I just print it.

In the example below, I can create multiple TCP connections by navigating to http://192.168.1.1:5000/addconnection.

Questions:

1) Is this approach reasonable? I think Flask may be creating a new thread for each /addconnection request. I'm not sure what performance limits I'll hit doing that.

2) Is it possible to keep track of each connection? I'd like to implement /listconnections and /removeconnections.

3) Is there a more Pythonic way to do this? I've read a little about Celery, but I don't really understand it very well yet. Perhaps there are other already existing tools for handling similar problems.

import trio
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"


@app.route("/addconnection")
def addconnection():

    async def receiver(client_stream):
        print("Receiver: started!")
        while True:
            data = await client_stream.receive_some(16800)
            print("Received Data: {}".format(data))

    async def parent():
        async with trio.open_nursery() as nursery:
            client_stream = await trio.open_tcp_stream('192.168.1.1', 1234)
            nursery.start_soon(receiver, client_stream)

    trio.run(parent)


Solution

  • 1) You will create a new event loop for each /addconnection request which will block the Flask runtime. This will likely limit you to a single request per thread.

    2) Yes, in the simplest case you can store them in a global set, see connections below.

    3) I'm the author of Quart-Trio, which I think is a better way. Quart is the Flask API re-implemented with async/await (which solves most of 1)). Quart-Trio is an extension to use Trio rather than asyncio for Quart.

    Roughly (and I've not tested this) your code becomes,

    import trio
    from quart_trio import QuartTrio
    
    connections = set()
    
    app = QuartTrio(__name__)
    
    @app.route("/")
    async def hello():
        return "Hello World!"
    
    
    @app.route("/addconnection")
    async def addconnection():
    
        async def receiver(client_stream):
            print("Receiver: started!")
            while True:
                data = await client_stream.receive_some(16800)
                print("Received Data: {}".format(data))
    
        async def parent():
            async with trio.open_nursery() as nursery:
                client_stream = await trio.open_tcp_stream('192.168.1.1', 1234)
                connections.add(client_stream)
                nursery.start_soon(receiver, client_stream)
            connections.remove(client_stream)
    
        app.nursery.start_soon(parent)
        return "Connection Created"
    
    if __name__ == "__main__":
        # Allows this to run and serve via python script.py
        # For production use `hypercorn -k trio script:app`
        app.run()