##// END OF EJS Templates
Move displayhook for ZMQ shell to zmq.displayhook, and rename to make the difference clearer.
Thomas Kluyver -
Show More
@@ -1,170 +1,170 b''
1 1 #!/usr/bin/env python
2 2 """A simple engine that talks to a controller over 0MQ.
3 3 it handles registration, etc. and launches a kernel
4 4 connected to the Controller's Schedulers.
5 5
6 6 Authors:
7 7
8 8 * Min RK
9 9 """
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2010-2011 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 from __future__ import print_function
18 18
19 19 import sys
20 20 import time
21 21
22 22 import zmq
23 23 from zmq.eventloop import ioloop, zmqstream
24 24
25 25 # internal
26 26 from IPython.utils.traitlets import Instance, Dict, Int, Type, CFloat, Unicode
27 27 # from IPython.utils.localinterfaces import LOCALHOST
28 28
29 29 from IPython.parallel.controller.heartmonitor import Heart
30 30 from IPython.parallel.factory import RegistrationFactory
31 31 from IPython.parallel.util import disambiguate_url
32 32
33 33 from IPython.zmq.session import Message
34 34
35 35 from .streamkernel import Kernel
36 36
37 37 class EngineFactory(RegistrationFactory):
38 38 """IPython engine"""
39 39
40 40 # configurables:
41 41 out_stream_factory=Type('IPython.zmq.iostream.OutStream', config=True,
42 42 help="""The OutStream for handling stdout/err.
43 43 Typically 'IPython.zmq.iostream.OutStream'""")
44 display_hook_factory=Type('IPython.zmq.displayhook.DisplayHook', config=True,
44 display_hook_factory=Type('IPython.zmq.displayhook.ZMQDisplayHook', config=True,
45 45 help="""The class for handling displayhook.
46 Typically 'IPython.zmq.displayhook.DisplayHook'""")
46 Typically 'IPython.zmq.displayhook.ZMQDisplayHook'""")
47 47 location=Unicode(config=True,
48 48 help="""The location (an IP address) of the controller. This is
49 49 used for disambiguating URLs, to determine whether
50 50 loopback should be used to connect or the public address.""")
51 51 timeout=CFloat(2,config=True,
52 52 help="""The time (in seconds) to wait for the Controller to respond
53 53 to registration requests before giving up.""")
54 54
55 55 # not configurable:
56 56 user_ns=Dict()
57 57 id=Int(allow_none=True)
58 58 registrar=Instance('zmq.eventloop.zmqstream.ZMQStream')
59 59 kernel=Instance(Kernel)
60 60
61 61
62 62 def __init__(self, **kwargs):
63 63 super(EngineFactory, self).__init__(**kwargs)
64 64 self.ident = self.session.session
65 65 ctx = self.context
66 66
67 67 reg = ctx.socket(zmq.XREQ)
68 68 reg.setsockopt(zmq.IDENTITY, self.ident)
69 69 reg.connect(self.url)
70 70 self.registrar = zmqstream.ZMQStream(reg, self.loop)
71 71
72 72 def register(self):
73 73 """send the registration_request"""
74 74
75 75 self.log.info("registering")
76 76 content = dict(queue=self.ident, heartbeat=self.ident, control=self.ident)
77 77 self.registrar.on_recv(self.complete_registration)
78 78 # print (self.session.key)
79 79 self.session.send(self.registrar, "registration_request",content=content)
80 80
81 81 def complete_registration(self, msg):
82 82 # print msg
83 83 self._abort_dc.stop()
84 84 ctx = self.context
85 85 loop = self.loop
86 86 identity = self.ident
87 87
88 88 idents,msg = self.session.feed_identities(msg)
89 89 msg = Message(self.session.unpack_message(msg))
90 90
91 91 if msg.content.status == 'ok':
92 92 self.id = int(msg.content.id)
93 93
94 94 # create Shell Streams (MUX, Task, etc.):
95 95 queue_addr = msg.content.mux
96 96 shell_addrs = [ str(queue_addr) ]
97 97 task_addr = msg.content.task
98 98 if task_addr:
99 99 shell_addrs.append(str(task_addr))
100 100
101 101 # Uncomment this to go back to two-socket model
102 102 # shell_streams = []
103 103 # for addr in shell_addrs:
104 104 # stream = zmqstream.ZMQStream(ctx.socket(zmq.XREP), loop)
105 105 # stream.setsockopt(zmq.IDENTITY, identity)
106 106 # stream.connect(disambiguate_url(addr, self.location))
107 107 # shell_streams.append(stream)
108 108
109 109 # Now use only one shell stream for mux and tasks
110 110 stream = zmqstream.ZMQStream(ctx.socket(zmq.XREP), loop)
111 111 stream.setsockopt(zmq.IDENTITY, identity)
112 112 shell_streams = [stream]
113 113 for addr in shell_addrs:
114 114 stream.connect(disambiguate_url(addr, self.location))
115 115 # end single stream-socket
116 116
117 117 # control stream:
118 118 control_addr = str(msg.content.control)
119 119 control_stream = zmqstream.ZMQStream(ctx.socket(zmq.XREP), loop)
120 120 control_stream.setsockopt(zmq.IDENTITY, identity)
121 121 control_stream.connect(disambiguate_url(control_addr, self.location))
122 122
123 123 # create iopub stream:
124 124 iopub_addr = msg.content.iopub
125 125 iopub_stream = zmqstream.ZMQStream(ctx.socket(zmq.PUB), loop)
126 126 iopub_stream.setsockopt(zmq.IDENTITY, identity)
127 127 iopub_stream.connect(disambiguate_url(iopub_addr, self.location))
128 128
129 129 # launch heartbeat
130 130 hb_addrs = msg.content.heartbeat
131 131 # print (hb_addrs)
132 132
133 133 # # Redirect input streams and set a display hook.
134 134 if self.out_stream_factory:
135 135 sys.stdout = self.out_stream_factory(self.session, iopub_stream, u'stdout')
136 136 sys.stdout.topic = 'engine.%i.stdout'%self.id
137 137 sys.stderr = self.out_stream_factory(self.session, iopub_stream, u'stderr')
138 138 sys.stderr.topic = 'engine.%i.stderr'%self.id
139 139 if self.display_hook_factory:
140 140 sys.displayhook = self.display_hook_factory(self.session, iopub_stream)
141 141 sys.displayhook.topic = 'engine.%i.pyout'%self.id
142 142
143 143 self.kernel = Kernel(config=self.config, int_id=self.id, ident=self.ident, session=self.session,
144 144 control_stream=control_stream, shell_streams=shell_streams, iopub_stream=iopub_stream,
145 145 loop=loop, user_ns = self.user_ns, log=self.log)
146 146 self.kernel.start()
147 147 hb_addrs = [ disambiguate_url(addr, self.location) for addr in hb_addrs ]
148 148 heart = Heart(*map(str, hb_addrs), heart_id=identity)
149 149 heart.start()
150 150
151 151
152 152 else:
153 153 self.log.fatal("Registration Failed: %s"%msg)
154 154 raise Exception("Registration Failed: %s"%msg)
155 155
156 156 self.log.info("Completed registration with id %i"%self.id)
157 157
158 158
159 159 def abort(self):
160 160 self.log.fatal("Registration timed out after %.1f seconds"%self.timeout)
161 161 self.session.send(self.registrar, "unregistration_request", content=dict(id=self.id))
162 162 time.sleep(1)
163 163 sys.exit(255)
164 164
165 165 def start(self):
166 166 dc = ioloop.DelayedCallback(self.register, 0, self.loop)
167 167 dc.start()
168 168 self._abort_dc = ioloop.DelayedCallback(self.abort, self.timeout*1000, self.loop)
169 169 self._abort_dc.start()
170 170
@@ -1,23 +1,64 b''
1 1 import __builtin__
2 from base64 import encodestring
2 3
3 from session import extract_header
4
5 class DisplayHook(object):
4 from IPython.core.displayhook import DisplayHook
5 from IPython.utils.traitlets import Instance, Dict
6 from session import extract_header, Session
6 7
8 class ZMQDisplayHook(object):
9 """A simple displayhook that publishes the object's repr over a ZeroMQ
10 socket."""
7 11 topic=None
8 12
9 13 def __init__(self, session, pub_socket):
10 14 self.session = session
11 15 self.pub_socket = pub_socket
12 16 self.parent_header = {}
13 17
14 18 def __call__(self, obj):
15 19 if obj is None:
16 20 return
17 21
18 22 __builtin__._ = obj
19 23 msg = self.session.send(self.pub_socket, u'pyout', {u'data':repr(obj)},
20 24 parent=self.parent_header, ident=self.topic)
21 25
22 26 def set_parent(self, parent):
23 self.parent_header = extract_header(parent) No newline at end of file
27 self.parent_header = extract_header(parent)
28
29
30 def _encode_png(data):
31 pngdata = data.get('image/png')
32 if pngdata is not None:
33 data['image/png'] = encodestring(pngdata)
34
35 class ZMQShellDisplayHook(DisplayHook):
36 """A displayhook subclass that publishes data using ZeroMQ. This is intended
37 to work with an InteractiveShell instance. It sends a dict of different
38 representations of the object."""
39
40 session = Instance(Session)
41 pub_socket = Instance('zmq.Socket')
42 parent_header = Dict({})
43
44 def set_parent(self, parent):
45 """Set the parent for outbound messages."""
46 self.parent_header = extract_header(parent)
47
48 def start_displayhook(self):
49 self.msg = self.session.msg(u'pyout', {}, parent=self.parent_header)
50
51 def write_output_prompt(self):
52 """Write the output prompt."""
53 if self.do_full_cache:
54 self.msg['content']['execution_count'] = self.prompt_count
55
56 def write_format_data(self, format_dict):
57 pngdata = format_dict.get('image/png')
58 _encode_png(format_dict)
59 self.msg['content']['data'] = format_dict
60
61 def finish_displayhook(self):
62 """Finish up all displayhook activities."""
63 self.session.send(self.pub_socket, self.msg)
64 self.msg = None
@@ -1,160 +1,159 b''
1 1 """ Defines helper functions for creating kernel entry points and process
2 2 launchers.
3 3 """
4 4
5 5 # Standard library imports.
6 6 import atexit
7 7 import os
8 8 import socket
9 9 from subprocess import Popen, PIPE
10 10 import sys
11 11
12 12 # Local imports.
13 13 from parentpoller import ParentPollerWindows
14 14
15 15
16
17 16 def base_launch_kernel(code, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
18 17 ip=None, stdin=None, stdout=None, stderr=None,
19 18 executable=None, independent=False, extra_arguments=[]):
20 19 """ Launches a localhost kernel, binding to the specified ports.
21 20
22 21 Parameters
23 22 ----------
24 23 code : str,
25 24 A string of Python code that imports and executes a kernel entry point.
26 25
27 26 shell_port : int, optional
28 27 The port to use for XREP channel.
29 28
30 29 iopub_port : int, optional
31 30 The port to use for the SUB channel.
32 31
33 32 stdin_port : int, optional
34 33 The port to use for the REQ (raw input) channel.
35 34
36 35 hb_port : int, optional
37 36 The port to use for the hearbeat REP channel.
38 37
39 38 ip : str, optional
40 39 The ip address the kernel will bind to.
41 40
42 41 stdin, stdout, stderr : optional (default None)
43 42 Standards streams, as defined in subprocess.Popen.
44 43
45 44 executable : str, optional (default sys.executable)
46 45 The Python executable to use for the kernel process.
47 46
48 47 independent : bool, optional (default False)
49 48 If set, the kernel process is guaranteed to survive if this process
50 49 dies. If not set, an effort is made to ensure that the kernel is killed
51 50 when this process dies. Note that in this case it is still good practice
52 51 to kill kernels manually before exiting.
53 52
54 53 extra_arguments = list, optional
55 54 A list of extra arguments to pass when executing the launch code.
56 55
57 56 Returns
58 57 -------
59 58 A tuple of form:
60 59 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
61 60 where kernel_process is a Popen object and the ports are integers.
62 61 """
63 62 # Find open ports as necessary.
64 63 ports = []
65 64 ports_needed = int(shell_port <= 0) + int(iopub_port <= 0) + \
66 65 int(stdin_port <= 0) + int(hb_port <= 0)
67 66 for i in xrange(ports_needed):
68 67 sock = socket.socket()
69 68 sock.bind(('', 0))
70 69 ports.append(sock)
71 70 for i, sock in enumerate(ports):
72 71 port = sock.getsockname()[1]
73 72 sock.close()
74 73 ports[i] = port
75 74 if shell_port <= 0:
76 75 shell_port = ports.pop(0)
77 76 if iopub_port <= 0:
78 77 iopub_port = ports.pop(0)
79 78 if stdin_port <= 0:
80 79 stdin_port = ports.pop(0)
81 80 if hb_port <= 0:
82 81 hb_port = ports.pop(0)
83 82
84 83 # Build the kernel launch command.
85 84 if executable is None:
86 85 executable = sys.executable
87 86 arguments = [ executable, '-c', code, 'shell=%i'%shell_port,
88 87 'iopub=%i'%iopub_port, 'stdin=%i'%stdin_port,
89 88 'hb=%i'%hb_port
90 89 ]
91 90 if ip is not None:
92 91 arguments.append('ip=%s'%ip)
93 92 arguments.extend(extra_arguments)
94 93
95 94 # Spawn a kernel.
96 95 if sys.platform == 'win32':
97 96 # Create a Win32 event for interrupting the kernel.
98 97 interrupt_event = ParentPollerWindows.create_interrupt_event()
99 98 arguments += [ 'interrupt=%i'%interrupt_event ]
100 99
101 100 # If this process in running on pythonw, stdin, stdout, and stderr are
102 101 # invalid. Popen will fail unless they are suitably redirected. We don't
103 102 # read from the pipes, but they must exist.
104 103 if sys.executable.endswith('pythonw.exe'):
105 104 redirect = True
106 105 _stdin = PIPE if stdin is None else stdin
107 106 _stdout = PIPE if stdout is None else stdout
108 107 _stderr = PIPE if stderr is None else stderr
109 108 else:
110 109 redirect = False
111 110 _stdin, _stdout, _stderr = stdin, stdout, stderr
112 111
113 112 # If the kernel is running on pythonw and stdout/stderr are not been
114 113 # re-directed, it will crash when more than 4KB of data is written to
115 114 # stdout or stderr. This is a bug that has been with Python for a very
116 115 # long time; see http://bugs.python.org/issue706263.
117 116 # A cleaner solution to this problem would be to pass os.devnull to
118 117 # Popen directly. Unfortunately, that does not work.
119 118 if executable.endswith('pythonw.exe'):
120 119 if stdout is None:
121 120 arguments.append('--no-stdout')
122 121 if stderr is None:
123 122 arguments.append('--no-stderr')
124 123
125 124 # Launch the kernel process.
126 125 if independent:
127 126 proc = Popen(arguments,
128 127 creationflags=512, # CREATE_NEW_PROCESS_GROUP
129 128 stdin=_stdin, stdout=_stdout, stderr=_stderr)
130 129 else:
131 130 from _subprocess import DuplicateHandle, GetCurrentProcess, \
132 131 DUPLICATE_SAME_ACCESS
133 132 pid = GetCurrentProcess()
134 133 handle = DuplicateHandle(pid, pid, pid, 0,
135 134 True, # Inheritable by new processes.
136 135 DUPLICATE_SAME_ACCESS)
137 136 proc = Popen(arguments + ['parent=%i'%int(handle)],
138 137 stdin=_stdin, stdout=_stdout, stderr=_stderr)
139 138
140 139 # Attach the interrupt event to the Popen objet so it can be used later.
141 140 proc.win32_interrupt_event = interrupt_event
142 141
143 142 # Clean up pipes created to work around Popen bug.
144 143 if redirect:
145 144 if stdin is None:
146 145 proc.stdin.close()
147 146 if stdout is None:
148 147 proc.stdout.close()
149 148 if stderr is None:
150 149 proc.stderr.close()
151 150
152 151 else:
153 152 if independent:
154 153 proc = Popen(arguments, preexec_fn=lambda: os.setsid(),
155 154 stdin=stdin, stdout=stdout, stderr=stderr)
156 155 else:
157 156 proc = Popen(arguments + ['parent=1'],
158 157 stdin=stdin, stdout=stdout, stderr=stderr)
159 158
160 159 return proc, shell_port, iopub_port, stdin_port, hb_port
@@ -1,483 +1,446 b''
1 1 """A ZMQ-based subclass of InteractiveShell.
2 2
3 3 This code is meant to ease the refactoring of the base InteractiveShell into
4 4 something with a cleaner architecture for 2-process use, without actually
5 5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
6 6 we subclass and override what we want to fix. Once this is working well, we
7 7 can go back to the base class and refactor the code for a cleaner inheritance
8 8 implementation that doesn't rely on so much monkeypatching.
9 9
10 10 But this lets us maintain a fully working IPython as we develop the new
11 11 machinery. This should thus be thought of as scaffolding.
12 12 """
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Stdlib
19 from base64 import encodestring
20 19 import inspect
21 20 import os
22 21
23 22 # Our own
24 23 from IPython.core.interactiveshell import (
25 24 InteractiveShell, InteractiveShellABC
26 25 )
27 26 from IPython.core import page
28 27 from IPython.core.autocall import ZMQExitAutocall
29 from IPython.core.displayhook import DisplayHook
30 28 from IPython.core.displaypub import DisplayPublisher
31 29 from IPython.core.macro import Macro
32 30 from IPython.core.magic import MacroToEdit
33 31 from IPython.core.payloadpage import install_payload_page
34 32 from IPython.utils import io
35 33 from IPython.utils.path import get_py_filename
36 34 from IPython.utils.traitlets import Instance, Type, Dict
37 35 from IPython.utils.warn import warn
36 from IPython.zmq.displayhook import ZMQShellDisplayHook
38 37 from IPython.zmq.session import extract_header
39 38 from session import Session
40 39
41 40 #-----------------------------------------------------------------------------
42 41 # Globals and side-effects
43 42 #-----------------------------------------------------------------------------
44 43
45 44 # Install the payload version of page.
46 45 install_payload_page()
47 46
48 47 #-----------------------------------------------------------------------------
49 48 # Functions and classes
50 49 #-----------------------------------------------------------------------------
51 50
52 def _encode_png(data):
53 pngdata = data.get('image/png')
54 if pngdata is not None:
55 data['image/png'] = encodestring(pngdata)
56
57
58 class ZMQDisplayHook(DisplayHook):
59 """A displayhook subclass that publishes data using ZeroMQ."""
60
61 session = Instance(Session)
62 pub_socket = Instance('zmq.Socket')
63 parent_header = Dict({})
64
65 def set_parent(self, parent):
66 """Set the parent for outbound messages."""
67 self.parent_header = extract_header(parent)
68
69 def start_displayhook(self):
70 self.msg = self.session.msg(u'pyout', {}, parent=self.parent_header)
71
72 def write_output_prompt(self):
73 """Write the output prompt."""
74 if self.do_full_cache:
75 self.msg['content']['execution_count'] = self.prompt_count
76
77 def write_format_data(self, format_dict):
78 pngdata = format_dict.get('image/png')
79 _encode_png(format_dict)
80 self.msg['content']['data'] = format_dict
81
82 def finish_displayhook(self):
83 """Finish up all displayhook activities."""
84 self.session.send(self.pub_socket, self.msg)
85 self.msg = None
86
87
88 51 class ZMQDisplayPublisher(DisplayPublisher):
89 52 """A display publisher that publishes data using a ZeroMQ PUB socket."""
90 53
91 54 session = Instance(Session)
92 55 pub_socket = Instance('zmq.Socket')
93 56 parent_header = Dict({})
94 57
95 58 def set_parent(self, parent):
96 59 """Set the parent for outbound messages."""
97 60 self.parent_header = extract_header(parent)
98 61
99 62 def publish(self, source, data, metadata=None):
100 63 if metadata is None:
101 64 metadata = {}
102 65 self._validate_data(source, data, metadata)
103 66 content = {}
104 67 content['source'] = source
105 68 _encode_png(data)
106 69 content['data'] = data
107 70 content['metadata'] = metadata
108 71 self.session.send(
109 72 self.pub_socket, u'display_data', content,
110 73 parent=self.parent_header
111 74 )
112 75
113 76
114 77 class ZMQInteractiveShell(InteractiveShell):
115 78 """A subclass of InteractiveShell for ZMQ."""
116 79
117 displayhook_class = Type(ZMQDisplayHook)
80 displayhook_class = Type(ZMQShellDisplayHook)
118 81 display_pub_class = Type(ZMQDisplayPublisher)
119 82
120 83 exiter = Instance(ZMQExitAutocall)
121 84 def _exiter_default(self):
122 85 return ZMQExitAutocall(self)
123 86
124 87 keepkernel_on_exit = None
125 88
126 89 def init_environment(self):
127 90 """Configure the user's environment.
128 91
129 92 """
130 93 env = os.environ
131 94 # These two ensure 'ls' produces nice coloring on BSD-derived systems
132 95 env['TERM'] = 'xterm-color'
133 96 env['CLICOLOR'] = '1'
134 97 # Since normal pagers don't work at all (over pexpect we don't have
135 98 # single-key control of the subprocess), try to disable paging in
136 99 # subprocesses as much as possible.
137 100 env['PAGER'] = 'cat'
138 101 env['GIT_PAGER'] = 'cat'
139 102
140 103 def auto_rewrite_input(self, cmd):
141 104 """Called to show the auto-rewritten input for autocall and friends.
142 105
143 106 FIXME: this payload is currently not correctly processed by the
144 107 frontend.
145 108 """
146 109 new = self.displayhook.prompt1.auto_rewrite() + cmd
147 110 payload = dict(
148 111 source='IPython.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input',
149 112 transformed_input=new,
150 113 )
151 114 self.payload_manager.write_payload(payload)
152 115
153 116 def ask_exit(self):
154 117 """Engage the exit actions."""
155 118 payload = dict(
156 119 source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit',
157 120 exit=True,
158 121 keepkernel=self.keepkernel_on_exit,
159 122 )
160 123 self.payload_manager.write_payload(payload)
161 124
162 125 def _showtraceback(self, etype, evalue, stb):
163 126
164 127 exc_content = {
165 128 u'traceback' : stb,
166 129 u'ename' : unicode(etype.__name__),
167 130 u'evalue' : unicode(evalue)
168 131 }
169 132
170 133 dh = self.displayhook
171 134 # Send exception info over pub socket for other clients than the caller
172 135 # to pick up
173 136 exc_msg = dh.session.send(dh.pub_socket, u'pyerr', exc_content, dh.parent_header)
174 137
175 138 # FIXME - Hack: store exception info in shell object. Right now, the
176 139 # caller is reading this info after the fact, we need to fix this logic
177 140 # to remove this hack. Even uglier, we need to store the error status
178 141 # here, because in the main loop, the logic that sets it is being
179 142 # skipped because runlines swallows the exceptions.
180 143 exc_content[u'status'] = u'error'
181 144 self._reply_content = exc_content
182 145 # /FIXME
183 146
184 147 return exc_content
185 148
186 149 #------------------------------------------------------------------------
187 150 # Magic overrides
188 151 #------------------------------------------------------------------------
189 152 # Once the base class stops inheriting from magic, this code needs to be
190 153 # moved into a separate machinery as well. For now, at least isolate here
191 154 # the magics which this class needs to implement differently from the base
192 155 # class, or that are unique to it.
193 156
194 157 def magic_doctest_mode(self,parameter_s=''):
195 158 """Toggle doctest mode on and off.
196 159
197 160 This mode is intended to make IPython behave as much as possible like a
198 161 plain Python shell, from the perspective of how its prompts, exceptions
199 162 and output look. This makes it easy to copy and paste parts of a
200 163 session into doctests. It does so by:
201 164
202 165 - Changing the prompts to the classic ``>>>`` ones.
203 166 - Changing the exception reporting mode to 'Plain'.
204 167 - Disabling pretty-printing of output.
205 168
206 169 Note that IPython also supports the pasting of code snippets that have
207 170 leading '>>>' and '...' prompts in them. This means that you can paste
208 171 doctests from files or docstrings (even if they have leading
209 172 whitespace), and the code will execute correctly. You can then use
210 173 '%history -t' to see the translated history; this will give you the
211 174 input after removal of all the leading prompts and whitespace, which
212 175 can be pasted back into an editor.
213 176
214 177 With these features, you can switch into this mode easily whenever you
215 178 need to do testing and changes to doctests, without having to leave
216 179 your existing IPython session.
217 180 """
218 181
219 182 from IPython.utils.ipstruct import Struct
220 183
221 184 # Shorthands
222 185 shell = self.shell
223 186 disp_formatter = self.shell.display_formatter
224 187 ptformatter = disp_formatter.formatters['text/plain']
225 188 # dstore is a data store kept in the instance metadata bag to track any
226 189 # changes we make, so we can undo them later.
227 190 dstore = shell.meta.setdefault('doctest_mode', Struct())
228 191 save_dstore = dstore.setdefault
229 192
230 193 # save a few values we'll need to recover later
231 194 mode = save_dstore('mode', False)
232 195 save_dstore('rc_pprint', ptformatter.pprint)
233 196 save_dstore('rc_plain_text_only',disp_formatter.plain_text_only)
234 197 save_dstore('xmode', shell.InteractiveTB.mode)
235 198
236 199 if mode == False:
237 200 # turn on
238 201 ptformatter.pprint = False
239 202 disp_formatter.plain_text_only = True
240 203 shell.magic_xmode('Plain')
241 204 else:
242 205 # turn off
243 206 ptformatter.pprint = dstore.rc_pprint
244 207 disp_formatter.plain_text_only = dstore.rc_plain_text_only
245 208 shell.magic_xmode(dstore.xmode)
246 209
247 210 # Store new mode and inform on console
248 211 dstore.mode = bool(1-int(mode))
249 212 mode_label = ['OFF','ON'][dstore.mode]
250 213 print('Doctest mode is:', mode_label)
251 214
252 215 # Send the payload back so that clients can modify their prompt display
253 216 payload = dict(
254 217 source='IPython.zmq.zmqshell.ZMQInteractiveShell.magic_doctest_mode',
255 218 mode=dstore.mode)
256 219 self.payload_manager.write_payload(payload)
257 220
258 221 def magic_edit(self,parameter_s='',last_call=['','']):
259 222 """Bring up an editor and execute the resulting code.
260 223
261 224 Usage:
262 225 %edit [options] [args]
263 226
264 227 %edit runs IPython's editor hook. The default version of this hook is
265 228 set to call the __IPYTHON__.rc.editor command. This is read from your
266 229 environment variable $EDITOR. If this isn't found, it will default to
267 230 vi under Linux/Unix and to notepad under Windows. See the end of this
268 231 docstring for how to change the editor hook.
269 232
270 233 You can also set the value of this editor via the command line option
271 234 '-editor' or in your ipythonrc file. This is useful if you wish to use
272 235 specifically for IPython an editor different from your typical default
273 236 (and for Windows users who typically don't set environment variables).
274 237
275 238 This command allows you to conveniently edit multi-line code right in
276 239 your IPython session.
277 240
278 241 If called without arguments, %edit opens up an empty editor with a
279 242 temporary file and will execute the contents of this file when you
280 243 close it (don't forget to save it!).
281 244
282 245
283 246 Options:
284 247
285 248 -n <number>: open the editor at a specified line number. By default,
286 249 the IPython editor hook uses the unix syntax 'editor +N filename', but
287 250 you can configure this by providing your own modified hook if your
288 251 favorite editor supports line-number specifications with a different
289 252 syntax.
290 253
291 254 -p: this will call the editor with the same data as the previous time
292 255 it was used, regardless of how long ago (in your current session) it
293 256 was.
294 257
295 258 -r: use 'raw' input. This option only applies to input taken from the
296 259 user's history. By default, the 'processed' history is used, so that
297 260 magics are loaded in their transformed version to valid Python. If
298 261 this option is given, the raw input as typed as the command line is
299 262 used instead. When you exit the editor, it will be executed by
300 263 IPython's own processor.
301 264
302 265 -x: do not execute the edited code immediately upon exit. This is
303 266 mainly useful if you are editing programs which need to be called with
304 267 command line arguments, which you can then do using %run.
305 268
306 269
307 270 Arguments:
308 271
309 272 If arguments are given, the following possibilites exist:
310 273
311 274 - The arguments are numbers or pairs of colon-separated numbers (like
312 275 1 4:8 9). These are interpreted as lines of previous input to be
313 276 loaded into the editor. The syntax is the same of the %macro command.
314 277
315 278 - If the argument doesn't start with a number, it is evaluated as a
316 279 variable and its contents loaded into the editor. You can thus edit
317 280 any string which contains python code (including the result of
318 281 previous edits).
319 282
320 283 - If the argument is the name of an object (other than a string),
321 284 IPython will try to locate the file where it was defined and open the
322 285 editor at the point where it is defined. You can use `%edit function`
323 286 to load an editor exactly at the point where 'function' is defined,
324 287 edit it and have the file be executed automatically.
325 288
326 289 If the object is a macro (see %macro for details), this opens up your
327 290 specified editor with a temporary file containing the macro's data.
328 291 Upon exit, the macro is reloaded with the contents of the file.
329 292
330 293 Note: opening at an exact line is only supported under Unix, and some
331 294 editors (like kedit and gedit up to Gnome 2.8) do not understand the
332 295 '+NUMBER' parameter necessary for this feature. Good editors like
333 296 (X)Emacs, vi, jed, pico and joe all do.
334 297
335 298 - If the argument is not found as a variable, IPython will look for a
336 299 file with that name (adding .py if necessary) and load it into the
337 300 editor. It will execute its contents with execfile() when you exit,
338 301 loading any code in the file into your interactive namespace.
339 302
340 303 After executing your code, %edit will return as output the code you
341 304 typed in the editor (except when it was an existing file). This way
342 305 you can reload the code in further invocations of %edit as a variable,
343 306 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
344 307 the output.
345 308
346 309 Note that %edit is also available through the alias %ed.
347 310
348 311 This is an example of creating a simple function inside the editor and
349 312 then modifying it. First, start up the editor:
350 313
351 314 In [1]: ed
352 315 Editing... done. Executing edited code...
353 316 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
354 317
355 318 We can then call the function foo():
356 319
357 320 In [2]: foo()
358 321 foo() was defined in an editing session
359 322
360 323 Now we edit foo. IPython automatically loads the editor with the
361 324 (temporary) file where foo() was previously defined:
362 325
363 326 In [3]: ed foo
364 327 Editing... done. Executing edited code...
365 328
366 329 And if we call foo() again we get the modified version:
367 330
368 331 In [4]: foo()
369 332 foo() has now been changed!
370 333
371 334 Here is an example of how to edit a code snippet successive
372 335 times. First we call the editor:
373 336
374 337 In [5]: ed
375 338 Editing... done. Executing edited code...
376 339 hello
377 340 Out[5]: "print 'hello'n"
378 341
379 342 Now we call it again with the previous output (stored in _):
380 343
381 344 In [6]: ed _
382 345 Editing... done. Executing edited code...
383 346 hello world
384 347 Out[6]: "print 'hello world'n"
385 348
386 349 Now we call it with the output #8 (stored in _8, also as Out[8]):
387 350
388 351 In [7]: ed _8
389 352 Editing... done. Executing edited code...
390 353 hello again
391 354 Out[7]: "print 'hello again'n"
392 355
393 356
394 357 Changing the default editor hook:
395 358
396 359 If you wish to write your own editor hook, you can put it in a
397 360 configuration file which you load at startup time. The default hook
398 361 is defined in the IPython.core.hooks module, and you can use that as a
399 362 starting example for further modifications. That file also has
400 363 general instructions on how to set a new hook for use once you've
401 364 defined it."""
402 365
403 366 opts,args = self.parse_options(parameter_s,'prn:')
404 367
405 368 try:
406 369 filename, lineno, _ = self._find_edit_target(args, opts, last_call)
407 370 except MacroToEdit as e:
408 371 # TODO: Implement macro editing over 2 processes.
409 372 print("Macro editing not yet implemented in 2-process model.")
410 373 return
411 374
412 375 # Make sure we send to the client an absolute path, in case the working
413 376 # directory of client and kernel don't match
414 377 filename = os.path.abspath(filename)
415 378
416 379 payload = {
417 380 'source' : 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic',
418 381 'filename' : filename,
419 382 'line_number' : lineno
420 383 }
421 384 self.payload_manager.write_payload(payload)
422 385
423 386 def magic_gui(self, *args, **kwargs):
424 387 raise NotImplementedError(
425 388 'GUI support must be enabled in command line options.')
426 389
427 390 def magic_pylab(self, *args, **kwargs):
428 391 raise NotImplementedError(
429 392 'pylab support must be enabled in command line options.')
430 393
431 394 # A few magics that are adapted to the specifics of using pexpect and a
432 395 # remote terminal
433 396
434 397 def magic_clear(self, arg_s):
435 398 """Clear the terminal."""
436 399 if os.name == 'posix':
437 400 self.shell.system("clear")
438 401 else:
439 402 self.shell.system("cls")
440 403
441 404 if os.name == 'nt':
442 405 # This is the usual name in windows
443 406 magic_cls = magic_clear
444 407
445 408 # Terminal pagers won't work over pexpect, but we do have our own pager
446 409
447 410 def magic_less(self, arg_s):
448 411 """Show a file through the pager.
449 412
450 413 Files ending in .py are syntax-highlighted."""
451 414 cont = open(arg_s).read()
452 415 if arg_s.endswith('.py'):
453 416 cont = self.shell.pycolorize(cont)
454 417 page.page(cont)
455 418
456 419 magic_more = magic_less
457 420
458 421 # Man calls a pager, so we also need to redefine it
459 422 if os.name == 'posix':
460 423 def magic_man(self, arg_s):
461 424 """Find the man page for the given command and display in pager."""
462 425 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
463 426 split=False))
464 427
465 428 # FIXME: this is specific to the GUI, so we should let the gui app load
466 429 # magics at startup that are only for the gui. Once the gui app has proper
467 430 # profile and configuration management, we can have it initialize a kernel
468 431 # with a special config file that provides these.
469 432 def magic_guiref(self, arg_s):
470 433 """Show a basic reference about the GUI console."""
471 434 from IPython.core.usage import gui_reference
472 435 page.page(gui_reference, auto_html=True)
473 436
474 437 def set_next_input(self, text):
475 438 """Send the specified text to the frontend to be presented at the next
476 439 input cell."""
477 440 payload = dict(
478 441 source='IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input',
479 442 text=text
480 443 )
481 444 self.payload_manager.write_payload(payload)
482 445
483 446 InteractiveShellABC.register(ZMQInteractiveShell)
General Comments 0
You need to be logged in to leave comments. Login now