##// END OF EJS Templates
Merged ipython-frontend branch. All changes in IPython.frontend except for updates to IPython.kernel.engineservice.ThreadedEngineService and associated tests.
Merged ipython-frontend branch. All changes in IPython.frontend except for updates to IPython.kernel.engineservice.ThreadedEngineService and associated tests.

File last commit:

r1234:52b55407
r1294:defe9154 merge
Show More
shell.py
357 lines | 13.6 KiB | text/x-python | PythonLexer
# 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):
print
##################################################################
# 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