##// END OF EJS Templates
Merge pull request #8483 from Carreau/keep-kernel...
Thomas Kluyver -
r21403:a4c5f92a merge
parent child Browse files
Show More
@@ -1,149 +1,150 b''
1 """ A minimal application using the ZMQ-based terminal IPython frontend.
1 """ A minimal application using the ZMQ-based terminal IPython frontend.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations.
4 input, there is no real readline support, among other limitations.
5
5
6 Authors:
6 Authors:
7
7
8 * Min RK
8 * Min RK
9 * Paul Ivanov
9 * Paul Ivanov
10
10
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 import signal
16 import signal
17
17
18 from IPython.terminal.ipapp import TerminalIPythonApp, frontend_flags as term_flags
18 from IPython.terminal.ipapp import TerminalIPythonApp, frontend_flags as term_flags
19
19
20 from IPython.utils.traitlets import (
20 from IPython.utils.traitlets import (
21 Dict, Any
21 Dict, Any
22 )
22 )
23 from IPython.utils.warn import error
23 from IPython.utils.warn import error
24
24
25 from IPython.consoleapp import (
25 from IPython.consoleapp import (
26 IPythonConsoleApp, app_aliases, app_flags, aliases, flags
26 IPythonConsoleApp, app_aliases, app_flags, aliases, flags
27 )
27 )
28
28
29 from IPython.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
29 from IPython.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
30
30
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # Globals
32 # Globals
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34
34
35 _examples = """
35 _examples = """
36 ipython console # start the ZMQ-based console
36 ipython console # start the ZMQ-based console
37 ipython console --existing # connect to an existing ipython session
37 ipython console --existing # connect to an existing ipython session
38 """
38 """
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Flags and Aliases
41 # Flags and Aliases
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44 # copy flags from mixin:
44 # copy flags from mixin:
45 flags = dict(flags)
45 flags = dict(flags)
46 # start with mixin frontend flags:
46 # start with mixin frontend flags:
47 frontend_flags = dict(app_flags)
47 frontend_flags = dict(app_flags)
48 # add TerminalIPApp flags:
48 # add TerminalIPApp flags:
49 frontend_flags.update(term_flags)
49 frontend_flags.update(term_flags)
50 # disable quick startup, as it won't propagate to the kernel anyway
50 # disable quick startup, as it won't propagate to the kernel anyway
51 frontend_flags.pop('quick')
51 frontend_flags.pop('quick')
52 # update full dict with frontend flags:
52 # update full dict with frontend flags:
53 flags.update(frontend_flags)
53 flags.update(frontend_flags)
54
54
55 # copy flags from mixin
55 # copy flags from mixin
56 aliases = dict(aliases)
56 aliases = dict(aliases)
57 # start with mixin frontend flags
57 # start with mixin frontend flags
58 frontend_aliases = dict(app_aliases)
58 frontend_aliases = dict(app_aliases)
59 # load updated frontend flags into full dict
59 # load updated frontend flags into full dict
60 aliases.update(frontend_aliases)
60 aliases.update(frontend_aliases)
61
61
62 # get flags&aliases into sets, and remove a couple that
62 # get flags&aliases into sets, and remove a couple that
63 # shouldn't be scrubbed from backend flags:
63 # shouldn't be scrubbed from backend flags:
64 frontend_aliases = set(frontend_aliases.keys())
64 frontend_aliases = set(frontend_aliases.keys())
65 frontend_flags = set(frontend_flags.keys())
65 frontend_flags = set(frontend_flags.keys())
66
66
67
67
68 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
69 # Classes
69 # Classes
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71
71
72
72
73 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):
73 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):
74 name = "ipython-console"
74 name = "ipython-console"
75 """Start a terminal frontend to the IPython zmq kernel."""
75 """Start a terminal frontend to the IPython zmq kernel."""
76
76
77 description = """
77 description = """
78 The IPython terminal-based Console.
78 The IPython terminal-based Console.
79
79
80 This launches a Console application inside a terminal.
80 This launches a Console application inside a terminal.
81
81
82 The Console supports various extra features beyond the traditional
82 The Console supports various extra features beyond the traditional
83 single-process Terminal IPython shell, such as connecting to an
83 single-process Terminal IPython shell, such as connecting to an
84 existing ipython session, via:
84 existing ipython session, via:
85
85
86 ipython console --existing
86 ipython console --existing
87
87
88 where the previous session could have been created by another ipython
88 where the previous session could have been created by another ipython
89 console, an ipython qtconsole, or by opening an ipython notebook.
89 console, an ipython qtconsole, or by opening an ipython notebook.
90
90
91 """
91 """
92 examples = _examples
92 examples = _examples
93
93
94 classes = [ZMQTerminalInteractiveShell] + IPythonConsoleApp.classes
94 classes = [ZMQTerminalInteractiveShell] + IPythonConsoleApp.classes
95 flags = Dict(flags)
95 flags = Dict(flags)
96 aliases = Dict(aliases)
96 aliases = Dict(aliases)
97 frontend_aliases = Any(frontend_aliases)
97 frontend_aliases = Any(frontend_aliases)
98 frontend_flags = Any(frontend_flags)
98 frontend_flags = Any(frontend_flags)
99
99
100 subcommands = Dict()
100 subcommands = Dict()
101
101
102 def parse_command_line(self, argv=None):
102 def parse_command_line(self, argv=None):
103 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
103 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
104 self.build_kernel_argv(argv)
104 self.build_kernel_argv(argv)
105
105
106 def init_shell(self):
106 def init_shell(self):
107 IPythonConsoleApp.initialize(self)
107 IPythonConsoleApp.initialize(self)
108 # relay sigint to kernel
108 # relay sigint to kernel
109 signal.signal(signal.SIGINT, self.handle_sigint)
109 signal.signal(signal.SIGINT, self.handle_sigint)
110 self.shell = ZMQTerminalInteractiveShell.instance(parent=self,
110 self.shell = ZMQTerminalInteractiveShell.instance(parent=self,
111 display_banner=False, profile_dir=self.profile_dir,
111 display_banner=False, profile_dir=self.profile_dir,
112 ipython_dir=self.ipython_dir,
112 ipython_dir=self.ipython_dir,
113 manager=self.kernel_manager,
113 manager=self.kernel_manager,
114 client=self.kernel_client,
114 client=self.kernel_client,
115 )
115 )
116 self.shell.own_kernel = not self.existing
116
117
117 def init_gui_pylab(self):
118 def init_gui_pylab(self):
118 # no-op, because we don't want to import matplotlib in the frontend.
119 # no-op, because we don't want to import matplotlib in the frontend.
119 pass
120 pass
120
121
121 def handle_sigint(self, *args):
122 def handle_sigint(self, *args):
122 if self.shell._executing:
123 if self.shell._executing:
123 if self.kernel_manager:
124 if self.kernel_manager:
124 # interrupt already gets passed to subprocess by signal handler.
125 # interrupt already gets passed to subprocess by signal handler.
125 # Only if we prevent that should we need to explicitly call
126 # Only if we prevent that should we need to explicitly call
126 # interrupt_kernel, until which time, this would result in a
127 # interrupt_kernel, until which time, this would result in a
127 # double-interrupt:
128 # double-interrupt:
128 # self.kernel_manager.interrupt_kernel()
129 # self.kernel_manager.interrupt_kernel()
129 pass
130 pass
130 else:
131 else:
131 self.shell.write_err('\n')
132 self.shell.write_err('\n')
132 error("Cannot interrupt kernels we didn't start.\n")
133 error("Cannot interrupt kernels we didn't start.\n")
133 else:
134 else:
134 # raise the KeyboardInterrupt if we aren't waiting for execution,
135 # raise the KeyboardInterrupt if we aren't waiting for execution,
135 # so that the interact loop advances, and prompt is redrawn, etc.
136 # so that the interact loop advances, and prompt is redrawn, etc.
136 raise KeyboardInterrupt
137 raise KeyboardInterrupt
137
138
138
139
139 def init_code(self):
140 def init_code(self):
140 # no-op in the frontend, code gets run in the backend
141 # no-op in the frontend, code gets run in the backend
141 pass
142 pass
142
143
143
144
144 launch_new_instance = ZMQTerminalIPythonApp.launch_instance
145 launch_new_instance = ZMQTerminalIPythonApp.launch_instance
145
146
146
147
147 if __name__ == '__main__':
148 if __name__ == '__main__':
148 launch_new_instance()
149 launch_new_instance()
149
150
@@ -1,585 +1,595 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """terminal client to the IPython kernel"""
2 """terminal client to the IPython kernel"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import base64
9 import base64
10 import bdb
10 import bdb
11 import errno
11 import errno
12 import signal
12 import signal
13 import os
13 import os
14 import sys
14 import sys
15 import time
15 import time
16 import subprocess
16 import subprocess
17 from getpass import getpass
17 from getpass import getpass
18 from io import BytesIO
18 from io import BytesIO
19
19
20 try:
20 try:
21 from queue import Empty # Py 3
21 from queue import Empty # Py 3
22 except ImportError:
22 except ImportError:
23 from Queue import Empty # Py 2
23 from Queue import Empty # Py 2
24
24
25 from zmq import ZMQError
25 from zmq import ZMQError
26
26
27 from IPython.core import page
27 from IPython.core import page
28 from IPython.core import release
28 from IPython.core import release
29 from IPython.terminal.console.zmqhistory import ZMQHistoryManager
29 from IPython.terminal.console.zmqhistory import ZMQHistoryManager
30 from IPython.utils.warn import warn, error
30 from IPython.utils.warn import warn, error
31 from IPython.utils import io
31 from IPython.utils import io
32 from IPython.utils.py3compat import string_types, input
32 from IPython.utils.py3compat import string_types, input
33 from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode, Float, Bool
33 from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode, Float, Bool
34 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
34 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
35
35
36 from IPython.terminal.interactiveshell import TerminalInteractiveShell
36 from IPython.terminal.interactiveshell import TerminalInteractiveShell
37 from IPython.terminal.console.completer import ZMQCompleter
37 from IPython.terminal.console.completer import ZMQCompleter
38
38
39 class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
39 class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
40 """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
40 """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
41 _executing = False
41 _executing = False
42 _execution_state = Unicode('')
42 _execution_state = Unicode('')
43 _pending_clearoutput = False
43 _pending_clearoutput = False
44 kernel_banner = Unicode('')
44 kernel_banner = Unicode('')
45 kernel_timeout = Float(60, config=True,
45 kernel_timeout = Float(60, config=True,
46 help="""Timeout for giving up on a kernel (in seconds).
46 help="""Timeout for giving up on a kernel (in seconds).
47
47
48 On first connect and restart, the console tests whether the
48 On first connect and restart, the console tests whether the
49 kernel is running and responsive by sending kernel_info_requests.
49 kernel is running and responsive by sending kernel_info_requests.
50 This sets the timeout in seconds for how long the kernel can take
50 This sets the timeout in seconds for how long the kernel can take
51 before being presumed dead.
51 before being presumed dead.
52 """
52 """
53 )
53 )
54
54
55 image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'),
55 image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'),
56 config=True, help=
56 config=True, help=
57 """
57 """
58 Handler for image type output. This is useful, for example,
58 Handler for image type output. This is useful, for example,
59 when connecting to the kernel in which pylab inline backend is
59 when connecting to the kernel in which pylab inline backend is
60 activated. There are four handlers defined. 'PIL': Use
60 activated. There are four handlers defined. 'PIL': Use
61 Python Imaging Library to popup image; 'stream': Use an
61 Python Imaging Library to popup image; 'stream': Use an
62 external program to show the image. Image will be fed into
62 external program to show the image. Image will be fed into
63 the STDIN of the program. You will need to configure
63 the STDIN of the program. You will need to configure
64 `stream_image_handler`; 'tempfile': Use an external program to
64 `stream_image_handler`; 'tempfile': Use an external program to
65 show the image. Image will be saved in a temporally file and
65 show the image. Image will be saved in a temporally file and
66 the program is called with the temporally file. You will need
66 the program is called with the temporally file. You will need
67 to configure `tempfile_image_handler`; 'callable': You can set
67 to configure `tempfile_image_handler`; 'callable': You can set
68 any Python callable which is called with the image data. You
68 any Python callable which is called with the image data. You
69 will need to configure `callable_image_handler`.
69 will need to configure `callable_image_handler`.
70 """
70 """
71 )
71 )
72
72
73 stream_image_handler = List(config=True, help=
73 stream_image_handler = List(config=True, help=
74 """
74 """
75 Command to invoke an image viewer program when you are using
75 Command to invoke an image viewer program when you are using
76 'stream' image handler. This option is a list of string where
76 'stream' image handler. This option is a list of string where
77 the first element is the command itself and reminders are the
77 the first element is the command itself and reminders are the
78 options for the command. Raw image data is given as STDIN to
78 options for the command. Raw image data is given as STDIN to
79 the program.
79 the program.
80 """
80 """
81 )
81 )
82
82
83 tempfile_image_handler = List(config=True, help=
83 tempfile_image_handler = List(config=True, help=
84 """
84 """
85 Command to invoke an image viewer program when you are using
85 Command to invoke an image viewer program when you are using
86 'tempfile' image handler. This option is a list of string
86 'tempfile' image handler. This option is a list of string
87 where the first element is the command itself and reminders
87 where the first element is the command itself and reminders
88 are the options for the command. You can use {file} and
88 are the options for the command. You can use {file} and
89 {format} in the string to represent the location of the
89 {format} in the string to represent the location of the
90 generated image file and image format.
90 generated image file and image format.
91 """
91 """
92 )
92 )
93
93
94 callable_image_handler = Any(config=True, help=
94 callable_image_handler = Any(config=True, help=
95 """
95 """
96 Callable object called via 'callable' image handler with one
96 Callable object called via 'callable' image handler with one
97 argument, `data`, which is `msg["content"]["data"]` where
97 argument, `data`, which is `msg["content"]["data"]` where
98 `msg` is the message from iopub channel. For exmaple, you can
98 `msg` is the message from iopub channel. For exmaple, you can
99 find base64 encoded PNG data as `data['image/png']`.
99 find base64 encoded PNG data as `data['image/png']`.
100 """
100 """
101 )
101 )
102
102
103 mime_preference = List(
103 mime_preference = List(
104 default_value=['image/png', 'image/jpeg', 'image/svg+xml'],
104 default_value=['image/png', 'image/jpeg', 'image/svg+xml'],
105 config=True, allow_none=False, help=
105 config=True, allow_none=False, help=
106 """
106 """
107 Preferred object representation MIME type in order. First
107 Preferred object representation MIME type in order. First
108 matched MIME type will be used.
108 matched MIME type will be used.
109 """
109 """
110 )
110 )
111
111
112 manager = Instance('IPython.kernel.KernelManager')
112 manager = Instance('IPython.kernel.KernelManager')
113 client = Instance('IPython.kernel.KernelClient')
113 client = Instance('IPython.kernel.KernelClient')
114 def _client_changed(self, name, old, new):
114 def _client_changed(self, name, old, new):
115 self.session_id = new.session.session
115 self.session_id = new.session.session
116 session_id = Unicode()
116 session_id = Unicode()
117
117
118 def init_completer(self):
118 def init_completer(self):
119 """Initialize the completion machinery.
119 """Initialize the completion machinery.
120
120
121 This creates completion machinery that can be used by client code,
121 This creates completion machinery that can be used by client code,
122 either interactively in-process (typically triggered by the readline
122 either interactively in-process (typically triggered by the readline
123 library), programmatically (such as in test suites) or out-of-process
123 library), programmatically (such as in test suites) or out-of-process
124 (typically over the network by remote frontends).
124 (typically over the network by remote frontends).
125 """
125 """
126 from IPython.core.completerlib import (module_completer,
126 from IPython.core.completerlib import (module_completer,
127 magic_run_completer, cd_completer)
127 magic_run_completer, cd_completer)
128
128
129 self.Completer = ZMQCompleter(self, self.client, config=self.config)
129 self.Completer = ZMQCompleter(self, self.client, config=self.config)
130
130
131
131
132 self.set_hook('complete_command', module_completer, str_key = 'import')
132 self.set_hook('complete_command', module_completer, str_key = 'import')
133 self.set_hook('complete_command', module_completer, str_key = 'from')
133 self.set_hook('complete_command', module_completer, str_key = 'from')
134 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
134 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
135 self.set_hook('complete_command', cd_completer, str_key = '%cd')
135 self.set_hook('complete_command', cd_completer, str_key = '%cd')
136
136
137 # Only configure readline if we truly are using readline. IPython can
137 # Only configure readline if we truly are using readline. IPython can
138 # do tab-completion over the network, in GUIs, etc, where readline
138 # do tab-completion over the network, in GUIs, etc, where readline
139 # itself may be absent
139 # itself may be absent
140 if self.has_readline:
140 if self.has_readline:
141 self.set_readline_completer()
141 self.set_readline_completer()
142
142
143 def run_cell(self, cell, store_history=True):
143 def run_cell(self, cell, store_history=True):
144 """Run a complete IPython cell.
144 """Run a complete IPython cell.
145
145
146 Parameters
146 Parameters
147 ----------
147 ----------
148 cell : str
148 cell : str
149 The code (including IPython code such as %magic functions) to run.
149 The code (including IPython code such as %magic functions) to run.
150 store_history : bool
150 store_history : bool
151 If True, the raw and translated cell will be stored in IPython's
151 If True, the raw and translated cell will be stored in IPython's
152 history. For user code calling back into IPython's machinery, this
152 history. For user code calling back into IPython's machinery, this
153 should be set to False.
153 should be set to False.
154 """
154 """
155 if (not cell) or cell.isspace():
155 if (not cell) or cell.isspace():
156 # pressing enter flushes any pending display
156 # pressing enter flushes any pending display
157 self.handle_iopub()
157 self.handle_iopub()
158 return
158 return
159
159
160 # flush stale replies, which could have been ignored, due to missed heartbeats
160 # flush stale replies, which could have been ignored, due to missed heartbeats
161 while self.client.shell_channel.msg_ready():
161 while self.client.shell_channel.msg_ready():
162 self.client.shell_channel.get_msg()
162 self.client.shell_channel.get_msg()
163 # execute takes 'hidden', which is the inverse of store_hist
163 # execute takes 'hidden', which is the inverse of store_hist
164 msg_id = self.client.execute(cell, not store_history)
164 msg_id = self.client.execute(cell, not store_history)
165
165
166 # first thing is wait for any side effects (output, stdin, etc.)
166 # first thing is wait for any side effects (output, stdin, etc.)
167 self._executing = True
167 self._executing = True
168 self._execution_state = "busy"
168 self._execution_state = "busy"
169 while self._execution_state != 'idle' and self.client.is_alive():
169 while self._execution_state != 'idle' and self.client.is_alive():
170 try:
170 try:
171 self.handle_input_request(msg_id, timeout=0.05)
171 self.handle_input_request(msg_id, timeout=0.05)
172 except Empty:
172 except Empty:
173 # display intermediate print statements, etc.
173 # display intermediate print statements, etc.
174 self.handle_iopub(msg_id)
174 self.handle_iopub(msg_id)
175 except ZMQError as e:
175 except ZMQError as e:
176 # Carry on if polling was interrupted by a signal
176 # Carry on if polling was interrupted by a signal
177 if e.errno != errno.EINTR:
177 if e.errno != errno.EINTR:
178 raise
178 raise
179
179
180 # after all of that is done, wait for the execute reply
180 # after all of that is done, wait for the execute reply
181 while self.client.is_alive():
181 while self.client.is_alive():
182 try:
182 try:
183 self.handle_execute_reply(msg_id, timeout=0.05)
183 self.handle_execute_reply(msg_id, timeout=0.05)
184 except Empty:
184 except Empty:
185 pass
185 pass
186 else:
186 else:
187 break
187 break
188 self._executing = False
188 self._executing = False
189
189
190 #-----------------
190 #-----------------
191 # message handlers
191 # message handlers
192 #-----------------
192 #-----------------
193
193
194 def handle_execute_reply(self, msg_id, timeout=None):
194 def handle_execute_reply(self, msg_id, timeout=None):
195 msg = self.client.shell_channel.get_msg(block=False, timeout=timeout)
195 msg = self.client.shell_channel.get_msg(block=False, timeout=timeout)
196 if msg["parent_header"].get("msg_id", None) == msg_id:
196 if msg["parent_header"].get("msg_id", None) == msg_id:
197
197
198 self.handle_iopub(msg_id)
198 self.handle_iopub(msg_id)
199
199
200 content = msg["content"]
200 content = msg["content"]
201 status = content['status']
201 status = content['status']
202
202
203 if status == 'aborted':
203 if status == 'aborted':
204 self.write('Aborted\n')
204 self.write('Aborted\n')
205 return
205 return
206 elif status == 'ok':
206 elif status == 'ok':
207 # handle payloads
207 # handle payloads
208 for item in content["payload"]:
208 for item in content["payload"]:
209 source = item['source']
209 source = item['source']
210 if source == 'page':
210 if source == 'page':
211 page.page(item['data']['text/plain'])
211 page.page(item['data']['text/plain'])
212 elif source == 'set_next_input':
212 elif source == 'set_next_input':
213 self.set_next_input(item['text'])
213 self.set_next_input(item['text'])
214 elif source == 'ask_exit':
214 elif source == 'ask_exit':
215 self.keepkernel=item.get('keepkernel', False)
215 self.ask_exit()
216 self.ask_exit()
216
217
217 elif status == 'error':
218 elif status == 'error':
218 for frame in content["traceback"]:
219 for frame in content["traceback"]:
219 print(frame, file=io.stderr)
220 print(frame, file=io.stderr)
220
221
221 self.execution_count = int(content["execution_count"] + 1)
222 self.execution_count = int(content["execution_count"] + 1)
222
223
223 include_other_output = Bool(False, config=True,
224 include_other_output = Bool(False, config=True,
224 help="""Whether to include output from clients
225 help="""Whether to include output from clients
225 other than this one sharing the same kernel.
226 other than this one sharing the same kernel.
226
227
227 Outputs are not displayed until enter is pressed.
228 Outputs are not displayed until enter is pressed.
228 """
229 """
229 )
230 )
230 other_output_prefix = Unicode("[remote] ", config=True,
231 other_output_prefix = Unicode("[remote] ", config=True,
231 help="""Prefix to add to outputs coming from clients other than this one.
232 help="""Prefix to add to outputs coming from clients other than this one.
232
233
233 Only relevant if include_other_output is True.
234 Only relevant if include_other_output is True.
234 """
235 """
235 )
236 )
236
237
237 def from_here(self, msg):
238 def from_here(self, msg):
238 """Return whether a message is from this session"""
239 """Return whether a message is from this session"""
239 return msg['parent_header'].get("session", self.session_id) == self.session_id
240 return msg['parent_header'].get("session", self.session_id) == self.session_id
240
241
241 def include_output(self, msg):
242 def include_output(self, msg):
242 """Return whether we should include a given output message"""
243 """Return whether we should include a given output message"""
243 from_here = self.from_here(msg)
244 from_here = self.from_here(msg)
244 if msg['msg_type'] == 'execute_input':
245 if msg['msg_type'] == 'execute_input':
245 # only echo inputs not from here
246 # only echo inputs not from here
246 return self.include_other_output and not from_here
247 return self.include_other_output and not from_here
247
248
248 if self.include_other_output:
249 if self.include_other_output:
249 return True
250 return True
250 else:
251 else:
251 return from_here
252 return from_here
252
253
253 def handle_iopub(self, msg_id=''):
254 def handle_iopub(self, msg_id=''):
254 """Process messages on the IOPub channel
255 """Process messages on the IOPub channel
255
256
256 This method consumes and processes messages on the IOPub channel,
257 This method consumes and processes messages on the IOPub channel,
257 such as stdout, stderr, execute_result and status.
258 such as stdout, stderr, execute_result and status.
258
259
259 It only displays output that is caused by this session.
260 It only displays output that is caused by this session.
260 """
261 """
261 while self.client.iopub_channel.msg_ready():
262 while self.client.iopub_channel.msg_ready():
262 sub_msg = self.client.iopub_channel.get_msg()
263 sub_msg = self.client.iopub_channel.get_msg()
263 msg_type = sub_msg['header']['msg_type']
264 msg_type = sub_msg['header']['msg_type']
264 parent = sub_msg["parent_header"]
265 parent = sub_msg["parent_header"]
265
266
266 if self.include_output(sub_msg):
267 if self.include_output(sub_msg):
267 if msg_type == 'status':
268 if msg_type == 'status':
268 self._execution_state = sub_msg["content"]["execution_state"]
269 self._execution_state = sub_msg["content"]["execution_state"]
269 elif msg_type == 'stream':
270 elif msg_type == 'stream':
270 if sub_msg["content"]["name"] == "stdout":
271 if sub_msg["content"]["name"] == "stdout":
271 if self._pending_clearoutput:
272 if self._pending_clearoutput:
272 print("\r", file=io.stdout, end="")
273 print("\r", file=io.stdout, end="")
273 self._pending_clearoutput = False
274 self._pending_clearoutput = False
274 print(sub_msg["content"]["text"], file=io.stdout, end="")
275 print(sub_msg["content"]["text"], file=io.stdout, end="")
275 io.stdout.flush()
276 io.stdout.flush()
276 elif sub_msg["content"]["name"] == "stderr":
277 elif sub_msg["content"]["name"] == "stderr":
277 if self._pending_clearoutput:
278 if self._pending_clearoutput:
278 print("\r", file=io.stderr, end="")
279 print("\r", file=io.stderr, end="")
279 self._pending_clearoutput = False
280 self._pending_clearoutput = False
280 print(sub_msg["content"]["text"], file=io.stderr, end="")
281 print(sub_msg["content"]["text"], file=io.stderr, end="")
281 io.stderr.flush()
282 io.stderr.flush()
282
283
283 elif msg_type == 'execute_result':
284 elif msg_type == 'execute_result':
284 if self._pending_clearoutput:
285 if self._pending_clearoutput:
285 print("\r", file=io.stdout, end="")
286 print("\r", file=io.stdout, end="")
286 self._pending_clearoutput = False
287 self._pending_clearoutput = False
287 self.execution_count = int(sub_msg["content"]["execution_count"])
288 self.execution_count = int(sub_msg["content"]["execution_count"])
288 if not self.from_here(sub_msg):
289 if not self.from_here(sub_msg):
289 sys.stdout.write(self.other_output_prefix)
290 sys.stdout.write(self.other_output_prefix)
290 format_dict = sub_msg["content"]["data"]
291 format_dict = sub_msg["content"]["data"]
291 self.handle_rich_data(format_dict)
292 self.handle_rich_data(format_dict)
292
293
293 # taken from DisplayHook.__call__:
294 # taken from DisplayHook.__call__:
294 hook = self.displayhook
295 hook = self.displayhook
295 hook.start_displayhook()
296 hook.start_displayhook()
296 hook.write_output_prompt()
297 hook.write_output_prompt()
297 hook.write_format_data(format_dict)
298 hook.write_format_data(format_dict)
298 hook.log_output(format_dict)
299 hook.log_output(format_dict)
299 hook.finish_displayhook()
300 hook.finish_displayhook()
300
301
301 elif msg_type == 'display_data':
302 elif msg_type == 'display_data':
302 data = sub_msg["content"]["data"]
303 data = sub_msg["content"]["data"]
303 handled = self.handle_rich_data(data)
304 handled = self.handle_rich_data(data)
304 if not handled:
305 if not handled:
305 if not self.from_here(sub_msg):
306 if not self.from_here(sub_msg):
306 sys.stdout.write(self.other_output_prefix)
307 sys.stdout.write(self.other_output_prefix)
307 # if it was an image, we handled it by now
308 # if it was an image, we handled it by now
308 if 'text/plain' in data:
309 if 'text/plain' in data:
309 print(data['text/plain'])
310 print(data['text/plain'])
310
311
311 elif msg_type == 'execute_input':
312 elif msg_type == 'execute_input':
312 content = sub_msg['content']
313 content = sub_msg['content']
313 self.execution_count = content['execution_count']
314 self.execution_count = content['execution_count']
314 if not self.from_here(sub_msg):
315 if not self.from_here(sub_msg):
315 sys.stdout.write(self.other_output_prefix)
316 sys.stdout.write(self.other_output_prefix)
316 sys.stdout.write(self.prompt_manager.render('in'))
317 sys.stdout.write(self.prompt_manager.render('in'))
317 sys.stdout.write(content['code'])
318 sys.stdout.write(content['code'])
318
319
319 elif msg_type == 'clear_output':
320 elif msg_type == 'clear_output':
320 if sub_msg["content"]["wait"]:
321 if sub_msg["content"]["wait"]:
321 self._pending_clearoutput = True
322 self._pending_clearoutput = True
322 else:
323 else:
323 print("\r", file=io.stdout, end="")
324 print("\r", file=io.stdout, end="")
324
325
325 _imagemime = {
326 _imagemime = {
326 'image/png': 'png',
327 'image/png': 'png',
327 'image/jpeg': 'jpeg',
328 'image/jpeg': 'jpeg',
328 'image/svg+xml': 'svg',
329 'image/svg+xml': 'svg',
329 }
330 }
330
331
331 def handle_rich_data(self, data):
332 def handle_rich_data(self, data):
332 for mime in self.mime_preference:
333 for mime in self.mime_preference:
333 if mime in data and mime in self._imagemime:
334 if mime in data and mime in self._imagemime:
334 self.handle_image(data, mime)
335 self.handle_image(data, mime)
335 return True
336 return True
336
337
337 def handle_image(self, data, mime):
338 def handle_image(self, data, mime):
338 handler = getattr(
339 handler = getattr(
339 self, 'handle_image_{0}'.format(self.image_handler), None)
340 self, 'handle_image_{0}'.format(self.image_handler), None)
340 if handler:
341 if handler:
341 handler(data, mime)
342 handler(data, mime)
342
343
343 def handle_image_PIL(self, data, mime):
344 def handle_image_PIL(self, data, mime):
344 if mime not in ('image/png', 'image/jpeg'):
345 if mime not in ('image/png', 'image/jpeg'):
345 return
346 return
346 import PIL.Image
347 import PIL.Image
347 raw = base64.decodestring(data[mime].encode('ascii'))
348 raw = base64.decodestring(data[mime].encode('ascii'))
348 img = PIL.Image.open(BytesIO(raw))
349 img = PIL.Image.open(BytesIO(raw))
349 img.show()
350 img.show()
350
351
351 def handle_image_stream(self, data, mime):
352 def handle_image_stream(self, data, mime):
352 raw = base64.decodestring(data[mime].encode('ascii'))
353 raw = base64.decodestring(data[mime].encode('ascii'))
353 imageformat = self._imagemime[mime]
354 imageformat = self._imagemime[mime]
354 fmt = dict(format=imageformat)
355 fmt = dict(format=imageformat)
355 args = [s.format(**fmt) for s in self.stream_image_handler]
356 args = [s.format(**fmt) for s in self.stream_image_handler]
356 with open(os.devnull, 'w') as devnull:
357 with open(os.devnull, 'w') as devnull:
357 proc = subprocess.Popen(
358 proc = subprocess.Popen(
358 args, stdin=subprocess.PIPE,
359 args, stdin=subprocess.PIPE,
359 stdout=devnull, stderr=devnull)
360 stdout=devnull, stderr=devnull)
360 proc.communicate(raw)
361 proc.communicate(raw)
361
362
362 def handle_image_tempfile(self, data, mime):
363 def handle_image_tempfile(self, data, mime):
363 raw = base64.decodestring(data[mime].encode('ascii'))
364 raw = base64.decodestring(data[mime].encode('ascii'))
364 imageformat = self._imagemime[mime]
365 imageformat = self._imagemime[mime]
365 filename = 'tmp.{0}'.format(imageformat)
366 filename = 'tmp.{0}'.format(imageformat)
366 with NamedFileInTemporaryDirectory(filename) as f, \
367 with NamedFileInTemporaryDirectory(filename) as f, \
367 open(os.devnull, 'w') as devnull:
368 open(os.devnull, 'w') as devnull:
368 f.write(raw)
369 f.write(raw)
369 f.flush()
370 f.flush()
370 fmt = dict(file=f.name, format=imageformat)
371 fmt = dict(file=f.name, format=imageformat)
371 args = [s.format(**fmt) for s in self.tempfile_image_handler]
372 args = [s.format(**fmt) for s in self.tempfile_image_handler]
372 subprocess.call(args, stdout=devnull, stderr=devnull)
373 subprocess.call(args, stdout=devnull, stderr=devnull)
373
374
374 def handle_image_callable(self, data, mime):
375 def handle_image_callable(self, data, mime):
375 self.callable_image_handler(data)
376 self.callable_image_handler(data)
376
377
377 def handle_input_request(self, msg_id, timeout=0.1):
378 def handle_input_request(self, msg_id, timeout=0.1):
378 """ Method to capture raw_input
379 """ Method to capture raw_input
379 """
380 """
380 req = self.client.stdin_channel.get_msg(timeout=timeout)
381 req = self.client.stdin_channel.get_msg(timeout=timeout)
381 # in case any iopub came while we were waiting:
382 # in case any iopub came while we were waiting:
382 self.handle_iopub(msg_id)
383 self.handle_iopub(msg_id)
383 if msg_id == req["parent_header"].get("msg_id"):
384 if msg_id == req["parent_header"].get("msg_id"):
384 # wrap SIGINT handler
385 # wrap SIGINT handler
385 real_handler = signal.getsignal(signal.SIGINT)
386 real_handler = signal.getsignal(signal.SIGINT)
386 def double_int(sig,frame):
387 def double_int(sig,frame):
387 # call real handler (forwards sigint to kernel),
388 # call real handler (forwards sigint to kernel),
388 # then raise local interrupt, stopping local raw_input
389 # then raise local interrupt, stopping local raw_input
389 real_handler(sig,frame)
390 real_handler(sig,frame)
390 raise KeyboardInterrupt
391 raise KeyboardInterrupt
391 signal.signal(signal.SIGINT, double_int)
392 signal.signal(signal.SIGINT, double_int)
392 content = req['content']
393 content = req['content']
393 read = getpass if content.get('password', False) else input
394 read = getpass if content.get('password', False) else input
394 try:
395 try:
395 raw_data = read(content["prompt"])
396 raw_data = read(content["prompt"])
396 except EOFError:
397 except EOFError:
397 # turn EOFError into EOF character
398 # turn EOFError into EOF character
398 raw_data = '\x04'
399 raw_data = '\x04'
399 except KeyboardInterrupt:
400 except KeyboardInterrupt:
400 sys.stdout.write('\n')
401 sys.stdout.write('\n')
401 return
402 return
402 finally:
403 finally:
403 # restore SIGINT handler
404 # restore SIGINT handler
404 signal.signal(signal.SIGINT, real_handler)
405 signal.signal(signal.SIGINT, real_handler)
405
406
406 # only send stdin reply if there *was not* another request
407 # only send stdin reply if there *was not* another request
407 # or execution finished while we were reading.
408 # or execution finished while we were reading.
408 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
409 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
409 self.client.input(raw_data)
410 self.client.input(raw_data)
410
411
411 def mainloop(self, display_banner=False):
412 def mainloop(self, display_banner=False):
413 self.keepkernel = False
412 while True:
414 while True:
413 try:
415 try:
414 self.interact(display_banner=display_banner)
416 self.interact(display_banner=display_banner)
415 #self.interact_with_readline()
417 #self.interact_with_readline()
416 # XXX for testing of a readline-decoupled repl loop, call
418 # XXX for testing of a readline-decoupled repl loop, call
417 # interact_with_readline above
419 # interact_with_readline above
418 break
420 break
419 except KeyboardInterrupt:
421 except KeyboardInterrupt:
420 # this should not be necessary, but KeyboardInterrupt
422 # this should not be necessary, but KeyboardInterrupt
421 # handling seems rather unpredictable...
423 # handling seems rather unpredictable...
422 self.write("\nKeyboardInterrupt in interact()\n")
424 self.write("\nKeyboardInterrupt in interact()\n")
423
425
424 self.client.shutdown()
426
427 if self.keepkernel and not self.own_kernel:
428 print('keeping kernel alive')
429 elif self.keepkernel and self.own_kernel :
430 print("owning kernel, cannot keep it alive")
431 self.client.shutdown()
432 else :
433 print("Shutting down kernel")
434 self.client.shutdown()
425
435
426 def _banner1_default(self):
436 def _banner1_default(self):
427 return "IPython Console {version}\n".format(version=release.version)
437 return "IPython Console {version}\n".format(version=release.version)
428
438
429 def compute_banner(self):
439 def compute_banner(self):
430 super(ZMQTerminalInteractiveShell, self).compute_banner()
440 super(ZMQTerminalInteractiveShell, self).compute_banner()
431 if self.client and not self.kernel_banner:
441 if self.client and not self.kernel_banner:
432 msg_id = self.client.kernel_info()
442 msg_id = self.client.kernel_info()
433 while True:
443 while True:
434 try:
444 try:
435 reply = self.client.get_shell_msg(timeout=1)
445 reply = self.client.get_shell_msg(timeout=1)
436 except Empty:
446 except Empty:
437 break
447 break
438 else:
448 else:
439 if reply['parent_header'].get('msg_id') == msg_id:
449 if reply['parent_header'].get('msg_id') == msg_id:
440 self.kernel_banner = reply['content'].get('banner', '')
450 self.kernel_banner = reply['content'].get('banner', '')
441 break
451 break
442 self.banner += self.kernel_banner
452 self.banner += self.kernel_banner
443
453
444 def wait_for_kernel(self, timeout=None):
454 def wait_for_kernel(self, timeout=None):
445 """method to wait for a kernel to be ready"""
455 """method to wait for a kernel to be ready"""
446 tic = time.time()
456 tic = time.time()
447 self.client.hb_channel.unpause()
457 self.client.hb_channel.unpause()
448 while True:
458 while True:
449 msg_id = self.client.kernel_info()
459 msg_id = self.client.kernel_info()
450 reply = None
460 reply = None
451 while True:
461 while True:
452 try:
462 try:
453 reply = self.client.get_shell_msg(timeout=1)
463 reply = self.client.get_shell_msg(timeout=1)
454 except Empty:
464 except Empty:
455 break
465 break
456 else:
466 else:
457 if reply['parent_header'].get('msg_id') == msg_id:
467 if reply['parent_header'].get('msg_id') == msg_id:
458 return True
468 return True
459 if timeout is not None \
469 if timeout is not None \
460 and (time.time() - tic) > timeout \
470 and (time.time() - tic) > timeout \
461 and not self.client.hb_channel.is_beating():
471 and not self.client.hb_channel.is_beating():
462 # heart failed
472 # heart failed
463 return False
473 return False
464 return True
474 return True
465
475
466 def interact(self, display_banner=None):
476 def interact(self, display_banner=None):
467 """Closely emulate the interactive Python console."""
477 """Closely emulate the interactive Python console."""
468
478
469 # batch run -> do not interact
479 # batch run -> do not interact
470 if self.exit_now:
480 if self.exit_now:
471 return
481 return
472
482
473 if display_banner is None:
483 if display_banner is None:
474 display_banner = self.display_banner
484 display_banner = self.display_banner
475
485
476 if isinstance(display_banner, string_types):
486 if isinstance(display_banner, string_types):
477 self.show_banner(display_banner)
487 self.show_banner(display_banner)
478 elif display_banner:
488 elif display_banner:
479 self.show_banner()
489 self.show_banner()
480
490
481 more = False
491 more = False
482
492
483 # run a non-empty no-op, so that we don't get a prompt until
493 # run a non-empty no-op, so that we don't get a prompt until
484 # we know the kernel is ready. This keeps the connection
494 # we know the kernel is ready. This keeps the connection
485 # message above the first prompt.
495 # message above the first prompt.
486 if not self.wait_for_kernel(self.kernel_timeout):
496 if not self.wait_for_kernel(self.kernel_timeout):
487 error("Kernel did not respond\n")
497 error("Kernel did not respond\n")
488 return
498 return
489
499
490 if self.has_readline:
500 if self.has_readline:
491 self.readline_startup_hook(self.pre_readline)
501 self.readline_startup_hook(self.pre_readline)
492 hlen_b4_cell = self.readline.get_current_history_length()
502 hlen_b4_cell = self.readline.get_current_history_length()
493 else:
503 else:
494 hlen_b4_cell = 0
504 hlen_b4_cell = 0
495 # exit_now is set by a call to %Exit or %Quit, through the
505 # exit_now is set by a call to %Exit or %Quit, through the
496 # ask_exit callback.
506 # ask_exit callback.
497
507
498 while not self.exit_now:
508 while not self.exit_now:
499 if not self.client.is_alive():
509 if not self.client.is_alive():
500 # kernel died, prompt for action or exit
510 # kernel died, prompt for action or exit
501
511
502 action = "restart" if self.manager else "wait for restart"
512 action = "restart" if self.manager else "wait for restart"
503 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
513 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
504 if ans:
514 if ans:
505 if self.manager:
515 if self.manager:
506 self.manager.restart_kernel(True)
516 self.manager.restart_kernel(True)
507 self.wait_for_kernel(self.kernel_timeout)
517 self.wait_for_kernel(self.kernel_timeout)
508 else:
518 else:
509 self.exit_now = True
519 self.exit_now = True
510 continue
520 continue
511 try:
521 try:
512 # protect prompt block from KeyboardInterrupt
522 # protect prompt block from KeyboardInterrupt
513 # when sitting on ctrl-C
523 # when sitting on ctrl-C
514 self.hooks.pre_prompt_hook()
524 self.hooks.pre_prompt_hook()
515 if more:
525 if more:
516 try:
526 try:
517 prompt = self.prompt_manager.render('in2')
527 prompt = self.prompt_manager.render('in2')
518 except Exception:
528 except Exception:
519 self.showtraceback()
529 self.showtraceback()
520 if self.autoindent:
530 if self.autoindent:
521 self.rl_do_indent = True
531 self.rl_do_indent = True
522
532
523 else:
533 else:
524 try:
534 try:
525 prompt = self.separate_in + self.prompt_manager.render('in')
535 prompt = self.separate_in + self.prompt_manager.render('in')
526 except Exception:
536 except Exception:
527 self.showtraceback()
537 self.showtraceback()
528
538
529 line = self.raw_input(prompt)
539 line = self.raw_input(prompt)
530 if self.exit_now:
540 if self.exit_now:
531 # quick exit on sys.std[in|out] close
541 # quick exit on sys.std[in|out] close
532 break
542 break
533 if self.autoindent:
543 if self.autoindent:
534 self.rl_do_indent = False
544 self.rl_do_indent = False
535
545
536 except KeyboardInterrupt:
546 except KeyboardInterrupt:
537 #double-guard against keyboardinterrupts during kbdint handling
547 #double-guard against keyboardinterrupts during kbdint handling
538 try:
548 try:
539 self.write('\n' + self.get_exception_only())
549 self.write('\n' + self.get_exception_only())
540 source_raw = self.input_splitter.raw_reset()
550 source_raw = self.input_splitter.raw_reset()
541 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
551 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
542 more = False
552 more = False
543 except KeyboardInterrupt:
553 except KeyboardInterrupt:
544 pass
554 pass
545 except EOFError:
555 except EOFError:
546 if self.autoindent:
556 if self.autoindent:
547 self.rl_do_indent = False
557 self.rl_do_indent = False
548 if self.has_readline:
558 if self.has_readline:
549 self.readline_startup_hook(None)
559 self.readline_startup_hook(None)
550 self.write('\n')
560 self.write('\n')
551 self.exit()
561 self.exit()
552 except bdb.BdbQuit:
562 except bdb.BdbQuit:
553 warn('The Python debugger has exited with a BdbQuit exception.\n'
563 warn('The Python debugger has exited with a BdbQuit exception.\n'
554 'Because of how pdb handles the stack, it is impossible\n'
564 'Because of how pdb handles the stack, it is impossible\n'
555 'for IPython to properly format this particular exception.\n'
565 'for IPython to properly format this particular exception.\n'
556 'IPython will resume normal operation.')
566 'IPython will resume normal operation.')
557 except:
567 except:
558 # exceptions here are VERY RARE, but they can be triggered
568 # exceptions here are VERY RARE, but they can be triggered
559 # asynchronously by signal handlers, for example.
569 # asynchronously by signal handlers, for example.
560 self.showtraceback()
570 self.showtraceback()
561 else:
571 else:
562 try:
572 try:
563 self.input_splitter.push(line)
573 self.input_splitter.push(line)
564 more = self.input_splitter.push_accepts_more()
574 more = self.input_splitter.push_accepts_more()
565 except SyntaxError:
575 except SyntaxError:
566 # Run the code directly - run_cell takes care of displaying
576 # Run the code directly - run_cell takes care of displaying
567 # the exception.
577 # the exception.
568 more = False
578 more = False
569 if (self.SyntaxTB.last_syntax_error and
579 if (self.SyntaxTB.last_syntax_error and
570 self.autoedit_syntax):
580 self.autoedit_syntax):
571 self.edit_syntax_error()
581 self.edit_syntax_error()
572 if not more:
582 if not more:
573 source_raw = self.input_splitter.raw_reset()
583 source_raw = self.input_splitter.raw_reset()
574 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
584 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
575 self.run_cell(source_raw)
585 self.run_cell(source_raw)
576
586
577
587
578 # Turn off the exit flag, so the mainloop can be restarted if desired
588 # Turn off the exit flag, so the mainloop can be restarted if desired
579 self.exit_now = False
589 self.exit_now = False
580
590
581 def init_history(self):
591 def init_history(self):
582 """Sets up the command history. """
592 """Sets up the command history. """
583 self.history_manager = ZMQHistoryManager(client=self.client)
593 self.history_manager = ZMQHistoryManager(client=self.client)
584 self.configurables.append(self.history_manager)
594 self.configurables.append(self.history_manager)
585
595
General Comments 0
You need to be logged in to leave comments. Login now