Async IO - reading char from input blocks output - python

Note: this example was tested on a linux terminal emulator, and due to the use of termios (which I have no idea if it's cross-platform) it might not work well on other operating systems' terminals.
I've been trying to make an "asynchronous" python prompt. What I mean by that is that, while the user is typing an input from a prompt, they can also receive messages, without cancelling the input.
Below is an implementation of it using asyncio.Queue and some termios flags (sorry in advance for the complexity, I tried to reduce it as much as possible):
import sys, termios, os
import asyncio
def readline(prompt: str = "Input: "):
# termios stuff to: disable automatic echo so that, when a character is typed, it is not immediately printed on screen
# read a single character from stdin without pressing <Enter> to finish
fd = sys.stdin.fileno()
orig_termios = termios.tcgetattr(fd)
new_termios = termios.tcgetattr(fd)
new_termios[3] &= ~(termios.ICANON | termios.ECHO)
# set to new termios
termios.tcsetattr(fd, termios.TCSADRAIN, new_termios)
async def terminput(queue: asyncio.Queue):
"""Get terminal input and send it to the queue."""
while True:
ch = sys.stdin.read(1) # read a single char (works because of the termios config)
if ch == "\n":
await queue.put(("finish", None)) # `None` here because we won't use the second argument
await asyncio.sleep(0) # strange workaround so the queues actually work
continue
await queue.put(("input", ch))
await asyncio.sleep(0) # strange workaround so the queues actually work
async def timedsender(queue: asyncio.Queue):
"""Every 0.5 seconds, send a message to the queue."""
while True:
await queue.put(("message", "I'm a message!"))
await asyncio.sleep(0.5)
async def receiver(queue: asyncio.Queue):
"""Handle the receiving of messages and input characters."""
# Late decision that I might be able to fix easily - I had to use a list to push characters into on a earlier version of the code. It can be a string now, though.
input_buffer = []
sys.stdout.write(prompt)
sys.stdout.flush()
def clear_line():
"""Clear the current line.
There might be an escape code that does this already. Eh, anyways...
"""
sys.stdout.write("\r")
sys.stdout.write(" " * os.get_terminal_size().columns)
sys.stdout.write("\r")
sys.stdout.flush()
def redraw_input_buffer():
"""Redraw the input buffer.
Shows the prompt and what has been typed until now.
"""
sys.stdout.write(prompt + "".join(input_buffer))
sys.stdout.flush()
while True:
# So, lemme explain what this format is.
# Each item sent on the queue should be a tuple.
# The first element is what should be done with the content (such as show message, add to input buffer), and the second element is the content itself.
kind, content = await queue.get()
if kind == "message":
clear_line()
sys.stdout.write(f"Message -- {content}\n")
sys.stdout.flush()
redraw_input_buffer()
elif kind == "input":
sys.stdout.write(content)
sys.stdout.flush()
input_buffer += content
elif kind == "finish":
sys.stdout.write("\n")
sys.stdout.write(f"INPUT FINISHED :: {repr(''.join(input_buffer))}\n")
sys.stdout.flush()
input_buffer.clear()
redraw_input_buffer()
# continue reading more input lines...
else:
raise ValueError(f"Unknown kind: {repr(kind)}")
queue.task_done()
async def main():
queue = asyncio.Queue()
senders = [terminput(queue), timedsender(queue)]
recv = receiver(queue)
await asyncio.gather(*senders, recv)
await queue.join()
recv.cancel()
try:
asyncio.run(main())
finally:
# reset to original termios
termios.tcsetattr(fd, termios.TCSADRAIN, orig_termios)
readline()
The main problem at question here is that the queue is only read when a character is typed, and even then, if I don't wait enough time to read the next char with, say, asyncio.sleep(0.1), usually just one message is received in the meantime.
I am not sure if the problem is the queue or some inner workings of the stdin-stdout mechanism (maybe I can't write to stdout while stdin is blocked).

Just figured out a solution for this problem - setting a max wait time for a character to be input.
At the top of readline():
def readline(prompt: str = "Input: "):
fd = sys.stdin.fileno()
orig_termios = termios.tcgetattr(fd)
new_termios = termios.tcgetattr(fd)
new_termios[3] &= ~(termios.ICANON | termios.ECHO)
# the following lines were added:
new_termios[6][termios.VMIN] = 0 # minimal amount of characters to
new_termios[6][termios.VTIME] = 1 # a max wait time of 1/10 second
When using this directly on C, on timeout the character returned would be of code 170, but here not even that seems to happen (the read operations from Python might already ignore them).

Related

How do I interrupt a infinite loop without using raw_input or Ctrl-C? Python 2.7

I have seen other posts, and searched for a while and read documentation. But I cannot seem to understand the answers. Closest I got was the signal module, but the documentation confused me honestly. I need to break from a loop, without using raw_input, the Ctrl-C is perfect, except I need to change it to activate if the user clicks SPACE or ENTER.
from time import sleep
try:
while True:
print "I'm looping!"
sleep(1)
except KeyboardInterrupt:
print "The loop has ended!"
This loop would be perfect If i could just change the keys for the KeyboardInterrupt error.
This is an interesting and surprisingly complicated problem (not sure why the downvotes...) You have to bypass the standard "read till the end of line" as well as add a timeout on normally blocking read. Here is my answer (which works only on linux/mac but see the links on ideas of extending it to Windows):
import select
import sys, termios
def getchar():
char = '_'
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
new = termios.tcgetattr(fd)
new[3] = new[3] & ~(termios.ECHO | termios.ICANON) # turn off echo and canonical mode which sends data on delimiters (new line or OEF, etc)
try:
termios.tcsetattr(fd, termios.TCSADRAIN, new) # terminal is now
ready, steady, go = select.select([sys.stdin], [], [], 1)
if ready:
char = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
return char
try:
while True:
print "I'm looping!"
c = getchar()
if c in ' \n':
print "The loop has ended!"
break
except KeyboardInterrupt:
print "The loop has ended!"
It is a combination of this answer and this answer. Apparently there is also readchar library as suggested by this answer.

A Python Wrapper or Handler for A Minecraft Server

I am using Windows and am looking for a handler or wrapper using Python for a Minecraft server so that I can automatically enter commands without user input. I have searched through many questions on the website and only found half answers (in my case at least). I believe I will need to use the subprocess module but cannot decide which to use at the moment I am experimenting with the Popen functions. I have found an answer which I modified for my case:
server = Popen("java -jar minecraft_server.jar nogui", stdin=PIPE, stdout=PIPE, stderr=STDOUT)
while True:
print(server.stdout.readline())
server.stdout.flush()
command = input("> ")
if command:
server.stdin.write(bytes(command + "\r\n", "ascii"))
server.stdin.flush()
This does work in some way but only prints a line for every time you enter a command, which cannot work and all my efforts to change this end up with the program unable to execute anything else and instead just read. This is not a duplicate question because none of the answers in similar questions could help me enough.
As you already know, your server.stdout.readline() and input("> ") are blocking your code execution.
You need to make your code non-blocking, by not waiting to actually return what you want, but by checking, if there is anything to read and ignore it, if there isn't and continue to do other things.
On Linux systems you might be able to use select module, but on Windows it only works on sockets.
I was able to make it work on Windows by using threads and queues. (note: it's Python 2 code)
import subprocess, sys
from Queue import Queue, Empty
from threading import Thread
def process_line(line):
if line == "stop\n": # lines have trailing new line characters
print "SERVER SHUTDOWN PREVENTED"
return None
elif line == "quit\n":
return "stop\n"
elif line == "l\n":
return "list\n"
return line
s = subprocess.Popen("java -jar minecraft_server.jar nogui", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
def read_lines(stream, queue):
while True:
queue.put(stream.readline())
# terminal reading thread
q = Queue()
t = Thread(target=read_lines, args=(sys.stdin, q))
t.daemon = True
t.start()
# server reading thread
qs = Queue()
ts = Thread(target=read_lines, args=(s.stdout, qs))
ts.daemon = True
ts.start()
while s.poll() == None: # loop while the server process is running
# get a user entered line and send it to the server
try:
line = q.get_nowait()
except Empty:
pass
else:
line = process_line(line) # do something with the user entered line
if line != None:
s.stdin.write(line)
s.stdin.flush()
# just pass-through data from the server to the terminal output
try:
line = qs.get_nowait()
except Empty:
pass
else:
sys.stdout.write(line)
sys.stdout.flush()

Python blockless subproccess input with constant output on Windows

I am trying to run a command with subproccess and the _thread modules. The subproccess has a stream of output. To combat this I used two threads, one constantly prints new lines and the other is checking for input. When I pass the subproccess input through proc.stdin.write('Some string') it returns 1 and then I get no output. Communicate doesn't work as per most other questions I have read because it blocks waiting for the EOF although it does print the first line of the whatever was going to be returned. I saw a few solutions using 'pty' but it is not supported on Windows.
The file in the server folder is just a minecraft server if you want to try it yourself.
from subprocess import Popen,PIPE
import _thread
import sys
# asdf
proc = None
run = True
stdout = None
stdin = None
def getInput():
global proc
global run, stdin, stdout
print("Proc inside the get input funct"+str(proc))
inputs = input("Enter Something" + "\n")
print("YOU ENTERED:", inputs)
print("ATTEMPTING TO PIPE IT INTO THE CMD")
run = True
"""----------------------------------------"""
""" Works but blocks outputs """
"""----------------------------------------"""
# out,err=proc.communicate(bytes(inputs,'UTF-8'))
# proc.stdin.flush()
# print("Out is: "+out)
"""----------------------------------------"""
""" Doesn't write but doesn't block """
"""----------------------------------------"""
# test = 0
# test=proc.stdin.write(bytes(inputs,'UTF-8'))
# print(test)
# proc.stdin.flush()
def execute(command):
global proc, stdin, stdout
proc = Popen(command, cwd='C://Users//Derek//Desktop//server//',stdin=PIPE,stdout=PIPE,stderr=stdout, shell=True)
lines_iterator = iter(proc.stdout.readline, "")
print("Proc inside of the execute funct:"+str(proc))
# print(lines_iterator)
for line in lines_iterator:
# print(str(line[2:-1]))
# if line.decode('UTF-8') != '':
print(line[:-2].decode('UTF-8')), # yield line
sys.stdout.flush()
threadTwo = _thread.start_new_thread(execute, (["java", "-jar", "minecraft_server.jar"], ))
while 1:
if run and proc!=None:
run = False
threadOne = _thread.start_new_thread(getInput, ( ))
pass
proc.communicate() waits for the subprocess to finish therefore it can be used at most once – you can pass all input at once and get all the output after the child process exits.
If you are not modifying input/output then you do not need to redirect subprocess' stdin/stdout.
To feed input to a subprocess in a background thread and to print its output as soon as it arrives line-by-line:
#!/usr/bin/env python3
import errno
from io import TextIOWrapper
from subprocess import Popen, PIPE
from threading import Thread
def feed(pipe):
while True:
try: # get input
line = input('Enter input for minecraft')
except EOFError:
break # no more input
else:
# ... do something with `line` here
# feed input to pipe
try:
print(line, file=pipe)
except BrokenPipeError:
break # can't write to pipe anymore
except OSError as e:
if e.errno == errno.EINVAL:
break # same as EPIPE on Windows
else:
raise # allow the error to propagate
try:
pipe.close() # inform subprocess -- no more input
except OSError:
pass # ignore
with Popen(["java", "-jar", "minecraft_server.jar"],
cwd=r'C:\Users\Derek\Desktop\server',
stdin=PIPE, stdout=PIPE, bufsize=1) as p, \
TextIOWrapper(p.stdin, encoding='utf-8',
write_through=True, line_buffering=True) as text_input:
Thread(target=feed, args=[text_input], daemon=True).start()
for line in TextIOWrapper(p.stdout, encoding='utf-8'):
# ... do something with `line` here
print(line, end='')
Note about p.stdin:
print() adds a newline at the end of each line. It is necessary because input() strips the newline
p.stdin.flush() is called after each line (line_buffering=True)
The output from minecraft may be delayed until its stdout buffer is flushed.
If you have nothing to add around the "do something with line here" comments then do not redirect corresponding pipes (ignoring character encoding issues for a moment).
TextIOWrapper uses the universal newline mode by default. Specify newline parameter explicitly if you do not want that.

Coding timed functions into text based game using Python

I am writing a simple text based adventure game in Python. I would like to have certain processes occur periodically regardless of what the user does, approximately every 2 minutes. For instance: Have NPC's move around the rooms, have people get hungry and thirsty, have people heal, and during combat, have the battle proceed. Right now, I'm using 'raw_input' to get commands from the user, but this essentially pauses the code. How can I make the game proceed even if the user just sits there and doesn't type anything?
I think typically in this situation you wouldn't have a background process or thread doing calculations. Instead, when the user types in some response do a time delta and based off the elapsed time between inputs calculate how much a player would have healed and what the battle events would have been etc.. That is if you don't want console updates while game is waiting for the user to respond.
Edit:
or try something like this:
import time
import sys
win32 = True
try:
from msvcrt import kbhit, getch
print "[+] Running on windows, using msvcrt."
except ImportError:
print "[+] Not running on windows, attempting unix-like."
win32 = False
import termios, fcntl, sys, os
import select
fd = sys.stdin.fileno()
oldterm = termios.tcgetattr(fd)
newattr = termios.tcgetattr(fd)
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, newattr)
oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
POLLTIME = 5
done = False
command = ""
while not done:
sys.stdout.write("\r")
print("Something happened (polling)%s" % (" " * command.__len__() ))
sys.stdout.write("Enter command: %s" % command)
sys.stdout.flush()
t = time.time()
if win32:
while time.time() - t < POLLTIME:
if kbhit():
c = getch()
if ord(c) < 127 and ord(c) > 31:
command += c
message = "\rEnter command: " + command
sys.stdout.write("\r%s" % message)
if "\r" == c:
if "quit\r" == command:
done = True
break
sys.stdout.write("\rThe command was: %s\n" % command)
command = ""
sys.stdout.write("\rEnter command: %s \b" %command)
elif "\b" == c:
command = command[:-1]
sys.stdout.write("\rEnter command: %s \b" %command)
sys.stdout.flush()
else:
while time.time() - t < POLLTIME:
try:
c = '\0'
if sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
c = sys.stdin.readline(1)
if ord(c) < 127 and ord(c) > 31:
command += c
message = "\rEnter command: " + command
sys.stdout.write("\r%s" % message)
if c == "\n":
if "quit" == command:
done = True
break
print("\rThe command was: %s" % command)
command = ""
message = "\rEnter command: " + command
sys.stdout.write("\r%s" % message)
if 127 == ord(c):
command = command[:-1]
sys.stdout.write("\rEnter command: %s \b" % command)
sys.stdout.flush()
except IOError:
pass
The answer is -- don't write real time for a console! If you want to do this text-based, you may wish to switch to Tkinter. This will allow you to do these things separately -- and also display text during these periodic events, and use a simple .after() call to execute them.
Sample the time after each input (up to you whether to do it only for successful commands or optionally include invalid ones).
Compare this time to the prior sample and divide by some world tick interval.
Iterate through the list of activities that happen per tick (for npc in npcs: npc.move_to_adjacent_posn(), e.g.).
There are ways to read user input without pausing the code. It's called "asynchronous I/O" or "non-blocking I/O". One way to do it is to create a separate thread to listen to the user's requests and queue them to process inside your game loop.
This question and its answers explain how to do non-blocking I/O in Python: Non-blocking read on a subprocess.PIPE in python
I am not sure how you can do this without using a separate thread (and it is easy to use a separate thread).
But my point here will be: look like your text-based function is a event/command based application? i.e. the client state won't change if there is no further command/event from the user? Not sure what you are trying to monitor with a timed function, but if your application is not already event-based, i.e. aggregate the state from the set of event the user perform/send, then you might want to make your application to be event-based, then you can get rid of the timed function. hope that help.

Python: how to tell if input file has input pending without waiting

Normally, you process a file line by line in Python using a loop like:
import sys
for s in sys.stdin:
# do something with the line in s
or
import sys
while True:
line = sys,stdin.readline()
if len(line) == 0: break
# process input line
Of course, you can also use raw_input() in soemthing like this:
try:
while True:
s = raw_input()
# process input line
except EOFError:
# there's EOF.
Of course in all these cases, if there's no input ready to be read, the underlying read() operation suspends waiting for I/O.
What I want to do is see if there is input pending without suspending, so I can read until input is exhausted and then go do something else. That is, I'd like to be able to do something like
while "there is input pending":
#get the input
but when no more input is pending, break the loop.
If you are using some variant of Unix, and your standard input is a pipe and not a file, you can use the select module to check to see whether there is waiting input. At a minimum, the code might look like:
import select
rlist, wlist, elist = select.select([sys.stdin], [], [])
if rlist:
s = raw_input()
else:
pass # no input ready right now
Okay, here's something that works well on UNIX:
import sys
import select
import tty
import termios
def isData():
return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])
old_settings = termios.tcgetattr(sys.stdin)
try:
tty.setcbreak(sys.stdin.fileno())
i = 0
while 1:
print i
i += 1
if isData():
c = sys.stdin.read(1)
if c == '\x1b': # x1b is ESC
break
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
I'll modify/extend this answer when I have a chance to make a somewhat better test program. I'm (so far) unclear on how well tty and termios work on Windows.
Update: Grmph. This depends on select. There are reasons I don't like Windows.

Categories

Resources