globalipapp.py
169 lines
| 5.7 KiB
| text/x-python
|
PythonLexer
Fernando Perez
|
r2423 | """Global IPython app to support test running. | ||
We must start our own ipython object and heavily muck with it so that all the | ||||
modifications IPython makes to system behavior don't send the doctest machinery | ||||
into a fit. This code should be considered a gross hack, but it gets the job | ||||
done. | ||||
""" | ||||
from __future__ import absolute_import | ||||
#----------------------------------------------------------------------------- | ||||
# Module imports | ||||
#----------------------------------------------------------------------------- | ||||
# From the standard library | ||||
import __builtin__ | ||||
import commands | ||||
import new | ||||
import os | ||||
import sys | ||||
from . import tools | ||||
#----------------------------------------------------------------------------- | ||||
# Functions | ||||
#----------------------------------------------------------------------------- | ||||
# Hack to modify the %run command so we can sync the user's namespace with the | ||||
# test globals. Once we move over to a clean magic system, this will be done | ||||
# with much less ugliness. | ||||
class py_file_finder(object): | ||||
def __init__(self,test_filename): | ||||
self.test_filename = test_filename | ||||
def __call__(self,name): | ||||
from IPython.utils.genutils import get_py_filename | ||||
try: | ||||
return get_py_filename(name) | ||||
except IOError: | ||||
test_dir = os.path.dirname(self.test_filename) | ||||
new_path = os.path.join(test_dir,name) | ||||
return get_py_filename(new_path) | ||||
def _run_ns_sync(self,arg_s,runner=None): | ||||
"""Modified version of %run that syncs testing namespaces. | ||||
This is strictly needed for running doctests that call %run. | ||||
""" | ||||
#print >> sys.stderr, 'in run_ns_sync', arg_s # dbg | ||||
_ip = get_ipython() | ||||
finder = py_file_finder(arg_s) | ||||
out = _ip.magic_run_ori(arg_s,runner,finder) | ||||
return out | ||||
class ipnsdict(dict): | ||||
"""A special subclass of dict for use as an IPython namespace in doctests. | ||||
This subclass adds a simple checkpointing capability so that when testing | ||||
machinery clears it (we use it as the test execution context), it doesn't | ||||
get completely destroyed. | ||||
""" | ||||
def __init__(self,*a): | ||||
dict.__init__(self,*a) | ||||
self._savedict = {} | ||||
def clear(self): | ||||
dict.clear(self) | ||||
self.update(self._savedict) | ||||
def _checkpoint(self): | ||||
self._savedict.clear() | ||||
self._savedict.update(self) | ||||
def update(self,other): | ||||
self._checkpoint() | ||||
dict.update(self,other) | ||||
# If '_' is in the namespace, python won't set it when executing code, | ||||
# and we have examples that test it. So we ensure that the namespace | ||||
# is always 'clean' of it before it's used for test code execution. | ||||
self.pop('_',None) | ||||
# The builtins namespace must *always* be the real __builtin__ module, | ||||
# else weird stuff happens. The main ipython code does have provisions | ||||
# to ensure this after %run, but since in this class we do some | ||||
# aggressive low-level cleaning of the execution namespace, we need to | ||||
# correct for that ourselves, to ensure consitency with the 'real' | ||||
# ipython. | ||||
self['__builtins__'] = __builtin__ | ||||
def get_ipython(): | ||||
# This will get replaced by the real thing once we start IPython below | ||||
return None | ||||
def start_ipython(): | ||||
"""Start a global IPython shell, which we need for IPython-specific syntax. | ||||
""" | ||||
global get_ipython | ||||
# This function should only ever run once! | ||||
if hasattr(start_ipython,'already_called'): | ||||
return | ||||
start_ipython.already_called = True | ||||
# Ok, first time we're called, go ahead | ||||
from IPython.core import ipapp, iplib | ||||
def xsys(cmd): | ||||
"""Execute a command and print its output. | ||||
This is just a convenience function to replace the IPython system call | ||||
with one that is more doctest-friendly. | ||||
""" | ||||
cmd = _ip.var_expand(cmd,depth=1) | ||||
sys.stdout.write(commands.getoutput(cmd)) | ||||
sys.stdout.flush() | ||||
# Store certain global objects that IPython modifies | ||||
_displayhook = sys.displayhook | ||||
_excepthook = sys.excepthook | ||||
_main = sys.modules.get('__main__') | ||||
argv = tools.default_argv() | ||||
# Start IPython instance. We customize it to start with minimal frills. | ||||
user_ns,global_ns = iplib.make_user_namespaces(ipnsdict(),{}) | ||||
ip = ipapp.IPythonApp(argv, user_ns=user_ns, user_global_ns=global_ns) | ||||
ip.initialize() | ||||
ip.shell.builtin_trap.set() | ||||
# Butcher the logger | ||||
ip.shell.log = lambda *a,**k: None | ||||
# Deactivate the various python system hooks added by ipython for | ||||
# interactive convenience so we don't confuse the doctest system | ||||
sys.modules['__main__'] = _main | ||||
sys.displayhook = _displayhook | ||||
sys.excepthook = _excepthook | ||||
# So that ipython magics and aliases can be doctested (they work by making | ||||
# a call into a global _ip object) | ||||
_ip = ip.shell | ||||
get_ipython = _ip.get_ipython | ||||
__builtin__._ip = _ip | ||||
__builtin__.get_ipython = get_ipython | ||||
# Modify the IPython system call with one that uses getoutput, so that we | ||||
# can capture subcommands and print them to Python's stdout, otherwise the | ||||
# doctest machinery would miss them. | ||||
ip.shell.system = xsys | ||||
# Also patch our %run function in. | ||||
## im = new.instancemethod(_run_ns_sync,_ip, _ip.__class__) | ||||
## ip.shell.magic_run_ori = _ip.magic_run | ||||
## ip.shell.magic_run = im | ||||
# XXX - For some very bizarre reason, the loading of %history by default is | ||||
# failing. This needs to be fixed later, but for now at least this ensures | ||||
# that tests that use %hist run to completion. | ||||
from IPython.core import history | ||||
history.init_ipython(ip.shell) | ||||
if not hasattr(ip.shell,'magic_history'): | ||||
raise RuntimeError("Can't load magics, aborting") | ||||