One of Python's best kept secrets is the ability to regain an
interactive console in the middle of non-interactive code. Using
code.interact,
we've saved thousands of development hours by instantly
checking assumptions and playing with data in ways logging just can't
match. It's been particularly effective inside error handling routines
to quickly identify what went wrong and develop a fix on the spot.
Here's what we've been using:
def interactiveShell(local, banner='interactive shell'):
import code
local = dict(globals(),**local) if local else globals()
code.interact(banner,local=local)
Unfortunately, it can't handle daemonized services like
web2py (a great python-based MVC
framework). Several heavy-handed solutions exist (potentially
pydbgr), but nothing that
matched the simplicity and availability of code.interact. A little
googling started to approach an answer in the form of
this post
in
March 2010. That led to developing the functions below to provide a
fully interactive Python console locally or remotely via TCP sockets.
The best part is the console is so intuitive that netcat and telnet can
talk with the server with ease.
import sys, socket, logging
import contextlib
@contextlib.contextmanager
def std_redirector(stdin, stdout, stderr):
orig_fds = sys.stdin, sys.stdout, sys.stderr
sys.stdin, sys.stdout, sys.stderr = stdin, stdout, stderr
yield
sys.stdin, sys.stdout, sys.stderr = orig_fds
class socketWrapper():
def __init__(self, s): self.s = s
def read(self, len): return self.s.recv(len)
def write(self, str): return self.s.send(str)
def readline(self):
data = ''
while True:
iota = self.read(1)
if not iota: break
else: data += iota
if iota in '\n': break
return data
def ishell(local=None, banner='interactive shell'):
import code
local = dict(globals(),**local) if local else globals()
code.interact(banner,local=local)
def linkup(local,link,banner):
import traceback
link = socketWrapper(link)
banner += '\nStack Trace\n'
banner += '----------------------------------------\n'
banner += ''.join(traceback.format_stack()[:-2])
banner += '----------------------------------------\n'
with std_redirector(link,link,link):
ishell(local,banner)
def listen(local=None, host='127.0.0.1', port=2000):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((host,port))
server.listen(1)
client,addr = server.accept()
linkup(local,client,'connected to %s:%d'%(host,port))
client.shutdown(socket.SHUT_RDWR)
server.shutdown(socket.SHUT_RDWR)
def connect(local=None, host='127.0.0.1', port=2000):
link = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
link.connect((host,port))
linkup(local,link,'connected to %s:%d'%(host,port))
link.shutdown(socket.SHUT_RDWR)
if __name__ == '__main__':
listen() # nc localhost 2000
connect() # nc -l 2000
Currently, the code doesn't provide any fancy features like
command history or tab completion, but
readline
with
rlcompleter
look promising.
- Kelson (kelson
@shysecurity.com)