tmbo / questionary

Python library to build pretty command line user prompts ✨Easy to use multi-select lists, confirmations, free text prompts ...
MIT License
1.57k stars 90 forks source link

Asking a blocking question in an async execution breaks the event loop (RuntimeError: This event loop is already running) #396

Open Alexander-Serov opened 3 months ago

Alexander-Serov commented 3 months ago

Describe the bug

I am writing a series of coroutines, which process data in parallel (async), but at some point may ask the user for confirmation. I would like to implement this question as a blocking questionary call, but this breaks the event loop, apparently trying to re-enter the same loop. I was wondering if I'm doing something wrong or do you see another way of implementing it.

Note: I wouldn't like to implement an async ask because the asks from different coroutines get mixed up. So this is not an option.

Example

import asyncio
import random

import questionary

async def background_task():
    await asyncio.sleep(random.randint(1, 5))
    if questionary.confirm('Do you want to continue?').ask():
        print('Confirmed')

async def main():
    tasks = []
    for _ in range(4):
        tasks.append(asyncio.create_task(background_task()))

    for task in asyncio.as_completed(tasks):
        await task

asyncio.run(main())

Steps to reproduce

Run the code above with python=3.8 and questionary==2.0.1

Expected behaviour

Each of the tasks stops to ask for confirmation and then the event loop continues. Instead the code above returns this error:

/Users/user/miniforge3/envs/clients/bin/python /Users/user/Library/Application Support/JetBrains/PyCharm2024.1/scratches/scratch_52.py 
Warning: Input is not a terminal (fd=0).
Traceback (most recent call last):
  File "/Users/user/Library/Application Support/JetBrains/PyCharm2024.1/scratches/scratch_52.py", line 21, in <module>
    asyncio.run(main())
  File "/Users/user/miniforge3/envs/clients/lib/python3.8/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/Users/user/miniforge3/envs/clients/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
    return future.result()
  File "/Users/user/Library/Application Support/JetBrains/PyCharm2024.1/scratches/scratch_52.py", line 19, in main
    await task
  File "/Users/user/miniforge3/envs/clients/lib/python3.8/asyncio/tasks.py", line 619, in _wait_for_one
    return f.result()  # May raise f.exception().
  File "/Users/user/Library/Application Support/JetBrains/PyCharm2024.1/scratches/scratch_52.py", line 9, in background_task
    if questionary.confirm('Do you want to continue?').ask():
  File "/Users/user/miniforge3/envs/clients/lib/python3.8/site-packages/questionary/question.py", line 64, in ask
    return self.unsafe_ask(patch_stdout)
  File "/Users/user/miniforge3/envs/clients/lib/python3.8/site-packages/questionary/question.py", line 89, in unsafe_ask
    return self.application.run()
  File "/Users/user/miniforge3/envs/clients/lib/python3.8/site-packages/prompt_toolkit/application/application.py", line 978, in run
    return loop.run_until_complete(
  File "/Users/user/miniforge3/envs/clients/lib/python3.8/asyncio/base_events.py", line 592, in run_until_complete
    self._check_running()
  File "/Users/user/miniforge3/envs/clients/lib/python3.8/asyncio/base_events.py", line 552, in _check_running
    raise RuntimeError('This event loop is already running')
RuntimeError: This event loop is already running
sys:1: RuntimeWarning: coroutine 'Application.run_async' was never awaited

Process finished with exit code 1

Something similar has been asked here, but the error is different.

Latest version

Questionary version

2.0.1

Prompt Toolkit version

3.0.36

Operating System

macOS