prompt-toolkit / python-prompt-toolkit

Library for building powerful interactive command line applications in Python
https://python-prompt-toolkit.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
9.27k stars 715 forks source link

How do I fix Exception This event loop is already running with KeyBindings? #1166

Open ninest opened 4 years ago

ninest commented 4 years ago

I'll show this issue with a small example and error I get. I'm making a notes CLI app where first the user is presented with a list of notes. They can enter a number (0-9) to go to the specific note. Once they are in the note, there's a timer for 1 second. Once this timer is complete, they will go back to the main page, where they can see all the notes again.

Here's the code:

import os
import time

from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit import prompt

notes = [
    {'t': 'First note', 'c': 'This is the content of the first notes'},
    {'t': 'Second note', 'c': 'This is the content of the second notes'},
    {'t': 'Third note', 'c': 'This is actually the content of the thid notes'}
]

os.system('clear')

def main():
  kb = KeyBindings()
  # control C to exit
  @kb.add('c-c')
  def _(event): exit()

  for i in range(10):
    # dynamically add keybindings 0 to 9 for each note
    @kb.add(f'{i}')
    def _(event):
      note_index = event.key_sequence[0].key
      goto_note(note_index)

  def show_notes():
    # show all notes and listen for key bindings
    for index, note in enumerate(notes):
      print(f'[{index}] {note["t"]}')
    _inp = prompt('> ', key_bindings=kb)

  def goto_note(index):
    # show separate notes
    print(f'going to note {index}')
    # after a second, go back to the main screen
    time.sleep(1)
    show_notes() # <--- error occurs (Exception This event loop is already running)

  show_notes()

if __name__ == '__main__':
  main()

The code works fine until I want to show the main screen after 1 second in the notes page. The error I get is:

Exception This event loop is already running

I believe this is because I am referencing the variable kb inside functions that are decorated by @kb.add.

Is there a way to fix this?

jonathanslenders commented 4 years ago

It is not possible to call prompt() within a key binding, because that would spawn a new event loop.

Instead, the key binding should return the result. event.app.exit(result=...) and the place where prompt() was called in the first place should handle the outcome and spawn a new prompt accordingly.

Also, remember that a for-loop does not create a scope, so the value of "i" will always be 9 for each of the key bindings in the above code.