From 5dc6bbd440ac46e81a926b6749969b98b7e33a9f Mon Sep 17 00:00:00 2001 From: "Ashwin V. Mohanan" <9155111+ashwinvis@users.noreply.github.com> Date: Fri, 24 Sep 2021 14:13:47 +0200 Subject: [PATCH] fix: async_run to allow nested event loops. (#1170) * Fix async_run to allow nested event loops. Fixes: https://github.com/snakemake/snakemake/issues/1105 * Use standard asyncio.run in outer loop for tests --- snakemake/common/__init__.py | 18 +++++++++++++++++- tests/testapi.py | 25 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/snakemake/common/__init__.py b/snakemake/common/__init__.py index 6b3f98b87..d8832dc2e 100644 --- a/snakemake/common/__init__.py +++ b/snakemake/common/__init__.py @@ -37,7 +37,23 @@ def async_run(coroutine): else: - async_run = asyncio.run + + def async_run(coroutine): + """Attaches to running event loop or creates a new one to execute a + coroutine. + + .. seealso:: + + https://github.com/snakemake/snakemake/issues/1105 + https://stackoverflow.com/a/65696398 + + """ + try: + _ = asyncio.get_running_loop() + except RuntimeError: + asyncio.run(coroutine) + else: + asyncio.create_task(coroutine) # A string that prints as TBD diff --git a/tests/testapi.py b/tests/testapi.py index 3264e5b13..0e741f772 100644 --- a/tests/testapi.py +++ b/tests/testapi.py @@ -2,6 +2,8 @@ Tests for Snakemake’s API """ from snakemake import snakemake +import asyncio +import sys import tempfile import os.path from textwrap import dedent @@ -32,3 +34,26 @@ def test_run_script_directive(): file=f, ) snakemake(path, workdir=tmpdir) + + +def test_run_script_directive_async(): + """Tests :func`snakemake.common.async_run`. The test ensure the ability to + execute Snakemake API even if an asyncio event loop is already running. + + """ + import tracemalloc + from snakemake.common import async_run + + tracemalloc.start() + + async def dummy_task(): + await asyncio.sleep(0.00001) + + async def main(): + async_run(dummy_task()) + test_run_script_directive() + + if sys.version_info < (3, 7): + async_run(main()) + else: + asyncio.run(main())