shell.py
357 lines
| 13.6 KiB
| text/x-python
|
PythonLexer
Brian E Granger
|
r1234 | # encoding: utf-8 | ||
# -*- test-case-name: IPython.test.test_shell -*- | ||||
"""The core IPython Shell""" | ||||
__docformat__ = "restructuredtext en" | ||||
#------------------------------------------------------------------------------- | ||||
# Copyright (C) 2008 The IPython Development Team | ||||
# | ||||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#------------------------------------------------------------------------------- | ||||
#------------------------------------------------------------------------------- | ||||
# Imports | ||||
#------------------------------------------------------------------------------- | ||||
import pprint | ||||
import signal | ||||
import sys | ||||
import threading | ||||
import time | ||||
from code import InteractiveConsole, softspace | ||||
from StringIO import StringIO | ||||
from IPython.OutputTrap import OutputTrap | ||||
from IPython import ultraTB | ||||
from IPython.kernel.error import NotDefined | ||||
class InteractiveShell(InteractiveConsole): | ||||
"""The Basic IPython Shell class. | ||||
This class provides the basic capabilities of IPython. Currently | ||||
this class does not do anything IPython specific. That is, it is | ||||
just a python shell. | ||||
It is modelled on code.InteractiveConsole, but adds additional | ||||
capabilities. These additional capabilities are what give IPython | ||||
its power. | ||||
The current version of this class is meant to be a prototype that guides | ||||
the future design of the IPython core. This class must not use Twisted | ||||
in any way, but it must be designed in a way that makes it easy to | ||||
incorporate into Twisted and hook network protocols up to. | ||||
Some of the methods of this class comprise the official IPython core | ||||
interface. These methods must be tread safe and they must return types | ||||
that can be easily serialized by protocols such as PB, XML-RPC and SOAP. | ||||
Locks have been provided for making the methods thread safe, but additional | ||||
locks can be added as needed. | ||||
Any method that is meant to be a part of the official interface must also | ||||
be declared in the kernel.coreservice.ICoreService interface. Eventually | ||||
all other methods should have single leading underscores to note that they | ||||
are not designed to be 'public.' Currently, because this class inherits | ||||
from code.InteractiveConsole there are many private methods w/o leading | ||||
underscores. The interface should be as simple as possible and methods | ||||
should not be added to the interface unless they really need to be there. | ||||
Note: | ||||
- For now I am using methods named put/get to move objects in/out of the | ||||
users namespace. Originally, I was calling these methods push/pull, but | ||||
because code.InteractiveConsole already has a push method, I had to use | ||||
something different. Eventually, we probably won't subclass this class | ||||
so we can call these methods whatever we want. So, what do we want to | ||||
call them? | ||||
- We need a way of running the trapping of stdout/stderr in different ways. | ||||
We should be able to i) trap, ii) not trap at all or iii) trap and echo | ||||
things to stdout and stderr. | ||||
- How should errors be handled? Should exceptions be raised? | ||||
- What should methods that don't compute anything return? The default of | ||||
None? | ||||
""" | ||||
def __init__(self, locals=None, filename="<console>"): | ||||
"""Creates a new TrappingInteractiveConsole object.""" | ||||
InteractiveConsole.__init__(self,locals,filename) | ||||
self._trap = OutputTrap(debug=0) | ||||
self._stdin = [] | ||||
self._stdout = [] | ||||
self._stderr = [] | ||||
self._last_type = self._last_traceback = self._last_value = None | ||||
#self._namespace_lock = threading.Lock() | ||||
#self._command_lock = threading.Lock() | ||||
self.lastCommandIndex = -1 | ||||
# I am using this user defined signal to interrupt the currently | ||||
# running command. I am not sure if this is the best way, but | ||||
# it is working! | ||||
# This doesn't work on Windows as it doesn't have this signal. | ||||
#signal.signal(signal.SIGUSR1, self._handleSIGUSR1) | ||||
# An exception handler. Experimental: later we need to make the | ||||
# modes/colors available to user configuration, etc. | ||||
self.tbHandler = ultraTB.FormattedTB(color_scheme='NoColor', | ||||
mode='Context', | ||||
tb_offset=2) | ||||
def _handleSIGUSR1(self, signum, frame): | ||||
"""Handle the SIGUSR1 signal by printing to stderr.""" | ||||
print>>sys.stderr, "Command stopped." | ||||
def _prefilter(self, line, more): | ||||
return line | ||||
def _trapRunlines(self, lines): | ||||
""" | ||||
This executes the python source code, source, in the | ||||
self.locals namespace and traps stdout and stderr. Upon | ||||
exiting, self.out and self.err contain the values of | ||||
stdout and stderr for the last executed command only. | ||||
""" | ||||
# Execute the code | ||||
#self._namespace_lock.acquire() | ||||
self._trap.flush() | ||||
self._trap.trap() | ||||
self._runlines(lines) | ||||
self.lastCommandIndex += 1 | ||||
self._trap.release() | ||||
#self._namespace_lock.release() | ||||
# Save stdin, stdout and stderr to lists | ||||
#self._command_lock.acquire() | ||||
self._stdin.append(lines) | ||||
self._stdout.append(self.prune_output(self._trap.out.getvalue())) | ||||
self._stderr.append(self.prune_output(self._trap.err.getvalue())) | ||||
#self._command_lock.release() | ||||
def prune_output(self, s): | ||||
"""Only return the first and last 1600 chars of stdout and stderr. | ||||
Something like this is required to make sure that the engine and | ||||
controller don't become overwhelmed by the size of stdout/stderr. | ||||
""" | ||||
if len(s) > 3200: | ||||
return s[:1600] + '\n............\n' + s[-1600:] | ||||
else: | ||||
return s | ||||
# Lifted from iplib.InteractiveShell | ||||
def _runlines(self,lines): | ||||
"""Run a string of one or more lines of source. | ||||
This method is capable of running a string containing multiple source | ||||
lines, as if they had been entered at the IPython prompt. Since it | ||||
exposes IPython's processing machinery, the given strings can contain | ||||
magic calls (%magic), special shell access (!cmd), etc.""" | ||||
# We must start with a clean buffer, in case this is run from an | ||||
# interactive IPython session (via a magic, for example). | ||||
self.resetbuffer() | ||||
lines = lines.split('\n') | ||||
more = 0 | ||||
for line in lines: | ||||
# skip blank lines so we don't mess up the prompt counter, but do | ||||
# NOT skip even a blank line if we are in a code block (more is | ||||
# true) | ||||
if line or more: | ||||
more = self.push((self._prefilter(line,more))) | ||||
# IPython's runsource returns None if there was an error | ||||
# compiling the code. This allows us to stop processing right | ||||
# away, so the user gets the error message at the right place. | ||||
if more is None: | ||||
break | ||||
# final newline in case the input didn't have it, so that the code | ||||
# actually does get executed | ||||
if more: | ||||
self.push('\n') | ||||
def runcode(self, code): | ||||
"""Execute a code object. | ||||
When an exception occurs, self.showtraceback() is called to | ||||
display a traceback. All exceptions are caught except | ||||
SystemExit, which is reraised. | ||||
A note about KeyboardInterrupt: this exception may occur | ||||
elsewhere in this code, and may not always be caught. The | ||||
caller should be prepared to deal with it. | ||||
""" | ||||
self._last_type = self._last_traceback = self._last_value = None | ||||
try: | ||||
exec code in self.locals | ||||
except: | ||||
# Since the exception info may need to travel across the wire, we | ||||
# pack it in right away. Note that we are abusing the exception | ||||
# value to store a fully formatted traceback, since the stack can | ||||
# not be serialized for network transmission. | ||||
et,ev,tb = sys.exc_info() | ||||
self._last_type = et | ||||
self._last_traceback = tb | ||||
tbinfo = self.tbHandler.text(et,ev,tb) | ||||
# Construct a meaningful traceback message for shipping over the | ||||
# wire. | ||||
buf = pprint.pformat(self.buffer) | ||||
try: | ||||
ename = et.__name__ | ||||
except: | ||||
ename = et | ||||
msg = """\ | ||||
%(ev)s | ||||
*************************************************************************** | ||||
An exception occurred in an IPython engine while executing user code. | ||||
Current execution buffer (lines being run): | ||||
%(buf)s | ||||
A full traceback from the actual engine: | ||||
%(tbinfo)s | ||||
*************************************************************************** | ||||
""" % locals() | ||||
self._last_value = msg | ||||
else: | ||||
if softspace(sys.stdout, 0): | ||||
################################################################## | ||||
# Methods that are a part of the official interface | ||||
# | ||||
# These methods should also be put in the | ||||
# kernel.coreservice.ICoreService interface. | ||||
# | ||||
# These methods must conform to certain restrictions that allow | ||||
# them to be exposed to various network protocols: | ||||
# | ||||
# - As much as possible, these methods must return types that can be | ||||
# serialized by PB, XML-RPC and SOAP. None is OK. | ||||
# - Every method must be thread safe. There are some locks provided | ||||
# for this purpose, but new, specialized locks can be added to the | ||||
# class. | ||||
################################################################## | ||||
# Methods for running code | ||||
def exc_info(self): | ||||
"""Return exception information much like sys.exc_info(). | ||||
This method returns the same (etype,evalue,tb) tuple as sys.exc_info, | ||||
but from the last time that the engine had an exception fire.""" | ||||
return self._last_type,self._last_value,self._last_traceback | ||||
def execute(self, lines): | ||||
self._trapRunlines(lines) | ||||
if self._last_type is None: | ||||
return self.getCommand() | ||||
else: | ||||
raise self._last_type(self._last_value) | ||||
# Methods for working with the namespace | ||||
def put(self, key, value): | ||||
"""Put value into locals namespace with name key. | ||||
I have often called this push(), but in this case the | ||||
InteractiveConsole class already defines a push() method that | ||||
is different. | ||||
""" | ||||
if not isinstance(key, str): | ||||
raise TypeError, "Objects must be keyed by strings." | ||||
self.update({key:value}) | ||||
def get(self, key): | ||||
"""Gets an item out of the self.locals dict by key. | ||||
Raise NameError if the object doesn't exist. | ||||
I have often called this pull(). I still like that better. | ||||
""" | ||||
class NotDefined(object): | ||||
"""A class to signify an objects that is not in the users ns.""" | ||||
pass | ||||
if not isinstance(key, str): | ||||
raise TypeError, "Objects must be keyed by strings." | ||||
result = self.locals.get(key, NotDefined()) | ||||
if isinstance(result, NotDefined): | ||||
raise NameError('name %s is not defined' % key) | ||||
else: | ||||
return result | ||||
def update(self, dictOfData): | ||||
"""Loads a dict of key value pairs into the self.locals namespace.""" | ||||
if not isinstance(dictOfData, dict): | ||||
raise TypeError, "update() takes a dict object." | ||||
#self._namespace_lock.acquire() | ||||
self.locals.update(dictOfData) | ||||
#self._namespace_lock.release() | ||||
# Methods for getting stdout/stderr/stdin | ||||
def reset(self): | ||||
"""Reset the InteractiveShell.""" | ||||
#self._command_lock.acquire() | ||||
self._stdin = [] | ||||
self._stdout = [] | ||||
self._stderr = [] | ||||
self.lastCommandIndex = -1 | ||||
#self._command_lock.release() | ||||
#self._namespace_lock.acquire() | ||||
# preserve id, mpi objects | ||||
mpi = self.locals.get('mpi', None) | ||||
id = self.locals.get('id', None) | ||||
del self.locals | ||||
self.locals = {'mpi': mpi, 'id': id} | ||||
#self._namespace_lock.release() | ||||
def getCommand(self,i=None): | ||||
"""Get the stdin/stdout/stderr of command i.""" | ||||
#self._command_lock.acquire() | ||||
if i is not None and not isinstance(i, int): | ||||
raise TypeError("Command index not an int: " + str(i)) | ||||
if i in range(self.lastCommandIndex + 1): | ||||
inResult = self._stdin[i] | ||||
outResult = self._stdout[i] | ||||
errResult = self._stderr[i] | ||||
cmdNum = i | ||||
elif i is None and self.lastCommandIndex >= 0: | ||||
inResult = self._stdin[self.lastCommandIndex] | ||||
outResult = self._stdout[self.lastCommandIndex] | ||||
errResult = self._stderr[self.lastCommandIndex] | ||||
cmdNum = self.lastCommandIndex | ||||
else: | ||||
inResult = None | ||||
outResult = None | ||||
errResult = None | ||||
#self._command_lock.release() | ||||
if inResult is not None: | ||||
return dict(commandIndex=cmdNum, stdin=inResult, stdout=outResult, stderr=errResult) | ||||
else: | ||||
raise IndexError("Command with index %s does not exist" % str(i)) | ||||
def getLastCommandIndex(self): | ||||
"""Get the index of the last command.""" | ||||
#self._command_lock.acquire() | ||||
ind = self.lastCommandIndex | ||||
#self._command_lock.release() | ||||
return ind | ||||