Search code examples
pythonprompt-toolkit

How to use prompt-toolkit key binding with inner prompt


I have a prompt_toolkit application where I want a bunch of keys to be acted on immediately, without pressing Enter. It's not a fullscreen app, it's just using the bottom_toolbar. So I have set up key bindings:

class Annotator:
    def process(self):
        kb = KeyBindings()

        @kb.add("5")
        def _(event):
            self.buildJohnnyFive()

        @kb.add("s")
        def _(event):
            self.needInput()

        text = prompt("> ", bottom_toolbar=self.bottomToolbar, key_bindings=kb)
        print(f"You said: {text}")

    def buildJohnnyFive(self):
        self.buildHim() # Do work

    def needInput(self):
        raw = prompt("input> ", bottom_toolbar=self.bottomToolbar)
        self.useInput(raw)

    def bottomToolbar(self):
        return f"Current: {self.status1} - {self.status2}"

This works well, and the functions get called. However, in some of these functions I need to accept more specific input from the user. But when I call prompt() from inside the function bound to a key, I get an exception:

  File "/opt/homebrew/Cellar/[email protected]/3.10.13_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/Users/nick/my_app/.venv/lib/python3.10/site-packages/prompt_toolkit/application/application.py", line 714, in read_from_input_in_context
    context.copy().run(read_from_input)
  File "/Users/nick/my_app/.venv/lib/python3.10/site-packages/prompt_toolkit/application/application.py", line 694, in read_from_input
    self.key_processor.process_keys()
  File "/Users/nick/my_app/.venv/lib/python3.10/site-packages/prompt_toolkit/key_binding/key_processor.py", line 273, in process_keys
    self._process_coroutine.send(key_press)
  File "/Users/nick/my_app/.venv/lib/python3.10/site-packages/prompt_toolkit/key_binding/key_processor.py", line 188, in _process
    self._call_handler(matches[-1], key_sequence=buffer[:])
  File "/Users/nick/my_app/.venv/lib/python3.10/site-packages/prompt_toolkit/key_binding/key_processor.py", line 323, in _call_handler
    handler.call(event)
  File "/Users/nick/my_app/.venv/lib/python3.10/site-packages/prompt_toolkit/key_binding/key_bindings.py", line 127, in call
    result = self.handler(event)
  File "/Users/nick/my_app/src/annotator.py", line 110, in _
    self.addCustomED()
  File "/Users/nick/my_app/src/annotator.py", line 145, in needInput
    raw = prompt("input> ", bottom_toolbar=self.bottom_toolbar)
  File "/Users/nick/my_app/.venv/lib/python3.10/site-packages/prompt_toolkit/shortcuts/prompt.py", line 1425, in prompt
    return session.prompt(
  File "/Users/nick/my_app/.venv/lib/python3.10/site-packages/prompt_toolkit/shortcuts/prompt.py", line 1035, in prompt
    return self.app.run(
  File "/Users/nick/my_app/.venv/lib/python3.10/site-packages/prompt_toolkit/application/application.py", line 1002, in run
    return asyncio.run(coro)
  File "/opt/homebrew/Cellar/[email protected]/3.10.13_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/asyncio/runners.py", line 33, in run
    raise RuntimeError(

Exception asyncio.run() cannot be called from a running event loop

I understand the problem: I'm already running a prompt(), which hasn't ended yet, and now I'm asking for another, inner prompt which conflicts with the existing event loop.

What I don't understand is: how am I supposed to do this? I need to call the initial prompt so that the user gets the info in the bottom toolbar. There needs to be a run loop so that the app doesn't quit before the user has time to press keys. But then how am I supposed to get input at this inner function? Can I somehow end or suspend the outer prompt while the inner prompt is waiting for the user's input? I don't want to switch to doing everything on the one prompt, because then the user will have to press Return every time. And I actually don't WANT the terminal to fill up with lines of input commands.

Some guidance would be much appreciated.


Solution

  • I couldn't test it but in source code for prompt() I found information about option in_thread

    (you can find it also in documentation for prompt())

    in_thread: Run the prompt in a background thread; block the current thread. 
               This avoids interference with an event loop in the current thread. 
    

    So maybe it will work

    prompt(">", in_thread=True, ...)