diff --git a/eventkit/event.py b/eventkit/event.py index a3b405b..22ce345 100644 --- a/eventkit/event.py +++ b/eventkit/event.py @@ -346,7 +346,36 @@ def run(self) -> List: await event.list() """ loop = get_event_loop() - return loop.run_until_complete(self.list()) + + if loop.is_running(): + raise RuntimeError( + "Event.run() cannot be called from within an already running " + "asyncio event loop. Use 'await event.list()' instead." + ) + + async def _run_and_clean(): + # This coroutine will be run to completion. + result = await self.list() + + # Now, perform the explicit cleanup of any other background tasks. + current_loop = asyncio.get_running_loop() + all_tasks = asyncio.all_tasks(loop=current_loop) + current_task = asyncio.current_task(loop=current_loop) + + pending_tasks = [ + task + for task in all_tasks + if task is not current_task and not task.done() + ] + + if pending_tasks: + for task in pending_tasks: + task.cancel() + await asyncio.gather(*pending_tasks, return_exceptions=True) + + return result + + return loop.run_until_complete(_run_and_clean()) def pipe(self, *targets: "Event"): """ diff --git a/eventkit/util.py b/eventkit/util.py index da34ab9..c704a28 100644 --- a/eventkit/util.py +++ b/eventkit/util.py @@ -1,3 +1,5 @@ +"""Eventkit utilities.""" + import asyncio import datetime as dt from typing import AsyncIterator, Final @@ -18,8 +20,15 @@ def __repr__(self): def get_event_loop(): """Get asyncio event loop or create one if it doesn't exist.""" - loop = asyncio.get_event_loop_policy().get_event_loop() - return loop + try: + return asyncio.get_running_loop() + except RuntimeError: + try: + return asyncio.get_event_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + return loop async def timerange(start=0, end=None, step: float = 1) -> AsyncIterator[dt.datetime]: