Using Python, how can I implement a foreground loop to run while waiting in the background for a keypress?

I realize there are similar themes in this (like the one here) , but my intended design is a little more complex.

I am developing a CLI script that will run in an SSH window. The script will be hosted and executed on Ubuntu 14.10 server. It is designed to actively monitor, in the foreground, the current status of ports and clients on a host switch. Every 30 seconds or as user defined, it will fetch data via SNMP and then update the information and display it on the screen. When it waits for the next update, a timer appears indicating when it will request the device again for information.

I want to allow the user to press certain keys to change the view of the output or edit key variables at any time. (The functionality is similar to Unix top

.) For example, pressing t

will ask them for the number of seconds it takes between loops. h

, m

or i

will show / hide certain columns. They do not pause the timer or exit the loop as the changes will be applied on the next update. r

will force you to immediately update and apply changes. q

or Ctrl+C

exit the script.

The main action will look like this:

Query loop <-----------------------------
     |                                   |
     |                              Process Data
     |                                   ^
     |                                   |
     v                               Query Data    #SNMPBULKWALK
   Timer <-------------                  ^
     | |               |                 |          
     | |        Check time remaining     |
     | |               ^                 |
     | |_______________|                 |
     |___________________________________|

      

When keystrokes are interrupted, it will act as follows:

Query loop <----------------------                      
    |                             |        ???<---Change variables
    |                        (Continue)                  ^
    V                             |                      |
  Timer <---------          !!INTERRUPT!!---------> Identify key
    | |           |               ^
    | |  Check time remaining     |
    | |           ^               |
    | |___________|               |
    |_____________________________|

      

I'm kind of excited here. I am convinced that I will probably need to implement threads - which I have no experience with - as a loop while

by itself does not satisfy what we need. I'm also not sure how to make changes to the object containing the variables (e.g. timer, flags for display formatting) as it will be constantly used by our loop.

+3


source to share


2 answers


It's nothing complicated and doesn't require any packages.

The only problem is that it requires you to return to normal terminal operation on program exit.

those. If the program crashes, the terminal will not be restored and the user will not see what he is typing.

But if the user knows what he is doing, he can force restart the shell and everything will be fine.

From corset you can use this code easier and use raw_input () to generate this stuff.

from thread import start_new_thread as thread
from time import sleep
# Get only one character from stdin without echoing it to stdout
import termios
import fcntl
import sys
import os
fd = sys.stdin.fileno()
oldterm, oldflags = None, None

def prepareterm ():
    """Turn off echoing"""
    global oldterm, oldflags
    if oldterm!=None and oldflags!=None: return
    oldterm = termios.tcgetattr(fd)
    newattr = oldterm[:] # Copy of attributes to change
    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)

def restoreterm ():
    """Restore terminal to its previous state"""
    global oldterm, oldflags
    if oldterm==None and oldflags==None: return
    termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
    fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
    oldterm, oldflags = None, None

def getchar():
    """Get character from stdin"""
    prepareterm()
    while 1:
        try:
            c = sys.stdin.read(1)
            break
        except IOError:
            try: sleep(0.001)
            except: restoreterm(); raise KeyboardInterrupt
    restoreterm()
    return c

def control ():
    """Waits for keypress"""
    global running, done
    while running:
        c = getchar().lower()
        print "Keypress:", c
        if c=="q": print "Quitting!"; running = 0; break
    done += 1

def work ():
    """Does the server-client part"""
    global done
    while running:
        # Do your stuff here!!!
        # Just to illustrate:
        print "I am protending to work!\nPress Q to kill me!"
        sleep(1)
    print "I am done!\nExiting . . ."
    done += 1

# Just to feel better
import atexit
atexit.register(restoreterm)

# Start the program
running = 1
done = 0
thread(work, ())
thread(control, ())
# Block the program not to close when threads detach from the main one:
while running:
    try: sleep(0.2)
    except: running = 0
# Wait for both threads to finish:
while done!=2:
    try: sleep(0.001)
    except: pass # Ignore KeyboardInterrupt
restoreterm() # Just in case!

      



In practice, the program will never be able to exit without restoring the terminal to normal, but shit happens.

Now you can simplify things by using just one thread and a work loop placed on the main thread and use raw_input to get commands from the user. Or maybe even better, put your server-client code in the background and wait for input on the main thread.

Also, it will probably be safer to use the streaming module instead of raw streams.

If you are using an async module, you will run each client for its own, and your main thread will be busy with asyncore.loop (). You can override it, Ie rewrite it to validate the input and other things you want to do while keeping the asynchronous checks in sync. Also, for heavy workloads it is required to override some nasty functions inside it, because its buffer is fixed at 512 bytes, if I'm not mistaken. Otherwise, it might be a good solution for your problem.

And finally, just to be clear, the code for no user echo is accepted and adapted from the getpass module. Just a little mine.

+1


source


Ctrl + C is simple: it throws an exception that you can catch (because the process is signaled when it does).



For interactivity while waiting, you should look at the async code. Twisted is time tested and capable, but has a slight learning curve (IMHO). There is also asyncore , which may be easier to get started with, but more limited and I'm not sure if it handles your use case. There is also asyncio , but it only exists in Python 3.4.

0


source







All Articles