From fa202107f8055d6c18b4dc3aac166bb4af9211e5 2012-05-31 00:27:54 From: Fernando Perez Date: 2012-05-31 00:27:54 Subject: [PATCH] Merge pull request #1791 from minrk/zmq_magics Update zmqshell magics, this updates the API with the changes from the big magics refactor (these ones were accidentally left out in said work). --- diff --git a/IPython/core/magics/code.py b/IPython/core/magics/code.py index 302dc71..6679abb 100644 --- a/IPython/core/magics/code.py +++ b/IPython/core/magics/code.py @@ -174,7 +174,8 @@ class CodeMagics(Magics): self.shell.set_next_input(contents) - def _find_edit_target(self, args, opts, last_call): + @staticmethod + def _find_edit_target(shell, args, opts, last_call): """Utility method used by magic_edit to find what to edit.""" def make_filename(arg): @@ -203,13 +204,13 @@ class CodeMagics(Magics): if opts_prev: args = '_%s' % last_call[0] - if not self.shell.user_ns.has_key(args): + if not shell.user_ns.has_key(args): args = last_call[1] # use last_call to remember the state of the previous call, but don't # let it be clobbered by successive '-p' calls. try: - last_call[0] = self.shell.displayhook.prompt_count + last_call[0] = shell.displayhook.prompt_count if not opts_prev: last_call[1] = args except: @@ -227,14 +228,14 @@ class CodeMagics(Magics): use_temp = False elif args: # Mode where user specifies ranges of lines, like in %macro. - data = self.shell.extract_input_lines(args, opts_raw) + data = shell.extract_input_lines(args, opts_raw) if not data: try: # Load the parameter given as a variable. If not a string, # process it as an object instead (below) #print '*** args',args,'type',type(args) # dbg - data = eval(args, self.shell.user_ns) + data = eval(args, shell.user_ns) if not isinstance(data, basestring): raise DataIsObject @@ -293,7 +294,7 @@ class CodeMagics(Magics): use_temp = False if use_temp: - filename = self.shell.mktempfile(data) + filename = shell.mktempfile(data) print 'IPython will make a temporary file named:',filename return filename, lineno, use_temp diff --git a/IPython/core/usage.py b/IPython/core/usage.py index 309d1e2..e9d4ef9 100644 --- a/IPython/core/usage.py +++ b/IPython/core/usage.py @@ -538,3 +538,11 @@ default_gui_banner_parts = default_banner_parts + [gui_note] default_banner = ''.join(default_banner_parts) default_gui_banner = ''.join(default_gui_banner_parts) + +# page GUI Reference, for use as a magic: + +def page_guiref(arg_s=None): + """Show a basic reference about the GUI Console.""" + from IPython.core import page + page.page(gui_reference, auto_html=True) + diff --git a/IPython/frontend/qt/base_frontend_mixin.py b/IPython/frontend/qt/base_frontend_mixin.py index 697d45f..f558e66 100644 --- a/IPython/frontend/qt/base_frontend_mixin.py +++ b/IPython/frontend/qt/base_frontend_mixin.py @@ -25,6 +25,7 @@ class BaseFrontendMixin(object): # Disconnect the old kernel manager, if necessary. old_manager = self._kernel_manager if old_manager is not None: + old_manager.started_kernel.disconnect(self._started_kernel) old_manager.started_channels.disconnect(self._started_channels) old_manager.stopped_channels.disconnect(self._stopped_channels) @@ -45,6 +46,7 @@ class BaseFrontendMixin(object): return # Connect the new kernel manager. + kernel_manager.started_kernel.connect(self._started_kernel) kernel_manager.started_channels.connect(self._started_channels) kernel_manager.stopped_channels.connect(self._stopped_channels) @@ -78,6 +80,11 @@ class BaseFrontendMixin(object): The time since the heartbeat was last received. """ + def _started_kernel(self): + """Called when the KernelManager starts (or restarts) the kernel subprocess. + Channels may or may not be running at this point. + """ + def _started_channels(self): """ Called when the KernelManager channels have started listening or when the frontend is assigned an already listening KernelManager. diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py index 46b6fd1..5d83e1a 100644 --- a/IPython/frontend/qt/console/ipython_widget.py +++ b/IPython/frontend/qt/console/ipython_widget.py @@ -255,11 +255,27 @@ class IPythonWidget(FrontendWidget): self._append_plain_text(u'\n', True) def _started_channels(self): - """ Reimplemented to make a history request. - """ + """Reimplemented to make a history request and load %guiref.""" super(IPythonWidget, self)._started_channels() + self._load_guiref_magic() self.kernel_manager.shell_channel.history(hist_access_type='tail', n=1000) + + def _started_kernel(self): + """Load %guiref when the kernel starts (if channels are also started). + + Principally triggered by kernel restart. + """ + if self.kernel_manager.shell_channel is not None: + self._load_guiref_magic() + + def _load_guiref_magic(self): + """Load %guiref magic.""" + self.kernel_manager.shell_channel.execute('\n'.join([ + "from IPython.core import usage", + "get_ipython().register_magic_function(usage.page_guiref, 'line', 'guiref')", + ]), silent=True) + #--------------------------------------------------------------------------- # 'ConsoleWidget' public interface #--------------------------------------------------------------------------- diff --git a/IPython/frontend/qt/kernelmanager.py b/IPython/frontend/qt/kernelmanager.py index 4e68562..387d667 100644 --- a/IPython/frontend/qt/kernelmanager.py +++ b/IPython/frontend/qt/kernelmanager.py @@ -183,6 +183,9 @@ class QtKernelManager(KernelManager, SuperQObject): __metaclass__ = MetaQObjectHasTraits # Emitted when the kernel manager has started listening. + started_kernel = QtCore.Signal() + + # Emitted when the kernel manager has started listening. started_channels = QtCore.Signal() # Emitted when the kernel manager has stopped listening. @@ -206,6 +209,7 @@ class QtKernelManager(KernelManager, SuperQObject): if self._shell_channel is not None: self._shell_channel.reset_first_reply() super(QtKernelManager, self).start_kernel(*args, **kw) + self.started_kernel.emit() #------ Channel management ------------------------------------------------- diff --git a/IPython/zmq/zmqshell.py b/IPython/zmq/zmqshell.py index 0a0709f..6f396f7 100644 --- a/IPython/zmq/zmqshell.py +++ b/IPython/zmq/zmqshell.py @@ -16,11 +16,9 @@ machinery. This should thus be thought of as scaffolding. from __future__ import print_function # Stdlib -import inspect import os import sys import time -from subprocess import Popen, PIPE # System library imports from zmq.eventloop import ioloop @@ -29,11 +27,11 @@ from zmq.eventloop import ioloop from IPython.core.interactiveshell import ( InteractiveShell, InteractiveShellABC ) -from IPython.core import page, pylabtools +from IPython.core import page from IPython.core.autocall import ZMQExitAutocall from IPython.core.displaypub import DisplayPublisher -from IPython.core.macro import Macro -from IPython.core.magics import MacroToEdit +from IPython.core.magics import MacroToEdit, CodeMagics +from IPython.core.magic import magics_class, line_magic, Magics from IPython.core.payloadpage import install_payload_page from IPython.lib.kernel import ( get_connection_file, get_connection_info, connect_qtconsole @@ -41,7 +39,6 @@ from IPython.lib.kernel import ( from IPython.testing.skipdoctest import skip_doctest from IPython.utils import io from IPython.utils.jsonutil import json_clean -from IPython.utils.path import get_py_filename from IPython.utils.process import arg_split from IPython.utils.traitlets import Instance, Type, Dict, CBool, CBytes from IPython.utils.warn import warn, error @@ -99,106 +96,8 @@ class ZMQDisplayPublisher(DisplayPublisher): parent=self.parent_header, ident=self.topic, ) -class ZMQInteractiveShell(InteractiveShell): - """A subclass of InteractiveShell for ZMQ.""" - - displayhook_class = Type(ZMQShellDisplayHook) - display_pub_class = Type(ZMQDisplayPublisher) - - # Override the traitlet in the parent class, because there's no point using - # readline for the kernel. Can be removed when the readline code is moved - # to the terminal frontend. - colors_force = CBool(True) - readline_use = CBool(False) - # autoindent has no meaning in a zmqshell, and attempting to enable it - # will print a warning in the absence of readline. - autoindent = CBool(False) - - exiter = Instance(ZMQExitAutocall) - def _exiter_default(self): - return ZMQExitAutocall(self) - - def _exit_now_changed(self, name, old, new): - """stop eventloop when exit_now fires""" - if new: - loop = ioloop.IOLoop.instance() - loop.add_timeout(time.time()+0.1, loop.stop) - - keepkernel_on_exit = None - - # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no - # interactive input being read; we provide event loop support in ipkernel - from .eventloops import enable_gui - enable_gui = staticmethod(enable_gui) - - def init_environment(self): - """Configure the user's environment. - - """ - env = os.environ - # These two ensure 'ls' produces nice coloring on BSD-derived systems - env['TERM'] = 'xterm-color' - env['CLICOLOR'] = '1' - # Since normal pagers don't work at all (over pexpect we don't have - # single-key control of the subprocess), try to disable paging in - # subprocesses as much as possible. - env['PAGER'] = 'cat' - env['GIT_PAGER'] = 'cat' - - # And install the payload version of page. - install_payload_page() - - def auto_rewrite_input(self, cmd): - """Called to show the auto-rewritten input for autocall and friends. - - FIXME: this payload is currently not correctly processed by the - frontend. - """ - new = self.prompt_manager.render('rewrite') + cmd - payload = dict( - source='IPython.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input', - transformed_input=new, - ) - self.payload_manager.write_payload(payload) - - def ask_exit(self): - """Engage the exit actions.""" - self.exit_now = True - payload = dict( - source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit', - exit=True, - keepkernel=self.keepkernel_on_exit, - ) - self.payload_manager.write_payload(payload) - - def _showtraceback(self, etype, evalue, stb): - - exc_content = { - u'traceback' : stb, - u'ename' : unicode(etype.__name__), - u'evalue' : unicode(evalue) - } - - dh = self.displayhook - # Send exception info over pub socket for other clients than the caller - # to pick up - topic = None - if dh.topic: - topic = dh.topic.replace(b'pyout', b'pyerr') - - exc_msg = dh.session.send(dh.pub_socket, u'pyerr', json_clean(exc_content), dh.parent_header, ident=topic) - - # FIXME - Hack: store exception info in shell object. Right now, the - # caller is reading this info after the fact, we need to fix this logic - # to remove this hack. Even uglier, we need to store the error status - # here, because in the main loop, the logic that sets it is being - # skipped because runlines swallows the exceptions. - exc_content[u'status'] = u'error' - self._reply_content = exc_content - # /FIXME - - return exc_content - +@magics_class +class KernelMagics(Magics): #------------------------------------------------------------------------ # Magic overrides #------------------------------------------------------------------------ @@ -207,7 +106,8 @@ class ZMQInteractiveShell(InteractiveShell): # the magics which this class needs to implement differently from the base # class, or that are unique to it. - def magic_doctest_mode(self,parameter_s=''): + @line_magic + def doctest_mode(self, parameter_s=''): """Toggle doctest mode on and off. This mode is intended to make IPython behave as much as possible like a @@ -253,12 +153,12 @@ class ZMQInteractiveShell(InteractiveShell): # turn on ptformatter.pprint = False disp_formatter.plain_text_only = True - shell.magic_xmode('Plain') + shell.magic('xmode Plain') else: # turn off ptformatter.pprint = dstore.rc_pprint disp_formatter.plain_text_only = dstore.rc_plain_text_only - shell.magic_xmode(dstore.xmode) + shell.magic("xmode " + dstore.xmode) # Store new mode and inform on console dstore.mode = bool(1-int(mode)) @@ -267,12 +167,16 @@ class ZMQInteractiveShell(InteractiveShell): # Send the payload back so that clients can modify their prompt display payload = dict( - source='IPython.zmq.zmqshell.ZMQInteractiveShell.magic_doctest_mode', + source='IPython.zmq.zmqshell.ZMQInteractiveShell.doctest_mode', mode=dstore.mode) - self.payload_manager.write_payload(payload) + shell.payload_manager.write_payload(payload) + + + _find_edit_target = CodeMagics._find_edit_target @skip_doctest - def magic_edit(self,parameter_s='',last_call=['','']): + @line_magic + def edit(self, parameter_s='', last_call=['','']): """Bring up an editor and execute the resulting code. Usage: @@ -404,7 +308,7 @@ class ZMQInteractiveShell(InteractiveShell): opts,args = self.parse_options(parameter_s,'prn:') try: - filename, lineno, _ = self._find_edit_target(args, opts, last_call) + filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call) except MacroToEdit as e: # TODO: Implement macro editing over 2 processes. print("Macro editing not yet implemented in 2-process model.") @@ -419,12 +323,13 @@ class ZMQInteractiveShell(InteractiveShell): 'filename' : filename, 'line_number' : lineno } - self.payload_manager.write_payload(payload) + self.shell.payload_manager.write_payload(payload) # A few magics that are adapted to the specifics of using pexpect and a # remote terminal - def magic_clear(self, arg_s): + @line_magic + def clear(self, arg_s): """Clear the terminal.""" if os.name == 'posix': self.shell.system("clear") @@ -433,11 +338,12 @@ class ZMQInteractiveShell(InteractiveShell): if os.name == 'nt': # This is the usual name in windows - magic_cls = magic_clear + cls = line_magic('cls')(clear) # Terminal pagers won't work over pexpect, but we do have our own pager - def magic_less(self, arg_s): + @line_magic + def less(self, arg_s): """Show a file through the pager. Files ending in .py are syntax-highlighted.""" @@ -446,25 +352,18 @@ class ZMQInteractiveShell(InteractiveShell): cont = self.shell.pycolorize(cont) page.page(cont) - magic_more = magic_less + more = line_magic('more')(less) # Man calls a pager, so we also need to redefine it if os.name == 'posix': - def magic_man(self, arg_s): + @line_magic + def man(self, arg_s): """Find the man page for the given command and display in pager.""" page.page(self.shell.getoutput('man %s | col -b' % arg_s, split=False)) - # FIXME: this is specific to the GUI, so we should let the gui app load - # magics at startup that are only for the gui. Once the gui app has proper - # profile and configuration management, we can have it initialize a kernel - # with a special config file that provides these. - def magic_guiref(self, arg_s): - """Show a basic reference about the GUI console.""" - from IPython.core.usage import gui_reference - page.page(gui_reference, auto_html=True) - - def magic_connect_info(self, arg_s): + @line_magic + def connect_info(self, arg_s): """Print information for connecting other clients to this kernel It will print the contents of this session's connection file, as well as @@ -514,7 +413,8 @@ class ZMQInteractiveShell(InteractiveShell): ) ) - def magic_qtconsole(self, arg_s): + @line_magic + def qtconsole(self, arg_s): """Open a qtconsole connected to this kernel. Useful for connecting a qtconsole to running notebooks, for better @@ -526,6 +426,107 @@ class ZMQInteractiveShell(InteractiveShell): error("Could not start qtconsole: %r" % e) return + +class ZMQInteractiveShell(InteractiveShell): + """A subclass of InteractiveShell for ZMQ.""" + + displayhook_class = Type(ZMQShellDisplayHook) + display_pub_class = Type(ZMQDisplayPublisher) + + # Override the traitlet in the parent class, because there's no point using + # readline for the kernel. Can be removed when the readline code is moved + # to the terminal frontend. + colors_force = CBool(True) + readline_use = CBool(False) + # autoindent has no meaning in a zmqshell, and attempting to enable it + # will print a warning in the absence of readline. + autoindent = CBool(False) + + exiter = Instance(ZMQExitAutocall) + def _exiter_default(self): + return ZMQExitAutocall(self) + + def _exit_now_changed(self, name, old, new): + """stop eventloop when exit_now fires""" + if new: + loop = ioloop.IOLoop.instance() + loop.add_timeout(time.time()+0.1, loop.stop) + + keepkernel_on_exit = None + + # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no + # interactive input being read; we provide event loop support in ipkernel + from .eventloops import enable_gui + enable_gui = staticmethod(enable_gui) + + def init_environment(self): + """Configure the user's environment. + + """ + env = os.environ + # These two ensure 'ls' produces nice coloring on BSD-derived systems + env['TERM'] = 'xterm-color' + env['CLICOLOR'] = '1' + # Since normal pagers don't work at all (over pexpect we don't have + # single-key control of the subprocess), try to disable paging in + # subprocesses as much as possible. + env['PAGER'] = 'cat' + env['GIT_PAGER'] = 'cat' + + # And install the payload version of page. + install_payload_page() + + def auto_rewrite_input(self, cmd): + """Called to show the auto-rewritten input for autocall and friends. + + FIXME: this payload is currently not correctly processed by the + frontend. + """ + new = self.prompt_manager.render('rewrite') + cmd + payload = dict( + source='IPython.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input', + transformed_input=new, + ) + self.payload_manager.write_payload(payload) + + def ask_exit(self): + """Engage the exit actions.""" + self.exit_now = True + payload = dict( + source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit', + exit=True, + keepkernel=self.keepkernel_on_exit, + ) + self.payload_manager.write_payload(payload) + + def _showtraceback(self, etype, evalue, stb): + + exc_content = { + u'traceback' : stb, + u'ename' : unicode(etype.__name__), + u'evalue' : unicode(evalue) + } + + dh = self.displayhook + # Send exception info over pub socket for other clients than the caller + # to pick up + topic = None + if dh.topic: + topic = dh.topic.replace(b'pyout', b'pyerr') + + exc_msg = dh.session.send(dh.pub_socket, u'pyerr', json_clean(exc_content), dh.parent_header, ident=topic) + + # FIXME - Hack: store exception info in shell object. Right now, the + # caller is reading this info after the fact, we need to fix this logic + # to remove this hack. Even uglier, we need to store the error status + # here, because in the main loop, the logic that sets it is being + # skipped because runlines swallows the exceptions. + exc_content[u'status'] = u'error' + self._reply_content = exc_content + # /FIXME + + return exc_content + def set_next_input(self, text): """Send the specified text to the frontend to be presented at the next input cell.""" @@ -534,6 +535,15 @@ class ZMQInteractiveShell(InteractiveShell): text=text ) self.payload_manager.write_payload(payload) + + #------------------------------------------------------------------------- + # Things related to magics + #------------------------------------------------------------------------- + + def init_magics(self): + super(ZMQInteractiveShell, self).init_magics() + self.register_magics(KernelMagics) + InteractiveShellABC.register(ZMQInteractiveShell)