Search code examples
pythonpython-asyncioquartgoblin

How to obtain an event loop from Quart


Hello I fairly new to Python and I am trying to convert an existing application I have on Flask into Quart (https://gitlab.com/pgjones/quart) which is supposed to be built on top of asyncio, so I can use Goblin OGM to interact with JanusGraph or TinkerPop. According to the examples I found on Goblin I need to obtain an event loop to run the commands asynchronously.

    >>> import asyncio
    >>> from goblin import Goblin

    >>> loop = asyncio.get_event_loop()
    >>> app = loop.run_until_complete(
    ...     Goblin.open(loop))
    >>> app.register(Person, Knows)

However I can't find a way to obtain the event loop from Quart even though it is build on top of asyncio.

Does anyone know how I can get that ? Any help will be highly appreciated.


Solution

  • TL;DR To obtain the event loop, call asyncio.get_event_loop().

    In an asyncio-based application, the event loop is typically not owned by Quart or any other protocol/application level component, it is provided by asyncio or possibly an accelerator like uvloop. The event loop is obtained by calling asyncio.get_event_loop(), and sometimes set with asyncio.set_event_loop().

    This is what quart's app.run() uses to run the application, which means it works with the default event loop created by asyncio for the main thread. In your case you could simply call quart's run() after registering Goblin:

    loop = asyncio.get_event_loop()
    goblin_app = loop.run_until_complete(Goblin.open(loop))
    goblin_app.register(Person, Knows)
    quart_app = Quart(...)
    # ... @app.route, etc
    
    # now they both run in the same event loop
    quart_app.run()
    


    The above should answer the question in the practical sense. But that approach wouldn't work if more than one component insisted on having their own run() method that spins the event loop - since app.run() doesn't return, you can only invoke one such function in a thread.

    If you look more closely, though, that is not really the case with quart either. While Quart examples do use app.run() to serve the application, if you take a look at the implementation of app.run(), you will see that it calls the convenience function run_app(), which trivially creates a server and spins up the main loop forever:

    def run_app(...):
        loop = asyncio.get_event_loop()
        # ...
        create_server = loop.create_server(
            lambda: Server(app, loop, ...), host, port, ...)
        server = loop.run_until_complete(create_server)
        # ...
        loop.run_forever()
    

    If you need to control how the event loop is actually run, you can always do it yourself:

    # obtain the event loop from asyncio
    loop = asyncio.get_event_loop()
    
    # hook Goblin to the loop
    goblin_app = loop.run_until_complete(Goblin.open(loop))
    goblin_app.register(Person, Knows)
    
    # hook Quart to the loop
    quart_server = loop.run_until_complete(loop.create_server(
            lambda: quart.serving.Server(quart_app, loop), host, port))
    
    # actually run the loop (and the program)
    try:
        loop.run_forever()
    except KeyboardInterrupt:  # pragma: no cover
        pass
    finally:
        quart_server.close()
        loop.run_until_complete(quart_server.wait_closed())
        loop.run_until_complete(loop.shutdown_asyncgens())
        loop.close()