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

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.

Related

Async IO - reading char from input blocks output

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).

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.

check if input is available on stdin with timeout

I have a python script that publishes messages that are read from stdin onto a message queue in the network. However, if there wasn't any message on the pipe for a specified amount of time I have to send a heartbeat.
So I have to distinguish between 3 cases:
There is input on the pipe that can be processed
There was no input for some specified amount of time
The piping process has been closed and we can gracefully terminate.
Currently, my code looks as follow:
import sys
for i, line in enumerate(sys.stdin):
connection.publish(line)
connection.close()
However I need to interrupt the for look if there was any timeout. I.e. sys.stdin has not delivered any data for some time.
This is what I finally came up with
import sys
import select
while True:
try:
if select.select([sys.stdin,],[],[],2.0)[0]:
line = sys.stdin.next()
print "Got:", line
else:
print "No data for 2 secs"
except StopIteration:
print 'EOF!'
break
If anybody is looking for a portable solution, this is what I came up with
after lots of trial and error.
It requires the pywin32 pypi package to work on windows
import os, sys
def is_windows() -> bool:
return os.name == "nt"
if is_windows():
import win32api, win32event, win32file, pywintypes
else:
import select
def stdin_has_content(timeout: float) -> bool:
assert timeout >= 0
if is_windows():
try:
# without this the wait might return despite there not being any input
win32file.FlushFileBuffers(win32api.STD_INPUT_HANDLE)
except pywintypes.error:
# this sometimes fails, but we don't mind
pass
return win32event.WaitForSingleObject(
win32api.STD_INPUT_HANDLE, int(timeout * 1000)
) == win32event.WAIT_OBJECT_0
else:
rlist, _, _ = select.select(
[sys.stdin], [], [], timeout
)
return bool(rlist)

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.

read raw input from keyboard in python

I'm trying to get the raw input of my keyboard in python. I have a Logitech gaming keyboard with programmable keys, but Logitech doesn't provide drivers for Linux. So i thought i could (try) to write my own driver for this. In think the solution could be something like:
with open('/dev/keyboard', 'rb') as keyboard:
while True:
inp = keyboard.read()
-do something-
English isn't my native language. If you find errors, please correct it.
Two input methods that rely on the OS handling the keyboard
import sys
for line in sys.stdin.readlines():
print line
This is one "simple" solution to your problem considering it reads sys.stdin you'll probably need a driver and if the OS strips stuff along the way it will probably break anyways.
This is another solution (linux only afaik):
import sys, select, tty, termios
class NonBlockingConsole(object):
def __enter__(self):
self.old_settings = termios.tcgetattr(sys.stdin)
tty.setcbreak(sys.stdin.fileno())
return self
def __exit__(self, type, value, traceback):
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.old_settings)
def get_data(self):
try:
if select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []):
return sys.stdin.read(1)
except:
return '[CTRL-C]'
return False
data = ''
printed = ''
last = ''
with NonBlockingConsole() as nbc:
while 1:
c = nbc.get_data()
if c:
if c == '\x1b': # x1b is ESC
break
elif c == '\x7f': # backspace
data = data[:-1]
printed = data[:-1]
last = ''
sys.stdout.write('\b')
elif c == '[CTRL-C]':
data = ''
last = ''
sys.stdout.write('\n')
elif c == '\n': # it's RETURN
sys.stdout.write('\n')
# parse data here
data = ''
else:
data += (c)
last = c
sys.stdout.write(c)
Driver issue?
If none of the above work, you woun't be able to get the keys within Python.
Most likely you'll need an actual driver that can parse the data sent from the keyboard that is not a normal keyboard event on the USB stack, meaning.. This is way to low-level for Python and you're out of luck... unless you know how to build linux drivers.
Anyway, have a look at: http://ubuntuforums.org/showthread.php?t=1490385
Looks like more people have tried to do something about it.
Trying PyUSB
http://pyusb.sourceforge.net/docs/1.0/tutorial.html
You could try a PyUSB solution and fetch raw data from the USB socket, but again.. if the G-keys are not registered as "traditional" USB data it might get dropped and you won't recieve it.
Hooking on to the input pipes in Linux
Another untested method, but might work //Hackaday:
Logitech doesn't provide drivers for Linux. So i thought i could (try) to write my own driver for this.
Linux drivers are written in C; it's very low-level code and runs in kernel space.

Categories

Resources