Notes on Using Curses with Python Asyncio

Curses has been around for a long time. Asyncio is more than a decade newer. Curses coding practices were well established by that time.

It came time for me to write my first curses application (needed a GUI with minimal infrastructure requirements), which needed asyncio capabilities. I wouldn’t have minded finding more information on the webs on how the two standard libraries interact.

Here’s what I eventually discovered after coding the app (with a bonus template to start your own python curses app).

It’s about Polling vs. Blocking I/O

The curses getch() call can operate in both a blocking and a non-blocking mode. Either will work in asyncio, with at least one caveat - if the blocking getch() is called in a separate thread via an asyncio executor, it will not return some events, notably KEY_RESIZE. A polling mechanism is therefore recommented, e.g.:

    self.stdscr.nodelay(True)

    while not self.done:
        char = self.stdscr.getch()
        if char == ERR:
            await asyncio.sleep(0.1)
        elif char == KEY_RESIZE:
            self.make_display()
        else:
            self.handle_char(char)

See a working demo template at curses_demo.py