Python asyncio ensure event loop utility function

Here’s a utility function that safely ensures an event loop is running in a Python asyncio application.

This is useful because of the discrepancy in expectations between the asyncio library and how it ends up being used in the real world in a lot of applications. The asyncio library reasonably expects users to be in control of the event loop for the whole application lifecycle, but this becomes complicated in various situations:

Unfortunately it’s not safe or reliable to call functions such as asyncio.get_event_loop() or asyncio.run_until_complete() without knowing the current context.

The ensure_event_loop() below aims to be a safe and reliable way to ensure an event loop is running and to access that event loop in most contexts. It is important to test this thoroughly before deploying it to production, though.

import asyncio
from asyncio import AbstractEventLoop

def ensure_event_loop() -> AbstractEventLoop:
    """
    Ensure that an event loop is running, by taking an existing open
    event loop, or creating and setting a new one.

    This is helpful because no event loop is running by default, and
    asyncio expects users to be in control of the top-level entry point
    to the async program.
    In reality that is often not feasible, due to various third party
    libraries having their own interactions with the event loop which are
    out of our control. In addition, control of the event loop is different
    between tests (where pytest-asyncio manages the top-level entry point),
    and when it runs in a deployment environment.
    """
    if open_loop := __get_open_event_loop():
        return open_loop
    new_loop = asyncio.new_event_loop()
    asyncio.set_event_loop(new_loop)
    return new_loop


def _get_open_event_loop() -> AbstractEventLoop | None:
    try:
        loop = asyncio.get_event_loop()
        if loop.is_running() and not loop.is_closed():
            return loop
        return None
    except RuntimeError:
        """
        Catching this RuntimeError is the only way to determine that there is
        no event loop.
        https://github.com/python/cpython/blob/main/Lib/asyncio/events.py#L708
        """
        return None

Tech mentioned