Shell.py
951 lines
| 34.3 KiB
| text/x-python
|
PythonLexer
/ IPython / Shell.py
fperez
|
r0 | # -*- coding: utf-8 -*- | ||
"""IPython Shell classes. | ||||
All the matplotlib support code was co-developed with John Hunter, | ||||
matplotlib's author. | ||||
fperez
|
r297 | $Id: Shell.py 1327 2006-05-25 03:33:58Z fperez $""" | ||
fperez
|
r0 | |||
#***************************************************************************** | ||||
fperez
|
r88 | # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu> | ||
fperez
|
r0 | # | ||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#***************************************************************************** | ||||
from IPython import Release | ||||
__author__ = '%s <%s>' % Release.authors['Fernando'] | ||||
__license__ = Release.license | ||||
# Code begins | ||||
import __builtin__ | ||||
vivainio
|
r136 | import __main__ | ||
import Queue | ||||
fperez
|
r0 | import os | ||
import signal | ||||
vivainio
|
r136 | import sys | ||
fperez
|
r52 | import threading | ||
vivainio
|
r136 | import time | ||
fperez
|
r0 | |||
import IPython | ||||
fperez
|
r52 | from IPython import ultraTB | ||
from IPython.genutils import Term,warn,error,flag_calls | ||||
fperez
|
r0 | from IPython.iplib import InteractiveShell | ||
from IPython.ipmaker import make_IPython | ||||
from IPython.Magic import Magic | ||||
fperez
|
r98 | from IPython.ipstruct import Struct | ||
fperez
|
r0 | |||
fperez
|
r21 | # global flag to pass around information about Ctrl-C without exceptions | ||
KBINT = False | ||||
fperez
|
r0 | # global flag to turn on/off Tk support. | ||
USE_TK = False | ||||
#----------------------------------------------------------------------------- | ||||
# This class is trivial now, but I want to have it in to publish a clean | ||||
# interface. Later when the internals are reorganized, code that uses this | ||||
# shouldn't have to change. | ||||
class IPShell: | ||||
"""Create an IPython instance.""" | ||||
fperez
|
r58 | def __init__(self,argv=None,user_ns=None,user_global_ns=None, | ||
debug=1,shell_class=InteractiveShell): | ||||
fperez
|
r296 | self.IP = make_IPython(argv,user_ns=user_ns, | ||
user_global_ns=user_global_ns, | ||||
fperez
|
r58 | debug=debug,shell_class=shell_class) | ||
fperez
|
r0 | |||
def mainloop(self,sys_exit=0,banner=None): | ||||
self.IP.mainloop(banner) | ||||
if sys_exit: | ||||
sys.exit() | ||||
#----------------------------------------------------------------------------- | ||||
class IPShellEmbed: | ||||
"""Allow embedding an IPython shell into a running program. | ||||
Instances of this class are callable, with the __call__ method being an | ||||
alias to the embed() method of an InteractiveShell instance. | ||||
Usage (see also the example-embed.py file for a running example): | ||||
ipshell = IPShellEmbed([argv,banner,exit_msg,rc_override]) | ||||
- argv: list containing valid command-line options for IPython, as they | ||||
would appear in sys.argv[1:]. | ||||
For example, the following command-line options: | ||||
$ ipython -prompt_in1 'Input <\\#>' -colors LightBG | ||||
would be passed in the argv list as: | ||||
['-prompt_in1','Input <\\#>','-colors','LightBG'] | ||||
- banner: string which gets printed every time the interpreter starts. | ||||
- exit_msg: string which gets printed every time the interpreter exits. | ||||
- rc_override: a dict or Struct of configuration options such as those | ||||
used by IPython. These options are read from your ~/.ipython/ipythonrc | ||||
file when the Shell object is created. Passing an explicit rc_override | ||||
dict with any options you want allows you to override those values at | ||||
creation time without having to modify the file. This way you can create | ||||
embeddable instances configured in any way you want without editing any | ||||
global files (thus keeping your interactive IPython configuration | ||||
unchanged). | ||||
Then the ipshell instance can be called anywhere inside your code: | ||||
ipshell(header='') -> Opens up an IPython shell. | ||||
- header: string printed by the IPython shell upon startup. This can let | ||||
you know where in your code you are when dropping into the shell. Note | ||||
that 'banner' gets prepended to all calls, so header is used for | ||||
location-specific information. | ||||
For more details, see the __call__ method below. | ||||
When the IPython shell is exited with Ctrl-D, normal program execution | ||||
resumes. | ||||
This functionality was inspired by a posting on comp.lang.python by cmkl | ||||
<cmkleffner@gmx.de> on Dec. 06/01 concerning similar uses of pyrepl, and | ||||
by the IDL stop/continue commands.""" | ||||
fperez
|
r296 | def __init__(self,argv=None,banner='',exit_msg=None,rc_override=None, | ||
user_ns=None): | ||||
fperez
|
r0 | """Note that argv here is a string, NOT a list.""" | ||
self.set_banner(banner) | ||||
self.set_exit_msg(exit_msg) | ||||
self.set_dummy_mode(0) | ||||
# sys.displayhook is a global, we need to save the user's original | ||||
# Don't rely on __displayhook__, as the user may have changed that. | ||||
self.sys_displayhook_ori = sys.displayhook | ||||
# save readline completer status | ||||
try: | ||||
#print 'Save completer',sys.ipcompleter # dbg | ||||
self.sys_ipcompleter_ori = sys.ipcompleter | ||||
except: | ||||
pass # not nested with IPython | ||||
fperez
|
r296 | self.IP = make_IPython(argv,rc_override=rc_override, | ||
embedded=True, | ||||
user_ns=user_ns) | ||||
fperez
|
r0 | |||
# copy our own displayhook also | ||||
self.sys_displayhook_embed = sys.displayhook | ||||
# and leave the system's display hook clean | ||||
sys.displayhook = self.sys_displayhook_ori | ||||
# don't use the ipython crash handler so that user exceptions aren't | ||||
# trapped | ||||
sys.excepthook = ultraTB.FormattedTB(color_scheme = self.IP.rc.colors, | ||||
mode = self.IP.rc.xmode, | ||||
call_pdb = self.IP.rc.pdb) | ||||
self.restore_system_completer() | ||||
def restore_system_completer(self): | ||||
"""Restores the readline completer which was in place. | ||||
This allows embedded IPython within IPython not to disrupt the | ||||
parent's completion. | ||||
""" | ||||
try: | ||||
self.IP.readline.set_completer(self.sys_ipcompleter_ori) | ||||
sys.ipcompleter = self.sys_ipcompleter_ori | ||||
except: | ||||
pass | ||||
def __call__(self,header='',local_ns=None,global_ns=None,dummy=None): | ||||
"""Activate the interactive interpreter. | ||||
__call__(self,header='',local_ns=None,global_ns,dummy=None) -> Start | ||||
the interpreter shell with the given local and global namespaces, and | ||||
optionally print a header string at startup. | ||||
The shell can be globally activated/deactivated using the | ||||
set/get_dummy_mode methods. This allows you to turn off a shell used | ||||
for debugging globally. | ||||
However, *each* time you call the shell you can override the current | ||||
state of dummy_mode with the optional keyword parameter 'dummy'. For | ||||
example, if you set dummy mode on with IPShell.set_dummy_mode(1), you | ||||
can still have a specific call work by making it as IPShell(dummy=0). | ||||
The optional keyword parameter dummy controls whether the call | ||||
actually does anything. """ | ||||
# Allow the dummy parameter to override the global __dummy_mode | ||||
if dummy or (dummy != 0 and self.__dummy_mode): | ||||
return | ||||
# Set global subsystems (display,completions) to our values | ||||
sys.displayhook = self.sys_displayhook_embed | ||||
if self.IP.has_readline: | ||||
self.IP.readline.set_completer(self.IP.Completer.complete) | ||||
if self.banner and header: | ||||
format = '%s\n%s\n' | ||||
else: | ||||
format = '%s%s\n' | ||||
banner = format % (self.banner,header) | ||||
# Call the embedding code with a stack depth of 1 so it can skip over | ||||
# our call and get the original caller's namespaces. | ||||
self.IP.embed_mainloop(banner,local_ns,global_ns,stack_depth=1) | ||||
if self.exit_msg: | ||||
print self.exit_msg | ||||
# Restore global systems (display, completion) | ||||
sys.displayhook = self.sys_displayhook_ori | ||||
self.restore_system_completer() | ||||
def set_dummy_mode(self,dummy): | ||||
"""Sets the embeddable shell's dummy mode parameter. | ||||
set_dummy_mode(dummy): dummy = 0 or 1. | ||||
This parameter is persistent and makes calls to the embeddable shell | ||||
silently return without performing any action. This allows you to | ||||
globally activate or deactivate a shell you're using with a single call. | ||||
If you need to manually""" | ||||
fperez
|
r47 | if dummy not in [0,1,False,True]: | ||
raise ValueError,'dummy parameter must be boolean' | ||||
fperez
|
r0 | self.__dummy_mode = dummy | ||
def get_dummy_mode(self): | ||||
"""Return the current value of the dummy mode parameter. | ||||
""" | ||||
return self.__dummy_mode | ||||
def set_banner(self,banner): | ||||
"""Sets the global banner. | ||||
This banner gets prepended to every header printed when the shell | ||||
instance is called.""" | ||||
self.banner = banner | ||||
def set_exit_msg(self,exit_msg): | ||||
"""Sets the global exit_msg. | ||||
This exit message gets printed upon exiting every time the embedded | ||||
shell is called. It is None by default. """ | ||||
self.exit_msg = exit_msg | ||||
#----------------------------------------------------------------------------- | ||||
def sigint_handler (signum,stack_frame): | ||||
"""Sigint handler for threaded apps. | ||||
fperez
|
r21 | |||
This is a horrible hack to pass information about SIGINT _without_ using | ||||
exceptions, since I haven't been able to properly manage cross-thread | ||||
exceptions in GTK/WX. In fact, I don't think it can be done (or at least | ||||
that's my understanding from a c.l.py thread where this was discussed).""" | ||||
global KBINT | ||||
print '\nKeyboardInterrupt - Press <Enter> to continue.', | ||||
Term.cout.flush() | ||||
# Set global flag so that runsource can know that Ctrl-C was hit | ||||
KBINT = True | ||||
fperez
|
r0 | |||
class MTInteractiveShell(InteractiveShell): | ||||
"""Simple multi-threaded shell.""" | ||||
# Threading strategy taken from: | ||||
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65109, by Brian | ||||
# McErlean and John Finlay. Modified with corrections by Antoon Pardon, | ||||
# from the pygtk mailing list, to avoid lockups with system calls. | ||||
fperez
|
r59 | # class attribute to indicate whether the class supports threads or not. | ||
# Subclasses with thread support should override this as needed. | ||||
isthreaded = True | ||||
fperez
|
r0 | def __init__(self,name,usage=None,rc=Struct(opts=None,args=None), | ||
fperez
|
r58 | user_ns=None,user_global_ns=None,banner2='',**kw): | ||
fperez
|
r0 | """Similar to the normal InteractiveShell, but with threading control""" | ||
fperez
|
r58 | InteractiveShell.__init__(self,name,usage,rc,user_ns, | ||
user_global_ns,banner2) | ||||
fperez
|
r0 | |||
vivainio
|
r136 | # Locking control variable. We need to use a norma lock, not an RLock | ||
# here. I'm not exactly sure why, it seems to me like it should be | ||||
# the opposite, but we deadlock with an RLock. Puzzled... | ||||
self.thread_ready = threading.Condition(threading.Lock()) | ||||
fperez
|
r0 | |||
vivainio
|
r136 | # A queue to hold the code to be executed. A scalar variable is NOT | ||
# enough, because uses like macros cause reentrancy. | ||||
self.code_queue = Queue.Queue() | ||||
fperez
|
r0 | # Stuff to do at closing time | ||
self._kill = False | ||||
on_kill = kw.get('on_kill') | ||||
if on_kill is None: | ||||
on_kill = [] | ||||
# Check that all things to kill are callable: | ||||
for t in on_kill: | ||||
if not callable(t): | ||||
raise TypeError,'on_kill must be a list of callables' | ||||
self.on_kill = on_kill | ||||
def runsource(self, source, filename="<input>", symbol="single"): | ||||
"""Compile and run some source in the interpreter. | ||||
Modified version of code.py's runsource(), to handle threading issues. | ||||
See the original for full docstring details.""" | ||||
fperez
|
r21 | |||
global KBINT | ||||
# If Ctrl-C was typed, we reset the flag and return right away | ||||
if KBINT: | ||||
KBINT = False | ||||
return False | ||||
fperez
|
r0 | |||
try: | ||||
code = self.compile(source, filename, symbol) | ||||
except (OverflowError, SyntaxError, ValueError): | ||||
# Case 1 | ||||
self.showsyntaxerror(filename) | ||||
return False | ||||
if code is None: | ||||
# Case 2 | ||||
return True | ||||
# Case 3 | ||||
vivainio
|
r136 | # Store code in queue, so the execution thread can handle it. | ||
# Note that with macros and other applications, we MAY re-enter this | ||||
# section, so we have to acquire the lock with non-blocking semantics, | ||||
# else we deadlock. | ||||
got_lock = self.thread_ready.acquire(False) | ||||
self.code_queue.put(code) | ||||
if got_lock: | ||||
self.thread_ready.wait() # Wait until processed in timeout interval | ||||
self.thread_ready.release() | ||||
fperez
|
r0 | |||
return False | ||||
def runcode(self): | ||||
"""Execute a code object. | ||||
Multithreaded wrapper around IPython's runcode().""" | ||||
# lock thread-protected stuff | ||||
fperez
|
r267 | got_lock = self.thread_ready.acquire(False) | ||
fperez
|
r0 | |||
# Install sigint handler | ||||
try: | ||||
signal.signal(signal.SIGINT, sigint_handler) | ||||
except SystemError: | ||||
# This happens under Windows, which seems to have all sorts | ||||
# of problems with signal handling. Oh well... | ||||
pass | ||||
if self._kill: | ||||
print >>Term.cout, 'Closing threads...', | ||||
Term.cout.flush() | ||||
for tokill in self.on_kill: | ||||
tokill() | ||||
print >>Term.cout, 'Done.' | ||||
vivainio
|
r136 | # Flush queue of pending code by calling the run methood of the parent | ||
# class with all items which may be in the queue. | ||||
while 1: | ||||
try: | ||||
code_to_run = self.code_queue.get_nowait() | ||||
except Queue.Empty: | ||||
break | ||||
fperez
|
r283 | if got_lock: | ||
self.thread_ready.notify() | ||||
InteractiveShell.runcode(self,code_to_run) | ||||
else: | ||||
break | ||||
fperez
|
r0 | |||
# We're done with thread-protected variables | ||||
fperez
|
r267 | if got_lock: | ||
self.thread_ready.release() | ||||
fperez
|
r0 | # This MUST return true for gtk threading to work | ||
return True | ||||
fperez
|
r283 | def kill(self): | ||
fperez
|
r0 | """Kill the thread, returning when it has been shut down.""" | ||
fperez
|
r283 | got_lock = self.thread_ready.acquire(False) | ||
fperez
|
r0 | self._kill = True | ||
fperez
|
r283 | if got_lock: | ||
self.thread_ready.release() | ||||
fperez
|
r0 | |||
class MatplotlibShellBase: | ||||
"""Mixin class to provide the necessary modifications to regular IPython | ||||
shell classes for matplotlib support. | ||||
Given Python's MRO, this should be used as the FIRST class in the | ||||
inheritance hierarchy, so that it overrides the relevant methods.""" | ||||
fperez
|
r296 | def _matplotlib_config(self,name,user_ns): | ||
vivainio
|
r136 | """Return items needed to setup the user's shell with matplotlib""" | ||
fperez
|
r0 | |||
# Initialize matplotlib to interactive mode always | ||||
import matplotlib | ||||
from matplotlib import backends | ||||
matplotlib.interactive(True) | ||||
def use(arg): | ||||
"""IPython wrapper for matplotlib's backend switcher. | ||||
In interactive use, we can not allow switching to a different | ||||
interactive backend, since thread conflicts will most likely crash | ||||
the python interpreter. This routine does a safety check first, | ||||
and refuses to perform a dangerous switch. It still allows | ||||
switching to non-interactive backends.""" | ||||
if arg in backends.interactive_bk and arg != self.mpl_backend: | ||||
m=('invalid matplotlib backend switch.\n' | ||||
'This script attempted to switch to the interactive ' | ||||
'backend: `%s`\n' | ||||
'Your current choice of interactive backend is: `%s`\n\n' | ||||
'Switching interactive matplotlib backends at runtime\n' | ||||
'would crash the python interpreter, ' | ||||
'and IPython has blocked it.\n\n' | ||||
'You need to either change your choice of matplotlib backend\n' | ||||
'by editing your .matplotlibrc file, or run this script as a \n' | ||||
'standalone file from the command line, not using IPython.\n' % | ||||
(arg,self.mpl_backend) ) | ||||
raise RuntimeError, m | ||||
else: | ||||
self.mpl_use(arg) | ||||
self.mpl_use._called = True | ||||
self.matplotlib = matplotlib | ||||
self.mpl_backend = matplotlib.rcParams['backend'] | ||||
# we also need to block switching of interactive backends by use() | ||||
self.mpl_use = matplotlib.use | ||||
self.mpl_use._called = False | ||||
# overwrite the original matplotlib.use with our wrapper | ||||
matplotlib.use = use | ||||
# This must be imported last in the matplotlib series, after | ||||
# backend/interactivity choices have been made | ||||
fperez
|
r296 | import matplotlib.pylab as pylab | ||
self.pylab = pylab | ||||
fperez
|
r0 | |||
tzanko
|
r2 | self.pylab.show._needmain = False | ||
# We need to detect at runtime whether show() is called by the user. | ||||
# For this, we wrap it into a decorator which adds a 'called' flag. | ||||
self.pylab.draw_if_interactive = flag_calls(self.pylab.draw_if_interactive) | ||||
fperez
|
r0 | # Build a user namespace initialized with matplotlib/matlab features. | ||
fperez
|
r296 | user_ns = IPython.ipapi.make_user_ns(user_ns) | ||
fperez
|
r0 | |||
exec ("import matplotlib\n" | ||||
fperez
|
r296 | "import matplotlib.pylab as pylab\n" | ||
"from matplotlib.pylab import *") in user_ns | ||||
fperez
|
r0 | |||
# Build matplotlib info banner | ||||
b=""" | ||||
Welcome to pylab, a matplotlib-based Python environment. | ||||
For more information, type 'help(pylab)'. | ||||
""" | ||||
return user_ns,b | ||||
def mplot_exec(self,fname,*where,**kw): | ||||
"""Execute a matplotlib script. | ||||
This is a call to execfile(), but wrapped in safeties to properly | ||||
handle interactive rendering and backend switching.""" | ||||
#print '*** Matplotlib runner ***' # dbg | ||||
# turn off rendering until end of script | ||||
isInteractive = self.matplotlib.rcParams['interactive'] | ||||
self.matplotlib.interactive(False) | ||||
self.safe_execfile(fname,*where,**kw) | ||||
self.matplotlib.interactive(isInteractive) | ||||
# make rendering call now, if the user tried to do it | ||||
if self.pylab.draw_if_interactive.called: | ||||
self.pylab.draw() | ||||
self.pylab.draw_if_interactive.called = False | ||||
# if a backend switch was performed, reverse it now | ||||
if self.mpl_use._called: | ||||
self.matplotlib.rcParams['backend'] = self.mpl_backend | ||||
def magic_run(self,parameter_s=''): | ||||
Magic.magic_run(self,parameter_s,runner=self.mplot_exec) | ||||
# Fix the docstring so users see the original as well | ||||
magic_run.__doc__ = "%s\n%s" % (Magic.magic_run.__doc__, | ||||
"\n *** Modified %run for Matplotlib," | ||||
" with proper interactive handling ***") | ||||
# Now we provide 2 versions of a matplotlib-aware IPython base shells, single | ||||
# and multithreaded. Note that these are meant for internal use, the IPShell* | ||||
# classes below are the ones meant for public consumption. | ||||
class MatplotlibShell(MatplotlibShellBase,InteractiveShell): | ||||
"""Single-threaded shell with matplotlib support.""" | ||||
def __init__(self,name,usage=None,rc=Struct(opts=None,args=None), | ||||
fperez
|
r58 | user_ns=None,user_global_ns=None,**kw): | ||
fperez
|
r296 | user_ns,b2 = self._matplotlib_config(name,user_ns) | ||
fperez
|
r58 | InteractiveShell.__init__(self,name,usage,rc,user_ns,user_global_ns, | ||
banner2=b2,**kw) | ||||
fperez
|
r0 | |||
class MatplotlibMTShell(MatplotlibShellBase,MTInteractiveShell): | ||||
"""Multi-threaded shell with matplotlib support.""" | ||||
def __init__(self,name,usage=None,rc=Struct(opts=None,args=None), | ||||
fperez
|
r58 | user_ns=None,user_global_ns=None, **kw): | ||
fperez
|
r297 | user_ns,b2 = self._matplotlib_config(name,user_ns) | ||
fperez
|
r58 | MTInteractiveShell.__init__(self,name,usage,rc,user_ns,user_global_ns, | ||
banner2=b2,**kw) | ||||
fperez
|
r0 | |||
#----------------------------------------------------------------------------- | ||||
# Utility functions for the different GUI enabled IPShell* classes. | ||||
def get_tk(): | ||||
"""Tries to import Tkinter and returns a withdrawn Tkinter root | ||||
window. If Tkinter is already imported or not available, this | ||||
returns None. This function calls `hijack_tk` underneath. | ||||
""" | ||||
if not USE_TK or sys.modules.has_key('Tkinter'): | ||||
return None | ||||
else: | ||||
try: | ||||
import Tkinter | ||||
except ImportError: | ||||
return None | ||||
else: | ||||
hijack_tk() | ||||
r = Tkinter.Tk() | ||||
r.withdraw() | ||||
return r | ||||
def hijack_tk(): | ||||
"""Modifies Tkinter's mainloop with a dummy so when a module calls | ||||
mainloop, it does not block. | ||||
""" | ||||
def misc_mainloop(self, n=0): | ||||
pass | ||||
def tkinter_mainloop(n=0): | ||||
pass | ||||
import Tkinter | ||||
Tkinter.Misc.mainloop = misc_mainloop | ||||
Tkinter.mainloop = tkinter_mainloop | ||||
def update_tk(tk): | ||||
"""Updates the Tkinter event loop. This is typically called from | ||||
the respective WX or GTK mainloops. | ||||
""" | ||||
if tk: | ||||
tk.update() | ||||
def hijack_wx(): | ||||
"""Modifies wxPython's MainLoop with a dummy so user code does not | ||||
block IPython. The hijacked mainloop function is returned. | ||||
""" | ||||
def dummy_mainloop(*args, **kw): | ||||
pass | ||||
import wxPython | ||||
ver = wxPython.__version__ | ||||
orig_mainloop = None | ||||
if ver[:3] >= '2.5': | ||||
import wx | ||||
if hasattr(wx, '_core_'): core = getattr(wx, '_core_') | ||||
elif hasattr(wx, '_core'): core = getattr(wx, '_core') | ||||
else: raise AttributeError('Could not find wx core module') | ||||
orig_mainloop = core.PyApp_MainLoop | ||||
core.PyApp_MainLoop = dummy_mainloop | ||||
elif ver[:3] == '2.4': | ||||
orig_mainloop = wxPython.wxc.wxPyApp_MainLoop | ||||
wxPython.wxc.wxPyApp_MainLoop = dummy_mainloop | ||||
else: | ||||
warn("Unable to find either wxPython version 2.4 or >= 2.5.") | ||||
return orig_mainloop | ||||
def hijack_gtk(): | ||||
"""Modifies pyGTK's mainloop with a dummy so user code does not | ||||
block IPython. This function returns the original `gtk.mainloop` | ||||
function that has been hijacked. | ||||
""" | ||||
def dummy_mainloop(*args, **kw): | ||||
pass | ||||
import gtk | ||||
if gtk.pygtk_version >= (2,4,0): orig_mainloop = gtk.main | ||||
else: orig_mainloop = gtk.mainloop | ||||
gtk.mainloop = dummy_mainloop | ||||
gtk.main = dummy_mainloop | ||||
return orig_mainloop | ||||
#----------------------------------------------------------------------------- | ||||
# The IPShell* classes below are the ones meant to be run by external code as | ||||
# IPython instances. Note that unless a specific threading strategy is | ||||
# desired, the factory function start() below should be used instead (it | ||||
# selects the proper threaded class). | ||||
class IPShellGTK(threading.Thread): | ||||
"""Run a gtk mainloop() in a separate thread. | ||||
Python commands can be passed to the thread where they will be executed. | ||||
This is implemented by periodically checking for passed code using a | ||||
GTK timeout callback.""" | ||||
TIMEOUT = 100 # Millisecond interval between timeouts. | ||||
fperez
|
r58 | def __init__(self,argv=None,user_ns=None,user_global_ns=None, | ||
debug=1,shell_class=MTInteractiveShell): | ||||
fperez
|
r0 | |||
import gtk | ||||
self.gtk = gtk | ||||
self.gtk_mainloop = hijack_gtk() | ||||
# Allows us to use both Tk and GTK. | ||||
self.tk = get_tk() | ||||
if gtk.pygtk_version >= (2,4,0): mainquit = self.gtk.main_quit | ||||
else: mainquit = self.gtk.mainquit | ||||
fperez
|
r58 | self.IP = make_IPython(argv,user_ns=user_ns, | ||
user_global_ns=user_global_ns, | ||||
debug=debug, | ||||
fperez
|
r0 | shell_class=shell_class, | ||
on_kill=[mainquit]) | ||||
fperez
|
r45 | |||
# HACK: slot for banner in self; it will be passed to the mainloop | ||||
# method only and .run() needs it. The actual value will be set by | ||||
# .mainloop(). | ||||
self._banner = None | ||||
fperez
|
r0 | threading.Thread.__init__(self) | ||
def run(self): | ||||
fperez
|
r45 | self.IP.mainloop(self._banner) | ||
fperez
|
r0 | self.IP.kill() | ||
fperez
|
r45 | def mainloop(self,sys_exit=0,banner=None): | ||
fperez
|
r0 | |||
fperez
|
r45 | self._banner = banner | ||
fperez
|
r0 | if self.gtk.pygtk_version >= (2,4,0): | ||
import gobject | ||||
fperez
|
r95 | gobject.idle_add(self.on_timer) | ||
fperez
|
r0 | else: | ||
fperez
|
r95 | self.gtk.idle_add(self.on_timer) | ||
fperez
|
r0 | |||
if sys.platform != 'win32': | ||||
try: | ||||
if self.gtk.gtk_version[0] >= 2: | ||||
self.gtk.threads_init() | ||||
except AttributeError: | ||||
pass | ||||
except RuntimeError: | ||||
error('Your pyGTK likely has not been compiled with ' | ||||
'threading support.\n' | ||||
'The exception printout is below.\n' | ||||
'You can either rebuild pyGTK with threads, or ' | ||||
'try using \n' | ||||
'matplotlib with a different backend (like Tk or WX).\n' | ||||
'Note that matplotlib will most likely not work in its ' | ||||
'current state!') | ||||
self.IP.InteractiveTB() | ||||
self.start() | ||||
self.gtk.threads_enter() | ||||
self.gtk_mainloop() | ||||
self.gtk.threads_leave() | ||||
self.join() | ||||
def on_timer(self): | ||||
fperez
|
r95 | """Called when GTK is idle. | ||
Must return True always, otherwise GTK stops calling it""" | ||||
fperez
|
r0 | |||
fperez
|
r95 | update_tk(self.tk) | ||
self.IP.runcode() | ||||
time.sleep(0.01) | ||||
return True | ||||
fperez
|
r0 | |||
class IPShellWX(threading.Thread): | ||||
"""Run a wx mainloop() in a separate thread. | ||||
Python commands can be passed to the thread where they will be executed. | ||||
This is implemented by periodically checking for passed code using a | ||||
GTK timeout callback.""" | ||||
TIMEOUT = 100 # Millisecond interval between timeouts. | ||||
fperez
|
r58 | def __init__(self,argv=None,user_ns=None,user_global_ns=None, | ||
debug=1,shell_class=MTInteractiveShell): | ||||
fperez
|
r0 | |||
fperez
|
r91 | self.IP = make_IPython(argv,user_ns=user_ns, | ||
user_global_ns=user_global_ns, | ||||
debug=debug, | ||||
shell_class=shell_class, | ||||
on_kill=[self.wxexit]) | ||||
wantedwxversion=self.IP.rc.wxversion | ||||
if wantedwxversion!="0": | ||||
try: | ||||
import wxversion | ||||
except ImportError: | ||||
error('The wxversion module is needed for WX version selection') | ||||
else: | ||||
try: | ||||
wxversion.select(wantedwxversion) | ||||
except: | ||||
self.IP.InteractiveTB() | ||||
error('Requested wxPython version %s could not be loaded' % | ||||
wantedwxversion) | ||||
fperez
|
r0 | import wxPython.wx as wx | ||
threading.Thread.__init__(self) | ||||
self.wx = wx | ||||
self.wx_mainloop = hijack_wx() | ||||
# Allows us to use both Tk and GTK. | ||||
self.tk = get_tk() | ||||
fperez
|
r91 | |||
fperez
|
r45 | # HACK: slot for banner in self; it will be passed to the mainloop | ||
# method only and .run() needs it. The actual value will be set by | ||||
# .mainloop(). | ||||
self._banner = None | ||||
fperez
|
r0 | self.app = None | ||
def wxexit(self, *args): | ||||
if self.app is not None: | ||||
self.app.agent.timer.Stop() | ||||
self.app.ExitMainLoop() | ||||
def run(self): | ||||
fperez
|
r45 | self.IP.mainloop(self._banner) | ||
fperez
|
r0 | self.IP.kill() | ||
fperez
|
r45 | def mainloop(self,sys_exit=0,banner=None): | ||
self._banner = banner | ||||
fperez
|
r0 | |||
self.start() | ||||
class TimerAgent(self.wx.wxMiniFrame): | ||||
wx = self.wx | ||||
IP = self.IP | ||||
tk = self.tk | ||||
def __init__(self, parent, interval): | ||||
style = self.wx.wxDEFAULT_FRAME_STYLE | self.wx.wxTINY_CAPTION_HORIZ | ||||
self.wx.wxMiniFrame.__init__(self, parent, -1, ' ', pos=(200, 200), | ||||
size=(100, 100),style=style) | ||||
self.Show(False) | ||||
self.interval = interval | ||||
self.timerId = self.wx.wxNewId() | ||||
def StartWork(self): | ||||
self.timer = self.wx.wxTimer(self, self.timerId) | ||||
self.wx.EVT_TIMER(self, self.timerId, self.OnTimer) | ||||
self.timer.Start(self.interval) | ||||
def OnTimer(self, event): | ||||
update_tk(self.tk) | ||||
self.IP.runcode() | ||||
class App(self.wx.wxApp): | ||||
wx = self.wx | ||||
TIMEOUT = self.TIMEOUT | ||||
def OnInit(self): | ||||
'Create the main window and insert the custom frame' | ||||
self.agent = TimerAgent(None, self.TIMEOUT) | ||||
self.agent.Show(self.wx.false) | ||||
self.agent.StartWork() | ||||
return self.wx.true | ||||
self.app = App(redirect=False) | ||||
self.wx_mainloop(self.app) | ||||
self.join() | ||||
class IPShellQt(threading.Thread): | ||||
"""Run a Qt event loop in a separate thread. | ||||
Python commands can be passed to the thread where they will be executed. | ||||
This is implemented by periodically checking for passed code using a | ||||
Qt timer / slot.""" | ||||
TIMEOUT = 100 # Millisecond interval between timeouts. | ||||
fperez
|
r58 | def __init__(self,argv=None,user_ns=None,user_global_ns=None, | ||
debug=0,shell_class=MTInteractiveShell): | ||||
fperez
|
r0 | |||
import qt | ||||
class newQApplication: | ||||
def __init__( self ): | ||||
self.QApplication = qt.QApplication | ||||
def __call__( *args, **kwargs ): | ||||
return qt.qApp | ||||
def exec_loop( *args, **kwargs ): | ||||
pass | ||||
def __getattr__( self, name ): | ||||
return getattr( self.QApplication, name ) | ||||
qt.QApplication = newQApplication() | ||||
# Allows us to use both Tk and QT. | ||||
self.tk = get_tk() | ||||
fperez
|
r58 | self.IP = make_IPython(argv,user_ns=user_ns, | ||
user_global_ns=user_global_ns, | ||||
debug=debug, | ||||
fperez
|
r0 | shell_class=shell_class, | ||
on_kill=[qt.qApp.exit]) | ||||
fperez
|
r45 | |||
# HACK: slot for banner in self; it will be passed to the mainloop | ||||
# method only and .run() needs it. The actual value will be set by | ||||
# .mainloop(). | ||||
self._banner = None | ||||
fperez
|
r0 | |||
threading.Thread.__init__(self) | ||||
def run(self): | ||||
fperez
|
r45 | self.IP.mainloop(self._banner) | ||
fperez
|
r0 | self.IP.kill() | ||
fperez
|
r45 | def mainloop(self,sys_exit=0,banner=None): | ||
import qt | ||||
self._banner = banner | ||||
fperez
|
r0 | if qt.QApplication.startingUp(): | ||
fperez
|
r45 | a = qt.QApplication.QApplication(sys.argv) | ||
fperez
|
r0 | self.timer = qt.QTimer() | ||
qt.QObject.connect( self.timer, qt.SIGNAL( 'timeout()' ), self.on_timer ) | ||||
self.start() | ||||
self.timer.start( self.TIMEOUT, True ) | ||||
while True: | ||||
if self.IP._kill: break | ||||
qt.qApp.exec_loop() | ||||
self.join() | ||||
def on_timer(self): | ||||
update_tk(self.tk) | ||||
result = self.IP.runcode() | ||||
self.timer.start( self.TIMEOUT, True ) | ||||
return result | ||||
# A set of matplotlib public IPython shell classes, for single-threaded | ||||
# (Tk* and FLTK* backends) and multithreaded (GTK* and WX* backends) use. | ||||
class IPShellMatplotlib(IPShell): | ||||
"""Subclass IPShell with MatplotlibShell as the internal shell. | ||||
Single-threaded class, meant for the Tk* and FLTK* backends. | ||||
Having this on a separate class simplifies the external driver code.""" | ||||
fperez
|
r58 | def __init__(self,argv=None,user_ns=None,user_global_ns=None,debug=1): | ||
IPShell.__init__(self,argv,user_ns,user_global_ns,debug, | ||||
shell_class=MatplotlibShell) | ||||
fperez
|
r0 | |||
class IPShellMatplotlibGTK(IPShellGTK): | ||||
"""Subclass IPShellGTK with MatplotlibMTShell as the internal shell. | ||||
Multi-threaded class, meant for the GTK* backends.""" | ||||
fperez
|
r58 | def __init__(self,argv=None,user_ns=None,user_global_ns=None,debug=1): | ||
IPShellGTK.__init__(self,argv,user_ns,user_global_ns,debug, | ||||
shell_class=MatplotlibMTShell) | ||||
fperez
|
r0 | |||
class IPShellMatplotlibWX(IPShellWX): | ||||
"""Subclass IPShellWX with MatplotlibMTShell as the internal shell. | ||||
Multi-threaded class, meant for the WX* backends.""" | ||||
fperez
|
r58 | def __init__(self,argv=None,user_ns=None,user_global_ns=None,debug=1): | ||
IPShellWX.__init__(self,argv,user_ns,user_global_ns,debug, | ||||
shell_class=MatplotlibMTShell) | ||||
fperez
|
r0 | |||
class IPShellMatplotlibQt(IPShellQt): | ||||
"""Subclass IPShellQt with MatplotlibMTShell as the internal shell. | ||||
Multi-threaded class, meant for the Qt* backends.""" | ||||
fperez
|
r58 | def __init__(self,argv=None,user_ns=None,user_global_ns=None,debug=1): | ||
IPShellQt.__init__(self,argv,user_ns,user_global_ns,debug, | ||||
shell_class=MatplotlibMTShell) | ||||
fperez
|
r0 | |||
#----------------------------------------------------------------------------- | ||||
# Factory functions to actually start the proper thread-aware shell | ||||
def _matplotlib_shell_class(): | ||||
"""Factory function to handle shell class selection for matplotlib. | ||||
The proper shell class to use depends on the matplotlib backend, since | ||||
each backend requires a different threading strategy.""" | ||||
try: | ||||
import matplotlib | ||||
except ImportError: | ||||
error('matplotlib could NOT be imported! Starting normal IPython.') | ||||
sh_class = IPShell | ||||
else: | ||||
backend = matplotlib.rcParams['backend'] | ||||
if backend.startswith('GTK'): | ||||
sh_class = IPShellMatplotlibGTK | ||||
elif backend.startswith('WX'): | ||||
sh_class = IPShellMatplotlibWX | ||||
elif backend.startswith('Qt'): | ||||
sh_class = IPShellMatplotlibQt | ||||
else: | ||||
sh_class = IPShellMatplotlib | ||||
#print 'Using %s with the %s backend.' % (sh_class,backend) # dbg | ||||
return sh_class | ||||
# This is the one which should be called by external code. | ||||
vivainio
|
r146 | def start(user_ns = None): | ||
fperez
|
r0 | """Return a running shell instance, dealing with threading options. | ||
This is a factory function which will instantiate the proper IPython shell | ||||
based on the user's threading choice. Such a selector is needed because | ||||
different GUI toolkits require different thread handling details.""" | ||||
global USE_TK | ||||
# Crude sys.argv hack to extract the threading options. | ||||
fperez
|
r47 | argv = sys.argv | ||
if len(argv) > 1: | ||||
if len(argv) > 2: | ||||
arg2 = argv[2] | ||||
fperez
|
r0 | if arg2.endswith('-tk'): | ||
USE_TK = True | ||||
fperez
|
r47 | arg1 = argv[1] | ||
fperez
|
r0 | if arg1.endswith('-gthread'): | ||
shell = IPShellGTK | ||||
elif arg1.endswith( '-qthread' ): | ||||
shell = IPShellQt | ||||
elif arg1.endswith('-wthread'): | ||||
shell = IPShellWX | ||||
elif arg1.endswith('-pylab'): | ||||
shell = _matplotlib_shell_class() | ||||
else: | ||||
shell = IPShell | ||||
else: | ||||
shell = IPShell | ||||
vivainio
|
r146 | return shell(user_ns = user_ns) | ||
fperez
|
r0 | |||
# Some aliases for backwards compatibility | ||||
IPythonShell = IPShell | ||||
IPythonShellEmbed = IPShellEmbed | ||||
#************************ End of file <Shell.py> *************************** | ||||