Here is a working example (Tested with Python 3.8.6 on Linux). See this answer if you need to modify for other systems.
The hinput function reads chars as they are typed and calls on_char for each new character passing both the new char and the entire line.
In my example when the user types x the on_char function returns True which causes the hinput function to stop waiting for new input.
If the user types hello it is autocompleted to hello, world and also terminates hinput
import sys
import termios
import tty
from typing import Callable
def main():
    hinput("prompt: ", on_char)
    return 0
def on_char(ch: str, line: str) -> bool:
    if ch == 'x':
        sys.stdout.write('\n')
        sys.stdout.flush()
        return True
    if line+ch == 'hello':
        sys.stdout.write("%s, world\n" % ch)
        sys.stdout.flush()
        return True
    return False
def hinput(prompt: str=None, hook: Callable[[str,str], bool]=None) -> str:
    """input with a hook for char-by-char processing."""
    fd = sys.stdin.fileno()
    old = termios.tcgetattr(fd)
    inpt = ""
    while True:
        sys.stdout.write('\r')
        if prompt is not None:
            sys.stdout.write(prompt)
        sys.stdout.write(inpt)
        sys.stdout.flush()
            
        ch = None
        try:
            tty.setraw(sys.stdin.fileno())
            ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old)
        if hook is not None and hook(ch, inpt):
            break
        if ord(ch) == 0x7f: #BACKSPACE
            if len(inpt) > 0:
                sys.stdout.write('\b \b')
                inpt = inpt[:-1]
            continue
        if ord(ch) == 0x0d: #ENTER
            sys.stdout.write('\n')
            sys.stdout.flush()
            break
        if ch.isprintable():
            inpt += ch
            
    return inpt
if __name__ == '__main__':
    sys.exit(main())