Search code examples
pythonreadlineread-eval-print-loop

How to implement a python REPL that nicely handles asynchronous output?


I have a Python-based app that can accept a few commands in a simple read-eval-print-loop. I'm using raw_input('> ') to get the input. On Unix-based systems, I also import readline to make things behave a little better. All this is working fine.

The problem is that there are asynchronous events coming in, and I'd like to print output as soon as they happen. Unfortunately, this makes things look ugly. The "> " string doesn't show up again after the output, and if the user is halfway through typing something, it chops their text in half. It should probably redraw the user's text-in-progress after printing something.

This seems like it must be a solved problem. What's the proper way to do this?

Also note that some of my users are Windows-based.

TIA

Edit: The accepted answer works under Unixy platforms (when the readline module is available), but if anyone knows how to make this work under Windows, it would be much appreciated!


Solution

  • Maybe something like this will do the trick:

    #!/usr/bin/env python2.6
    
    from __future__ import print_function
    
    import readline
    import threading
    
    PROMPT = '> '
    
    def interrupt():
        print() # Don't want to end up on the same line the user is typing on.
        print('Interrupting cow -- moo!')
        print(PROMPT, readline.get_line_buffer(), sep='', end='')
    
    def cli():
        while True:
            cli = str(raw_input(PROMPT))
    
    if __name__ == '__main__':
        threading.Thread(target=cli).start()
        threading.Timer(2, interrupt).start()
    

    I don't think that stdin is thread-safe, so you can end up losing characters to the interrupting thread (that the user will have to retype at the end of the interrupt). I exaggerated the amount of interrupt time with the time.sleep call. The readline.get_line_buffer call won't display the characters that get lost, so it all turns out alright.

    Note that stdout itself isn't thread safe, so if you've got multiple interrupting threads of execution, this can still end up looking gross.