In Python 3.12+, tty.setraw is already returning the old settings, you don't need to do it yourself. And, in fact, the tty module is re-using all the symbols from the termios module, so you can only use it in lieu of termios, giving you something like:
def prompt_yes_no():
fd = sys.stdin.fileno()
old = tty.setraw(fd)
try:
print('Do you want to continue? (y/n): ', end='', flush=True)
while True:
match sys.stdin.read(1).lower():
case 'y':
return True
case 'n':
return False
case _ as char:
print(repr(char), 'is not a recognized input', end='\r\n')
print('Do you want to continue? (y/n): ', end='', flush=True)
finally:
tty.tcsetattr(fd, tty.TCSADRAIN, old)
But the try … finally really feels like it would be better as a context manager:
import sys
import tty
import contextlib
@contextlib.contextmanager
def make_raw(stream=sys.stdin):
fd = stream.fileno()
old = tty.setraw(fd)
try:
yield stream
finally:
tty.tcsetattr(fd, tty.TCSADRAIN, old)
And if you really want a function to prompt yes or no, you can build on it, thus better separating concerns:
def prompt_yes_no(prompt='>', default_to=True):
with make_raw() as stream:
while True:
print(prompt, end=' ', flush=True)
match stream.read(1):
case 'y' | 'Y':
return True
case 'n' | 'N':
return False
case '\r':
return default_to
case _ as char:
print(repr(char), 'is not a recognized input', end='\r\n')
Or you can just call the proper function in each cases… YMMV.
However, all that being said, setraw is way too heavy for the job. Especially given the fact that it breaks user expectations about how the terminal should behave. Not only do you need special care to properly handle outputs (see the end='\r\n' bit in the code snippets) and inputs (getting only \r when the user presses the enter key instead of the usual \n), it also ignores the SIGINT signal leaving the user unable to Ctrl+C while on your prompt.
I would, instead, recommend the use of tty.setcbreak that still have good properties for what you’re trying to achieve but leave the terminal in a more user-friendly state. Note that, doing so, you will need to change the case '\r' into a case '\n' if you want to handle a default value.