5

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.

Matthieu Brucher
  • 21,634
  • 7
  • 38
  • 62
Cracoras
  • 347
  • 3
  • 16
  • Hi, I'm curious if my answer helped you with your issue? – user4815162342 Feb 26 '18 at 21:24
  • Hi yes it did answer my main question which was how to get the event loop. Because I read somewhere that Quart was built on top of asyncio I would obtain the lopp from a method implemented by Quart and inherited from asyncio. However I didn't finish the implementation with the suggestion yet because in my case I have an application with multiple modules and I am trying to figure out where to first get tne loop and pass around to the other modules. – Cracoras Feb 27 '18 at 01:42
  • You don't need to pass it around; you can just call `asyncio.get_event_loop()` wherever you need it. The code that passes loop around explicitly was needed in old versions of asyncio where `get_event_loop()` was not guaranteed to give you the correct event loop when called from a coroutine. – user4815162342 Feb 27 '18 at 07:33

1 Answers1

4

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()
user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • One follow-up question on your answer. I am really sorry if this is obvious, but I am just getting started with Python.In the first scenario you described, I am assuming I add the code to initialize the Quart and Goblin objects in the init.py file for my application, and call the run on the Quart app. Then on a separate controller module, I want to need to use the Goblin object/app to send transactions to the database. How can I modularize this initialization so that the Goblin object becomes available across all modules and also the Quart app can be used for the routes ? – Cracoras Mar 23 '18 at 21:15
  • @Cracoras I haven't personally used Quart nor Goblin, so I may be misunderstanding your question. The second part of the answer tries to show that you **don't** need to call run on the Quart app, you can prepare both goblin and quart independently of each other, and then spin up the event loop that accommodates both. – user4815162342 Mar 26 '18 at 19:09
  • this manual server creation doesn't work for me. I don't see any server running at 127.0.0.1 it is not listening. – nurettin Dec 18 '18 at 13:17
  • By default, `Quart.run` uses `asyncio.run()`, which creates a new asyncio event loop. contradicts "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. " – Smart Manoj Jun 27 '19 at 12:21
  • 2
    @SmartManoj At the time this answer was written, `asyncio.run()` was not yet available. (It was introduced in Python 3.7.0 in June 2018.) If `Quart.run` now uses `asyncio.run()`, that's technically an incompatible change that invalidates the first part of the response. The second part should still work just fine, and should probably be updated to use `asyncio.run()` itself. – user4815162342 Jun 27 '19 at 12:47