##// END OF EJS Templates
Merge pull request #864 from ipython/termzmq...
Fernando Perez -
r5633:afa2b63b merge
parent child Browse files
Show More
@@ -0,0 +1,352 b''
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
2
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. This is a
5 refactoring of what used to be the IPython/frontend/qt/console/qtconsoleapp.py
6
7 Authors:
8
9 * Evan Patterson
10 * Min RK
11 * Erik Tollerud
12 * Fernando Perez
13 * Bussonnier Matthias
14 * Thomas Kluyver
15 * Paul Ivanov
16
17 """
18
19 #-----------------------------------------------------------------------------
20 # Imports
21 #-----------------------------------------------------------------------------
22
23 # stdlib imports
24 import atexit
25 import json
26 import os
27 import signal
28 import sys
29 import uuid
30
31
32 # Local imports
33 from IPython.config.application import boolean_flag
34 from IPython.config.configurable import Configurable
35 from IPython.core.profiledir import ProfileDir
36 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
37 from IPython.zmq.blockingkernelmanager import BlockingKernelManager
38 from IPython.utils.path import filefind
39 from IPython.utils.py3compat import str_to_bytes
40 from IPython.utils.traitlets import (
41 Dict, List, Unicode, CUnicode, Int, CBool, Any
42 )
43 from IPython.zmq.ipkernel import (
44 flags as ipkernel_flags,
45 aliases as ipkernel_aliases,
46 IPKernelApp
47 )
48 from IPython.zmq.session import Session, default_secure
49 from IPython.zmq.zmqshell import ZMQInteractiveShell
50
51 #-----------------------------------------------------------------------------
52 # Network Constants
53 #-----------------------------------------------------------------------------
54
55 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
56
57 #-----------------------------------------------------------------------------
58 # Globals
59 #-----------------------------------------------------------------------------
60
61
62 #-----------------------------------------------------------------------------
63 # Aliases and Flags
64 #-----------------------------------------------------------------------------
65
66 flags = dict(ipkernel_flags)
67
68 # the flags that are specific to the frontend
69 # these must be scrubbed before being passed to the kernel,
70 # or it will raise an error on unrecognized flags
71 app_flags = {
72 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
73 "Connect to an existing kernel. If no argument specified, guess most recent"),
74 }
75 app_flags.update(boolean_flag(
76 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
77 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
78 to force a direct exit without any confirmation.
79 """,
80 """Don't prompt the user when exiting. This will terminate the kernel
81 if it is owned by the frontend, and leave it alive if it is external.
82 """
83 ))
84 flags.update(app_flags)
85
86 aliases = dict(ipkernel_aliases)
87
88 # also scrub aliases from the frontend
89 app_aliases = dict(
90 hb = 'IPythonConsoleApp.hb_port',
91 shell = 'IPythonConsoleApp.shell_port',
92 iopub = 'IPythonConsoleApp.iopub_port',
93 stdin = 'IPythonConsoleApp.stdin_port',
94 ip = 'IPythonConsoleApp.ip',
95 existing = 'IPythonConsoleApp.existing',
96 f = 'IPythonConsoleApp.connection_file',
97
98
99 ssh = 'IPythonConsoleApp.sshserver',
100 )
101 aliases.update(app_aliases)
102
103 #-----------------------------------------------------------------------------
104 # Classes
105 #-----------------------------------------------------------------------------
106
107 #-----------------------------------------------------------------------------
108 # IPythonConsole
109 #-----------------------------------------------------------------------------
110
111
112 class IPythonConsoleApp(Configurable):
113 name = 'ipython-console-mixin'
114 default_config_file_name='ipython_config.py'
115
116 description = """
117 The IPython Mixin Console.
118
119 This class contains the common portions of console client (QtConsole,
120 ZMQ-based terminal console, etc). It is not a full console, in that
121 launched terminal subprocesses will not be able to accept input.
122
123 The Console using this mixing supports various extra features beyond
124 the single-process Terminal IPython shell, such as connecting to
125 existing kernel, via:
126
127 ipython <appname> --existing
128
129 as well as tunnel via SSH
130
131 """
132
133 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session]
134 flags = Dict(flags)
135 aliases = Dict(aliases)
136 kernel_manager_class = BlockingKernelManager
137
138 kernel_argv = List(Unicode)
139 # frontend flags&aliases to be stripped when building kernel_argv
140 frontend_flags = Any(app_flags)
141 frontend_aliases = Any(app_aliases)
142
143 pure = CBool(False, config=True,
144 help="Use a pure Python kernel instead of an IPython kernel.")
145 # create requested profiles by default, if they don't exist:
146 auto_create = CBool(True)
147 # connection info:
148 ip = Unicode(LOCALHOST, config=True,
149 help="""Set the kernel\'s IP address [default localhost].
150 If the IP address is something other than localhost, then
151 Consoles on other machines will be able to connect
152 to the Kernel, so be careful!"""
153 )
154
155 sshserver = Unicode('', config=True,
156 help="""The SSH server to use to connect to the kernel.""")
157 sshkey = Unicode('', config=True,
158 help="""Path to the ssh key to use for logging in to the ssh server.""")
159
160 hb_port = Int(0, config=True,
161 help="set the heartbeat port [default: random]")
162 shell_port = Int(0, config=True,
163 help="set the shell (XREP) port [default: random]")
164 iopub_port = Int(0, config=True,
165 help="set the iopub (PUB) port [default: random]")
166 stdin_port = Int(0, config=True,
167 help="set the stdin (XREQ) port [default: random]")
168 connection_file = Unicode('', config=True,
169 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
170
171 This file will contain the IP, ports, and authentication key needed to connect
172 clients to this kernel. By default, this file will be created in the security-dir
173 of the current profile, but can be specified by absolute path.
174 """)
175 def _connection_file_default(self):
176 return 'kernel-%i.json' % os.getpid()
177
178 existing = CUnicode('', config=True,
179 help="""Connect to an already running kernel""")
180
181 confirm_exit = CBool(True, config=True,
182 help="""
183 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
184 to force a direct exit without any confirmation.""",
185 )
186
187
188 def build_kernel_argv(self, argv=None):
189 """build argv to be passed to kernel subprocess"""
190 if argv is None:
191 argv = sys.argv[1:]
192 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
193 # kernel should inherit default config file from frontend
194 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
195
196 def init_connection_file(self):
197 """find the connection file, and load the info if found.
198
199 The current working directory and the current profile's security
200 directory will be searched for the file if it is not given by
201 absolute path.
202
203 When attempting to connect to an existing kernel and the `--existing`
204 argument does not match an existing file, it will be interpreted as a
205 fileglob, and the matching file in the current profile's security dir
206 with the latest access time will be used.
207
208 After this method is called, self.connection_file contains the *full path*
209 to the connection file, never just its name.
210 """
211 if self.existing:
212 try:
213 cf = find_connection_file(self.existing)
214 except Exception:
215 self.log.critical("Could not find existing kernel connection file %s", self.existing)
216 self.exit(1)
217 self.log.info("Connecting to existing kernel: %s" % cf)
218 self.connection_file = cf
219 else:
220 # not existing, check if we are going to write the file
221 # and ensure that self.connection_file is a full path, not just the shortname
222 try:
223 cf = find_connection_file(self.connection_file)
224 except Exception:
225 # file might not exist
226 if self.connection_file == os.path.basename(self.connection_file):
227 # just shortname, put it in security dir
228 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
229 else:
230 cf = self.connection_file
231 self.connection_file = cf
232
233 # should load_connection_file only be used for existing?
234 # as it is now, this allows reusing ports if an existing
235 # file is requested
236 try:
237 self.load_connection_file()
238 except Exception:
239 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
240 self.exit(1)
241
242 def load_connection_file(self):
243 """load ip/port/hmac config from JSON connection file"""
244 # this is identical to KernelApp.load_connection_file
245 # perhaps it can be centralized somewhere?
246 try:
247 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
248 except IOError:
249 self.log.debug("Connection File not found: %s", self.connection_file)
250 return
251 self.log.debug(u"Loading connection file %s", fname)
252 with open(fname) as f:
253 s = f.read()
254 cfg = json.loads(s)
255 if self.ip == LOCALHOST and 'ip' in cfg:
256 # not overridden by config or cl_args
257 self.ip = cfg['ip']
258 for channel in ('hb', 'shell', 'iopub', 'stdin'):
259 name = channel + '_port'
260 if getattr(self, name) == 0 and name in cfg:
261 # not overridden by config or cl_args
262 setattr(self, name, cfg[name])
263 if 'key' in cfg:
264 self.config.Session.key = str_to_bytes(cfg['key'])
265
266 def init_ssh(self):
267 """set up ssh tunnels, if needed."""
268 if not self.sshserver and not self.sshkey:
269 return
270
271 if self.sshkey and not self.sshserver:
272 # specifying just the key implies that we are connecting directly
273 self.sshserver = self.ip
274 self.ip = LOCALHOST
275
276 # build connection dict for tunnels:
277 info = dict(ip=self.ip,
278 shell_port=self.shell_port,
279 iopub_port=self.iopub_port,
280 stdin_port=self.stdin_port,
281 hb_port=self.hb_port
282 )
283
284 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
285
286 # tunnels return a new set of ports, which will be on localhost:
287 self.ip = LOCALHOST
288 try:
289 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
290 except:
291 # even catch KeyboardInterrupt
292 self.log.error("Could not setup tunnels", exc_info=True)
293 self.exit(1)
294
295 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
296
297 cf = self.connection_file
298 base,ext = os.path.splitext(cf)
299 base = os.path.basename(base)
300 self.connection_file = os.path.basename(base)+'-ssh'+ext
301 self.log.critical("To connect another client via this tunnel, use:")
302 self.log.critical("--existing %s" % self.connection_file)
303
304 def _new_connection_file(self):
305 cf = ''
306 while not cf:
307 # we don't need a 128b id to distinguish kernels, use more readable
308 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
309 # kernels can subclass.
310 ident = str(uuid.uuid4()).split('-')[-1]
311 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
312 # only keep if it's actually new. Protect against unlikely collision
313 # in 48b random search space
314 cf = cf if not os.path.exists(cf) else ''
315 return cf
316
317 def init_kernel_manager(self):
318 # Don't let Qt or ZMQ swallow KeyboardInterupts.
319 signal.signal(signal.SIGINT, signal.SIG_DFL)
320
321 # Create a KernelManager and start a kernel.
322 self.kernel_manager = self.kernel_manager_class(
323 ip=self.ip,
324 shell_port=self.shell_port,
325 iopub_port=self.iopub_port,
326 stdin_port=self.stdin_port,
327 hb_port=self.hb_port,
328 connection_file=self.connection_file,
329 config=self.config,
330 )
331 # start the kernel
332 if not self.existing:
333 kwargs = dict(ipython=not self.pure)
334 kwargs['extra_arguments'] = self.kernel_argv
335 self.kernel_manager.start_kernel(**kwargs)
336 elif self.sshserver:
337 # ssh, write new connection file
338 self.kernel_manager.write_connection_file()
339 atexit.register(self.kernel_manager.cleanup_connection_file)
340 self.kernel_manager.start_channels()
341
342
343 def initialize(self, argv=None):
344 """
345 Classes which mix this class in should call:
346 IPythonConsoleApp.initialize(self,argv)
347 """
348 self.init_connection_file()
349 default_secure(self.config)
350 self.init_ssh()
351 self.init_kernel_manager()
352
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,152 b''
1 """ A minimal application using the ZMQ-based terminal IPython frontend.
2
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.
5
6 Authors:
7
8 * Min RK
9 * Paul Ivanov
10
11 """
12
13 #-----------------------------------------------------------------------------
14 # Imports
15 #-----------------------------------------------------------------------------
16 import signal
17 import sys
18 import time
19
20 from IPython.frontend.terminal.ipapp import TerminalIPythonApp, frontend_flags as term_flags
21
22 from IPython.utils.traitlets import (
23 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
24 )
25 from IPython.utils.warn import warn,error
26
27 from IPython.zmq.ipkernel import IPKernelApp
28 from IPython.zmq.session import Session, default_secure
29 from IPython.zmq.zmqshell import ZMQInteractiveShell
30 from IPython.frontend.consoleapp import (
31 IPythonConsoleApp, app_aliases, app_flags, aliases, app_aliases, flags
32 )
33
34 from IPython.frontend.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
35
36 #-----------------------------------------------------------------------------
37 # Globals
38 #-----------------------------------------------------------------------------
39
40 _examples = """
41 ipython console # start the ZMQ-based console
42 ipython console --existing # connect to an existing ipython session
43 """
44
45 #-----------------------------------------------------------------------------
46 # Flags and Aliases
47 #-----------------------------------------------------------------------------
48
49 # copy flags from mixin:
50 flags = dict(flags)
51 # start with mixin frontend flags:
52 frontend_flags = dict(app_flags)
53 # add TerminalIPApp flags:
54 frontend_flags.update(term_flags)
55 # pylab is not frontend-specific in two-process IPython
56 frontend_flags.pop('pylab')
57 # disable quick startup, as it won't propagate to the kernel anyway
58 frontend_flags.pop('quick')
59 # update full dict with frontend flags:
60 flags.update(frontend_flags)
61
62 # copy flags from mixin
63 aliases = dict(aliases)
64 # start with mixin frontend flags
65 frontend_aliases = dict(app_aliases)
66 # load updated frontend flags into full dict
67 aliases.update(frontend_aliases)
68
69 # get flags&aliases into sets, and remove a couple that
70 # shouldn't be scrubbed from backend flags:
71 frontend_aliases = set(frontend_aliases.keys())
72 frontend_flags = set(frontend_flags.keys())
73
74
75 #-----------------------------------------------------------------------------
76 # Classes
77 #-----------------------------------------------------------------------------
78
79
80 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):
81 name = "ipython-console"
82 """Start a terminal frontend to the IPython zmq kernel."""
83
84 description = """
85 The IPython terminal-based Console.
86
87 This launches a Console application inside a terminal.
88
89 The Console supports various extra features beyond the traditional
90 single-process Terminal IPython shell, such as connecting to an
91 existing ipython session, via:
92
93 ipython console --existing
94
95 where the previous session could have been created by another ipython
96 console, an ipython qtconsole, or by opening an ipython notebook.
97
98 """
99 examples = _examples
100
101 classes = List([IPKernelApp, ZMQTerminalInteractiveShell, Session])
102 flags = Dict(flags)
103 aliases = Dict(aliases)
104 frontend_aliases = Any(frontend_aliases)
105 frontend_flags = Any(frontend_flags)
106
107 subcommands = Dict()
108
109 def parse_command_line(self, argv=None):
110 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
111 self.build_kernel_argv(argv)
112
113 def init_shell(self):
114 IPythonConsoleApp.initialize(self)
115 # relay sigint to kernel
116 signal.signal(signal.SIGINT, self.handle_sigint)
117 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
118 display_banner=False, profile_dir=self.profile_dir,
119 ipython_dir=self.ipython_dir, kernel_manager=self.kernel_manager)
120
121 def handle_sigint(self, *args):
122 if self.shell._executing:
123 if self.kernel_manager.has_kernel:
124 # interrupt already gets passed to subprocess by signal handler.
125 # Only if we prevent that should we need to explicitly call
126 # interrupt_kernel, until which time, this would result in a
127 # double-interrupt:
128 # self.kernel_manager.interrupt_kernel()
129 pass
130 else:
131 self.shell.write_err('\n')
132 error("Cannot interrupt kernels we didn't start.\n")
133 else:
134 # raise the KeyboardInterrupt if we aren't waiting for execution,
135 # so that the interact loop advances, and prompt is redrawn, etc.
136 raise KeyboardInterrupt
137
138
139 def init_code(self):
140 # no-op in the frontend, code gets run in the backend
141 pass
142
143 def launch_new_instance():
144 """Create and run a full blown IPython instance"""
145 app = ZMQTerminalIPythonApp.instance()
146 app.initialize()
147 app.start()
148
149
150 if __name__ == '__main__':
151 launch_new_instance()
152
@@ -0,0 +1,44 b''
1 # -*- coding: utf-8 -*-
2 import readline
3 from Queue import Empty
4
5 class ZMQCompleter(object):
6 """Client-side completion machinery.
7
8 How it works: self.complete will be called multiple times, with
9 state=0,1,2,... When state=0 it should compute ALL the completion matches,
10 and then return them for each value of state."""
11
12 def __init__(self, shell, km):
13 self.shell = shell
14 self.km = km
15 self.matches = []
16
17 def complete_request(self,text):
18 line = readline.get_line_buffer()
19 cursor_pos = readline.get_endidx()
20
21 # send completion request to kernel
22 # Give the kernel up to 0.5s to respond
23 msg_id = self.km.shell_channel.complete(text=text, line=line,
24 cursor_pos=cursor_pos)
25
26 msg = self.km.shell_channel.get_msg(timeout=0.5)
27 if msg['parent_header']['msg_id'] == msg_id:
28 return msg["content"]["matches"]
29 return []
30
31 def rlcomplete(self, text, state):
32 if state == 0:
33 try:
34 self.matches = self.complete_request(text)
35 except Empty:
36 print('WARNING: Kernel timeout on tab completion.')
37
38 try:
39 return self.matches[state]
40 except IndexError:
41 return None
42
43 def complete(self, text, line, cursor_pos=None):
44 return self.rlcomplete(text, 0)
@@ -0,0 +1,342 b''
1 # -*- coding: utf-8 -*-
2 """Frontend of ipython working with python-zmq
3
4 Ipython's frontend, is a ipython interface that send request to kernel and proccess the kernel's outputs.
5
6 For more details, see the ipython-zmq design
7 """
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18 from __future__ import print_function
19
20 import bdb
21 import signal
22 import sys
23 import time
24
25 from Queue import Empty
26
27 from IPython.core.alias import AliasManager, AliasError
28 from IPython.core import page
29 from IPython.utils.warn import warn, error, fatal
30 from IPython.utils import io
31
32 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
33 from IPython.frontend.terminal.console.completer import ZMQCompleter
34
35
36 class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
37 """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
38 _executing = False
39
40 def __init__(self, *args, **kwargs):
41 self.km = kwargs.pop('kernel_manager')
42 self.session_id = self.km.session.session
43 super(ZMQTerminalInteractiveShell, self).__init__(*args, **kwargs)
44
45 def init_completer(self):
46 """Initialize the completion machinery.
47
48 This creates completion machinery that can be used by client code,
49 either interactively in-process (typically triggered by the readline
50 library), programatically (such as in test suites) or out-of-prcess
51 (typically over the network by remote frontends).
52 """
53 from IPython.core.completerlib import (module_completer,
54 magic_run_completer, cd_completer)
55
56 self.Completer = ZMQCompleter(self, self.km)
57
58
59 self.set_hook('complete_command', module_completer, str_key = 'import')
60 self.set_hook('complete_command', module_completer, str_key = 'from')
61 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
62 self.set_hook('complete_command', cd_completer, str_key = '%cd')
63
64 # Only configure readline if we truly are using readline. IPython can
65 # do tab-completion over the network, in GUIs, etc, where readline
66 # itself may be absent
67 if self.has_readline:
68 self.set_readline_completer()
69
70 def run_cell(self, cell, store_history=True):
71 """Run a complete IPython cell.
72
73 Parameters
74 ----------
75 cell : str
76 The code (including IPython code such as %magic functions) to run.
77 store_history : bool
78 If True, the raw and translated cell will be stored in IPython's
79 history. For user code calling back into IPython's machinery, this
80 should be set to False.
81 """
82 if (not cell) or cell.isspace():
83 return
84
85 if cell.strip() == 'exit':
86 # explicitly handle 'exit' command
87 return self.ask_exit()
88
89 self._executing = True
90 # flush stale replies, which could have been ignored, due to missed heartbeats
91 while self.km.shell_channel.msg_ready():
92 self.km.shell_channel.get_msg()
93 # shell_channel.execute takes 'hidden', which is the inverse of store_hist
94 msg_id = self.km.shell_channel.execute(cell, not store_history)
95 while not self.km.shell_channel.msg_ready() and self.km.is_alive:
96 try:
97 self.handle_stdin_request(timeout=0.05)
98 except Empty:
99 # display intermediate print statements, etc.
100 self.handle_iopub()
101 pass
102 if self.km.shell_channel.msg_ready():
103 self.handle_execute_reply(msg_id)
104 self._executing = False
105
106 #-----------------
107 # message handlers
108 #-----------------
109
110 def handle_execute_reply(self, msg_id):
111 msg = self.km.shell_channel.get_msg()
112 if msg["parent_header"].get("msg_id", None) == msg_id:
113
114 self.handle_iopub()
115
116 content = msg["content"]
117 status = content['status']
118
119 if status == 'aborted':
120 self.write('Aborted\n')
121 return
122 elif status == 'ok':
123 # print execution payloads as well:
124 for item in content["payload"]:
125 text = item.get('text', None)
126 if text:
127 page.page(text)
128
129 elif status == 'error':
130 for frame in content["traceback"]:
131 print(frame, file=io.stderr)
132
133 self.execution_count = int(content["execution_count"] + 1)
134
135
136 def handle_iopub(self):
137 """ Method to procces subscribe channel's messages
138
139 This method reads a message and processes the content in different
140 outputs like stdout, stderr, pyout and status
141
142 Arguments:
143 sub_msg: message receive from kernel in the sub socket channel
144 capture by kernel manager.
145 """
146 while self.km.sub_channel.msg_ready():
147 sub_msg = self.km.sub_channel.get_msg()
148 msg_type = sub_msg['header']['msg_type']
149 parent = sub_msg["parent_header"]
150 if (not parent) or self.session_id == parent['session']:
151 if msg_type == 'status' :
152 if sub_msg["content"]["execution_state"] == "busy" :
153 pass
154
155 elif msg_type == 'stream' :
156 if sub_msg["content"]["name"] == "stdout":
157 print(sub_msg["content"]["data"], file=io.stdout, end="")
158 io.stdout.flush()
159 elif sub_msg["content"]["name"] == "stderr" :
160 print(sub_msg["content"]["data"], file=io.stderr, end="")
161 io.stderr.flush()
162
163 elif msg_type == 'pyout':
164 self.execution_count = int(sub_msg["content"]["execution_count"])
165 format_dict = sub_msg["content"]["data"]
166 # taken from DisplayHook.__call__:
167 hook = self.displayhook
168 hook.start_displayhook()
169 hook.write_output_prompt()
170 hook.write_format_data(format_dict)
171 hook.log_output(format_dict)
172 hook.finish_displayhook()
173
174 def handle_stdin_request(self, timeout=0.1):
175 """ Method to capture raw_input
176 """
177 msg_rep = self.km.stdin_channel.get_msg(timeout=timeout)
178 # in case any iopub came while we were waiting:
179 self.handle_iopub()
180 if self.session_id == msg_rep["parent_header"].get("session"):
181 # wrap SIGINT handler
182 real_handler = signal.getsignal(signal.SIGINT)
183 def double_int(sig,frame):
184 # call real handler (forwards sigint to kernel),
185 # then raise local interrupt, stopping local raw_input
186 real_handler(sig,frame)
187 raise KeyboardInterrupt
188 signal.signal(signal.SIGINT, double_int)
189
190 try:
191 raw_data = raw_input(msg_rep["content"]["prompt"])
192 except EOFError:
193 # turn EOFError into EOF character
194 raw_data = '\x04'
195 except KeyboardInterrupt:
196 sys.stdout.write('\n')
197 return
198 finally:
199 # restore SIGINT handler
200 signal.signal(signal.SIGINT, real_handler)
201
202 # only send stdin reply if there *was not* another request
203 # or execution finished while we were reading.
204 if not (self.km.stdin_channel.msg_ready() or self.km.shell_channel.msg_ready()):
205 self.km.stdin_channel.input(raw_data)
206
207 def mainloop(self, display_banner=False):
208 while True:
209 try:
210 self.interact(display_banner=display_banner)
211 #self.interact_with_readline()
212 # XXX for testing of a readline-decoupled repl loop, call
213 # interact_with_readline above
214 break
215 except KeyboardInterrupt:
216 # this should not be necessary, but KeyboardInterrupt
217 # handling seems rather unpredictable...
218 self.write("\nKeyboardInterrupt in interact()\n")
219
220 def wait_for_kernel(self, timeout=None):
221 """method to wait for a kernel to be ready"""
222 tic = time.time()
223 self.km.hb_channel.unpause()
224 while True:
225 self.run_cell('1', False)
226 if self.km.hb_channel.is_beating():
227 # heart failure was not the reason this returned
228 break
229 else:
230 # heart failed
231 if timeout is not None and (time.time() - tic) > timeout:
232 return False
233 return True
234
235 def interact(self, display_banner=None):
236 """Closely emulate the interactive Python console."""
237
238 # batch run -> do not interact
239 if self.exit_now:
240 return
241
242 if display_banner is None:
243 display_banner = self.display_banner
244
245 if isinstance(display_banner, basestring):
246 self.show_banner(display_banner)
247 elif display_banner:
248 self.show_banner()
249
250 more = False
251
252 # run a non-empty no-op, so that we don't get a prompt until
253 # we know the kernel is ready. This keeps the connection
254 # message above the first prompt.
255 if not self.wait_for_kernel(3):
256 error("Kernel did not respond\n")
257 return
258
259 if self.has_readline:
260 self.readline_startup_hook(self.pre_readline)
261 hlen_b4_cell = self.readline.get_current_history_length()
262 else:
263 hlen_b4_cell = 0
264 # exit_now is set by a call to %Exit or %Quit, through the
265 # ask_exit callback.
266
267 while not self.exit_now:
268 if not self.km.is_alive:
269 # kernel died, prompt for action or exit
270 action = "restart" if self.km.has_kernel else "wait for restart"
271 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
272 if ans:
273 if self.km.has_kernel:
274 self.km.restart_kernel(True)
275 self.wait_for_kernel(3)
276 else:
277 self.exit_now = True
278 continue
279 try:
280 # protect prompt block from KeyboardInterrupt
281 # when sitting on ctrl-C
282 self.hooks.pre_prompt_hook()
283 if more:
284 try:
285 prompt = self.prompt_manager.render('in2')
286 except Exception:
287 self.showtraceback()
288 if self.autoindent:
289 self.rl_do_indent = True
290
291 else:
292 try:
293 prompt = self.separate_in + self.prompt_manager.render('in')
294 except Exception:
295 self.showtraceback()
296
297 line = self.raw_input(prompt)
298 if self.exit_now:
299 # quick exit on sys.std[in|out] close
300 break
301 if self.autoindent:
302 self.rl_do_indent = False
303
304 except KeyboardInterrupt:
305 #double-guard against keyboardinterrupts during kbdint handling
306 try:
307 self.write('\nKeyboardInterrupt\n')
308 source_raw = self.input_splitter.source_raw_reset()[1]
309 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
310 more = False
311 except KeyboardInterrupt:
312 pass
313 except EOFError:
314 if self.autoindent:
315 self.rl_do_indent = False
316 if self.has_readline:
317 self.readline_startup_hook(None)
318 self.write('\n')
319 self.exit()
320 except bdb.BdbQuit:
321 warn('The Python debugger has exited with a BdbQuit exception.\n'
322 'Because of how pdb handles the stack, it is impossible\n'
323 'for IPython to properly format this particular exception.\n'
324 'IPython will resume normal operation.')
325 except:
326 # exceptions here are VERY RARE, but they can be triggered
327 # asynchronously by signal handlers, for example.
328 self.showtraceback()
329 else:
330 self.input_splitter.push(line)
331 more = self.input_splitter.push_accepts_more()
332 if (self.SyntaxTB.last_syntax_error and
333 self.autoedit_syntax):
334 self.edit_syntax_error()
335 if not more:
336 source_raw = self.input_splitter.source_reset()
337 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
338 self.run_cell(source_raw)
339
340
341 # Turn off the exit flag, so the mainloop can be restarted if desired
342 self.exit_now = False
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,59 b''
1 """Tests for two-process terminal frontend
2
3 Currenlty only has the most simple test possible, starting a console and running
4 a single command.
5
6 Authors:
7
8 * Min RK
9 """
10
11 #-----------------------------------------------------------------------------
12 # Imports
13 #-----------------------------------------------------------------------------
14
15 import time
16
17 import nose.tools as nt
18 from nose import SkipTest
19
20 from IPython.testing import decorators as dec
21 from IPython.testing import tools as tt
22 from IPython.utils import py3compat
23 from IPython.utils.process import find_cmd
24
25 #-----------------------------------------------------------------------------
26 # Test functions begin
27 #-----------------------------------------------------------------------------
28
29 @dec.skip_win32
30 def test_console_starts():
31 """test that `ipython console` starts a terminal"""
32 from IPython.external import pexpect
33
34 # weird IOErrors prevent this from firing sometimes:
35 ipython_cmd = None
36 for i in range(5):
37 try:
38 ipython_cmd = find_cmd('ipython3' if py3compat.PY3 else 'ipython')
39 except IOError:
40 time.sleep(0.1)
41 else:
42 break
43 if ipython_cmd is None:
44 raise SkipTest("Could not determine ipython command")
45
46 p = pexpect.spawn(ipython_cmd, args=['console', '--colors=NoColor'])
47 idx = p.expect([r'In \[\d+\]', pexpect.EOF], timeout=4)
48 nt.assert_equals(idx, 0, "expected in prompt")
49 p.sendline('5')
50 idx = p.expect([r'Out\[\d+\]: 5', pexpect.EOF], timeout=1)
51 nt.assert_equals(idx, 0, "expected out prompt")
52 idx = p.expect([r'In \[\d+\]', pexpect.EOF], timeout=1)
53 nt.assert_equals(idx, 0, "expected second in prompt")
54 # send ctrl-D;ctrl-D to exit
55 p.sendeof()
56 p.sendeof()
57 p.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=1)
58 if p.isalive():
59 p.terminate()
@@ -0,0 +1,63 b''
1 """Tests for kernel utility functions
2
3 Authors
4 -------
5 * MinRK
6 """
7 #-----------------------------------------------------------------------------
8 # Copyright (c) 2011, the IPython Development Team.
9 #
10 # Distributed under the terms of the Modified BSD License.
11 #
12 # The full license is in the file COPYING.txt, distributed with this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
19 # Stdlib imports
20 from unittest import TestCase
21
22 # Third-party imports
23 import nose.tools as nt
24
25 # Our own imports
26 from IPython.testing import decorators as dec
27 from IPython.lib import kernel
28
29 #-----------------------------------------------------------------------------
30 # Classes and functions
31 #-----------------------------------------------------------------------------
32
33 @dec.parametric
34 def test_swallow_argv():
35 tests = [
36 # expected , argv , aliases, flags
37 (['-a', '5'], ['-a', '5'], None, None),
38 (['5'], ['-a', '5'], None, ['a']),
39 ([], ['-a', '5'], ['a'], None),
40 ([], ['-a', '5'], ['a'], ['a']),
41 ([], ['--foo'], None, ['foo']),
42 (['--foo'], ['--foo'], ['foobar'], []),
43 ([], ['--foo', '5'], ['foo'], []),
44 ([], ['--foo=5'], ['foo'], []),
45 (['--foo=5'], ['--foo=5'], [], ['foo']),
46 (['5'], ['--foo', '5'], [], ['foo']),
47 (['bar'], ['--foo', '5', 'bar'], ['foo'], ['foo']),
48 (['bar'], ['--foo=5', 'bar'], ['foo'], ['foo']),
49 (['5','bar'], ['--foo', '5', 'bar'], None, ['foo']),
50 (['bar'], ['--foo', '5', 'bar'], ['foo'], None),
51 (['bar'], ['--foo=5', 'bar'], ['foo'], None),
52 ]
53 for expected, argv, aliases, flags in tests:
54 stripped = kernel.swallow_argv(argv, aliases=aliases, flags=flags)
55 message = '\n'.join(['',
56 "argv: %r" % argv,
57 "aliases: %r" % aliases,
58 "flags : %r" % flags,
59 "expected : %r" % expected,
60 "returned : %r" % stripped,
61 ])
62 yield nt.assert_equal(expected, stripped, message)
63
@@ -55,6 +55,7 b' from .notebookmanager import NotebookManager'
55 from IPython.config.application import catch_config_error
55 from IPython.config.application import catch_config_error
56 from IPython.core.application import BaseIPythonApplication
56 from IPython.core.application import BaseIPythonApplication
57 from IPython.core.profiledir import ProfileDir
57 from IPython.core.profiledir import ProfileDir
58 from IPython.lib.kernel import swallow_argv
58 from IPython.zmq.session import Session, default_secure
59 from IPython.zmq.session import Session, default_secure
59 from IPython.zmq.zmqshell import ZMQInteractiveShell
60 from IPython.zmq.zmqshell import ZMQInteractiveShell
60 from IPython.zmq.ipkernel import (
61 from IPython.zmq.ipkernel import (
@@ -283,27 +284,10 b' class NotebookApp(BaseIPythonApplication):'
283 if argv is None:
284 if argv is None:
284 argv = sys.argv[1:]
285 argv = sys.argv[1:]
285
286
286 self.kernel_argv = list(argv) # copy
287 # Scrub frontend-specific flags
288 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
287 # Kernel should inherit default config file from frontend
289 # Kernel should inherit default config file from frontend
288 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
290 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
289 # Scrub frontend-specific flags
290 for a in argv:
291 if a.startswith('-') and a.lstrip('-') in notebook_flags:
292 self.kernel_argv.remove(a)
293 swallow_next = False
294 for a in argv:
295 if swallow_next:
296 self.kernel_argv.remove(a)
297 swallow_next = False
298 continue
299 if a.startswith('-'):
300 split = a.lstrip('-').split('=')
301 alias = split[0]
302 if alias in notebook_aliases:
303 self.kernel_argv.remove(a)
304 if len(split) == 1:
305 # alias passed with arg via space
306 swallow_next = True
307
291
308 def init_configurables(self):
292 def init_configurables(self):
309 # Don't let Qt or ZMQ swallow KeyboardInterupts.
293 # Don't let Qt or ZMQ swallow KeyboardInterupts.
@@ -22,5 +22,6 b' class TestKernelManager(TestCase):'
22 self.assert_('shell_port' in port_dict)
22 self.assert_('shell_port' in port_dict)
23 self.assert_('hb_port' in port_dict)
23 self.assert_('hb_port' in port_dict)
24 km.get_kernel(kid)
24 km.get_kernel(kid)
25 km.kill_kernel(kid)
25
26
26
27
@@ -11,6 +11,7 b' Authors:'
11 * Fernando Perez
11 * Fernando Perez
12 * Bussonnier Matthias
12 * Bussonnier Matthias
13 * Thomas Kluyver
13 * Thomas Kluyver
14 * Paul Ivanov
14
15
15 """
16 """
16
17
@@ -26,7 +27,7 b' import sys'
26 import uuid
27 import uuid
27
28
28 # System library imports
29 # System library imports
29 from IPython.external.qt import QtGui
30 from IPython.external.qt import QtCore, QtGui
30
31
31 # Local imports
32 # Local imports
32 from IPython.config.application import boolean_flag, catch_config_error
33 from IPython.config.application import boolean_flag, catch_config_error
@@ -44,14 +45,14 b' from IPython.utils.py3compat import str_to_bytes'
44 from IPython.utils.traitlets import (
45 from IPython.utils.traitlets import (
45 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
46 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
46 )
47 )
47 from IPython.zmq.ipkernel import (
48 from IPython.zmq.ipkernel import IPKernelApp
48 flags as ipkernel_flags,
49 aliases as ipkernel_aliases,
50 IPKernelApp
51 )
52 from IPython.zmq.session import Session, default_secure
49 from IPython.zmq.session import Session, default_secure
53 from IPython.zmq.zmqshell import ZMQInteractiveShell
50 from IPython.zmq.zmqshell import ZMQInteractiveShell
54
51
52 from IPython.frontend.consoleapp import (
53 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
54 )
55
55 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
56 # Network Constants
57 # Network Constants
57 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
@@ -71,10 +72,9 b' ipython qtconsole --pylab=inline # start with pylab in inline plotting mode'
71 # Aliases and Flags
72 # Aliases and Flags
72 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
73
74
74 flags = dict(ipkernel_flags)
75 # start with copy of flags
76 flags = dict(flags)
75 qt_flags = {
77 qt_flags = {
76 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
77 "Connect to an existing kernel. If no argument specified, guess most recent"),
78 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
78 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
79 "Use a pure Python kernel instead of an IPython kernel."),
79 "Use a pure Python kernel instead of an IPython kernel."),
80 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
80 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
@@ -85,27 +85,14 b' qt_flags.update(boolean_flag('
85 "use a GUI widget for tab completion",
85 "use a GUI widget for tab completion",
86 "use plaintext output for completion"
86 "use plaintext output for completion"
87 ))
87 ))
88 qt_flags.update(boolean_flag(
88 # and app_flags from the Console Mixin
89 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
89 qt_flags.update(app_flags)
90 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
90 # add frontend flags to the full set
91 to force a direct exit without any confirmation.
92 """,
93 """Don't prompt the user when exiting. This will terminate the kernel
94 if it is owned by the frontend, and leave it alive if it is external.
95 """
96 ))
97 flags.update(qt_flags)
91 flags.update(qt_flags)
98
92
99 aliases = dict(ipkernel_aliases)
93 # start with copy of front&backend aliases list
100
94 aliases = dict(aliases)
101 qt_aliases = dict(
95 qt_aliases = dict(
102 hb = 'IPythonQtConsoleApp.hb_port',
103 shell = 'IPythonQtConsoleApp.shell_port',
104 iopub = 'IPythonQtConsoleApp.iopub_port',
105 stdin = 'IPythonQtConsoleApp.stdin_port',
106 ip = 'IPythonQtConsoleApp.ip',
107 existing = 'IPythonQtConsoleApp.existing',
108 f = 'IPythonQtConsoleApp.connection_file',
109
96
110 style = 'IPythonWidget.syntax_style',
97 style = 'IPythonWidget.syntax_style',
111 stylesheet = 'IPythonQtConsoleApp.stylesheet',
98 stylesheet = 'IPythonQtConsoleApp.stylesheet',
@@ -113,10 +100,18 b' qt_aliases = dict('
113
100
114 editor = 'IPythonWidget.editor',
101 editor = 'IPythonWidget.editor',
115 paging = 'ConsoleWidget.paging',
102 paging = 'ConsoleWidget.paging',
116 ssh = 'IPythonQtConsoleApp.sshserver',
117 )
103 )
104 # and app_aliases from the Console Mixin
105 qt_aliases.update(app_aliases)
106 # add frontend aliases to the full set
118 aliases.update(qt_aliases)
107 aliases.update(qt_aliases)
119
108
109 # get flags&aliases into sets, and remove a couple that
110 # shouldn't be scrubbed from backend flags:
111 qt_aliases = set(qt_aliases.keys())
112 qt_aliases.remove('colors')
113 qt_flags = set(qt_flags.keys())
114
120 #-----------------------------------------------------------------------------
115 #-----------------------------------------------------------------------------
121 # Classes
116 # Classes
122 #-----------------------------------------------------------------------------
117 #-----------------------------------------------------------------------------
@@ -126,9 +121,8 b' aliases.update(qt_aliases)'
126 #-----------------------------------------------------------------------------
121 #-----------------------------------------------------------------------------
127
122
128
123
129 class IPythonQtConsoleApp(BaseIPythonApplication):
124 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
130 name = 'ipython-qtconsole'
125 name = 'ipython-qtconsole'
131 default_config_file_name='ipython_config.py'
132
126
133 description = """
127 description = """
134 The IPython QtConsole.
128 The IPython QtConsole.
@@ -150,50 +144,13 b' class IPythonQtConsoleApp(BaseIPythonApplication):'
150 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
144 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
151 flags = Dict(flags)
145 flags = Dict(flags)
152 aliases = Dict(aliases)
146 aliases = Dict(aliases)
153
147 frontend_flags = Any(qt_flags)
154 kernel_argv = List(Unicode)
148 frontend_aliases = Any(qt_aliases)
155
149 kernel_manager_class = QtKernelManager
156 # create requested profiles by default, if they don't exist:
157 auto_create = CBool(True)
158 # connection info:
159 ip = Unicode(LOCALHOST, config=True,
160 help="""Set the kernel\'s IP address [default localhost].
161 If the IP address is something other than localhost, then
162 Consoles on other machines will be able to connect
163 to the Kernel, so be careful!"""
164 )
165
166 sshserver = Unicode('', config=True,
167 help="""The SSH server to use to connect to the kernel.""")
168 sshkey = Unicode('', config=True,
169 help="""Path to the ssh key to use for logging in to the ssh server.""")
170
171 hb_port = Integer(0, config=True,
172 help="set the heartbeat port [default: random]")
173 shell_port = Integer(0, config=True,
174 help="set the shell (XREP) port [default: random]")
175 iopub_port = Integer(0, config=True,
176 help="set the iopub (PUB) port [default: random]")
177 stdin_port = Integer(0, config=True,
178 help="set the stdin (XREQ) port [default: random]")
179 connection_file = Unicode('', config=True,
180 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
181
182 This file will contain the IP, ports, and authentication key needed to connect
183 clients to this kernel. By default, this file will be created in the security-dir
184 of the current profile, but can be specified by absolute path.
185 """)
186 def _connection_file_default(self):
187 return 'kernel-%i.json' % os.getpid()
188
189 existing = Unicode('', config=True,
190 help="""Connect to an already running kernel""")
191
150
192 stylesheet = Unicode('', config=True,
151 stylesheet = Unicode('', config=True,
193 help="path to a custom CSS stylesheet")
152 help="path to a custom CSS stylesheet")
194
153
195 pure = CBool(False, config=True,
196 help="Use a pure Python kernel instead of an IPython kernel.")
197 plain = CBool(False, config=True,
154 plain = CBool(False, config=True,
198 help="Use a plaintext widget instead of rich text (plain can't print/save).")
155 help="Use a plaintext widget instead of rich text (plain can't print/save).")
199
156
@@ -209,178 +166,13 b' class IPythonQtConsoleApp(BaseIPythonApplication):'
209
166
210 _plain_changed = _pure_changed
167 _plain_changed = _pure_changed
211
168
212 confirm_exit = CBool(True, config=True,
213 help="""
214 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
215 to force a direct exit without any confirmation.""",
216 )
217
218 # the factory for creating a widget
169 # the factory for creating a widget
219 widget_factory = Any(RichIPythonWidget)
170 widget_factory = Any(RichIPythonWidget)
220
171
221 def parse_command_line(self, argv=None):
172 def parse_command_line(self, argv=None):
222 super(IPythonQtConsoleApp, self).parse_command_line(argv)
173 super(IPythonQtConsoleApp, self).parse_command_line(argv)
223 if argv is None:
174 self.build_kernel_argv(argv)
224 argv = sys.argv[1:]
175
225 self.kernel_argv = list(argv) # copy
226 # kernel should inherit default config file from frontend
227 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
228 # Scrub frontend-specific flags
229 swallow_next = False
230 was_flag = False
231 # copy again, in case some aliases have the same name as a flag
232 # argv = list(self.kernel_argv)
233 for a in argv:
234 if swallow_next:
235 swallow_next = False
236 # last arg was an alias, remove the next one
237 # *unless* the last alias has a no-arg flag version, in which
238 # case, don't swallow the next arg if it's also a flag:
239 if not (was_flag and a.startswith('-')):
240 self.kernel_argv.remove(a)
241 continue
242 if a.startswith('-'):
243 split = a.lstrip('-').split('=')
244 alias = split[0]
245 if alias in qt_aliases:
246 self.kernel_argv.remove(a)
247 if len(split) == 1:
248 # alias passed with arg via space
249 swallow_next = True
250 # could have been a flag that matches an alias, e.g. `existing`
251 # in which case, we might not swallow the next arg
252 was_flag = alias in qt_flags
253 elif alias in qt_flags:
254 # strip flag, but don't swallow next, as flags don't take args
255 self.kernel_argv.remove(a)
256
257 def init_connection_file(self):
258 """find the connection file, and load the info if found.
259
260 The current working directory and the current profile's security
261 directory will be searched for the file if it is not given by
262 absolute path.
263
264 When attempting to connect to an existing kernel and the `--existing`
265 argument does not match an existing file, it will be interpreted as a
266 fileglob, and the matching file in the current profile's security dir
267 with the latest access time will be used.
268 """
269 if self.existing:
270 try:
271 cf = find_connection_file(self.existing)
272 except Exception:
273 self.log.critical("Could not find existing kernel connection file %s", self.existing)
274 self.exit(1)
275 self.log.info("Connecting to existing kernel: %s" % cf)
276 self.connection_file = cf
277 # should load_connection_file only be used for existing?
278 # as it is now, this allows reusing ports if an existing
279 # file is requested
280 try:
281 self.load_connection_file()
282 except Exception:
283 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
284 self.exit(1)
285
286 def load_connection_file(self):
287 """load ip/port/hmac config from JSON connection file"""
288 # this is identical to KernelApp.load_connection_file
289 # perhaps it can be centralized somewhere?
290 try:
291 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
292 except IOError:
293 self.log.debug("Connection File not found: %s", self.connection_file)
294 return
295 self.log.debug(u"Loading connection file %s", fname)
296 with open(fname) as f:
297 s = f.read()
298 cfg = json.loads(s)
299 if self.ip == LOCALHOST and 'ip' in cfg:
300 # not overridden by config or cl_args
301 self.ip = cfg['ip']
302 for channel in ('hb', 'shell', 'iopub', 'stdin'):
303 name = channel + '_port'
304 if getattr(self, name) == 0 and name in cfg:
305 # not overridden by config or cl_args
306 setattr(self, name, cfg[name])
307 if 'key' in cfg:
308 self.config.Session.key = str_to_bytes(cfg['key'])
309
310 def init_ssh(self):
311 """set up ssh tunnels, if needed."""
312 if not self.sshserver and not self.sshkey:
313 return
314
315 if self.sshkey and not self.sshserver:
316 # specifying just the key implies that we are connecting directly
317 self.sshserver = self.ip
318 self.ip = LOCALHOST
319
320 # build connection dict for tunnels:
321 info = dict(ip=self.ip,
322 shell_port=self.shell_port,
323 iopub_port=self.iopub_port,
324 stdin_port=self.stdin_port,
325 hb_port=self.hb_port
326 )
327
328 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
329
330 # tunnels return a new set of ports, which will be on localhost:
331 self.ip = LOCALHOST
332 try:
333 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
334 except:
335 # even catch KeyboardInterrupt
336 self.log.error("Could not setup tunnels", exc_info=True)
337 self.exit(1)
338
339 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
340
341 cf = self.connection_file
342 base,ext = os.path.splitext(cf)
343 base = os.path.basename(base)
344 self.connection_file = os.path.basename(base)+'-ssh'+ext
345 self.log.critical("To connect another client via this tunnel, use:")
346 self.log.critical("--existing %s" % self.connection_file)
347
348 def _new_connection_file(self):
349 return os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % uuid.uuid4())
350
351 def init_kernel_manager(self):
352 # Don't let Qt or ZMQ swallow KeyboardInterupts.
353 signal.signal(signal.SIGINT, signal.SIG_DFL)
354 sec = self.profile_dir.security_dir
355 try:
356 cf = filefind(self.connection_file, ['.', sec])
357 except IOError:
358 # file might not exist
359 if self.connection_file == os.path.basename(self.connection_file):
360 # just shortname, put it in security dir
361 cf = os.path.join(sec, self.connection_file)
362 else:
363 cf = self.connection_file
364
365 # Create a KernelManager and start a kernel.
366 self.kernel_manager = QtKernelManager(
367 ip=self.ip,
368 shell_port=self.shell_port,
369 iopub_port=self.iopub_port,
370 stdin_port=self.stdin_port,
371 hb_port=self.hb_port,
372 connection_file=cf,
373 config=self.config,
374 )
375 # start the kernel
376 if not self.existing:
377 kwargs = dict(ipython=not self.pure)
378 kwargs['extra_arguments'] = self.kernel_argv
379 self.kernel_manager.start_kernel(**kwargs)
380 elif self.sshserver:
381 # ssh, write new connection file
382 self.kernel_manager.write_connection_file()
383 self.kernel_manager.start_channels()
384
176
385 def new_frontend_master(self):
177 def new_frontend_master(self):
386 """ Create and return new frontend attached to new kernel, launched on localhost.
178 """ Create and return new frontend attached to new kernel, launched on localhost.
@@ -517,15 +309,26 b' class IPythonQtConsoleApp(BaseIPythonApplication):'
517 else:
309 else:
518 raise IOError("Stylesheet %r not found."%self.stylesheet)
310 raise IOError("Stylesheet %r not found."%self.stylesheet)
519
311
312 def init_signal(self):
313 """allow clean shutdown on sigint"""
314 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
315 # need a timer, so that QApplication doesn't block until a real
316 # Qt event fires (can require mouse movement)
317 # timer trick from http://stackoverflow.com/q/4938723/938949
318 timer = QtCore.QTimer()
319 # Let the interpreter run each 200 ms:
320 timer.timeout.connect(lambda: None)
321 timer.start(200)
322 # hold onto ref, so the timer doesn't get cleaned up
323 self._sigint_timer = timer
324
520 @catch_config_error
325 @catch_config_error
521 def initialize(self, argv=None):
326 def initialize(self, argv=None):
522 super(IPythonQtConsoleApp, self).initialize(argv)
327 super(IPythonQtConsoleApp, self).initialize(argv)
523 self.init_connection_file()
328 IPythonConsoleApp.initialize(self,argv)
524 default_secure(self.config)
525 self.init_ssh()
526 self.init_kernel_manager()
527 self.init_qt_elements()
329 self.init_qt_elements()
528 self.init_colors()
330 self.init_colors()
331 self.init_signal()
529
332
530 def start(self):
333 def start(self):
531
334
@@ -69,6 +69,9 b' ipython --profile=foo # start with profile foo'
69 ipython qtconsole # start the qtconsole GUI application
69 ipython qtconsole # start the qtconsole GUI application
70 ipython qtconsole -h # show the help string for the qtconsole subcmd
70 ipython qtconsole -h # show the help string for the qtconsole subcmd
71
71
72 ipython console # start the terminal-based console application
73 ipython console -h # show the help string for the console subcmd
74
72 ipython profile create foo # create profile foo w/ default config files
75 ipython profile create foo # create profile foo w/ default config files
73 ipython profile -h # show the help string for the profile subcmd
76 ipython profile -h # show the help string for the profile subcmd
74 """
77 """
@@ -112,7 +115,8 b' class IPAppCrashHandler(CrashHandler):'
112 #-----------------------------------------------------------------------------
115 #-----------------------------------------------------------------------------
113 flags = dict(base_flags)
116 flags = dict(base_flags)
114 flags.update(shell_flags)
117 flags.update(shell_flags)
115 addflag = lambda *args: flags.update(boolean_flag(*args))
118 frontend_flags = {}
119 addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
116 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
120 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
117 'Turn on auto editing of files with syntax errors.',
121 'Turn on auto editing of files with syntax errors.',
118 'Turn off auto editing of files with syntax errors.'
122 'Turn off auto editing of files with syntax errors.'
@@ -143,7 +147,7 b" classic_config.InteractiveShell.separate_out2 = ''"
143 classic_config.InteractiveShell.colors = 'NoColor'
147 classic_config.InteractiveShell.colors = 'NoColor'
144 classic_config.InteractiveShell.xmode = 'Plain'
148 classic_config.InteractiveShell.xmode = 'Plain'
145
149
146 flags['classic']=(
150 frontend_flags['classic']=(
147 classic_config,
151 classic_config,
148 "Gives IPython a similar feel to the classic Python prompt."
152 "Gives IPython a similar feel to the classic Python prompt."
149 )
153 )
@@ -153,21 +157,22 b" flags['classic']=("
153 # help="Start logging to the default log file (./ipython_log.py).")
157 # help="Start logging to the default log file (./ipython_log.py).")
154 #
158 #
155 # # quick is harder to implement
159 # # quick is harder to implement
156 flags['quick']=(
160 frontend_flags['quick']=(
157 {'TerminalIPythonApp' : {'quick' : True}},
161 {'TerminalIPythonApp' : {'quick' : True}},
158 "Enable quick startup with no config files."
162 "Enable quick startup with no config files."
159 )
163 )
160
164
161 flags['i'] = (
165 frontend_flags['i'] = (
162 {'TerminalIPythonApp' : {'force_interact' : True}},
166 {'TerminalIPythonApp' : {'force_interact' : True}},
163 """If running code from the command line, become interactive afterwards.
167 """If running code from the command line, become interactive afterwards.
164 Note: can also be given simply as '-i.'"""
168 Note: can also be given simply as '-i.'"""
165 )
169 )
166 flags['pylab'] = (
170 frontend_flags['pylab'] = (
167 {'TerminalIPythonApp' : {'pylab' : 'auto'}},
171 {'TerminalIPythonApp' : {'pylab' : 'auto'}},
168 """Pre-load matplotlib and numpy for interactive use with
172 """Pre-load matplotlib and numpy for interactive use with
169 the default matplotlib backend."""
173 the default matplotlib backend."""
170 )
174 )
175 flags.update(frontend_flags)
171
176
172 aliases = dict(base_aliases)
177 aliases = dict(base_aliases)
173 aliases.update(shell_aliases)
178 aliases.update(shell_aliases)
@@ -209,7 +214,7 b' class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):'
209 """Launch the IPython Qt Console."""
214 """Launch the IPython Qt Console."""
210 ),
215 ),
211 notebook=('IPython.frontend.html.notebook.notebookapp.NotebookApp',
216 notebook=('IPython.frontend.html.notebook.notebookapp.NotebookApp',
212 """Launch the IPython HTML Notebook Server"""
217 """Launch the IPython HTML Notebook Server."""
213 ),
218 ),
214 profile = ("IPython.core.profileapp.ProfileApp",
219 profile = ("IPython.core.profileapp.ProfileApp",
215 "Create and manage IPython profiles."
220 "Create and manage IPython profiles."
@@ -217,6 +222,9 b' class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):'
217 kernel = ("IPython.zmq.ipkernel.IPKernelApp",
222 kernel = ("IPython.zmq.ipkernel.IPKernelApp",
218 "Start a kernel without an attached frontend."
223 "Start a kernel without an attached frontend."
219 ),
224 ),
225 console=('IPython.frontend.terminal.console.app.ZMQTerminalIPythonApp',
226 """Launch the IPython terminal-based Console."""
227 ),
220 ))
228 ))
221
229
222 # *do* autocreate requested profile, but don't create the config file.
230 # *do* autocreate requested profile, but don't create the config file.
@@ -253,3 +253,63 b' def tunnel_to_kernel(connection_info, sshserver, sshkey=None):'
253 return tuple(lports)
253 return tuple(lports)
254
254
255
255
256 def swallow_argv(argv, aliases=None, flags=None):
257 """strip frontend-specific aliases and flags from an argument list
258
259 For use primarily in frontend apps that want to pass a subset of command-line
260 arguments through to a subprocess, where frontend-specific flags and aliases
261 should be removed from the list.
262
263 Parameters
264 ----------
265
266 argv : list(str)
267 The starting argv, to be filtered
268 aliases : container of aliases (dict, list, set, etc.)
269 The frontend-specific aliases to be removed
270 flags : container of flags (dict, list, set, etc.)
271 The frontend-specific flags to be removed
272
273 Returns
274 -------
275
276 argv : list(str)
277 The argv list, excluding flags and aliases that have been stripped
278 """
279
280 if aliases is None:
281 aliases = set()
282 if flags is None:
283 flags = set()
284
285 stripped = list(argv) # copy
286
287 swallow_next = False
288 was_flag = False
289 for a in argv:
290 if swallow_next:
291 swallow_next = False
292 # last arg was an alias, remove the next one
293 # *unless* the last alias has a no-arg flag version, in which
294 # case, don't swallow the next arg if it's also a flag:
295 if not (was_flag and a.startswith('-')):
296 stripped.remove(a)
297 continue
298 if a.startswith('-'):
299 split = a.lstrip('-').split('=')
300 alias = split[0]
301 if alias in aliases:
302 stripped.remove(a)
303 if len(split) == 1:
304 # alias passed with arg via space
305 swallow_next = True
306 # could have been a flag that matches an alias, e.g. `existing`
307 # in which case, we might not swallow the next arg
308 was_flag = alias in flags
309 elif alias in flags and len(split) == 1:
310 # strip flag, but don't swallow next, as flags don't take args
311 stripped.remove(a)
312
313 # return shortened list
314 return stripped
315
@@ -12,6 +12,7 b''
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 import sys
15 import unittest
16 import unittest
16 from compiler.consts import CO_GENERATOR
17 from compiler.consts import CO_GENERATOR
17
18
@@ -45,7 +46,7 b' class ParametricTestCase(unittest.TestCase):'
45 except KeyboardInterrupt:
46 except KeyboardInterrupt:
46 raise
47 raise
47 except:
48 except:
48 result.addError(self, self._exc_info())
49 result.addError(self, sys.exc_info())
49 return
50 return
50 # Test execution
51 # Test execution
51 ok = False
52 ok = False
@@ -56,18 +57,18 b' class ParametricTestCase(unittest.TestCase):'
56 # We stop the loop
57 # We stop the loop
57 break
58 break
58 except self.failureException:
59 except self.failureException:
59 result.addFailure(self, self._exc_info())
60 result.addFailure(self, sys.exc_info())
60 except KeyboardInterrupt:
61 except KeyboardInterrupt:
61 raise
62 raise
62 except:
63 except:
63 result.addError(self, self._exc_info())
64 result.addError(self, sys.exc_info())
64 # TearDown
65 # TearDown
65 try:
66 try:
66 self.tearDown()
67 self.tearDown()
67 except KeyboardInterrupt:
68 except KeyboardInterrupt:
68 raise
69 raise
69 except:
70 except:
70 result.addError(self, self._exc_info())
71 result.addError(self, sys.exc_info())
71 ok = False
72 ok = False
72 if ok: result.addSuccess(self)
73 if ok: result.addSuccess(self)
73
74
@@ -16,6 +16,7 b' from __future__ import print_function'
16
16
17 # Stdlib
17 # Stdlib
18 from Queue import Queue, Empty
18 from Queue import Queue, Empty
19 from threading import Event
19
20
20 # Our own
21 # Our own
21 from IPython.utils import io
22 from IPython.utils import io
@@ -104,18 +105,42 b' class BlockingShellSocketChannel(ShellSocketChannel):'
104
105
105 class BlockingStdInSocketChannel(StdInSocketChannel):
106 class BlockingStdInSocketChannel(StdInSocketChannel):
106
107
108 def __init__(self, context, session, address=None):
109 super(BlockingStdInSocketChannel, self).__init__(context, session, address)
110 self._in_queue = Queue()
111
107 def call_handlers(self, msg):
112 def call_handlers(self, msg):
108 #io.rprint('[[Rep]]', msg) # dbg
113 #io.rprint('[[Rep]]', msg) # dbg
109 pass
114 self._in_queue.put(msg)
115
116 def get_msg(self, block=True, timeout=None):
117 "Gets a message if there is one that is ready."
118 return self._in_queue.get(block, timeout)
119
120 def get_msgs(self):
121 """Get all messages that are currently ready."""
122 msgs = []
123 while True:
124 try:
125 msgs.append(self.get_msg(block=False))
126 except Empty:
127 break
128 return msgs
129
130 def msg_ready(self):
131 "Is there a message that has been received?"
132 return not self._in_queue.empty()
110
133
111
134
112 class BlockingHBSocketChannel(HBSocketChannel):
135 class BlockingHBSocketChannel(HBSocketChannel):
113
136
114 # This kernel needs rapid monitoring capabilities
137 # This kernel needs quicker monitoring, shorten to 1 sec.
115 time_to_dead = 0.2
138 # less than 0.5s is unreliable, and will get occasional
139 # false reports of missed beats.
140 time_to_dead = 1.
116
141
117 def call_handlers(self, since_last_heartbeat):
142 def call_handlers(self, since_last_heartbeat):
118 #io.rprint('[[Heart]]', since_last_heartbeat) # dbg
143 """pause beating on missed heartbeat"""
119 pass
144 pass
120
145
121
146
@@ -22,7 +22,9 b' import sys'
22 import time
22 import time
23 import traceback
23 import traceback
24 import logging
24 import logging
25
25 from signal import (
26 signal, default_int_handler, SIGINT, SIG_IGN
27 )
26 # System library imports.
28 # System library imports.
27 import zmq
29 import zmq
28
30
@@ -168,6 +170,9 b' class Kernel(Configurable):'
168 def start(self):
170 def start(self):
169 """ Start the kernel main loop.
171 """ Start the kernel main loop.
170 """
172 """
173 # a KeyboardInterrupt (SIGINT) can occur on any python statement, so
174 # let's ignore (SIG_IGN) them until we're in a place to handle them properly
175 signal(SIGINT,SIG_IGN)
171 poller = zmq.Poller()
176 poller = zmq.Poller()
172 poller.register(self.shell_socket, zmq.POLLIN)
177 poller.register(self.shell_socket, zmq.POLLIN)
173 # loop while self.eventloop has not been overridden
178 # loop while self.eventloop has not been overridden
@@ -182,12 +187,20 b' class Kernel(Configurable):'
182 # due to pyzmq Issue #130
187 # due to pyzmq Issue #130
183 try:
188 try:
184 poller.poll(10*1000*self._poll_interval)
189 poller.poll(10*1000*self._poll_interval)
190 # restore raising of KeyboardInterrupt
191 signal(SIGINT, default_int_handler)
185 self.do_one_iteration()
192 self.do_one_iteration()
186 except:
193 except:
187 raise
194 raise
195 finally:
196 # prevent raising of KeyboardInterrupt
197 signal(SIGINT,SIG_IGN)
188 except KeyboardInterrupt:
198 except KeyboardInterrupt:
189 # Ctrl-C shouldn't crash the kernel
199 # Ctrl-C shouldn't crash the kernel
190 io.raw_print("KeyboardInterrupt caught in kernel")
200 io.raw_print("KeyboardInterrupt caught in kernel")
201 # stop ignoring sigint, now that we are out of our own loop,
202 # we don't want to prevent future code from handling it
203 signal(SIGINT, default_int_handler)
191 if self.eventloop is not None:
204 if self.eventloop is not None:
192 try:
205 try:
193 self.eventloop(self)
206 self.eventloop(self)
@@ -456,6 +469,9 b' class Kernel(Configurable):'
456 self.log.error("Got bad raw_input reply: ")
469 self.log.error("Got bad raw_input reply: ")
457 self.log.error(str(Message(parent)))
470 self.log.error(str(Message(parent)))
458 value = ''
471 value = ''
472 if value == '\x04':
473 # EOF
474 raise EOFError
459 return value
475 return value
460
476
461 def _complete(self, msg):
477 def _complete(self, msg):
@@ -471,83 +471,89 b' class HBSocketChannel(ZMQSocketChannel):'
471 poller = None
471 poller = None
472 _running = None
472 _running = None
473 _pause = None
473 _pause = None
474 _beating = None
474
475
475 def __init__(self, context, session, address):
476 def __init__(self, context, session, address):
476 super(HBSocketChannel, self).__init__(context, session, address)
477 super(HBSocketChannel, self).__init__(context, session, address)
477 self._running = False
478 self._running = False
478 self._pause = True
479 self._pause =True
480 self.poller = zmq.Poller()
479
481
480 def _create_socket(self):
482 def _create_socket(self):
483 if self.socket is not None:
484 # close previous socket, before opening a new one
485 self.poller.unregister(self.socket)
486 self.socket.close()
481 self.socket = self.context.socket(zmq.REQ)
487 self.socket = self.context.socket(zmq.REQ)
482 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
488 self.socket.setsockopt(zmq.LINGER, 0)
483 self.socket.connect('tcp://%s:%i' % self.address)
489 self.socket.connect('tcp://%s:%i' % self.address)
484 self.poller = zmq.Poller()
490
485 self.poller.register(self.socket, zmq.POLLIN)
491 self.poller.register(self.socket, zmq.POLLIN)
492
493 def _poll(self, start_time):
494 """poll for heartbeat replies until we reach self.time_to_dead
495
496 Ignores interrupts, and returns the result of poll(), which
497 will be an empty list if no messages arrived before the timeout,
498 or the event tuple if there is a message to receive.
499 """
500
501 until_dead = self.time_to_dead - (time.time() - start_time)
502 # ensure poll at least once
503 until_dead = max(until_dead, 1e-3)
504 events = []
505 while True:
506 try:
507 events = self.poller.poll(1000 * until_dead)
508 except zmq.ZMQError as e:
509 if e.errno == errno.EINTR:
510 # ignore interrupts during heartbeat
511 # this may never actually happen
512 until_dead = self.time_to_dead - (time.time() - start_time)
513 until_dead = max(until_dead, 1e-3)
514 pass
515 else:
516 raise
517 else:
518 break
519 return events
486
520
487 def run(self):
521 def run(self):
488 """The thread's main activity. Call start() instead."""
522 """The thread's main activity. Call start() instead."""
489 self._create_socket()
523 self._create_socket()
490 self._running = True
524 self._running = True
525 self._beating = True
526
491 while self._running:
527 while self._running:
492 if self._pause:
528 if self._pause:
529 # just sleep, and skip the rest of the loop
493 time.sleep(self.time_to_dead)
530 time.sleep(self.time_to_dead)
531 continue
532
533 since_last_heartbeat = 0.0
534 # io.rprint('Ping from HB channel') # dbg
535 # no need to catch EFSM here, because the previous event was
536 # either a recv or connect, which cannot be followed by EFSM
537 self.socket.send(b'ping')
538 request_time = time.time()
539 ready = self._poll(request_time)
540 if ready:
541 self._beating = True
542 # the poll above guarantees we have something to recv
543 self.socket.recv()
544 # sleep the remainder of the cycle
545 remainder = self.time_to_dead - (time.time() - request_time)
546 if remainder > 0:
547 time.sleep(remainder)
548 continue
494 else:
549 else:
495 since_last_heartbeat = 0.0
550 # nothing was received within the time limit, signal heart failure
496 request_time = time.time()
551 self._beating = False
497 try:
552 since_last_heartbeat = time.time() - request_time
498 #io.rprint('Ping from HB channel') # dbg
553 self.call_handlers(since_last_heartbeat)
499 self.socket.send(b'ping')
554 # and close/reopen the socket, because the REQ/REP cycle has been broken
500 except zmq.ZMQError, e:
555 self._create_socket()
501 #io.rprint('*** HB Error:', e) # dbg
556 continue
502 if e.errno == zmq.EFSM:
503 #io.rprint('sleep...', self.time_to_dead) # dbg
504 time.sleep(self.time_to_dead)
505 self._create_socket()
506 else:
507 raise
508 else:
509 while True:
510 try:
511 self.socket.recv(zmq.NOBLOCK)
512 except zmq.ZMQError, e:
513 #io.rprint('*** HB Error 2:', e) # dbg
514 if e.errno == zmq.EAGAIN:
515 before_poll = time.time()
516 until_dead = self.time_to_dead - (before_poll -
517 request_time)
518
519 # When the return value of poll() is an empty
520 # list, that is when things have gone wrong
521 # (zeromq bug). As long as it is not an empty
522 # list, poll is working correctly even if it
523 # returns quickly. Note: poll timeout is in
524 # milliseconds.
525 if until_dead > 0.0:
526 while True:
527 try:
528 self.poller.poll(1000 * until_dead)
529 except zmq.ZMQError as e:
530 if e.errno == errno.EINTR:
531 continue
532 else:
533 raise
534 else:
535 break
536
537 since_last_heartbeat = time.time()-request_time
538 if since_last_heartbeat > self.time_to_dead:
539 self.call_handlers(since_last_heartbeat)
540 break
541 else:
542 # FIXME: We should probably log this instead.
543 raise
544 else:
545 until_dead = self.time_to_dead - (time.time() -
546 request_time)
547 if until_dead > 0.0:
548 #io.rprint('sleep...', self.time_to_dead) # dbg
549 time.sleep(until_dead)
550 break
551
557
552 def pause(self):
558 def pause(self):
553 """Pause the heartbeat."""
559 """Pause the heartbeat."""
@@ -558,8 +564,8 b' class HBSocketChannel(ZMQSocketChannel):'
558 self._pause = False
564 self._pause = False
559
565
560 def is_beating(self):
566 def is_beating(self):
561 """Is the heartbeat running and not paused."""
567 """Is the heartbeat running and responsive (and not paused)."""
562 if self.is_alive() and not self._pause:
568 if self.is_alive() and not self._pause and self._beating:
563 return True
569 return True
564 else:
570 else:
565 return False
571 return False
@@ -573,7 +579,7 b' class HBSocketChannel(ZMQSocketChannel):'
573
579
574 Subclasses should override this method to handle incoming messages.
580 Subclasses should override this method to handle incoming messages.
575 It is important to remember that this method is called in the thread
581 It is important to remember that this method is called in the thread
576 so that some logic must be done to ensure that the application leve
582 so that some logic must be done to ensure that the application level
577 handlers are called in the application thread.
583 handlers are called in the application thread.
578 """
584 """
579 raise NotImplementedError('call_handlers must be defined in a subclass.')
585 raise NotImplementedError('call_handlers must be defined in a subclass.')
@@ -639,14 +645,8 b' class KernelManager(HasTraits):'
639 self.session = Session(config=self.config)
645 self.session = Session(config=self.config)
640
646
641 def __del__(self):
647 def __del__(self):
642 if self._connection_file_written:
648 self.cleanup_connection_file()
643 # cleanup connection files on full shutdown of kernel we started
649
644 self._connection_file_written = False
645 try:
646 os.remove(self.connection_file)
647 except IOError:
648 pass
649
650
650
651 #--------------------------------------------------------------------------
651 #--------------------------------------------------------------------------
652 # Channel management methods:
652 # Channel management methods:
@@ -694,6 +694,19 b' class KernelManager(HasTraits):'
694 # Kernel process management methods:
694 # Kernel process management methods:
695 #--------------------------------------------------------------------------
695 #--------------------------------------------------------------------------
696
696
697 def cleanup_connection_file(self):
698 """cleanup connection file *if we wrote it*
699
700 Will not raise if the connection file was already removed somehow.
701 """
702 if self._connection_file_written:
703 # cleanup connection files on full shutdown of kernel we started
704 self._connection_file_written = False
705 try:
706 os.remove(self.connection_file)
707 except OSError:
708 pass
709
697 def load_connection_file(self):
710 def load_connection_file(self):
698 """load connection info from JSON dict in self.connection_file"""
711 """load connection info from JSON dict in self.connection_file"""
699 with open(self.connection_file) as f:
712 with open(self.connection_file) as f:
@@ -893,16 +906,18 b' class KernelManager(HasTraits):'
893 @property
906 @property
894 def is_alive(self):
907 def is_alive(self):
895 """Is the kernel process still running?"""
908 """Is the kernel process still running?"""
896 # FIXME: not using a heartbeat means this method is broken for any
897 # remote kernel, it's only capable of handling local kernels.
898 if self.has_kernel:
909 if self.has_kernel:
899 if self.kernel.poll() is None:
910 if self.kernel.poll() is None:
900 return True
911 return True
901 else:
912 else:
902 return False
913 return False
914 elif self._hb_channel is not None:
915 # We didn't start the kernel with this KernelManager so we
916 # use the heartbeat.
917 return self._hb_channel.is_beating()
903 else:
918 else:
904 # We didn't start the kernel with this KernelManager so we don't
919 # no heartbeat and not local, we can't tell if it's running,
905 # know if it is running. We should use a heartbeat for this case.
920 # so naively return True
906 return True
921 return True
907
922
908 #--------------------------------------------------------------------------
923 #--------------------------------------------------------------------------
@@ -44,12 +44,6 b' from IPython.zmq.displayhook import ZMQShellDisplayHook, _encode_binary'
44 from IPython.zmq.session import extract_header
44 from IPython.zmq.session import extract_header
45 from session import Session
45 from session import Session
46
46
47 #-----------------------------------------------------------------------------
48 # Globals and side-effects
49 #-----------------------------------------------------------------------------
50
51 # Install the payload version of page.
52 install_payload_page()
53
47
54 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
55 # Functions and classes
49 # Functions and classes
@@ -126,6 +120,9 b' class ZMQInteractiveShell(InteractiveShell):'
126 # subprocesses as much as possible.
120 # subprocesses as much as possible.
127 env['PAGER'] = 'cat'
121 env['PAGER'] = 'cat'
128 env['GIT_PAGER'] = 'cat'
122 env['GIT_PAGER'] = 'cat'
123
124 # And install the payload version of page.
125 install_payload_page()
129
126
130 def auto_rewrite_input(self, cmd):
127 def auto_rewrite_input(self, cmd):
131 """Called to show the auto-rewritten input for autocall and friends.
128 """Called to show the auto-rewritten input for autocall and friends.
General Comments 0
You need to be logged in to leave comments. Login now