##// 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
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
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
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
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
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
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
@@ -1,385 +1,369
1 """A tornado based IPython notebook server.
1 """A tornado based IPython notebook server.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # stdlib
19 # stdlib
20 import errno
20 import errno
21 import logging
21 import logging
22 import os
22 import os
23 import signal
23 import signal
24 import socket
24 import socket
25 import sys
25 import sys
26 import threading
26 import threading
27 import webbrowser
27 import webbrowser
28
28
29 # Third party
29 # Third party
30 import zmq
30 import zmq
31
31
32 # Install the pyzmq ioloop. This has to be done before anything else from
32 # Install the pyzmq ioloop. This has to be done before anything else from
33 # tornado is imported.
33 # tornado is imported.
34 from zmq.eventloop import ioloop
34 from zmq.eventloop import ioloop
35 # FIXME: ioloop.install is new in pyzmq-2.1.7, so remove this conditional
35 # FIXME: ioloop.install is new in pyzmq-2.1.7, so remove this conditional
36 # when pyzmq dependency is updated beyond that.
36 # when pyzmq dependency is updated beyond that.
37 if hasattr(ioloop, 'install'):
37 if hasattr(ioloop, 'install'):
38 ioloop.install()
38 ioloop.install()
39 else:
39 else:
40 import tornado.ioloop
40 import tornado.ioloop
41 tornado.ioloop.IOLoop = ioloop.IOLoop
41 tornado.ioloop.IOLoop = ioloop.IOLoop
42
42
43 from tornado import httpserver
43 from tornado import httpserver
44 from tornado import web
44 from tornado import web
45
45
46 # Our own libraries
46 # Our own libraries
47 from .kernelmanager import MappingKernelManager
47 from .kernelmanager import MappingKernelManager
48 from .handlers import (LoginHandler, LogoutHandler,
48 from .handlers import (LoginHandler, LogoutHandler,
49 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
49 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
50 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
50 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
51 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
51 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
52 )
52 )
53 from .notebookmanager import NotebookManager
53 from .notebookmanager import NotebookManager
54
54
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 (
61 flags as ipkernel_flags,
62 flags as ipkernel_flags,
62 aliases as ipkernel_aliases,
63 aliases as ipkernel_aliases,
63 IPKernelApp
64 IPKernelApp
64 )
65 )
65 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
66 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
66
67
67 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
68 # Module globals
69 # Module globals
69 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
70
71
71 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
72 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
72 _kernel_action_regex = r"(?P<action>restart|interrupt)"
73 _kernel_action_regex = r"(?P<action>restart|interrupt)"
73 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
74 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
74
75
75 LOCALHOST = '127.0.0.1'
76 LOCALHOST = '127.0.0.1'
76
77
77 _examples = """
78 _examples = """
78 ipython notebook # start the notebook
79 ipython notebook # start the notebook
79 ipython notebook --profile=sympy # use the sympy profile
80 ipython notebook --profile=sympy # use the sympy profile
80 ipython notebook --pylab=inline # pylab in inline plotting mode
81 ipython notebook --pylab=inline # pylab in inline plotting mode
81 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
82 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
82 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
83 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
83 """
84 """
84
85
85 #-----------------------------------------------------------------------------
86 #-----------------------------------------------------------------------------
86 # The Tornado web application
87 # The Tornado web application
87 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
88
89
89 class NotebookWebApplication(web.Application):
90 class NotebookWebApplication(web.Application):
90
91
91 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
92 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
92 handlers = [
93 handlers = [
93 (r"/", ProjectDashboardHandler),
94 (r"/", ProjectDashboardHandler),
94 (r"/login", LoginHandler),
95 (r"/login", LoginHandler),
95 (r"/logout", LogoutHandler),
96 (r"/logout", LogoutHandler),
96 (r"/new", NewHandler),
97 (r"/new", NewHandler),
97 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
98 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
98 (r"/kernels", MainKernelHandler),
99 (r"/kernels", MainKernelHandler),
99 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
100 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
100 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
101 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
101 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
102 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
102 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
103 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
103 (r"/notebooks", NotebookRootHandler),
104 (r"/notebooks", NotebookRootHandler),
104 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
105 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
105 (r"/rstservice/render", RSTHandler)
106 (r"/rstservice/render", RSTHandler)
106 ]
107 ]
107 settings = dict(
108 settings = dict(
108 template_path=os.path.join(os.path.dirname(__file__), "templates"),
109 template_path=os.path.join(os.path.dirname(__file__), "templates"),
109 static_path=os.path.join(os.path.dirname(__file__), "static"),
110 static_path=os.path.join(os.path.dirname(__file__), "static"),
110 cookie_secret=os.urandom(1024),
111 cookie_secret=os.urandom(1024),
111 login_url="/login",
112 login_url="/login",
112 )
113 )
113 web.Application.__init__(self, handlers, **settings)
114 web.Application.__init__(self, handlers, **settings)
114
115
115 self.kernel_manager = kernel_manager
116 self.kernel_manager = kernel_manager
116 self.log = log
117 self.log = log
117 self.notebook_manager = notebook_manager
118 self.notebook_manager = notebook_manager
118 self.ipython_app = ipython_app
119 self.ipython_app = ipython_app
119 self.read_only = self.ipython_app.read_only
120 self.read_only = self.ipython_app.read_only
120
121
121
122
122 #-----------------------------------------------------------------------------
123 #-----------------------------------------------------------------------------
123 # Aliases and Flags
124 # Aliases and Flags
124 #-----------------------------------------------------------------------------
125 #-----------------------------------------------------------------------------
125
126
126 flags = dict(ipkernel_flags)
127 flags = dict(ipkernel_flags)
127 flags['no-browser']=(
128 flags['no-browser']=(
128 {'NotebookApp' : {'open_browser' : False}},
129 {'NotebookApp' : {'open_browser' : False}},
129 "Don't open the notebook in a browser after startup."
130 "Don't open the notebook in a browser after startup."
130 )
131 )
131 flags['no-mathjax']=(
132 flags['no-mathjax']=(
132 {'NotebookApp' : {'enable_mathjax' : False}},
133 {'NotebookApp' : {'enable_mathjax' : False}},
133 """Disable MathJax
134 """Disable MathJax
134
135
135 MathJax is the javascript library IPython uses to render math/LaTeX. It is
136 MathJax is the javascript library IPython uses to render math/LaTeX. It is
136 very large, so you may want to disable it if you have a slow internet
137 very large, so you may want to disable it if you have a slow internet
137 connection, or for offline use of the notebook.
138 connection, or for offline use of the notebook.
138
139
139 When disabled, equations etc. will appear as their untransformed TeX source.
140 When disabled, equations etc. will appear as their untransformed TeX source.
140 """
141 """
141 )
142 )
142 flags['read-only'] = (
143 flags['read-only'] = (
143 {'NotebookApp' : {'read_only' : True}},
144 {'NotebookApp' : {'read_only' : True}},
144 """Allow read-only access to notebooks.
145 """Allow read-only access to notebooks.
145
146
146 When using a password to protect the notebook server, this flag
147 When using a password to protect the notebook server, this flag
147 allows unauthenticated clients to view the notebook list, and
148 allows unauthenticated clients to view the notebook list, and
148 individual notebooks, but not edit them, start kernels, or run
149 individual notebooks, but not edit them, start kernels, or run
149 code.
150 code.
150
151
151 If no password is set, the server will be entirely read-only.
152 If no password is set, the server will be entirely read-only.
152 """
153 """
153 )
154 )
154
155
155 # the flags that are specific to the frontend
156 # the flags that are specific to the frontend
156 # these must be scrubbed before being passed to the kernel,
157 # these must be scrubbed before being passed to the kernel,
157 # or it will raise an error on unrecognized flags
158 # or it will raise an error on unrecognized flags
158 notebook_flags = ['no-browser', 'no-mathjax', 'read-only']
159 notebook_flags = ['no-browser', 'no-mathjax', 'read-only']
159
160
160 aliases = dict(ipkernel_aliases)
161 aliases = dict(ipkernel_aliases)
161
162
162 aliases.update({
163 aliases.update({
163 'ip': 'NotebookApp.ip',
164 'ip': 'NotebookApp.ip',
164 'port': 'NotebookApp.port',
165 'port': 'NotebookApp.port',
165 'keyfile': 'NotebookApp.keyfile',
166 'keyfile': 'NotebookApp.keyfile',
166 'certfile': 'NotebookApp.certfile',
167 'certfile': 'NotebookApp.certfile',
167 'notebook-dir': 'NotebookManager.notebook_dir',
168 'notebook-dir': 'NotebookManager.notebook_dir',
168 })
169 })
169
170
170 # remove ipkernel flags that are singletons, and don't make sense in
171 # remove ipkernel flags that are singletons, and don't make sense in
171 # multi-kernel evironment:
172 # multi-kernel evironment:
172 aliases.pop('f', None)
173 aliases.pop('f', None)
173
174
174 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
175 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
175 u'notebook-dir']
176 u'notebook-dir']
176
177
177 #-----------------------------------------------------------------------------
178 #-----------------------------------------------------------------------------
178 # NotebookApp
179 # NotebookApp
179 #-----------------------------------------------------------------------------
180 #-----------------------------------------------------------------------------
180
181
181 class NotebookApp(BaseIPythonApplication):
182 class NotebookApp(BaseIPythonApplication):
182
183
183 name = 'ipython-notebook'
184 name = 'ipython-notebook'
184 default_config_file_name='ipython_notebook_config.py'
185 default_config_file_name='ipython_notebook_config.py'
185
186
186 description = """
187 description = """
187 The IPython HTML Notebook.
188 The IPython HTML Notebook.
188
189
189 This launches a Tornado based HTML Notebook Server that serves up an
190 This launches a Tornado based HTML Notebook Server that serves up an
190 HTML5/Javascript Notebook client.
191 HTML5/Javascript Notebook client.
191 """
192 """
192 examples = _examples
193 examples = _examples
193
194
194 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
195 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
195 MappingKernelManager, NotebookManager]
196 MappingKernelManager, NotebookManager]
196 flags = Dict(flags)
197 flags = Dict(flags)
197 aliases = Dict(aliases)
198 aliases = Dict(aliases)
198
199
199 kernel_argv = List(Unicode)
200 kernel_argv = List(Unicode)
200
201
201 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
202 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
202 default_value=logging.INFO,
203 default_value=logging.INFO,
203 config=True,
204 config=True,
204 help="Set the log level by value or name.")
205 help="Set the log level by value or name.")
205
206
206 # Network related information.
207 # Network related information.
207
208
208 ip = Unicode(LOCALHOST, config=True,
209 ip = Unicode(LOCALHOST, config=True,
209 help="The IP address the notebook server will listen on."
210 help="The IP address the notebook server will listen on."
210 )
211 )
211
212
212 def _ip_changed(self, name, old, new):
213 def _ip_changed(self, name, old, new):
213 if new == u'*': self.ip = u''
214 if new == u'*': self.ip = u''
214
215
215 port = Integer(8888, config=True,
216 port = Integer(8888, config=True,
216 help="The port the notebook server will listen on."
217 help="The port the notebook server will listen on."
217 )
218 )
218
219
219 certfile = Unicode(u'', config=True,
220 certfile = Unicode(u'', config=True,
220 help="""The full path to an SSL/TLS certificate file."""
221 help="""The full path to an SSL/TLS certificate file."""
221 )
222 )
222
223
223 keyfile = Unicode(u'', config=True,
224 keyfile = Unicode(u'', config=True,
224 help="""The full path to a private key file for usage with SSL/TLS."""
225 help="""The full path to a private key file for usage with SSL/TLS."""
225 )
226 )
226
227
227 password = Unicode(u'', config=True,
228 password = Unicode(u'', config=True,
228 help="""Hashed password to use for web authentication.
229 help="""Hashed password to use for web authentication.
229
230
230 To generate, type in a python/IPython shell:
231 To generate, type in a python/IPython shell:
231
232
232 from IPython.lib import passwd; passwd()
233 from IPython.lib import passwd; passwd()
233
234
234 The string should be of the form type:salt:hashed-password.
235 The string should be of the form type:salt:hashed-password.
235 """
236 """
236 )
237 )
237
238
238 open_browser = Bool(True, config=True,
239 open_browser = Bool(True, config=True,
239 help="Whether to open in a browser after starting.")
240 help="Whether to open in a browser after starting.")
240
241
241 read_only = Bool(False, config=True,
242 read_only = Bool(False, config=True,
242 help="Whether to prevent editing/execution of notebooks."
243 help="Whether to prevent editing/execution of notebooks."
243 )
244 )
244
245
245 enable_mathjax = Bool(True, config=True,
246 enable_mathjax = Bool(True, config=True,
246 help="""Whether to enable MathJax for typesetting math/TeX
247 help="""Whether to enable MathJax for typesetting math/TeX
247
248
248 MathJax is the javascript library IPython uses to render math/LaTeX. It is
249 MathJax is the javascript library IPython uses to render math/LaTeX. It is
249 very large, so you may want to disable it if you have a slow internet
250 very large, so you may want to disable it if you have a slow internet
250 connection, or for offline use of the notebook.
251 connection, or for offline use of the notebook.
251
252
252 When disabled, equations etc. will appear as their untransformed TeX source.
253 When disabled, equations etc. will appear as their untransformed TeX source.
253 """
254 """
254 )
255 )
255 def _enable_mathjax_changed(self, name, old, new):
256 def _enable_mathjax_changed(self, name, old, new):
256 """set mathjax url to empty if mathjax is disabled"""
257 """set mathjax url to empty if mathjax is disabled"""
257 if not new:
258 if not new:
258 self.mathjax_url = u''
259 self.mathjax_url = u''
259
260
260 mathjax_url = Unicode("", config=True,
261 mathjax_url = Unicode("", config=True,
261 help="""The url for MathJax.js."""
262 help="""The url for MathJax.js."""
262 )
263 )
263 def _mathjax_url_default(self):
264 def _mathjax_url_default(self):
264 if not self.enable_mathjax:
265 if not self.enable_mathjax:
265 return u''
266 return u''
266 static_path = os.path.join(os.path.dirname(__file__), "static")
267 static_path = os.path.join(os.path.dirname(__file__), "static")
267 if os.path.exists(os.path.join(static_path, 'mathjax', "MathJax.js")):
268 if os.path.exists(os.path.join(static_path, 'mathjax', "MathJax.js")):
268 self.log.info("Using local MathJax")
269 self.log.info("Using local MathJax")
269 return u"static/mathjax/MathJax.js"
270 return u"static/mathjax/MathJax.js"
270 else:
271 else:
271 self.log.info("Using MathJax from CDN")
272 self.log.info("Using MathJax from CDN")
272 return u"http://cdn.mathjax.org/mathjax/latest/MathJax.js"
273 return u"http://cdn.mathjax.org/mathjax/latest/MathJax.js"
273
274
274 def _mathjax_url_changed(self, name, old, new):
275 def _mathjax_url_changed(self, name, old, new):
275 if new and not self.enable_mathjax:
276 if new and not self.enable_mathjax:
276 # enable_mathjax=False overrides mathjax_url
277 # enable_mathjax=False overrides mathjax_url
277 self.mathjax_url = u''
278 self.mathjax_url = u''
278 else:
279 else:
279 self.log.info("Using MathJax: %s", new)
280 self.log.info("Using MathJax: %s", new)
280
281
281 def parse_command_line(self, argv=None):
282 def parse_command_line(self, argv=None):
282 super(NotebookApp, self).parse_command_line(argv)
283 super(NotebookApp, self).parse_command_line(argv)
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.
310 signal.signal(signal.SIGINT, signal.SIG_DFL)
294 signal.signal(signal.SIGINT, signal.SIG_DFL)
311
295
312 # force Session default to be secure
296 # force Session default to be secure
313 default_secure(self.config)
297 default_secure(self.config)
314 # Create a KernelManager and start a kernel.
298 # Create a KernelManager and start a kernel.
315 self.kernel_manager = MappingKernelManager(
299 self.kernel_manager = MappingKernelManager(
316 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
300 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
317 connection_dir = self.profile_dir.security_dir,
301 connection_dir = self.profile_dir.security_dir,
318 )
302 )
319 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
303 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
320 self.notebook_manager.list_notebooks()
304 self.notebook_manager.list_notebooks()
321
305
322 def init_logging(self):
306 def init_logging(self):
323 super(NotebookApp, self).init_logging()
307 super(NotebookApp, self).init_logging()
324 # This prevents double log messages because tornado use a root logger that
308 # This prevents double log messages because tornado use a root logger that
325 # self.log is a child of. The logging module dipatches log messages to a log
309 # self.log is a child of. The logging module dipatches log messages to a log
326 # and all of its ancenstors until propagate is set to False.
310 # and all of its ancenstors until propagate is set to False.
327 self.log.propagate = False
311 self.log.propagate = False
328
312
329 @catch_config_error
313 @catch_config_error
330 def initialize(self, argv=None):
314 def initialize(self, argv=None):
331 super(NotebookApp, self).initialize(argv)
315 super(NotebookApp, self).initialize(argv)
332 self.init_configurables()
316 self.init_configurables()
333 self.web_app = NotebookWebApplication(
317 self.web_app = NotebookWebApplication(
334 self, self.kernel_manager, self.notebook_manager, self.log
318 self, self.kernel_manager, self.notebook_manager, self.log
335 )
319 )
336 if self.certfile:
320 if self.certfile:
337 ssl_options = dict(certfile=self.certfile)
321 ssl_options = dict(certfile=self.certfile)
338 if self.keyfile:
322 if self.keyfile:
339 ssl_options['keyfile'] = self.keyfile
323 ssl_options['keyfile'] = self.keyfile
340 else:
324 else:
341 ssl_options = None
325 ssl_options = None
342 self.web_app.password = self.password
326 self.web_app.password = self.password
343 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
327 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
344 if ssl_options is None and not self.ip:
328 if ssl_options is None and not self.ip:
345 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
329 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
346 'but not using any encryption or authentication. This is highly '
330 'but not using any encryption or authentication. This is highly '
347 'insecure and not recommended.')
331 'insecure and not recommended.')
348
332
349 # Try random ports centered around the default.
333 # Try random ports centered around the default.
350 from random import randint
334 from random import randint
351 n = 50 # Max number of attempts, keep reasonably large.
335 n = 50 # Max number of attempts, keep reasonably large.
352 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
336 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
353 try:
337 try:
354 self.http_server.listen(port, self.ip)
338 self.http_server.listen(port, self.ip)
355 except socket.error, e:
339 except socket.error, e:
356 if e.errno != errno.EADDRINUSE:
340 if e.errno != errno.EADDRINUSE:
357 raise
341 raise
358 self.log.info('The port %i is already in use, trying another random port.' % port)
342 self.log.info('The port %i is already in use, trying another random port.' % port)
359 else:
343 else:
360 self.port = port
344 self.port = port
361 break
345 break
362
346
363 def start(self):
347 def start(self):
364 ip = self.ip if self.ip else '[all ip addresses on your system]'
348 ip = self.ip if self.ip else '[all ip addresses on your system]'
365 proto = 'https' if self.certfile else 'http'
349 proto = 'https' if self.certfile else 'http'
366 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
350 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
367 ip,
351 ip,
368 self.port))
352 self.port))
369 if self.open_browser:
353 if self.open_browser:
370 ip = self.ip or '127.0.0.1'
354 ip = self.ip or '127.0.0.1'
371 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
355 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
372 new=2)
356 new=2)
373 threading.Thread(target=b).start()
357 threading.Thread(target=b).start()
374
358
375 ioloop.IOLoop.instance().start()
359 ioloop.IOLoop.instance().start()
376
360
377 #-----------------------------------------------------------------------------
361 #-----------------------------------------------------------------------------
378 # Main entry point
362 # Main entry point
379 #-----------------------------------------------------------------------------
363 #-----------------------------------------------------------------------------
380
364
381 def launch_new_instance():
365 def launch_new_instance():
382 app = NotebookApp()
366 app = NotebookApp()
383 app.initialize()
367 app.initialize()
384 app.start()
368 app.start()
385
369
@@ -1,26 +1,27
1 """Tests for the notebook kernel and session manager."""
1 """Tests for the notebook kernel and session manager."""
2
2
3 from unittest import TestCase
3 from unittest import TestCase
4
4
5 from IPython.frontend.html.notebook.kernelmanager import MultiKernelManager
5 from IPython.frontend.html.notebook.kernelmanager import MultiKernelManager
6
6
7 class TestKernelManager(TestCase):
7 class TestKernelManager(TestCase):
8
8
9 def test_km_lifecycle(self):
9 def test_km_lifecycle(self):
10 km = MultiKernelManager()
10 km = MultiKernelManager()
11 kid = km.start_kernel()
11 kid = km.start_kernel()
12 self.assert_(kid in km)
12 self.assert_(kid in km)
13 self.assertEquals(len(km),1)
13 self.assertEquals(len(km),1)
14 km.kill_kernel(kid)
14 km.kill_kernel(kid)
15 self.assert_(not kid in km)
15 self.assert_(not kid in km)
16
16
17 kid = km.start_kernel()
17 kid = km.start_kernel()
18 self.assertEquals('127.0.0.1',km.get_kernel_ip(kid))
18 self.assertEquals('127.0.0.1',km.get_kernel_ip(kid))
19 port_dict = km.get_kernel_ports(kid)
19 port_dict = km.get_kernel_ports(kid)
20 self.assert_('stdin_port' in port_dict)
20 self.assert_('stdin_port' in port_dict)
21 self.assert_('iopub_port' in port_dict)
21 self.assert_('iopub_port' in port_dict)
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
@@ -1,549 +1,352
1 """ A minimal application using the Qt console-style IPython frontend.
1 """ A minimal application using the Qt console-style 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 * Evan Patterson
8 * Evan Patterson
9 * Min RK
9 * Min RK
10 * Erik Tollerud
10 * Erik Tollerud
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
17 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
18 # Imports
19 # Imports
19 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
20
21
21 # stdlib imports
22 # stdlib imports
22 import json
23 import json
23 import os
24 import os
24 import signal
25 import signal
25 import sys
26 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
33 from IPython.core.application import BaseIPythonApplication
34 from IPython.core.application import BaseIPythonApplication
34 from IPython.core.profiledir import ProfileDir
35 from IPython.core.profiledir import ProfileDir
35 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
36 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
36 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
37 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
37 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
38 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
38 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
39 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
39 from IPython.frontend.qt.console import styles
40 from IPython.frontend.qt.console import styles
40 from IPython.frontend.qt.console.mainwindow import MainWindow
41 from IPython.frontend.qt.console.mainwindow import MainWindow
41 from IPython.frontend.qt.kernelmanager import QtKernelManager
42 from IPython.frontend.qt.kernelmanager import QtKernelManager
42 from IPython.utils.path import filefind
43 from IPython.utils.path import filefind
43 from IPython.utils.py3compat import str_to_bytes
44 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 #-----------------------------------------------------------------------------
58
59
59 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
60 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
60
61
61 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
62 # Globals
63 # Globals
63 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
64
65
65 _examples = """
66 _examples = """
66 ipython qtconsole # start the qtconsole
67 ipython qtconsole # start the qtconsole
67 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
68 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
68 """
69 """
69
70
70 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
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'}},
81 "Disable rich text support."),
81 "Disable rich text support."),
82 }
82 }
83 qt_flags.update(boolean_flag(
83 qt_flags.update(boolean_flag(
84 'gui-completion', 'ConsoleWidget.gui_completion',
84 'gui-completion', 'ConsoleWidget.gui_completion',
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',
112 colors = 'ZMQInteractiveShell.colors',
99 colors = 'ZMQInteractiveShell.colors',
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 #-----------------------------------------------------------------------------
123
118
124 #-----------------------------------------------------------------------------
119 #-----------------------------------------------------------------------------
125 # IPythonQtConsole
120 # IPythonQtConsole
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.
135
129
136 This launches a Console-style application using Qt. It is not a full
130 This launches a Console-style application using Qt. It is not a full
137 console, in that launched terminal subprocesses will not be able to accept
131 console, in that launched terminal subprocesses will not be able to accept
138 input.
132 input.
139
133
140 The QtConsole supports various extra features beyond the Terminal IPython
134 The QtConsole supports various extra features beyond the Terminal IPython
141 shell, such as inline plotting with matplotlib, via:
135 shell, such as inline plotting with matplotlib, via:
142
136
143 ipython qtconsole --pylab=inline
137 ipython qtconsole --pylab=inline
144
138
145 as well as saving your session as HTML, and printing the output.
139 as well as saving your session as HTML, and printing the output.
146
140
147 """
141 """
148 examples = _examples
142 examples = _examples
149
143
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
200 def _pure_changed(self, name, old, new):
157 def _pure_changed(self, name, old, new):
201 kind = 'plain' if self.plain else 'rich'
158 kind = 'plain' if self.plain else 'rich'
202 self.config.ConsoleWidget.kind = kind
159 self.config.ConsoleWidget.kind = kind
203 if self.pure:
160 if self.pure:
204 self.widget_factory = FrontendWidget
161 self.widget_factory = FrontendWidget
205 elif self.plain:
162 elif self.plain:
206 self.widget_factory = IPythonWidget
163 self.widget_factory = IPythonWidget
207 else:
164 else:
208 self.widget_factory = RichIPythonWidget
165 self.widget_factory = RichIPythonWidget
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:]
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
175
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.
387 """
179 """
388 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
180 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
389 kernel_manager = QtKernelManager(
181 kernel_manager = QtKernelManager(
390 ip=ip,
182 ip=ip,
391 connection_file=self._new_connection_file(),
183 connection_file=self._new_connection_file(),
392 config=self.config,
184 config=self.config,
393 )
185 )
394 # start the kernel
186 # start the kernel
395 kwargs = dict(ipython=not self.pure)
187 kwargs = dict(ipython=not self.pure)
396 kwargs['extra_arguments'] = self.kernel_argv
188 kwargs['extra_arguments'] = self.kernel_argv
397 kernel_manager.start_kernel(**kwargs)
189 kernel_manager.start_kernel(**kwargs)
398 kernel_manager.start_channels()
190 kernel_manager.start_channels()
399 widget = self.widget_factory(config=self.config,
191 widget = self.widget_factory(config=self.config,
400 local_kernel=True)
192 local_kernel=True)
401 widget.kernel_manager = kernel_manager
193 widget.kernel_manager = kernel_manager
402 widget._existing = False
194 widget._existing = False
403 widget._may_close = True
195 widget._may_close = True
404 widget._confirm_exit = self.confirm_exit
196 widget._confirm_exit = self.confirm_exit
405 return widget
197 return widget
406
198
407 def new_frontend_slave(self, current_widget):
199 def new_frontend_slave(self, current_widget):
408 """Create and return a new frontend attached to an existing kernel.
200 """Create and return a new frontend attached to an existing kernel.
409
201
410 Parameters
202 Parameters
411 ----------
203 ----------
412 current_widget : IPythonWidget
204 current_widget : IPythonWidget
413 The IPythonWidget whose kernel this frontend is to share
205 The IPythonWidget whose kernel this frontend is to share
414 """
206 """
415 kernel_manager = QtKernelManager(
207 kernel_manager = QtKernelManager(
416 connection_file=current_widget.kernel_manager.connection_file,
208 connection_file=current_widget.kernel_manager.connection_file,
417 config = self.config,
209 config = self.config,
418 )
210 )
419 kernel_manager.load_connection_file()
211 kernel_manager.load_connection_file()
420 kernel_manager.start_channels()
212 kernel_manager.start_channels()
421 widget = self.widget_factory(config=self.config,
213 widget = self.widget_factory(config=self.config,
422 local_kernel=False)
214 local_kernel=False)
423 widget._existing = True
215 widget._existing = True
424 widget._may_close = False
216 widget._may_close = False
425 widget._confirm_exit = False
217 widget._confirm_exit = False
426 widget.kernel_manager = kernel_manager
218 widget.kernel_manager = kernel_manager
427 return widget
219 return widget
428
220
429 def init_qt_elements(self):
221 def init_qt_elements(self):
430 # Create the widget.
222 # Create the widget.
431 self.app = QtGui.QApplication([])
223 self.app = QtGui.QApplication([])
432
224
433 base_path = os.path.abspath(os.path.dirname(__file__))
225 base_path = os.path.abspath(os.path.dirname(__file__))
434 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
226 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
435 self.app.icon = QtGui.QIcon(icon_path)
227 self.app.icon = QtGui.QIcon(icon_path)
436 QtGui.QApplication.setWindowIcon(self.app.icon)
228 QtGui.QApplication.setWindowIcon(self.app.icon)
437
229
438 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
230 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
439 self.widget = self.widget_factory(config=self.config,
231 self.widget = self.widget_factory(config=self.config,
440 local_kernel=local_kernel)
232 local_kernel=local_kernel)
441 self.widget._existing = self.existing
233 self.widget._existing = self.existing
442 self.widget._may_close = not self.existing
234 self.widget._may_close = not self.existing
443 self.widget._confirm_exit = self.confirm_exit
235 self.widget._confirm_exit = self.confirm_exit
444
236
445 self.widget.kernel_manager = self.kernel_manager
237 self.widget.kernel_manager = self.kernel_manager
446 self.window = MainWindow(self.app,
238 self.window = MainWindow(self.app,
447 confirm_exit=self.confirm_exit,
239 confirm_exit=self.confirm_exit,
448 new_frontend_factory=self.new_frontend_master,
240 new_frontend_factory=self.new_frontend_master,
449 slave_frontend_factory=self.new_frontend_slave,
241 slave_frontend_factory=self.new_frontend_slave,
450 )
242 )
451 self.window.log = self.log
243 self.window.log = self.log
452 self.window.add_tab_with_frontend(self.widget)
244 self.window.add_tab_with_frontend(self.widget)
453 self.window.init_menu_bar()
245 self.window.init_menu_bar()
454
246
455 self.window.setWindowTitle('Python' if self.pure else 'IPython')
247 self.window.setWindowTitle('Python' if self.pure else 'IPython')
456
248
457 def init_colors(self):
249 def init_colors(self):
458 """Configure the coloring of the widget"""
250 """Configure the coloring of the widget"""
459 # Note: This will be dramatically simplified when colors
251 # Note: This will be dramatically simplified when colors
460 # are removed from the backend.
252 # are removed from the backend.
461
253
462 if self.pure:
254 if self.pure:
463 # only IPythonWidget supports styling
255 # only IPythonWidget supports styling
464 return
256 return
465
257
466 # parse the colors arg down to current known labels
258 # parse the colors arg down to current known labels
467 try:
259 try:
468 colors = self.config.ZMQInteractiveShell.colors
260 colors = self.config.ZMQInteractiveShell.colors
469 except AttributeError:
261 except AttributeError:
470 colors = None
262 colors = None
471 try:
263 try:
472 style = self.config.IPythonWidget.syntax_style
264 style = self.config.IPythonWidget.syntax_style
473 except AttributeError:
265 except AttributeError:
474 style = None
266 style = None
475
267
476 # find the value for colors:
268 # find the value for colors:
477 if colors:
269 if colors:
478 colors=colors.lower()
270 colors=colors.lower()
479 if colors in ('lightbg', 'light'):
271 if colors in ('lightbg', 'light'):
480 colors='lightbg'
272 colors='lightbg'
481 elif colors in ('dark', 'linux'):
273 elif colors in ('dark', 'linux'):
482 colors='linux'
274 colors='linux'
483 else:
275 else:
484 colors='nocolor'
276 colors='nocolor'
485 elif style:
277 elif style:
486 if style=='bw':
278 if style=='bw':
487 colors='nocolor'
279 colors='nocolor'
488 elif styles.dark_style(style):
280 elif styles.dark_style(style):
489 colors='linux'
281 colors='linux'
490 else:
282 else:
491 colors='lightbg'
283 colors='lightbg'
492 else:
284 else:
493 colors=None
285 colors=None
494
286
495 # Configure the style.
287 # Configure the style.
496 widget = self.widget
288 widget = self.widget
497 if style:
289 if style:
498 widget.style_sheet = styles.sheet_from_template(style, colors)
290 widget.style_sheet = styles.sheet_from_template(style, colors)
499 widget.syntax_style = style
291 widget.syntax_style = style
500 widget._syntax_style_changed()
292 widget._syntax_style_changed()
501 widget._style_sheet_changed()
293 widget._style_sheet_changed()
502 elif colors:
294 elif colors:
503 # use a default style
295 # use a default style
504 widget.set_default_style(colors=colors)
296 widget.set_default_style(colors=colors)
505 else:
297 else:
506 # this is redundant for now, but allows the widget's
298 # this is redundant for now, but allows the widget's
507 # defaults to change
299 # defaults to change
508 widget.set_default_style()
300 widget.set_default_style()
509
301
510 if self.stylesheet:
302 if self.stylesheet:
511 # we got an expicit stylesheet
303 # we got an expicit stylesheet
512 if os.path.isfile(self.stylesheet):
304 if os.path.isfile(self.stylesheet):
513 with open(self.stylesheet) as f:
305 with open(self.stylesheet) as f:
514 sheet = f.read()
306 sheet = f.read()
515 widget.style_sheet = sheet
307 widget.style_sheet = sheet
516 widget._style_sheet_changed()
308 widget._style_sheet_changed()
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
532 # draw the window
335 # draw the window
533 self.window.show()
336 self.window.show()
534
337
535 # Start the application main loop.
338 # Start the application main loop.
536 self.app.exec_()
339 self.app.exec_()
537
340
538 #-----------------------------------------------------------------------------
341 #-----------------------------------------------------------------------------
539 # Main entry point
342 # Main entry point
540 #-----------------------------------------------------------------------------
343 #-----------------------------------------------------------------------------
541
344
542 def main():
345 def main():
543 app = IPythonQtConsoleApp()
346 app = IPythonQtConsoleApp()
544 app.initialize()
347 app.initialize()
545 app.start()
348 app.start()
546
349
547
350
548 if __name__ == '__main__':
351 if __name__ == '__main__':
549 main()
352 main()
@@ -1,399 +1,407
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The :class:`~IPython.core.application.Application` object for the command
4 The :class:`~IPython.core.application.Application` object for the command
5 line :command:`ipython` program.
5 line :command:`ipython` program.
6
6
7 Authors
7 Authors
8 -------
8 -------
9
9
10 * Brian Granger
10 * Brian Granger
11 * Fernando Perez
11 * Fernando Perez
12 * Min Ragan-Kelley
12 * Min Ragan-Kelley
13 """
13 """
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Copyright (C) 2008-2011 The IPython Development Team
16 # Copyright (C) 2008-2011 The IPython Development Team
17 #
17 #
18 # Distributed under the terms of the BSD License. The full license is in
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
19 # the file COPYING, distributed as part of this software.
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 from __future__ import absolute_import
26 from __future__ import absolute_import
27
27
28 import logging
28 import logging
29 import os
29 import os
30 import sys
30 import sys
31
31
32 from IPython.config.loader import (
32 from IPython.config.loader import (
33 Config, PyFileConfigLoader, ConfigFileNotFound
33 Config, PyFileConfigLoader, ConfigFileNotFound
34 )
34 )
35 from IPython.config.application import boolean_flag, catch_config_error
35 from IPython.config.application import boolean_flag, catch_config_error
36 from IPython.core import release
36 from IPython.core import release
37 from IPython.core import usage
37 from IPython.core import usage
38 from IPython.core.completer import IPCompleter
38 from IPython.core.completer import IPCompleter
39 from IPython.core.crashhandler import CrashHandler
39 from IPython.core.crashhandler import CrashHandler
40 from IPython.core.formatters import PlainTextFormatter
40 from IPython.core.formatters import PlainTextFormatter
41 from IPython.core.prompts import PromptManager
41 from IPython.core.prompts import PromptManager
42 from IPython.core.application import (
42 from IPython.core.application import (
43 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
43 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
44 )
44 )
45 from IPython.core.shellapp import (
45 from IPython.core.shellapp import (
46 InteractiveShellApp, shell_flags, shell_aliases
46 InteractiveShellApp, shell_flags, shell_aliases
47 )
47 )
48 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
48 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
49 from IPython.lib import inputhook
49 from IPython.lib import inputhook
50 from IPython.utils import warn
50 from IPython.utils import warn
51 from IPython.utils.path import get_ipython_dir, check_for_old_config
51 from IPython.utils.path import get_ipython_dir, check_for_old_config
52 from IPython.utils.traitlets import (
52 from IPython.utils.traitlets import (
53 Bool, List, Dict, CaselessStrEnum
53 Bool, List, Dict, CaselessStrEnum
54 )
54 )
55
55
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57 # Globals, utilities and helpers
57 # Globals, utilities and helpers
58 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
59
59
60 #: The default config file name for this application.
60 #: The default config file name for this application.
61 default_config_file_name = u'ipython_config.py'
61 default_config_file_name = u'ipython_config.py'
62
62
63 _examples = """
63 _examples = """
64 ipython --pylab # start in pylab mode
64 ipython --pylab # start in pylab mode
65 ipython --pylab=qt # start in pylab mode with the qt4 backend
65 ipython --pylab=qt # start in pylab mode with the qt4 backend
66 ipython --log-level=DEBUG # set logging to DEBUG
66 ipython --log-level=DEBUG # set logging to DEBUG
67 ipython --profile=foo # start with profile foo
67 ipython --profile=foo # start with profile foo
68
68
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 """
75
78
76 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
77 # Crash handler for this application
80 # Crash handler for this application
78 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
79
82
80 class IPAppCrashHandler(CrashHandler):
83 class IPAppCrashHandler(CrashHandler):
81 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
84 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
82
85
83 def __init__(self, app):
86 def __init__(self, app):
84 contact_name = release.authors['Fernando'][0]
87 contact_name = release.authors['Fernando'][0]
85 contact_email = release.author_email
88 contact_email = release.author_email
86 bug_tracker = 'https://github.com/ipython/ipython/issues'
89 bug_tracker = 'https://github.com/ipython/ipython/issues'
87 super(IPAppCrashHandler,self).__init__(
90 super(IPAppCrashHandler,self).__init__(
88 app, contact_name, contact_email, bug_tracker
91 app, contact_name, contact_email, bug_tracker
89 )
92 )
90
93
91 def make_report(self,traceback):
94 def make_report(self,traceback):
92 """Return a string containing a crash report."""
95 """Return a string containing a crash report."""
93
96
94 sec_sep = self.section_sep
97 sec_sep = self.section_sep
95 # Start with parent report
98 # Start with parent report
96 report = [super(IPAppCrashHandler, self).make_report(traceback)]
99 report = [super(IPAppCrashHandler, self).make_report(traceback)]
97 # Add interactive-specific info we may have
100 # Add interactive-specific info we may have
98 rpt_add = report.append
101 rpt_add = report.append
99 try:
102 try:
100 rpt_add(sec_sep+"History of session input:")
103 rpt_add(sec_sep+"History of session input:")
101 for line in self.app.shell.user_ns['_ih']:
104 for line in self.app.shell.user_ns['_ih']:
102 rpt_add(line)
105 rpt_add(line)
103 rpt_add('\n*** Last line of input (may not be in above history):\n')
106 rpt_add('\n*** Last line of input (may not be in above history):\n')
104 rpt_add(self.app.shell._last_input_line+'\n')
107 rpt_add(self.app.shell._last_input_line+'\n')
105 except:
108 except:
106 pass
109 pass
107
110
108 return ''.join(report)
111 return ''.join(report)
109
112
110 #-----------------------------------------------------------------------------
113 #-----------------------------------------------------------------------------
111 # Aliases and Flags
114 # Aliases and Flags
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.'
119 )
123 )
120 addflag('banner', 'TerminalIPythonApp.display_banner',
124 addflag('banner', 'TerminalIPythonApp.display_banner',
121 "Display a banner upon starting IPython.",
125 "Display a banner upon starting IPython.",
122 "Don't display a banner upon starting IPython."
126 "Don't display a banner upon starting IPython."
123 )
127 )
124 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
128 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
125 """Set to confirm when you try to exit IPython with an EOF (Control-D
129 """Set to confirm when you try to exit IPython with an EOF (Control-D
126 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
130 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
127 you can force a direct exit without any confirmation.""",
131 you can force a direct exit without any confirmation.""",
128 "Don't prompt the user when exiting."
132 "Don't prompt the user when exiting."
129 )
133 )
130 addflag('term-title', 'TerminalInteractiveShell.term_title',
134 addflag('term-title', 'TerminalInteractiveShell.term_title',
131 "Enable auto setting the terminal title.",
135 "Enable auto setting the terminal title.",
132 "Disable auto setting the terminal title."
136 "Disable auto setting the terminal title."
133 )
137 )
134 classic_config = Config()
138 classic_config = Config()
135 classic_config.InteractiveShell.cache_size = 0
139 classic_config.InteractiveShell.cache_size = 0
136 classic_config.PlainTextFormatter.pprint = False
140 classic_config.PlainTextFormatter.pprint = False
137 classic_config.PromptManager.in_template = '>>> '
141 classic_config.PromptManager.in_template = '>>> '
138 classic_config.PromptManager.in2_template = '... '
142 classic_config.PromptManager.in2_template = '... '
139 classic_config.PromptManager.out_template = ''
143 classic_config.PromptManager.out_template = ''
140 classic_config.InteractiveShell.separate_in = ''
144 classic_config.InteractiveShell.separate_in = ''
141 classic_config.InteractiveShell.separate_out = ''
145 classic_config.InteractiveShell.separate_out = ''
142 classic_config.InteractiveShell.separate_out2 = ''
146 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 )
150 # # log doesn't make so much sense this way anymore
154 # # log doesn't make so much sense this way anymore
151 # paa('--log','-l',
155 # paa('--log','-l',
152 # action='store_true', dest='InteractiveShell.logstart',
156 # action='store_true', dest='InteractiveShell.logstart',
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)
174
179
175 # it's possible we don't want short aliases for *all* of these:
180 # it's possible we don't want short aliases for *all* of these:
176 aliases.update(dict(
181 aliases.update(dict(
177 gui='TerminalIPythonApp.gui',
182 gui='TerminalIPythonApp.gui',
178 pylab='TerminalIPythonApp.pylab',
183 pylab='TerminalIPythonApp.pylab',
179 ))
184 ))
180
185
181 #-----------------------------------------------------------------------------
186 #-----------------------------------------------------------------------------
182 # Main classes and functions
187 # Main classes and functions
183 #-----------------------------------------------------------------------------
188 #-----------------------------------------------------------------------------
184
189
185 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
190 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
186 name = u'ipython'
191 name = u'ipython'
187 description = usage.cl_usage
192 description = usage.cl_usage
188 default_config_file_name = default_config_file_name
193 default_config_file_name = default_config_file_name
189 crash_handler_class = IPAppCrashHandler
194 crash_handler_class = IPAppCrashHandler
190 examples = _examples
195 examples = _examples
191
196
192 flags = Dict(flags)
197 flags = Dict(flags)
193 aliases = Dict(aliases)
198 aliases = Dict(aliases)
194 classes = List()
199 classes = List()
195 def _classes_default(self):
200 def _classes_default(self):
196 """This has to be in a method, for TerminalIPythonApp to be available."""
201 """This has to be in a method, for TerminalIPythonApp to be available."""
197 return [
202 return [
198 InteractiveShellApp, # ShellApp comes before TerminalApp, because
203 InteractiveShellApp, # ShellApp comes before TerminalApp, because
199 self.__class__, # it will also affect subclasses (e.g. QtConsole)
204 self.__class__, # it will also affect subclasses (e.g. QtConsole)
200 TerminalInteractiveShell,
205 TerminalInteractiveShell,
201 PromptManager,
206 PromptManager,
202 ProfileDir,
207 ProfileDir,
203 PlainTextFormatter,
208 PlainTextFormatter,
204 IPCompleter,
209 IPCompleter,
205 ]
210 ]
206
211
207 subcommands = Dict(dict(
212 subcommands = Dict(dict(
208 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
213 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
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."
216 ),
221 ),
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.
223 auto_create=Bool(True)
231 auto_create=Bool(True)
224 # configurables
232 # configurables
225 ignore_old_config=Bool(False, config=True,
233 ignore_old_config=Bool(False, config=True,
226 help="Suppress warning messages about legacy config files"
234 help="Suppress warning messages about legacy config files"
227 )
235 )
228 quick = Bool(False, config=True,
236 quick = Bool(False, config=True,
229 help="""Start IPython quickly by skipping the loading of config files."""
237 help="""Start IPython quickly by skipping the loading of config files."""
230 )
238 )
231 def _quick_changed(self, name, old, new):
239 def _quick_changed(self, name, old, new):
232 if new:
240 if new:
233 self.load_config_file = lambda *a, **kw: None
241 self.load_config_file = lambda *a, **kw: None
234 self.ignore_old_config=True
242 self.ignore_old_config=True
235
243
236 gui = CaselessStrEnum(('qt', 'wx', 'gtk', 'glut', 'pyglet'), config=True,
244 gui = CaselessStrEnum(('qt', 'wx', 'gtk', 'glut', 'pyglet'), config=True,
237 help="Enable GUI event loop integration ('qt', 'wx', 'gtk', 'glut', 'pyglet')."
245 help="Enable GUI event loop integration ('qt', 'wx', 'gtk', 'glut', 'pyglet')."
238 )
246 )
239 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'auto'],
247 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'auto'],
240 config=True,
248 config=True,
241 help="""Pre-load matplotlib and numpy for interactive use,
249 help="""Pre-load matplotlib and numpy for interactive use,
242 selecting a particular matplotlib backend and loop integration.
250 selecting a particular matplotlib backend and loop integration.
243 """
251 """
244 )
252 )
245 display_banner = Bool(True, config=True,
253 display_banner = Bool(True, config=True,
246 help="Whether to display a banner upon starting IPython."
254 help="Whether to display a banner upon starting IPython."
247 )
255 )
248
256
249 # if there is code of files to run from the cmd line, don't interact
257 # if there is code of files to run from the cmd line, don't interact
250 # unless the --i flag (App.force_interact) is true.
258 # unless the --i flag (App.force_interact) is true.
251 force_interact = Bool(False, config=True,
259 force_interact = Bool(False, config=True,
252 help="""If a command or file is given via the command-line,
260 help="""If a command or file is given via the command-line,
253 e.g. 'ipython foo.py"""
261 e.g. 'ipython foo.py"""
254 )
262 )
255 def _force_interact_changed(self, name, old, new):
263 def _force_interact_changed(self, name, old, new):
256 if new:
264 if new:
257 self.interact = True
265 self.interact = True
258
266
259 def _file_to_run_changed(self, name, old, new):
267 def _file_to_run_changed(self, name, old, new):
260 if new and not self.force_interact:
268 if new and not self.force_interact:
261 self.interact = False
269 self.interact = False
262 _code_to_run_changed = _file_to_run_changed
270 _code_to_run_changed = _file_to_run_changed
263
271
264 # internal, not-configurable
272 # internal, not-configurable
265 interact=Bool(True)
273 interact=Bool(True)
266
274
267
275
268 def parse_command_line(self, argv=None):
276 def parse_command_line(self, argv=None):
269 """override to allow old '-pylab' flag with deprecation warning"""
277 """override to allow old '-pylab' flag with deprecation warning"""
270
278
271 argv = sys.argv[1:] if argv is None else argv
279 argv = sys.argv[1:] if argv is None else argv
272
280
273 if '-pylab' in argv:
281 if '-pylab' in argv:
274 # deprecated `-pylab` given,
282 # deprecated `-pylab` given,
275 # warn and transform into current syntax
283 # warn and transform into current syntax
276 argv = argv[:] # copy, don't clobber
284 argv = argv[:] # copy, don't clobber
277 idx = argv.index('-pylab')
285 idx = argv.index('-pylab')
278 warn.warn("`-pylab` flag has been deprecated.\n"
286 warn.warn("`-pylab` flag has been deprecated.\n"
279 " Use `--pylab` instead, or `--pylab=foo` to specify a backend.")
287 " Use `--pylab` instead, or `--pylab=foo` to specify a backend.")
280 sub = '--pylab'
288 sub = '--pylab'
281 if len(argv) > idx+1:
289 if len(argv) > idx+1:
282 # check for gui arg, as in '-pylab qt'
290 # check for gui arg, as in '-pylab qt'
283 gui = argv[idx+1]
291 gui = argv[idx+1]
284 if gui in ('wx', 'qt', 'qt4', 'gtk', 'auto'):
292 if gui in ('wx', 'qt', 'qt4', 'gtk', 'auto'):
285 sub = '--pylab='+gui
293 sub = '--pylab='+gui
286 argv.pop(idx+1)
294 argv.pop(idx+1)
287 argv[idx] = sub
295 argv[idx] = sub
288
296
289 return super(TerminalIPythonApp, self).parse_command_line(argv)
297 return super(TerminalIPythonApp, self).parse_command_line(argv)
290
298
291 @catch_config_error
299 @catch_config_error
292 def initialize(self, argv=None):
300 def initialize(self, argv=None):
293 """Do actions after construct, but before starting the app."""
301 """Do actions after construct, but before starting the app."""
294 super(TerminalIPythonApp, self).initialize(argv)
302 super(TerminalIPythonApp, self).initialize(argv)
295 if self.subapp is not None:
303 if self.subapp is not None:
296 # don't bother initializing further, starting subapp
304 # don't bother initializing further, starting subapp
297 return
305 return
298 if not self.ignore_old_config:
306 if not self.ignore_old_config:
299 check_for_old_config(self.ipython_dir)
307 check_for_old_config(self.ipython_dir)
300 # print self.extra_args
308 # print self.extra_args
301 if self.extra_args:
309 if self.extra_args:
302 self.file_to_run = self.extra_args[0]
310 self.file_to_run = self.extra_args[0]
303 # create the shell
311 # create the shell
304 self.init_shell()
312 self.init_shell()
305 # and draw the banner
313 # and draw the banner
306 self.init_banner()
314 self.init_banner()
307 # Now a variety of things that happen after the banner is printed.
315 # Now a variety of things that happen after the banner is printed.
308 self.init_gui_pylab()
316 self.init_gui_pylab()
309 self.init_extensions()
317 self.init_extensions()
310 self.init_code()
318 self.init_code()
311
319
312 def init_shell(self):
320 def init_shell(self):
313 """initialize the InteractiveShell instance"""
321 """initialize the InteractiveShell instance"""
314 # I am a little hesitant to put these into InteractiveShell itself.
322 # I am a little hesitant to put these into InteractiveShell itself.
315 # But that might be the place for them
323 # But that might be the place for them
316 sys.path.insert(0, '')
324 sys.path.insert(0, '')
317
325
318 # Create an InteractiveShell instance.
326 # Create an InteractiveShell instance.
319 # shell.display_banner should always be False for the terminal
327 # shell.display_banner should always be False for the terminal
320 # based app, because we call shell.show_banner() by hand below
328 # based app, because we call shell.show_banner() by hand below
321 # so the banner shows *before* all extension loading stuff.
329 # so the banner shows *before* all extension loading stuff.
322 self.shell = TerminalInteractiveShell.instance(config=self.config,
330 self.shell = TerminalInteractiveShell.instance(config=self.config,
323 display_banner=False, profile_dir=self.profile_dir,
331 display_banner=False, profile_dir=self.profile_dir,
324 ipython_dir=self.ipython_dir)
332 ipython_dir=self.ipython_dir)
325 self.shell.configurables.append(self)
333 self.shell.configurables.append(self)
326
334
327 def init_banner(self):
335 def init_banner(self):
328 """optionally display the banner"""
336 """optionally display the banner"""
329 if self.display_banner and self.interact:
337 if self.display_banner and self.interact:
330 self.shell.show_banner()
338 self.shell.show_banner()
331 # Make sure there is a space below the banner.
339 # Make sure there is a space below the banner.
332 if self.log_level <= logging.INFO: print
340 if self.log_level <= logging.INFO: print
333
341
334
342
335 def init_gui_pylab(self):
343 def init_gui_pylab(self):
336 """Enable GUI event loop integration, taking pylab into account."""
344 """Enable GUI event loop integration, taking pylab into account."""
337 gui = self.gui
345 gui = self.gui
338
346
339 # Using `pylab` will also require gui activation, though which toolkit
347 # Using `pylab` will also require gui activation, though which toolkit
340 # to use may be chosen automatically based on mpl configuration.
348 # to use may be chosen automatically based on mpl configuration.
341 if self.pylab:
349 if self.pylab:
342 activate = self.shell.enable_pylab
350 activate = self.shell.enable_pylab
343 if self.pylab == 'auto':
351 if self.pylab == 'auto':
344 gui = None
352 gui = None
345 else:
353 else:
346 gui = self.pylab
354 gui = self.pylab
347 else:
355 else:
348 # Enable only GUI integration, no pylab
356 # Enable only GUI integration, no pylab
349 activate = inputhook.enable_gui
357 activate = inputhook.enable_gui
350
358
351 if gui or self.pylab:
359 if gui or self.pylab:
352 try:
360 try:
353 self.log.info("Enabling GUI event loop integration, "
361 self.log.info("Enabling GUI event loop integration, "
354 "toolkit=%s, pylab=%s" % (gui, self.pylab) )
362 "toolkit=%s, pylab=%s" % (gui, self.pylab) )
355 if self.pylab:
363 if self.pylab:
356 activate(gui, import_all=self.pylab_import_all)
364 activate(gui, import_all=self.pylab_import_all)
357 else:
365 else:
358 activate(gui)
366 activate(gui)
359 except:
367 except:
360 self.log.warn("Error in enabling GUI event loop integration:")
368 self.log.warn("Error in enabling GUI event loop integration:")
361 self.shell.showtraceback()
369 self.shell.showtraceback()
362
370
363 def start(self):
371 def start(self):
364 if self.subapp is not None:
372 if self.subapp is not None:
365 return self.subapp.start()
373 return self.subapp.start()
366 # perform any prexec steps:
374 # perform any prexec steps:
367 if self.interact:
375 if self.interact:
368 self.log.debug("Starting IPython's mainloop...")
376 self.log.debug("Starting IPython's mainloop...")
369 self.shell.mainloop()
377 self.shell.mainloop()
370 else:
378 else:
371 self.log.debug("IPython not interactive...")
379 self.log.debug("IPython not interactive...")
372
380
373
381
374 def load_default_config(ipython_dir=None):
382 def load_default_config(ipython_dir=None):
375 """Load the default config file from the default ipython_dir.
383 """Load the default config file from the default ipython_dir.
376
384
377 This is useful for embedded shells.
385 This is useful for embedded shells.
378 """
386 """
379 if ipython_dir is None:
387 if ipython_dir is None:
380 ipython_dir = get_ipython_dir()
388 ipython_dir = get_ipython_dir()
381 profile_dir = os.path.join(ipython_dir, 'profile_default')
389 profile_dir = os.path.join(ipython_dir, 'profile_default')
382 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
390 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
383 try:
391 try:
384 config = cl.load_config()
392 config = cl.load_config()
385 except ConfigFileNotFound:
393 except ConfigFileNotFound:
386 # no config found
394 # no config found
387 config = Config()
395 config = Config()
388 return config
396 return config
389
397
390
398
391 def launch_new_instance():
399 def launch_new_instance():
392 """Create and run a full blown IPython instance"""
400 """Create and run a full blown IPython instance"""
393 app = TerminalIPythonApp.instance()
401 app = TerminalIPythonApp.instance()
394 app.initialize()
402 app.initialize()
395 app.start()
403 app.start()
396
404
397
405
398 if __name__ == '__main__':
406 if __name__ == '__main__':
399 launch_new_instance()
407 launch_new_instance()
@@ -1,255 +1,315
1 """Utilities for connecting to kernels
1 """Utilities for connecting to kernels
2
2
3 Authors:
3 Authors:
4
4
5 * Min Ragan-Kelley
5 * Min Ragan-Kelley
6
6
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2011 The IPython Development Team
10 # Copyright (C) 2011 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 import glob
20 import glob
21 import json
21 import json
22 import os
22 import os
23 import sys
23 import sys
24 from getpass import getpass
24 from getpass import getpass
25 from subprocess import Popen, PIPE
25 from subprocess import Popen, PIPE
26
26
27 # external imports
27 # external imports
28 from IPython.external.ssh import tunnel
28 from IPython.external.ssh import tunnel
29
29
30 # IPython imports
30 # IPython imports
31 from IPython.core.profiledir import ProfileDir
31 from IPython.core.profiledir import ProfileDir
32 from IPython.utils.path import filefind, get_ipython_dir
32 from IPython.utils.path import filefind, get_ipython_dir
33 from IPython.utils.py3compat import str_to_bytes
33 from IPython.utils.py3compat import str_to_bytes
34
34
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Functions
37 # Functions
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 def get_connection_file(app=None):
40 def get_connection_file(app=None):
41 """Return the path to the connection file of an app
41 """Return the path to the connection file of an app
42
42
43 Parameters
43 Parameters
44 ----------
44 ----------
45 app : KernelApp instance [optional]
45 app : KernelApp instance [optional]
46 If unspecified, the currently running app will be used
46 If unspecified, the currently running app will be used
47 """
47 """
48 if app is None:
48 if app is None:
49 from IPython.zmq.kernelapp import KernelApp
49 from IPython.zmq.kernelapp import KernelApp
50 if not KernelApp.initialized():
50 if not KernelApp.initialized():
51 raise RuntimeError("app not specified, and not in a running Kernel")
51 raise RuntimeError("app not specified, and not in a running Kernel")
52
52
53 app = KernelApp.instance()
53 app = KernelApp.instance()
54 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
54 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
55
55
56 def find_connection_file(filename, profile=None):
56 def find_connection_file(filename, profile=None):
57 """find a connection file, and return its absolute path.
57 """find a connection file, and return its absolute path.
58
58
59 The current working directory and the profile's security
59 The current working directory and the profile's security
60 directory will be searched for the file if it is not given by
60 directory will be searched for the file if it is not given by
61 absolute path.
61 absolute path.
62
62
63 If profile is unspecified, then the current running application's
63 If profile is unspecified, then the current running application's
64 profile will be used, or 'default', if not run from IPython.
64 profile will be used, or 'default', if not run from IPython.
65
65
66 If the argument does not match an existing file, it will be interpreted as a
66 If the argument does not match an existing file, it will be interpreted as a
67 fileglob, and the matching file in the profile's security dir with
67 fileglob, and the matching file in the profile's security dir with
68 the latest access time will be used.
68 the latest access time will be used.
69
69
70 Parameters
70 Parameters
71 ----------
71 ----------
72 filename : str
72 filename : str
73 The connection file or fileglob to search for.
73 The connection file or fileglob to search for.
74 profile : str [optional]
74 profile : str [optional]
75 The name of the profile to use when searching for the connection file,
75 The name of the profile to use when searching for the connection file,
76 if different from the current IPython session or 'default'.
76 if different from the current IPython session or 'default'.
77
77
78 Returns
78 Returns
79 -------
79 -------
80 str : The absolute path of the connection file.
80 str : The absolute path of the connection file.
81 """
81 """
82 from IPython.core.application import BaseIPythonApplication as IPApp
82 from IPython.core.application import BaseIPythonApplication as IPApp
83 try:
83 try:
84 # quick check for absolute path, before going through logic
84 # quick check for absolute path, before going through logic
85 return filefind(filename)
85 return filefind(filename)
86 except IOError:
86 except IOError:
87 pass
87 pass
88
88
89 if profile is None:
89 if profile is None:
90 # profile unspecified, check if running from an IPython app
90 # profile unspecified, check if running from an IPython app
91 if IPApp.initialized():
91 if IPApp.initialized():
92 app = IPApp.instance()
92 app = IPApp.instance()
93 profile_dir = app.profile_dir
93 profile_dir = app.profile_dir
94 else:
94 else:
95 # not running in IPython, use default profile
95 # not running in IPython, use default profile
96 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
96 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
97 else:
97 else:
98 # find profiledir by profile name:
98 # find profiledir by profile name:
99 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
99 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
100 security_dir = profile_dir.security_dir
100 security_dir = profile_dir.security_dir
101
101
102 try:
102 try:
103 # first, try explicit name
103 # first, try explicit name
104 return filefind(filename, ['.', security_dir])
104 return filefind(filename, ['.', security_dir])
105 except IOError:
105 except IOError:
106 pass
106 pass
107
107
108 # not found by full name
108 # not found by full name
109
109
110 if '*' in filename:
110 if '*' in filename:
111 # given as a glob already
111 # given as a glob already
112 pat = filename
112 pat = filename
113 else:
113 else:
114 # accept any substring match
114 # accept any substring match
115 pat = '*%s*' % filename
115 pat = '*%s*' % filename
116 matches = glob.glob( os.path.join(security_dir, pat) )
116 matches = glob.glob( os.path.join(security_dir, pat) )
117 if not matches:
117 if not matches:
118 raise IOError("Could not find %r in %r" % (filename, security_dir))
118 raise IOError("Could not find %r in %r" % (filename, security_dir))
119 elif len(matches) == 1:
119 elif len(matches) == 1:
120 return matches[0]
120 return matches[0]
121 else:
121 else:
122 # get most recent match, by access time:
122 # get most recent match, by access time:
123 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
123 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
124
124
125 def get_connection_info(connection_file=None, unpack=False, profile=None):
125 def get_connection_info(connection_file=None, unpack=False, profile=None):
126 """Return the connection information for the current Kernel.
126 """Return the connection information for the current Kernel.
127
127
128 Parameters
128 Parameters
129 ----------
129 ----------
130 connection_file : str [optional]
130 connection_file : str [optional]
131 The connection file to be used. Can be given by absolute path, or
131 The connection file to be used. Can be given by absolute path, or
132 IPython will search in the security directory of a given profile.
132 IPython will search in the security directory of a given profile.
133 If run from IPython,
133 If run from IPython,
134
134
135 If unspecified, the connection file for the currently running
135 If unspecified, the connection file for the currently running
136 IPython Kernel will be used, which is only allowed from inside a kernel.
136 IPython Kernel will be used, which is only allowed from inside a kernel.
137 unpack : bool [default: False]
137 unpack : bool [default: False]
138 if True, return the unpacked dict, otherwise just the string contents
138 if True, return the unpacked dict, otherwise just the string contents
139 of the file.
139 of the file.
140 profile : str [optional]
140 profile : str [optional]
141 The name of the profile to use when searching for the connection file,
141 The name of the profile to use when searching for the connection file,
142 if different from the current IPython session or 'default'.
142 if different from the current IPython session or 'default'.
143
143
144
144
145 Returns
145 Returns
146 -------
146 -------
147 The connection dictionary of the current kernel, as string or dict,
147 The connection dictionary of the current kernel, as string or dict,
148 depending on `unpack`.
148 depending on `unpack`.
149 """
149 """
150 if connection_file is None:
150 if connection_file is None:
151 # get connection file from current kernel
151 # get connection file from current kernel
152 cf = get_connection_file()
152 cf = get_connection_file()
153 else:
153 else:
154 # connection file specified, allow shortnames:
154 # connection file specified, allow shortnames:
155 cf = find_connection_file(connection_file, profile=profile)
155 cf = find_connection_file(connection_file, profile=profile)
156
156
157 with open(cf) as f:
157 with open(cf) as f:
158 info = f.read()
158 info = f.read()
159
159
160 if unpack:
160 if unpack:
161 info = json.loads(info)
161 info = json.loads(info)
162 # ensure key is bytes:
162 # ensure key is bytes:
163 info['key'] = str_to_bytes(info.get('key', ''))
163 info['key'] = str_to_bytes(info.get('key', ''))
164 return info
164 return info
165
165
166 def connect_qtconsole(connection_file=None, argv=None, profile=None):
166 def connect_qtconsole(connection_file=None, argv=None, profile=None):
167 """Connect a qtconsole to the current kernel.
167 """Connect a qtconsole to the current kernel.
168
168
169 This is useful for connecting a second qtconsole to a kernel, or to a
169 This is useful for connecting a second qtconsole to a kernel, or to a
170 local notebook.
170 local notebook.
171
171
172 Parameters
172 Parameters
173 ----------
173 ----------
174 connection_file : str [optional]
174 connection_file : str [optional]
175 The connection file to be used. Can be given by absolute path, or
175 The connection file to be used. Can be given by absolute path, or
176 IPython will search in the security directory of a given profile.
176 IPython will search in the security directory of a given profile.
177 If run from IPython,
177 If run from IPython,
178
178
179 If unspecified, the connection file for the currently running
179 If unspecified, the connection file for the currently running
180 IPython Kernel will be used, which is only allowed from inside a kernel.
180 IPython Kernel will be used, which is only allowed from inside a kernel.
181 argv : list [optional]
181 argv : list [optional]
182 Any extra args to be passed to the console.
182 Any extra args to be passed to the console.
183 profile : str [optional]
183 profile : str [optional]
184 The name of the profile to use when searching for the connection file,
184 The name of the profile to use when searching for the connection file,
185 if different from the current IPython session or 'default'.
185 if different from the current IPython session or 'default'.
186
186
187
187
188 Returns
188 Returns
189 -------
189 -------
190 subprocess.Popen instance running the qtconsole frontend
190 subprocess.Popen instance running the qtconsole frontend
191 """
191 """
192 argv = [] if argv is None else argv
192 argv = [] if argv is None else argv
193
193
194 if connection_file is None:
194 if connection_file is None:
195 # get connection file from current kernel
195 # get connection file from current kernel
196 cf = get_connection_file()
196 cf = get_connection_file()
197 else:
197 else:
198 cf = find_connection_file(connection_file, profile=profile)
198 cf = find_connection_file(connection_file, profile=profile)
199
199
200 cmd = ';'.join([
200 cmd = ';'.join([
201 "from IPython.frontend.qt.console import qtconsoleapp",
201 "from IPython.frontend.qt.console import qtconsoleapp",
202 "qtconsoleapp.main()"
202 "qtconsoleapp.main()"
203 ])
203 ])
204
204
205 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
205 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
206
206
207 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
207 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
208 """tunnel connections to a kernel via ssh
208 """tunnel connections to a kernel via ssh
209
209
210 This will open four SSH tunnels from localhost on this machine to the
210 This will open four SSH tunnels from localhost on this machine to the
211 ports associated with the kernel. They can be either direct
211 ports associated with the kernel. They can be either direct
212 localhost-localhost tunnels, or if an intermediate server is necessary,
212 localhost-localhost tunnels, or if an intermediate server is necessary,
213 the kernel must be listening on a public IP.
213 the kernel must be listening on a public IP.
214
214
215 Parameters
215 Parameters
216 ----------
216 ----------
217 connection_info : dict or str (path)
217 connection_info : dict or str (path)
218 Either a connection dict, or the path to a JSON connection file
218 Either a connection dict, or the path to a JSON connection file
219 sshserver : str
219 sshserver : str
220 The ssh sever to use to tunnel to the kernel. Can be a full
220 The ssh sever to use to tunnel to the kernel. Can be a full
221 `user@server:port` string. ssh config aliases are respected.
221 `user@server:port` string. ssh config aliases are respected.
222 sshkey : str [optional]
222 sshkey : str [optional]
223 Path to file containing ssh key to use for authentication.
223 Path to file containing ssh key to use for authentication.
224 Only necessary if your ssh config does not already associate
224 Only necessary if your ssh config does not already associate
225 a keyfile with the host.
225 a keyfile with the host.
226
226
227 Returns
227 Returns
228 -------
228 -------
229
229
230 (shell, iopub, stdin, hb) : ints
230 (shell, iopub, stdin, hb) : ints
231 The four ports on localhost that have been forwarded to the kernel.
231 The four ports on localhost that have been forwarded to the kernel.
232 """
232 """
233 if isinstance(connection_info, basestring):
233 if isinstance(connection_info, basestring):
234 # it's a path, unpack it
234 # it's a path, unpack it
235 with open(connection_info) as f:
235 with open(connection_info) as f:
236 connection_info = json.loads(f.read())
236 connection_info = json.loads(f.read())
237
237
238 cf = connection_info
238 cf = connection_info
239
239
240 lports = tunnel.select_random_ports(4)
240 lports = tunnel.select_random_ports(4)
241 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
241 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
242
242
243 remote_ip = cf['ip']
243 remote_ip = cf['ip']
244
244
245 if tunnel.try_passwordless_ssh(sshserver, sshkey):
245 if tunnel.try_passwordless_ssh(sshserver, sshkey):
246 password=False
246 password=False
247 else:
247 else:
248 password = getpass("SSH Password for %s: "%sshserver)
248 password = getpass("SSH Password for %s: "%sshserver)
249
249
250 for lp,rp in zip(lports, rports):
250 for lp,rp in zip(lports, rports):
251 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
251 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
252
252
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
@@ -1,96 +1,97
1 """Implementation of the parametric test support for Python 2.x
1 """Implementation of the parametric test support for Python 2.x
2 """
2 """
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2009-2011 The IPython Development Team
5 # Copyright (C) 2009-2011 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
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
18 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
19 # Classes and functions
20 # Classes and functions
20 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
21
22
22 def isgenerator(func):
23 def isgenerator(func):
23 try:
24 try:
24 return func.func_code.co_flags & CO_GENERATOR != 0
25 return func.func_code.co_flags & CO_GENERATOR != 0
25 except AttributeError:
26 except AttributeError:
26 return False
27 return False
27
28
28 class ParametricTestCase(unittest.TestCase):
29 class ParametricTestCase(unittest.TestCase):
29 """Write parametric tests in normal unittest testcase form.
30 """Write parametric tests in normal unittest testcase form.
30
31
31 Limitations: the last iteration misses printing out a newline when running
32 Limitations: the last iteration misses printing out a newline when running
32 in verbose mode.
33 in verbose mode.
33 """
34 """
34 def run_parametric(self, result, testMethod):
35 def run_parametric(self, result, testMethod):
35 # But if we have a test generator, we iterate it ourselves
36 # But if we have a test generator, we iterate it ourselves
36 testgen = testMethod()
37 testgen = testMethod()
37 while True:
38 while True:
38 try:
39 try:
39 # Initialize test
40 # Initialize test
40 result.startTest(self)
41 result.startTest(self)
41
42
42 # SetUp
43 # SetUp
43 try:
44 try:
44 self.setUp()
45 self.setUp()
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
52 try:
53 try:
53 testgen.next()
54 testgen.next()
54 ok = True
55 ok = True
55 except StopIteration:
56 except StopIteration:
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
74 finally:
75 finally:
75 result.stopTest(self)
76 result.stopTest(self)
76
77
77 def run(self, result=None):
78 def run(self, result=None):
78 if result is None:
79 if result is None:
79 result = self.defaultTestResult()
80 result = self.defaultTestResult()
80 testMethod = getattr(self, self._testMethodName)
81 testMethod = getattr(self, self._testMethodName)
81 # For normal tests, we just call the base class and return that
82 # For normal tests, we just call the base class and return that
82 if isgenerator(testMethod):
83 if isgenerator(testMethod):
83 return self.run_parametric(result, testMethod)
84 return self.run_parametric(result, testMethod)
84 else:
85 else:
85 return super(ParametricTestCase, self).run(result)
86 return super(ParametricTestCase, self).run(result)
86
87
87
88
88 def parametric(func):
89 def parametric(func):
89 """Decorator to make a simple function into a normal test via unittest."""
90 """Decorator to make a simple function into a normal test via unittest."""
90
91
91 class Tester(ParametricTestCase):
92 class Tester(ParametricTestCase):
92 test = staticmethod(func)
93 test = staticmethod(func)
93
94
94 Tester.__name__ = func.__name__
95 Tester.__name__ = func.__name__
95
96
96 return Tester
97 return Tester
@@ -1,129 +1,154
1 """Implement a fully blocking kernel manager.
1 """Implement a fully blocking kernel manager.
2
2
3 Useful for test suites and blocking terminal interfaces.
3 Useful for test suites and blocking terminal interfaces.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2010-2011 The IPython Development Team
6 # Copyright (C) 2010-2011 The IPython Development Team
7 #
7 #
8 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING.txt, distributed as part of this software.
9 # the file COPYING.txt, distributed as part of this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from __future__ import print_function
15 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
22 from IPython.utils.traitlets import Type
23 from IPython.utils.traitlets import Type
23
24
24 from .kernelmanager import (KernelManager, SubSocketChannel, HBSocketChannel,
25 from .kernelmanager import (KernelManager, SubSocketChannel, HBSocketChannel,
25 ShellSocketChannel, StdInSocketChannel)
26 ShellSocketChannel, StdInSocketChannel)
26
27
27 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
28 # Functions and classes
29 # Functions and classes
29 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
30
31
31 class BlockingSubSocketChannel(SubSocketChannel):
32 class BlockingSubSocketChannel(SubSocketChannel):
32
33
33 def __init__(self, context, session, address=None):
34 def __init__(self, context, session, address=None):
34 super(BlockingSubSocketChannel, self).__init__(context, session,
35 super(BlockingSubSocketChannel, self).__init__(context, session,
35 address)
36 address)
36 self._in_queue = Queue()
37 self._in_queue = Queue()
37
38
38 def call_handlers(self, msg):
39 def call_handlers(self, msg):
39 #io.rprint('[[Sub]]', msg) # dbg
40 #io.rprint('[[Sub]]', msg) # dbg
40 self._in_queue.put(msg)
41 self._in_queue.put(msg)
41
42
42 def msg_ready(self):
43 def msg_ready(self):
43 """Is there a message that has been received?"""
44 """Is there a message that has been received?"""
44 if self._in_queue.qsize() == 0:
45 if self._in_queue.qsize() == 0:
45 return False
46 return False
46 else:
47 else:
47 return True
48 return True
48
49
49 def get_msg(self, block=True, timeout=None):
50 def get_msg(self, block=True, timeout=None):
50 """Get a message if there is one that is ready."""
51 """Get a message if there is one that is ready."""
51 if block and timeout is None:
52 if block and timeout is None:
52 # never use timeout=None, because get
53 # never use timeout=None, because get
53 # becomes uninterruptible
54 # becomes uninterruptible
54 timeout = 1e6
55 timeout = 1e6
55 return self._in_queue.get(block, timeout)
56 return self._in_queue.get(block, timeout)
56
57
57 def get_msgs(self):
58 def get_msgs(self):
58 """Get all messages that are currently ready."""
59 """Get all messages that are currently ready."""
59 msgs = []
60 msgs = []
60 while True:
61 while True:
61 try:
62 try:
62 msgs.append(self.get_msg(block=False))
63 msgs.append(self.get_msg(block=False))
63 except Empty:
64 except Empty:
64 break
65 break
65 return msgs
66 return msgs
66
67
67
68
68 class BlockingShellSocketChannel(ShellSocketChannel):
69 class BlockingShellSocketChannel(ShellSocketChannel):
69
70
70 def __init__(self, context, session, address=None):
71 def __init__(self, context, session, address=None):
71 super(BlockingShellSocketChannel, self).__init__(context, session,
72 super(BlockingShellSocketChannel, self).__init__(context, session,
72 address)
73 address)
73 self._in_queue = Queue()
74 self._in_queue = Queue()
74
75
75 def call_handlers(self, msg):
76 def call_handlers(self, msg):
76 #io.rprint('[[Shell]]', msg) # dbg
77 #io.rprint('[[Shell]]', msg) # dbg
77 self._in_queue.put(msg)
78 self._in_queue.put(msg)
78
79
79 def msg_ready(self):
80 def msg_ready(self):
80 """Is there a message that has been received?"""
81 """Is there a message that has been received?"""
81 if self._in_queue.qsize() == 0:
82 if self._in_queue.qsize() == 0:
82 return False
83 return False
83 else:
84 else:
84 return True
85 return True
85
86
86 def get_msg(self, block=True, timeout=None):
87 def get_msg(self, block=True, timeout=None):
87 """Get a message if there is one that is ready."""
88 """Get a message if there is one that is ready."""
88 if block and timeout is None:
89 if block and timeout is None:
89 # never use timeout=None, because get
90 # never use timeout=None, because get
90 # becomes uninterruptible
91 # becomes uninterruptible
91 timeout = 1e6
92 timeout = 1e6
92 return self._in_queue.get(block, timeout)
93 return self._in_queue.get(block, timeout)
93
94
94 def get_msgs(self):
95 def get_msgs(self):
95 """Get all messages that are currently ready."""
96 """Get all messages that are currently ready."""
96 msgs = []
97 msgs = []
97 while True:
98 while True:
98 try:
99 try:
99 msgs.append(self.get_msg(block=False))
100 msgs.append(self.get_msg(block=False))
100 except Empty:
101 except Empty:
101 break
102 break
102 return msgs
103 return msgs
103
104
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
122 class BlockingKernelManager(KernelManager):
147 class BlockingKernelManager(KernelManager):
123
148
124 # The classes to use for the various channels.
149 # The classes to use for the various channels.
125 shell_channel_class = Type(BlockingShellSocketChannel)
150 shell_channel_class = Type(BlockingShellSocketChannel)
126 sub_channel_class = Type(BlockingSubSocketChannel)
151 sub_channel_class = Type(BlockingSubSocketChannel)
127 stdin_channel_class = Type(BlockingStdInSocketChannel)
152 stdin_channel_class = Type(BlockingStdInSocketChannel)
128 hb_channel_class = Type(BlockingHBSocketChannel)
153 hb_channel_class = Type(BlockingHBSocketChannel)
129
154
@@ -1,635 +1,651
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """A simple interactive kernel that talks to a frontend over 0MQ.
2 """A simple interactive kernel that talks to a frontend over 0MQ.
3
3
4 Things to do:
4 Things to do:
5
5
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 call set_parent on all the PUB objects with the message about to be executed.
7 call set_parent on all the PUB objects with the message about to be executed.
8 * Implement random port and security key logic.
8 * Implement random port and security key logic.
9 * Implement control messages.
9 * Implement control messages.
10 * Implement event loop and poll version.
10 * Implement event loop and poll version.
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import __builtin__
19 import __builtin__
20 import atexit
20 import atexit
21 import sys
21 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
29 # Local imports.
31 # Local imports.
30 from IPython.core import pylabtools
32 from IPython.core import pylabtools
31 from IPython.config.configurable import Configurable
33 from IPython.config.configurable import Configurable
32 from IPython.config.application import boolean_flag, catch_config_error
34 from IPython.config.application import boolean_flag, catch_config_error
33 from IPython.core.application import ProfileDir
35 from IPython.core.application import ProfileDir
34 from IPython.core.error import StdinNotImplementedError
36 from IPython.core.error import StdinNotImplementedError
35 from IPython.core.shellapp import (
37 from IPython.core.shellapp import (
36 InteractiveShellApp, shell_flags, shell_aliases
38 InteractiveShellApp, shell_flags, shell_aliases
37 )
39 )
38 from IPython.utils import io
40 from IPython.utils import io
39 from IPython.utils import py3compat
41 from IPython.utils import py3compat
40 from IPython.utils.jsonutil import json_clean
42 from IPython.utils.jsonutil import json_clean
41 from IPython.utils.traitlets import (
43 from IPython.utils.traitlets import (
42 Any, Instance, Float, Dict, CaselessStrEnum
44 Any, Instance, Float, Dict, CaselessStrEnum
43 )
45 )
44
46
45 from entry_point import base_launch_kernel
47 from entry_point import base_launch_kernel
46 from kernelapp import KernelApp, kernel_flags, kernel_aliases
48 from kernelapp import KernelApp, kernel_flags, kernel_aliases
47 from session import Session, Message
49 from session import Session, Message
48 from zmqshell import ZMQInteractiveShell
50 from zmqshell import ZMQInteractiveShell
49
51
50
52
51 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
52 # Main kernel class
54 # Main kernel class
53 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
54
56
55 class Kernel(Configurable):
57 class Kernel(Configurable):
56
58
57 #---------------------------------------------------------------------------
59 #---------------------------------------------------------------------------
58 # Kernel interface
60 # Kernel interface
59 #---------------------------------------------------------------------------
61 #---------------------------------------------------------------------------
60
62
61 # attribute to override with a GUI
63 # attribute to override with a GUI
62 eventloop = Any(None)
64 eventloop = Any(None)
63
65
64 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
66 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
65 session = Instance(Session)
67 session = Instance(Session)
66 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
68 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
67 shell_socket = Instance('zmq.Socket')
69 shell_socket = Instance('zmq.Socket')
68 iopub_socket = Instance('zmq.Socket')
70 iopub_socket = Instance('zmq.Socket')
69 stdin_socket = Instance('zmq.Socket')
71 stdin_socket = Instance('zmq.Socket')
70 log = Instance(logging.Logger)
72 log = Instance(logging.Logger)
71
73
72 # Private interface
74 # Private interface
73
75
74 # Time to sleep after flushing the stdout/err buffers in each execute
76 # Time to sleep after flushing the stdout/err buffers in each execute
75 # cycle. While this introduces a hard limit on the minimal latency of the
77 # cycle. While this introduces a hard limit on the minimal latency of the
76 # execute cycle, it helps prevent output synchronization problems for
78 # execute cycle, it helps prevent output synchronization problems for
77 # clients.
79 # clients.
78 # Units are in seconds. The minimum zmq latency on local host is probably
80 # Units are in seconds. The minimum zmq latency on local host is probably
79 # ~150 microseconds, set this to 500us for now. We may need to increase it
81 # ~150 microseconds, set this to 500us for now. We may need to increase it
80 # a little if it's not enough after more interactive testing.
82 # a little if it's not enough after more interactive testing.
81 _execute_sleep = Float(0.0005, config=True)
83 _execute_sleep = Float(0.0005, config=True)
82
84
83 # Frequency of the kernel's event loop.
85 # Frequency of the kernel's event loop.
84 # Units are in seconds, kernel subclasses for GUI toolkits may need to
86 # Units are in seconds, kernel subclasses for GUI toolkits may need to
85 # adapt to milliseconds.
87 # adapt to milliseconds.
86 _poll_interval = Float(0.05, config=True)
88 _poll_interval = Float(0.05, config=True)
87
89
88 # If the shutdown was requested over the network, we leave here the
90 # If the shutdown was requested over the network, we leave here the
89 # necessary reply message so it can be sent by our registered atexit
91 # necessary reply message so it can be sent by our registered atexit
90 # handler. This ensures that the reply is only sent to clients truly at
92 # handler. This ensures that the reply is only sent to clients truly at
91 # the end of our shutdown process (which happens after the underlying
93 # the end of our shutdown process (which happens after the underlying
92 # IPython shell's own shutdown).
94 # IPython shell's own shutdown).
93 _shutdown_message = None
95 _shutdown_message = None
94
96
95 # This is a dict of port number that the kernel is listening on. It is set
97 # This is a dict of port number that the kernel is listening on. It is set
96 # by record_ports and used by connect_request.
98 # by record_ports and used by connect_request.
97 _recorded_ports = Dict()
99 _recorded_ports = Dict()
98
100
99
101
100
102
101 def __init__(self, **kwargs):
103 def __init__(self, **kwargs):
102 super(Kernel, self).__init__(**kwargs)
104 super(Kernel, self).__init__(**kwargs)
103
105
104 # Before we even start up the shell, register *first* our exit handlers
106 # Before we even start up the shell, register *first* our exit handlers
105 # so they come before the shell's
107 # so they come before the shell's
106 atexit.register(self._at_shutdown)
108 atexit.register(self._at_shutdown)
107
109
108 # Initialize the InteractiveShell subclass
110 # Initialize the InteractiveShell subclass
109 self.shell = ZMQInteractiveShell.instance(config=self.config,
111 self.shell = ZMQInteractiveShell.instance(config=self.config,
110 profile_dir = self.profile_dir,
112 profile_dir = self.profile_dir,
111 )
113 )
112 self.shell.displayhook.session = self.session
114 self.shell.displayhook.session = self.session
113 self.shell.displayhook.pub_socket = self.iopub_socket
115 self.shell.displayhook.pub_socket = self.iopub_socket
114 self.shell.display_pub.session = self.session
116 self.shell.display_pub.session = self.session
115 self.shell.display_pub.pub_socket = self.iopub_socket
117 self.shell.display_pub.pub_socket = self.iopub_socket
116
118
117 # TMP - hack while developing
119 # TMP - hack while developing
118 self.shell._reply_content = None
120 self.shell._reply_content = None
119
121
120 # Build dict of handlers for message types
122 # Build dict of handlers for message types
121 msg_types = [ 'execute_request', 'complete_request',
123 msg_types = [ 'execute_request', 'complete_request',
122 'object_info_request', 'history_request',
124 'object_info_request', 'history_request',
123 'connect_request', 'shutdown_request']
125 'connect_request', 'shutdown_request']
124 self.handlers = {}
126 self.handlers = {}
125 for msg_type in msg_types:
127 for msg_type in msg_types:
126 self.handlers[msg_type] = getattr(self, msg_type)
128 self.handlers[msg_type] = getattr(self, msg_type)
127
129
128 def do_one_iteration(self):
130 def do_one_iteration(self):
129 """Do one iteration of the kernel's evaluation loop.
131 """Do one iteration of the kernel's evaluation loop.
130 """
132 """
131 try:
133 try:
132 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
134 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
133 except Exception:
135 except Exception:
134 self.log.warn("Invalid Message:", exc_info=True)
136 self.log.warn("Invalid Message:", exc_info=True)
135 return
137 return
136 if msg is None:
138 if msg is None:
137 return
139 return
138
140
139 msg_type = msg['header']['msg_type']
141 msg_type = msg['header']['msg_type']
140
142
141 # This assert will raise in versions of zeromq 2.0.7 and lesser.
143 # This assert will raise in versions of zeromq 2.0.7 and lesser.
142 # We now require 2.0.8 or above, so we can uncomment for safety.
144 # We now require 2.0.8 or above, so we can uncomment for safety.
143 # print(ident,msg, file=sys.__stdout__)
145 # print(ident,msg, file=sys.__stdout__)
144 assert ident is not None, "Missing message part."
146 assert ident is not None, "Missing message part."
145
147
146 # Print some info about this message and leave a '--->' marker, so it's
148 # Print some info about this message and leave a '--->' marker, so it's
147 # easier to trace visually the message chain when debugging. Each
149 # easier to trace visually the message chain when debugging. Each
148 # handler prints its message at the end.
150 # handler prints its message at the end.
149 self.log.debug('\n*** MESSAGE TYPE:'+str(msg_type)+'***')
151 self.log.debug('\n*** MESSAGE TYPE:'+str(msg_type)+'***')
150 self.log.debug(' Content: '+str(msg['content'])+'\n --->\n ')
152 self.log.debug(' Content: '+str(msg['content'])+'\n --->\n ')
151
153
152 # Find and call actual handler for message
154 # Find and call actual handler for message
153 handler = self.handlers.get(msg_type, None)
155 handler = self.handlers.get(msg_type, None)
154 if handler is None:
156 if handler is None:
155 self.log.error("UNKNOWN MESSAGE TYPE:" +str(msg))
157 self.log.error("UNKNOWN MESSAGE TYPE:" +str(msg))
156 else:
158 else:
157 handler(ident, msg)
159 handler(ident, msg)
158
160
159 # Check whether we should exit, in case the incoming message set the
161 # Check whether we should exit, in case the incoming message set the
160 # exit flag on
162 # exit flag on
161 if self.shell.exit_now:
163 if self.shell.exit_now:
162 self.log.debug('\nExiting IPython kernel...')
164 self.log.debug('\nExiting IPython kernel...')
163 # We do a normal, clean exit, which allows any actions registered
165 # We do a normal, clean exit, which allows any actions registered
164 # via atexit (such as history saving) to take place.
166 # via atexit (such as history saving) to take place.
165 sys.exit(0)
167 sys.exit(0)
166
168
167
169
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
174 while self.eventloop is None:
179 while self.eventloop is None:
175 try:
180 try:
176 # scale by extra factor of 10, because there is no
181 # scale by extra factor of 10, because there is no
177 # reason for this to be anything less than ~ 0.1s
182 # reason for this to be anything less than ~ 0.1s
178 # since it is a real poller and will respond
183 # since it is a real poller and will respond
179 # to events immediately
184 # to events immediately
180
185
181 # double nested try/except, to properly catch KeyboardInterrupt
186 # double nested try/except, to properly catch KeyboardInterrupt
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)
194 except KeyboardInterrupt:
207 except KeyboardInterrupt:
195 # Ctrl-C shouldn't crash the kernel
208 # Ctrl-C shouldn't crash the kernel
196 io.raw_print("KeyboardInterrupt caught in kernel")
209 io.raw_print("KeyboardInterrupt caught in kernel")
197
210
198
211
199 def record_ports(self, ports):
212 def record_ports(self, ports):
200 """Record the ports that this kernel is using.
213 """Record the ports that this kernel is using.
201
214
202 The creator of the Kernel instance must call this methods if they
215 The creator of the Kernel instance must call this methods if they
203 want the :meth:`connect_request` method to return the port numbers.
216 want the :meth:`connect_request` method to return the port numbers.
204 """
217 """
205 self._recorded_ports = ports
218 self._recorded_ports = ports
206
219
207 #---------------------------------------------------------------------------
220 #---------------------------------------------------------------------------
208 # Kernel request handlers
221 # Kernel request handlers
209 #---------------------------------------------------------------------------
222 #---------------------------------------------------------------------------
210
223
211 def _publish_pyin(self, code, parent):
224 def _publish_pyin(self, code, parent):
212 """Publish the code request on the pyin stream."""
225 """Publish the code request on the pyin stream."""
213
226
214 self.session.send(self.iopub_socket, u'pyin', {u'code':code},
227 self.session.send(self.iopub_socket, u'pyin', {u'code':code},
215 parent=parent)
228 parent=parent)
216
229
217 def execute_request(self, ident, parent):
230 def execute_request(self, ident, parent):
218
231
219 self.session.send(self.iopub_socket,
232 self.session.send(self.iopub_socket,
220 u'status',
233 u'status',
221 {u'execution_state':u'busy'},
234 {u'execution_state':u'busy'},
222 parent=parent )
235 parent=parent )
223
236
224 try:
237 try:
225 content = parent[u'content']
238 content = parent[u'content']
226 code = content[u'code']
239 code = content[u'code']
227 silent = content[u'silent']
240 silent = content[u'silent']
228 except:
241 except:
229 self.log.error("Got bad msg: ")
242 self.log.error("Got bad msg: ")
230 self.log.error(str(Message(parent)))
243 self.log.error(str(Message(parent)))
231 return
244 return
232
245
233 shell = self.shell # we'll need this a lot here
246 shell = self.shell # we'll need this a lot here
234
247
235 # Replace raw_input. Note that is not sufficient to replace
248 # Replace raw_input. Note that is not sufficient to replace
236 # raw_input in the user namespace.
249 # raw_input in the user namespace.
237 if content.get('allow_stdin', False):
250 if content.get('allow_stdin', False):
238 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
251 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
239 else:
252 else:
240 raw_input = lambda prompt='' : self._no_raw_input()
253 raw_input = lambda prompt='' : self._no_raw_input()
241
254
242 if py3compat.PY3:
255 if py3compat.PY3:
243 __builtin__.input = raw_input
256 __builtin__.input = raw_input
244 else:
257 else:
245 __builtin__.raw_input = raw_input
258 __builtin__.raw_input = raw_input
246
259
247 # Set the parent message of the display hook and out streams.
260 # Set the parent message of the display hook and out streams.
248 shell.displayhook.set_parent(parent)
261 shell.displayhook.set_parent(parent)
249 shell.display_pub.set_parent(parent)
262 shell.display_pub.set_parent(parent)
250 sys.stdout.set_parent(parent)
263 sys.stdout.set_parent(parent)
251 sys.stderr.set_parent(parent)
264 sys.stderr.set_parent(parent)
252
265
253 # Re-broadcast our input for the benefit of listening clients, and
266 # Re-broadcast our input for the benefit of listening clients, and
254 # start computing output
267 # start computing output
255 if not silent:
268 if not silent:
256 self._publish_pyin(code, parent)
269 self._publish_pyin(code, parent)
257
270
258 reply_content = {}
271 reply_content = {}
259 try:
272 try:
260 if silent:
273 if silent:
261 # run_code uses 'exec' mode, so no displayhook will fire, and it
274 # run_code uses 'exec' mode, so no displayhook will fire, and it
262 # doesn't call logging or history manipulations. Print
275 # doesn't call logging or history manipulations. Print
263 # statements in that code will obviously still execute.
276 # statements in that code will obviously still execute.
264 shell.run_code(code)
277 shell.run_code(code)
265 else:
278 else:
266 # FIXME: the shell calls the exception handler itself.
279 # FIXME: the shell calls the exception handler itself.
267 shell.run_cell(code, store_history=True)
280 shell.run_cell(code, store_history=True)
268 except:
281 except:
269 status = u'error'
282 status = u'error'
270 # FIXME: this code right now isn't being used yet by default,
283 # FIXME: this code right now isn't being used yet by default,
271 # because the run_cell() call above directly fires off exception
284 # because the run_cell() call above directly fires off exception
272 # reporting. This code, therefore, is only active in the scenario
285 # reporting. This code, therefore, is only active in the scenario
273 # where runlines itself has an unhandled exception. We need to
286 # where runlines itself has an unhandled exception. We need to
274 # uniformize this, for all exception construction to come from a
287 # uniformize this, for all exception construction to come from a
275 # single location in the codbase.
288 # single location in the codbase.
276 etype, evalue, tb = sys.exc_info()
289 etype, evalue, tb = sys.exc_info()
277 tb_list = traceback.format_exception(etype, evalue, tb)
290 tb_list = traceback.format_exception(etype, evalue, tb)
278 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
291 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
279 else:
292 else:
280 status = u'ok'
293 status = u'ok'
281
294
282 reply_content[u'status'] = status
295 reply_content[u'status'] = status
283
296
284 # Return the execution counter so clients can display prompts
297 # Return the execution counter so clients can display prompts
285 reply_content['execution_count'] = shell.execution_count -1
298 reply_content['execution_count'] = shell.execution_count -1
286
299
287 # FIXME - fish exception info out of shell, possibly left there by
300 # FIXME - fish exception info out of shell, possibly left there by
288 # runlines. We'll need to clean up this logic later.
301 # runlines. We'll need to clean up this logic later.
289 if shell._reply_content is not None:
302 if shell._reply_content is not None:
290 reply_content.update(shell._reply_content)
303 reply_content.update(shell._reply_content)
291 # reset after use
304 # reset after use
292 shell._reply_content = None
305 shell._reply_content = None
293
306
294 # At this point, we can tell whether the main code execution succeeded
307 # At this point, we can tell whether the main code execution succeeded
295 # or not. If it did, we proceed to evaluate user_variables/expressions
308 # or not. If it did, we proceed to evaluate user_variables/expressions
296 if reply_content['status'] == 'ok':
309 if reply_content['status'] == 'ok':
297 reply_content[u'user_variables'] = \
310 reply_content[u'user_variables'] = \
298 shell.user_variables(content[u'user_variables'])
311 shell.user_variables(content[u'user_variables'])
299 reply_content[u'user_expressions'] = \
312 reply_content[u'user_expressions'] = \
300 shell.user_expressions(content[u'user_expressions'])
313 shell.user_expressions(content[u'user_expressions'])
301 else:
314 else:
302 # If there was an error, don't even try to compute variables or
315 # If there was an error, don't even try to compute variables or
303 # expressions
316 # expressions
304 reply_content[u'user_variables'] = {}
317 reply_content[u'user_variables'] = {}
305 reply_content[u'user_expressions'] = {}
318 reply_content[u'user_expressions'] = {}
306
319
307 # Payloads should be retrieved regardless of outcome, so we can both
320 # Payloads should be retrieved regardless of outcome, so we can both
308 # recover partial output (that could have been generated early in a
321 # recover partial output (that could have been generated early in a
309 # block, before an error) and clear the payload system always.
322 # block, before an error) and clear the payload system always.
310 reply_content[u'payload'] = shell.payload_manager.read_payload()
323 reply_content[u'payload'] = shell.payload_manager.read_payload()
311 # Be agressive about clearing the payload because we don't want
324 # Be agressive about clearing the payload because we don't want
312 # it to sit in memory until the next execute_request comes in.
325 # it to sit in memory until the next execute_request comes in.
313 shell.payload_manager.clear_payload()
326 shell.payload_manager.clear_payload()
314
327
315 # Flush output before sending the reply.
328 # Flush output before sending the reply.
316 sys.stdout.flush()
329 sys.stdout.flush()
317 sys.stderr.flush()
330 sys.stderr.flush()
318 # FIXME: on rare occasions, the flush doesn't seem to make it to the
331 # FIXME: on rare occasions, the flush doesn't seem to make it to the
319 # clients... This seems to mitigate the problem, but we definitely need
332 # clients... This seems to mitigate the problem, but we definitely need
320 # to better understand what's going on.
333 # to better understand what's going on.
321 if self._execute_sleep:
334 if self._execute_sleep:
322 time.sleep(self._execute_sleep)
335 time.sleep(self._execute_sleep)
323
336
324 # Send the reply.
337 # Send the reply.
325 reply_content = json_clean(reply_content)
338 reply_content = json_clean(reply_content)
326 reply_msg = self.session.send(self.shell_socket, u'execute_reply',
339 reply_msg = self.session.send(self.shell_socket, u'execute_reply',
327 reply_content, parent, ident=ident)
340 reply_content, parent, ident=ident)
328 self.log.debug(str(reply_msg))
341 self.log.debug(str(reply_msg))
329
342
330 if reply_msg['content']['status'] == u'error':
343 if reply_msg['content']['status'] == u'error':
331 self._abort_queue()
344 self._abort_queue()
332
345
333 self.session.send(self.iopub_socket,
346 self.session.send(self.iopub_socket,
334 u'status',
347 u'status',
335 {u'execution_state':u'idle'},
348 {u'execution_state':u'idle'},
336 parent=parent )
349 parent=parent )
337
350
338 def complete_request(self, ident, parent):
351 def complete_request(self, ident, parent):
339 txt, matches = self._complete(parent)
352 txt, matches = self._complete(parent)
340 matches = {'matches' : matches,
353 matches = {'matches' : matches,
341 'matched_text' : txt,
354 'matched_text' : txt,
342 'status' : 'ok'}
355 'status' : 'ok'}
343 matches = json_clean(matches)
356 matches = json_clean(matches)
344 completion_msg = self.session.send(self.shell_socket, 'complete_reply',
357 completion_msg = self.session.send(self.shell_socket, 'complete_reply',
345 matches, parent, ident)
358 matches, parent, ident)
346 self.log.debug(str(completion_msg))
359 self.log.debug(str(completion_msg))
347
360
348 def object_info_request(self, ident, parent):
361 def object_info_request(self, ident, parent):
349 object_info = self.shell.object_inspect(parent['content']['oname'])
362 object_info = self.shell.object_inspect(parent['content']['oname'])
350 # Before we send this object over, we scrub it for JSON usage
363 # Before we send this object over, we scrub it for JSON usage
351 oinfo = json_clean(object_info)
364 oinfo = json_clean(object_info)
352 msg = self.session.send(self.shell_socket, 'object_info_reply',
365 msg = self.session.send(self.shell_socket, 'object_info_reply',
353 oinfo, parent, ident)
366 oinfo, parent, ident)
354 self.log.debug(msg)
367 self.log.debug(msg)
355
368
356 def history_request(self, ident, parent):
369 def history_request(self, ident, parent):
357 # We need to pull these out, as passing **kwargs doesn't work with
370 # We need to pull these out, as passing **kwargs doesn't work with
358 # unicode keys before Python 2.6.5.
371 # unicode keys before Python 2.6.5.
359 hist_access_type = parent['content']['hist_access_type']
372 hist_access_type = parent['content']['hist_access_type']
360 raw = parent['content']['raw']
373 raw = parent['content']['raw']
361 output = parent['content']['output']
374 output = parent['content']['output']
362 if hist_access_type == 'tail':
375 if hist_access_type == 'tail':
363 n = parent['content']['n']
376 n = parent['content']['n']
364 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
377 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
365 include_latest=True)
378 include_latest=True)
366
379
367 elif hist_access_type == 'range':
380 elif hist_access_type == 'range':
368 session = parent['content']['session']
381 session = parent['content']['session']
369 start = parent['content']['start']
382 start = parent['content']['start']
370 stop = parent['content']['stop']
383 stop = parent['content']['stop']
371 hist = self.shell.history_manager.get_range(session, start, stop,
384 hist = self.shell.history_manager.get_range(session, start, stop,
372 raw=raw, output=output)
385 raw=raw, output=output)
373
386
374 elif hist_access_type == 'search':
387 elif hist_access_type == 'search':
375 pattern = parent['content']['pattern']
388 pattern = parent['content']['pattern']
376 hist = self.shell.history_manager.search(pattern, raw=raw,
389 hist = self.shell.history_manager.search(pattern, raw=raw,
377 output=output)
390 output=output)
378
391
379 else:
392 else:
380 hist = []
393 hist = []
381 content = {'history' : list(hist)}
394 content = {'history' : list(hist)}
382 content = json_clean(content)
395 content = json_clean(content)
383 msg = self.session.send(self.shell_socket, 'history_reply',
396 msg = self.session.send(self.shell_socket, 'history_reply',
384 content, parent, ident)
397 content, parent, ident)
385 self.log.debug(str(msg))
398 self.log.debug(str(msg))
386
399
387 def connect_request(self, ident, parent):
400 def connect_request(self, ident, parent):
388 if self._recorded_ports is not None:
401 if self._recorded_ports is not None:
389 content = self._recorded_ports.copy()
402 content = self._recorded_ports.copy()
390 else:
403 else:
391 content = {}
404 content = {}
392 msg = self.session.send(self.shell_socket, 'connect_reply',
405 msg = self.session.send(self.shell_socket, 'connect_reply',
393 content, parent, ident)
406 content, parent, ident)
394 self.log.debug(msg)
407 self.log.debug(msg)
395
408
396 def shutdown_request(self, ident, parent):
409 def shutdown_request(self, ident, parent):
397 self.shell.exit_now = True
410 self.shell.exit_now = True
398 self._shutdown_message = self.session.msg(u'shutdown_reply',
411 self._shutdown_message = self.session.msg(u'shutdown_reply',
399 parent['content'], parent)
412 parent['content'], parent)
400 sys.exit(0)
413 sys.exit(0)
401
414
402 #---------------------------------------------------------------------------
415 #---------------------------------------------------------------------------
403 # Protected interface
416 # Protected interface
404 #---------------------------------------------------------------------------
417 #---------------------------------------------------------------------------
405
418
406 def _abort_queue(self):
419 def _abort_queue(self):
407 while True:
420 while True:
408 try:
421 try:
409 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
422 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
410 except Exception:
423 except Exception:
411 self.log.warn("Invalid Message:", exc_info=True)
424 self.log.warn("Invalid Message:", exc_info=True)
412 continue
425 continue
413 if msg is None:
426 if msg is None:
414 break
427 break
415 else:
428 else:
416 assert ident is not None, \
429 assert ident is not None, \
417 "Unexpected missing message part."
430 "Unexpected missing message part."
418
431
419 self.log.debug("Aborting:\n"+str(Message(msg)))
432 self.log.debug("Aborting:\n"+str(Message(msg)))
420 msg_type = msg['header']['msg_type']
433 msg_type = msg['header']['msg_type']
421 reply_type = msg_type.split('_')[0] + '_reply'
434 reply_type = msg_type.split('_')[0] + '_reply'
422 reply_msg = self.session.send(self.shell_socket, reply_type,
435 reply_msg = self.session.send(self.shell_socket, reply_type,
423 {'status' : 'aborted'}, msg, ident=ident)
436 {'status' : 'aborted'}, msg, ident=ident)
424 self.log.debug(reply_msg)
437 self.log.debug(reply_msg)
425 # We need to wait a bit for requests to come in. This can probably
438 # We need to wait a bit for requests to come in. This can probably
426 # be set shorter for true asynchronous clients.
439 # be set shorter for true asynchronous clients.
427 time.sleep(0.1)
440 time.sleep(0.1)
428
441
429 def _no_raw_input(self):
442 def _no_raw_input(self):
430 """Raise StdinNotImplentedError if active frontend doesn't support
443 """Raise StdinNotImplentedError if active frontend doesn't support
431 stdin."""
444 stdin."""
432 raise StdinNotImplementedError("raw_input was called, but this "
445 raise StdinNotImplementedError("raw_input was called, but this "
433 "frontend does not support stdin.")
446 "frontend does not support stdin.")
434
447
435 def _raw_input(self, prompt, ident, parent):
448 def _raw_input(self, prompt, ident, parent):
436 # Flush output before making the request.
449 # Flush output before making the request.
437 sys.stderr.flush()
450 sys.stderr.flush()
438 sys.stdout.flush()
451 sys.stdout.flush()
439
452
440 # Send the input request.
453 # Send the input request.
441 content = json_clean(dict(prompt=prompt))
454 content = json_clean(dict(prompt=prompt))
442 self.session.send(self.stdin_socket, u'input_request', content, parent,
455 self.session.send(self.stdin_socket, u'input_request', content, parent,
443 ident=ident)
456 ident=ident)
444
457
445 # Await a response.
458 # Await a response.
446 while True:
459 while True:
447 try:
460 try:
448 ident, reply = self.session.recv(self.stdin_socket, 0)
461 ident, reply = self.session.recv(self.stdin_socket, 0)
449 except Exception:
462 except Exception:
450 self.log.warn("Invalid Message:", exc_info=True)
463 self.log.warn("Invalid Message:", exc_info=True)
451 else:
464 else:
452 break
465 break
453 try:
466 try:
454 value = reply['content']['value']
467 value = reply['content']['value']
455 except:
468 except:
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):
462 c = msg['content']
478 c = msg['content']
463 try:
479 try:
464 cpos = int(c['cursor_pos'])
480 cpos = int(c['cursor_pos'])
465 except:
481 except:
466 # If we don't get something that we can convert to an integer, at
482 # If we don't get something that we can convert to an integer, at
467 # least attempt the completion guessing the cursor is at the end of
483 # least attempt the completion guessing the cursor is at the end of
468 # the text, if there's any, and otherwise of the line
484 # the text, if there's any, and otherwise of the line
469 cpos = len(c['text'])
485 cpos = len(c['text'])
470 if cpos==0:
486 if cpos==0:
471 cpos = len(c['line'])
487 cpos = len(c['line'])
472 return self.shell.complete(c['text'], c['line'], cpos)
488 return self.shell.complete(c['text'], c['line'], cpos)
473
489
474 def _object_info(self, context):
490 def _object_info(self, context):
475 symbol, leftover = self._symbol_from_context(context)
491 symbol, leftover = self._symbol_from_context(context)
476 if symbol is not None and not leftover:
492 if symbol is not None and not leftover:
477 doc = getattr(symbol, '__doc__', '')
493 doc = getattr(symbol, '__doc__', '')
478 else:
494 else:
479 doc = ''
495 doc = ''
480 object_info = dict(docstring = doc)
496 object_info = dict(docstring = doc)
481 return object_info
497 return object_info
482
498
483 def _symbol_from_context(self, context):
499 def _symbol_from_context(self, context):
484 if not context:
500 if not context:
485 return None, context
501 return None, context
486
502
487 base_symbol_string = context[0]
503 base_symbol_string = context[0]
488 symbol = self.shell.user_ns.get(base_symbol_string, None)
504 symbol = self.shell.user_ns.get(base_symbol_string, None)
489 if symbol is None:
505 if symbol is None:
490 symbol = __builtin__.__dict__.get(base_symbol_string, None)
506 symbol = __builtin__.__dict__.get(base_symbol_string, None)
491 if symbol is None:
507 if symbol is None:
492 return None, context
508 return None, context
493
509
494 context = context[1:]
510 context = context[1:]
495 for i, name in enumerate(context):
511 for i, name in enumerate(context):
496 new_symbol = getattr(symbol, name, None)
512 new_symbol = getattr(symbol, name, None)
497 if new_symbol is None:
513 if new_symbol is None:
498 return symbol, context[i:]
514 return symbol, context[i:]
499 else:
515 else:
500 symbol = new_symbol
516 symbol = new_symbol
501
517
502 return symbol, []
518 return symbol, []
503
519
504 def _at_shutdown(self):
520 def _at_shutdown(self):
505 """Actions taken at shutdown by the kernel, called by python's atexit.
521 """Actions taken at shutdown by the kernel, called by python's atexit.
506 """
522 """
507 # io.rprint("Kernel at_shutdown") # dbg
523 # io.rprint("Kernel at_shutdown") # dbg
508 if self._shutdown_message is not None:
524 if self._shutdown_message is not None:
509 self.session.send(self.shell_socket, self._shutdown_message)
525 self.session.send(self.shell_socket, self._shutdown_message)
510 self.session.send(self.iopub_socket, self._shutdown_message)
526 self.session.send(self.iopub_socket, self._shutdown_message)
511 self.log.debug(str(self._shutdown_message))
527 self.log.debug(str(self._shutdown_message))
512 # A very short sleep to give zmq time to flush its message buffers
528 # A very short sleep to give zmq time to flush its message buffers
513 # before Python truly shuts down.
529 # before Python truly shuts down.
514 time.sleep(0.01)
530 time.sleep(0.01)
515
531
516 #-----------------------------------------------------------------------------
532 #-----------------------------------------------------------------------------
517 # Aliases and Flags for the IPKernelApp
533 # Aliases and Flags for the IPKernelApp
518 #-----------------------------------------------------------------------------
534 #-----------------------------------------------------------------------------
519
535
520 flags = dict(kernel_flags)
536 flags = dict(kernel_flags)
521 flags.update(shell_flags)
537 flags.update(shell_flags)
522
538
523 addflag = lambda *args: flags.update(boolean_flag(*args))
539 addflag = lambda *args: flags.update(boolean_flag(*args))
524
540
525 flags['pylab'] = (
541 flags['pylab'] = (
526 {'IPKernelApp' : {'pylab' : 'auto'}},
542 {'IPKernelApp' : {'pylab' : 'auto'}},
527 """Pre-load matplotlib and numpy for interactive use with
543 """Pre-load matplotlib and numpy for interactive use with
528 the default matplotlib backend."""
544 the default matplotlib backend."""
529 )
545 )
530
546
531 aliases = dict(kernel_aliases)
547 aliases = dict(kernel_aliases)
532 aliases.update(shell_aliases)
548 aliases.update(shell_aliases)
533
549
534 # it's possible we don't want short aliases for *all* of these:
550 # it's possible we don't want short aliases for *all* of these:
535 aliases.update(dict(
551 aliases.update(dict(
536 pylab='IPKernelApp.pylab',
552 pylab='IPKernelApp.pylab',
537 ))
553 ))
538
554
539 #-----------------------------------------------------------------------------
555 #-----------------------------------------------------------------------------
540 # The IPKernelApp class
556 # The IPKernelApp class
541 #-----------------------------------------------------------------------------
557 #-----------------------------------------------------------------------------
542
558
543 class IPKernelApp(KernelApp, InteractiveShellApp):
559 class IPKernelApp(KernelApp, InteractiveShellApp):
544 name = 'ipkernel'
560 name = 'ipkernel'
545
561
546 aliases = Dict(aliases)
562 aliases = Dict(aliases)
547 flags = Dict(flags)
563 flags = Dict(flags)
548 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
564 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
549 # configurables
565 # configurables
550 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'inline', 'auto'],
566 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'inline', 'auto'],
551 config=True,
567 config=True,
552 help="""Pre-load matplotlib and numpy for interactive use,
568 help="""Pre-load matplotlib and numpy for interactive use,
553 selecting a particular matplotlib backend and loop integration.
569 selecting a particular matplotlib backend and loop integration.
554 """
570 """
555 )
571 )
556
572
557 @catch_config_error
573 @catch_config_error
558 def initialize(self, argv=None):
574 def initialize(self, argv=None):
559 super(IPKernelApp, self).initialize(argv)
575 super(IPKernelApp, self).initialize(argv)
560 self.init_shell()
576 self.init_shell()
561 self.init_extensions()
577 self.init_extensions()
562 self.init_code()
578 self.init_code()
563
579
564 def init_kernel(self):
580 def init_kernel(self):
565
581
566 kernel = Kernel(config=self.config, session=self.session,
582 kernel = Kernel(config=self.config, session=self.session,
567 shell_socket=self.shell_socket,
583 shell_socket=self.shell_socket,
568 iopub_socket=self.iopub_socket,
584 iopub_socket=self.iopub_socket,
569 stdin_socket=self.stdin_socket,
585 stdin_socket=self.stdin_socket,
570 log=self.log,
586 log=self.log,
571 profile_dir=self.profile_dir,
587 profile_dir=self.profile_dir,
572 )
588 )
573 self.kernel = kernel
589 self.kernel = kernel
574 kernel.record_ports(self.ports)
590 kernel.record_ports(self.ports)
575 shell = kernel.shell
591 shell = kernel.shell
576 if self.pylab:
592 if self.pylab:
577 try:
593 try:
578 gui, backend = pylabtools.find_gui_and_backend(self.pylab)
594 gui, backend = pylabtools.find_gui_and_backend(self.pylab)
579 shell.enable_pylab(gui, import_all=self.pylab_import_all)
595 shell.enable_pylab(gui, import_all=self.pylab_import_all)
580 except Exception:
596 except Exception:
581 self.log.error("Pylab initialization failed", exc_info=True)
597 self.log.error("Pylab initialization failed", exc_info=True)
582 # print exception straight to stdout, because normally
598 # print exception straight to stdout, because normally
583 # _showtraceback associates the reply with an execution,
599 # _showtraceback associates the reply with an execution,
584 # which means frontends will never draw it, as this exception
600 # which means frontends will never draw it, as this exception
585 # is not associated with any execute request.
601 # is not associated with any execute request.
586
602
587 # replace pyerr-sending traceback with stdout
603 # replace pyerr-sending traceback with stdout
588 _showtraceback = shell._showtraceback
604 _showtraceback = shell._showtraceback
589 def print_tb(etype, evalue, stb):
605 def print_tb(etype, evalue, stb):
590 print ("Error initializing pylab, pylab mode will not "
606 print ("Error initializing pylab, pylab mode will not "
591 "be active", file=io.stderr)
607 "be active", file=io.stderr)
592 print (shell.InteractiveTB.stb2text(stb), file=io.stdout)
608 print (shell.InteractiveTB.stb2text(stb), file=io.stdout)
593 shell._showtraceback = print_tb
609 shell._showtraceback = print_tb
594
610
595 # send the traceback over stdout
611 # send the traceback over stdout
596 shell.showtraceback(tb_offset=0)
612 shell.showtraceback(tb_offset=0)
597
613
598 # restore proper _showtraceback method
614 # restore proper _showtraceback method
599 shell._showtraceback = _showtraceback
615 shell._showtraceback = _showtraceback
600
616
601
617
602 def init_shell(self):
618 def init_shell(self):
603 self.shell = self.kernel.shell
619 self.shell = self.kernel.shell
604 self.shell.configurables.append(self)
620 self.shell.configurables.append(self)
605
621
606
622
607 #-----------------------------------------------------------------------------
623 #-----------------------------------------------------------------------------
608 # Kernel main and launch functions
624 # Kernel main and launch functions
609 #-----------------------------------------------------------------------------
625 #-----------------------------------------------------------------------------
610
626
611 def launch_kernel(*args, **kwargs):
627 def launch_kernel(*args, **kwargs):
612 """Launches a localhost IPython kernel, binding to the specified ports.
628 """Launches a localhost IPython kernel, binding to the specified ports.
613
629
614 This function simply calls entry_point.base_launch_kernel with the right
630 This function simply calls entry_point.base_launch_kernel with the right
615 first command to start an ipkernel. See base_launch_kernel for arguments.
631 first command to start an ipkernel. See base_launch_kernel for arguments.
616
632
617 Returns
633 Returns
618 -------
634 -------
619 A tuple of form:
635 A tuple of form:
620 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
636 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
621 where kernel_process is a Popen object and the ports are integers.
637 where kernel_process is a Popen object and the ports are integers.
622 """
638 """
623 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
639 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
624 *args, **kwargs)
640 *args, **kwargs)
625
641
626
642
627 def main():
643 def main():
628 """Run an IPKernel as an application"""
644 """Run an IPKernel as an application"""
629 app = IPKernelApp.instance()
645 app = IPKernelApp.instance()
630 app.initialize()
646 app.initialize()
631 app.start()
647 app.start()
632
648
633
649
634 if __name__ == '__main__':
650 if __name__ == '__main__':
635 main()
651 main()
@@ -1,947 +1,962
1 """Base classes to manage the interaction with a running kernel.
1 """Base classes to manage the interaction with a running kernel.
2
2
3 TODO
3 TODO
4 * Create logger to handle debugging and console messages.
4 * Create logger to handle debugging and console messages.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2011 The IPython Development Team
8 # Copyright (C) 2008-2011 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import errno
19 import errno
20 import json
20 import json
21 from subprocess import Popen
21 from subprocess import Popen
22 import os
22 import os
23 import signal
23 import signal
24 import sys
24 import sys
25 from threading import Thread
25 from threading import Thread
26 import time
26 import time
27
27
28 # System library imports.
28 # System library imports.
29 import zmq
29 import zmq
30 from zmq.eventloop import ioloop, zmqstream
30 from zmq.eventloop import ioloop, zmqstream
31
31
32 # Local imports.
32 # Local imports.
33 from IPython.config.loader import Config
33 from IPython.config.loader import Config
34 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
34 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
35 from IPython.utils.traitlets import (
35 from IPython.utils.traitlets import (
36 HasTraits, Any, Instance, Type, Unicode, Integer, Bool
36 HasTraits, Any, Instance, Type, Unicode, Integer, Bool
37 )
37 )
38 from IPython.utils.py3compat import str_to_bytes
38 from IPython.utils.py3compat import str_to_bytes
39 from IPython.zmq.entry_point import write_connection_file
39 from IPython.zmq.entry_point import write_connection_file
40 from session import Session
40 from session import Session
41
41
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43 # Constants and exceptions
43 # Constants and exceptions
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45
45
46 class InvalidPortNumber(Exception):
46 class InvalidPortNumber(Exception):
47 pass
47 pass
48
48
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50 # Utility functions
50 # Utility functions
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52
52
53 # some utilities to validate message structure, these might get moved elsewhere
53 # some utilities to validate message structure, these might get moved elsewhere
54 # if they prove to have more generic utility
54 # if they prove to have more generic utility
55
55
56 def validate_string_list(lst):
56 def validate_string_list(lst):
57 """Validate that the input is a list of strings.
57 """Validate that the input is a list of strings.
58
58
59 Raises ValueError if not."""
59 Raises ValueError if not."""
60 if not isinstance(lst, list):
60 if not isinstance(lst, list):
61 raise ValueError('input %r must be a list' % lst)
61 raise ValueError('input %r must be a list' % lst)
62 for x in lst:
62 for x in lst:
63 if not isinstance(x, basestring):
63 if not isinstance(x, basestring):
64 raise ValueError('element %r in list must be a string' % x)
64 raise ValueError('element %r in list must be a string' % x)
65
65
66
66
67 def validate_string_dict(dct):
67 def validate_string_dict(dct):
68 """Validate that the input is a dict with string keys and values.
68 """Validate that the input is a dict with string keys and values.
69
69
70 Raises ValueError if not."""
70 Raises ValueError if not."""
71 for k,v in dct.iteritems():
71 for k,v in dct.iteritems():
72 if not isinstance(k, basestring):
72 if not isinstance(k, basestring):
73 raise ValueError('key %r in dict must be a string' % k)
73 raise ValueError('key %r in dict must be a string' % k)
74 if not isinstance(v, basestring):
74 if not isinstance(v, basestring):
75 raise ValueError('value %r in dict must be a string' % v)
75 raise ValueError('value %r in dict must be a string' % v)
76
76
77
77
78 #-----------------------------------------------------------------------------
78 #-----------------------------------------------------------------------------
79 # ZMQ Socket Channel classes
79 # ZMQ Socket Channel classes
80 #-----------------------------------------------------------------------------
80 #-----------------------------------------------------------------------------
81
81
82 class ZMQSocketChannel(Thread):
82 class ZMQSocketChannel(Thread):
83 """The base class for the channels that use ZMQ sockets.
83 """The base class for the channels that use ZMQ sockets.
84 """
84 """
85 context = None
85 context = None
86 session = None
86 session = None
87 socket = None
87 socket = None
88 ioloop = None
88 ioloop = None
89 stream = None
89 stream = None
90 _address = None
90 _address = None
91
91
92 def __init__(self, context, session, address):
92 def __init__(self, context, session, address):
93 """Create a channel
93 """Create a channel
94
94
95 Parameters
95 Parameters
96 ----------
96 ----------
97 context : :class:`zmq.Context`
97 context : :class:`zmq.Context`
98 The ZMQ context to use.
98 The ZMQ context to use.
99 session : :class:`session.Session`
99 session : :class:`session.Session`
100 The session to use.
100 The session to use.
101 address : tuple
101 address : tuple
102 Standard (ip, port) tuple that the kernel is listening on.
102 Standard (ip, port) tuple that the kernel is listening on.
103 """
103 """
104 super(ZMQSocketChannel, self).__init__()
104 super(ZMQSocketChannel, self).__init__()
105 self.daemon = True
105 self.daemon = True
106
106
107 self.context = context
107 self.context = context
108 self.session = session
108 self.session = session
109 if address[1] == 0:
109 if address[1] == 0:
110 message = 'The port number for a channel cannot be 0.'
110 message = 'The port number for a channel cannot be 0.'
111 raise InvalidPortNumber(message)
111 raise InvalidPortNumber(message)
112 self._address = address
112 self._address = address
113
113
114 def _run_loop(self):
114 def _run_loop(self):
115 """Run my loop, ignoring EINTR events in the poller"""
115 """Run my loop, ignoring EINTR events in the poller"""
116 while True:
116 while True:
117 try:
117 try:
118 self.ioloop.start()
118 self.ioloop.start()
119 except zmq.ZMQError as e:
119 except zmq.ZMQError as e:
120 if e.errno == errno.EINTR:
120 if e.errno == errno.EINTR:
121 continue
121 continue
122 else:
122 else:
123 raise
123 raise
124 else:
124 else:
125 break
125 break
126
126
127 def stop(self):
127 def stop(self):
128 """Stop the channel's activity.
128 """Stop the channel's activity.
129
129
130 This calls :method:`Thread.join` and returns when the thread
130 This calls :method:`Thread.join` and returns when the thread
131 terminates. :class:`RuntimeError` will be raised if
131 terminates. :class:`RuntimeError` will be raised if
132 :method:`self.start` is called again.
132 :method:`self.start` is called again.
133 """
133 """
134 self.join()
134 self.join()
135
135
136 @property
136 @property
137 def address(self):
137 def address(self):
138 """Get the channel's address as an (ip, port) tuple.
138 """Get the channel's address as an (ip, port) tuple.
139
139
140 By the default, the address is (localhost, 0), where 0 means a random
140 By the default, the address is (localhost, 0), where 0 means a random
141 port.
141 port.
142 """
142 """
143 return self._address
143 return self._address
144
144
145 def _queue_send(self, msg):
145 def _queue_send(self, msg):
146 """Queue a message to be sent from the IOLoop's thread.
146 """Queue a message to be sent from the IOLoop's thread.
147
147
148 Parameters
148 Parameters
149 ----------
149 ----------
150 msg : message to send
150 msg : message to send
151
151
152 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
152 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
153 thread control of the action.
153 thread control of the action.
154 """
154 """
155 def thread_send():
155 def thread_send():
156 self.session.send(self.stream, msg)
156 self.session.send(self.stream, msg)
157 self.ioloop.add_callback(thread_send)
157 self.ioloop.add_callback(thread_send)
158
158
159 def _handle_recv(self, msg):
159 def _handle_recv(self, msg):
160 """callback for stream.on_recv
160 """callback for stream.on_recv
161
161
162 unpacks message, and calls handlers with it.
162 unpacks message, and calls handlers with it.
163 """
163 """
164 ident,smsg = self.session.feed_identities(msg)
164 ident,smsg = self.session.feed_identities(msg)
165 self.call_handlers(self.session.unserialize(smsg))
165 self.call_handlers(self.session.unserialize(smsg))
166
166
167
167
168
168
169 class ShellSocketChannel(ZMQSocketChannel):
169 class ShellSocketChannel(ZMQSocketChannel):
170 """The XREQ channel for issues request/replies to the kernel.
170 """The XREQ channel for issues request/replies to the kernel.
171 """
171 """
172
172
173 command_queue = None
173 command_queue = None
174 # flag for whether execute requests should be allowed to call raw_input:
174 # flag for whether execute requests should be allowed to call raw_input:
175 allow_stdin = True
175 allow_stdin = True
176
176
177 def __init__(self, context, session, address):
177 def __init__(self, context, session, address):
178 super(ShellSocketChannel, self).__init__(context, session, address)
178 super(ShellSocketChannel, self).__init__(context, session, address)
179 self.ioloop = ioloop.IOLoop()
179 self.ioloop = ioloop.IOLoop()
180
180
181 def run(self):
181 def run(self):
182 """The thread's main activity. Call start() instead."""
182 """The thread's main activity. Call start() instead."""
183 self.socket = self.context.socket(zmq.DEALER)
183 self.socket = self.context.socket(zmq.DEALER)
184 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
184 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
185 self.socket.connect('tcp://%s:%i' % self.address)
185 self.socket.connect('tcp://%s:%i' % self.address)
186 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
186 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
187 self.stream.on_recv(self._handle_recv)
187 self.stream.on_recv(self._handle_recv)
188 self._run_loop()
188 self._run_loop()
189
189
190 def stop(self):
190 def stop(self):
191 self.ioloop.stop()
191 self.ioloop.stop()
192 super(ShellSocketChannel, self).stop()
192 super(ShellSocketChannel, self).stop()
193
193
194 def call_handlers(self, msg):
194 def call_handlers(self, msg):
195 """This method is called in the ioloop thread when a message arrives.
195 """This method is called in the ioloop thread when a message arrives.
196
196
197 Subclasses should override this method to handle incoming messages.
197 Subclasses should override this method to handle incoming messages.
198 It is important to remember that this method is called in the thread
198 It is important to remember that this method is called in the thread
199 so that some logic must be done to ensure that the application leve
199 so that some logic must be done to ensure that the application leve
200 handlers are called in the application thread.
200 handlers are called in the application thread.
201 """
201 """
202 raise NotImplementedError('call_handlers must be defined in a subclass.')
202 raise NotImplementedError('call_handlers must be defined in a subclass.')
203
203
204 def execute(self, code, silent=False,
204 def execute(self, code, silent=False,
205 user_variables=None, user_expressions=None, allow_stdin=None):
205 user_variables=None, user_expressions=None, allow_stdin=None):
206 """Execute code in the kernel.
206 """Execute code in the kernel.
207
207
208 Parameters
208 Parameters
209 ----------
209 ----------
210 code : str
210 code : str
211 A string of Python code.
211 A string of Python code.
212
212
213 silent : bool, optional (default False)
213 silent : bool, optional (default False)
214 If set, the kernel will execute the code as quietly possible.
214 If set, the kernel will execute the code as quietly possible.
215
215
216 user_variables : list, optional
216 user_variables : list, optional
217 A list of variable names to pull from the user's namespace. They
217 A list of variable names to pull from the user's namespace. They
218 will come back as a dict with these names as keys and their
218 will come back as a dict with these names as keys and their
219 :func:`repr` as values.
219 :func:`repr` as values.
220
220
221 user_expressions : dict, optional
221 user_expressions : dict, optional
222 A dict with string keys and to pull from the user's
222 A dict with string keys and to pull from the user's
223 namespace. They will come back as a dict with these names as keys
223 namespace. They will come back as a dict with these names as keys
224 and their :func:`repr` as values.
224 and their :func:`repr` as values.
225
225
226 allow_stdin : bool, optional
226 allow_stdin : bool, optional
227 Flag for
227 Flag for
228 A dict with string keys and to pull from the user's
228 A dict with string keys and to pull from the user's
229 namespace. They will come back as a dict with these names as keys
229 namespace. They will come back as a dict with these names as keys
230 and their :func:`repr` as values.
230 and their :func:`repr` as values.
231
231
232 Returns
232 Returns
233 -------
233 -------
234 The msg_id of the message sent.
234 The msg_id of the message sent.
235 """
235 """
236 if user_variables is None:
236 if user_variables is None:
237 user_variables = []
237 user_variables = []
238 if user_expressions is None:
238 if user_expressions is None:
239 user_expressions = {}
239 user_expressions = {}
240 if allow_stdin is None:
240 if allow_stdin is None:
241 allow_stdin = self.allow_stdin
241 allow_stdin = self.allow_stdin
242
242
243
243
244 # Don't waste network traffic if inputs are invalid
244 # Don't waste network traffic if inputs are invalid
245 if not isinstance(code, basestring):
245 if not isinstance(code, basestring):
246 raise ValueError('code %r must be a string' % code)
246 raise ValueError('code %r must be a string' % code)
247 validate_string_list(user_variables)
247 validate_string_list(user_variables)
248 validate_string_dict(user_expressions)
248 validate_string_dict(user_expressions)
249
249
250 # Create class for content/msg creation. Related to, but possibly
250 # Create class for content/msg creation. Related to, but possibly
251 # not in Session.
251 # not in Session.
252 content = dict(code=code, silent=silent,
252 content = dict(code=code, silent=silent,
253 user_variables=user_variables,
253 user_variables=user_variables,
254 user_expressions=user_expressions,
254 user_expressions=user_expressions,
255 allow_stdin=allow_stdin,
255 allow_stdin=allow_stdin,
256 )
256 )
257 msg = self.session.msg('execute_request', content)
257 msg = self.session.msg('execute_request', content)
258 self._queue_send(msg)
258 self._queue_send(msg)
259 return msg['header']['msg_id']
259 return msg['header']['msg_id']
260
260
261 def complete(self, text, line, cursor_pos, block=None):
261 def complete(self, text, line, cursor_pos, block=None):
262 """Tab complete text in the kernel's namespace.
262 """Tab complete text in the kernel's namespace.
263
263
264 Parameters
264 Parameters
265 ----------
265 ----------
266 text : str
266 text : str
267 The text to complete.
267 The text to complete.
268 line : str
268 line : str
269 The full line of text that is the surrounding context for the
269 The full line of text that is the surrounding context for the
270 text to complete.
270 text to complete.
271 cursor_pos : int
271 cursor_pos : int
272 The position of the cursor in the line where the completion was
272 The position of the cursor in the line where the completion was
273 requested.
273 requested.
274 block : str, optional
274 block : str, optional
275 The full block of code in which the completion is being requested.
275 The full block of code in which the completion is being requested.
276
276
277 Returns
277 Returns
278 -------
278 -------
279 The msg_id of the message sent.
279 The msg_id of the message sent.
280 """
280 """
281 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
281 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
282 msg = self.session.msg('complete_request', content)
282 msg = self.session.msg('complete_request', content)
283 self._queue_send(msg)
283 self._queue_send(msg)
284 return msg['header']['msg_id']
284 return msg['header']['msg_id']
285
285
286 def object_info(self, oname):
286 def object_info(self, oname):
287 """Get metadata information about an object.
287 """Get metadata information about an object.
288
288
289 Parameters
289 Parameters
290 ----------
290 ----------
291 oname : str
291 oname : str
292 A string specifying the object name.
292 A string specifying the object name.
293
293
294 Returns
294 Returns
295 -------
295 -------
296 The msg_id of the message sent.
296 The msg_id of the message sent.
297 """
297 """
298 content = dict(oname=oname)
298 content = dict(oname=oname)
299 msg = self.session.msg('object_info_request', content)
299 msg = self.session.msg('object_info_request', content)
300 self._queue_send(msg)
300 self._queue_send(msg)
301 return msg['header']['msg_id']
301 return msg['header']['msg_id']
302
302
303 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
303 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
304 """Get entries from the history list.
304 """Get entries from the history list.
305
305
306 Parameters
306 Parameters
307 ----------
307 ----------
308 raw : bool
308 raw : bool
309 If True, return the raw input.
309 If True, return the raw input.
310 output : bool
310 output : bool
311 If True, then return the output as well.
311 If True, then return the output as well.
312 hist_access_type : str
312 hist_access_type : str
313 'range' (fill in session, start and stop params), 'tail' (fill in n)
313 'range' (fill in session, start and stop params), 'tail' (fill in n)
314 or 'search' (fill in pattern param).
314 or 'search' (fill in pattern param).
315
315
316 session : int
316 session : int
317 For a range request, the session from which to get lines. Session
317 For a range request, the session from which to get lines. Session
318 numbers are positive integers; negative ones count back from the
318 numbers are positive integers; negative ones count back from the
319 current session.
319 current session.
320 start : int
320 start : int
321 The first line number of a history range.
321 The first line number of a history range.
322 stop : int
322 stop : int
323 The final (excluded) line number of a history range.
323 The final (excluded) line number of a history range.
324
324
325 n : int
325 n : int
326 The number of lines of history to get for a tail request.
326 The number of lines of history to get for a tail request.
327
327
328 pattern : str
328 pattern : str
329 The glob-syntax pattern for a search request.
329 The glob-syntax pattern for a search request.
330
330
331 Returns
331 Returns
332 -------
332 -------
333 The msg_id of the message sent.
333 The msg_id of the message sent.
334 """
334 """
335 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
335 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
336 **kwargs)
336 **kwargs)
337 msg = self.session.msg('history_request', content)
337 msg = self.session.msg('history_request', content)
338 self._queue_send(msg)
338 self._queue_send(msg)
339 return msg['header']['msg_id']
339 return msg['header']['msg_id']
340
340
341 def shutdown(self, restart=False):
341 def shutdown(self, restart=False):
342 """Request an immediate kernel shutdown.
342 """Request an immediate kernel shutdown.
343
343
344 Upon receipt of the (empty) reply, client code can safely assume that
344 Upon receipt of the (empty) reply, client code can safely assume that
345 the kernel has shut down and it's safe to forcefully terminate it if
345 the kernel has shut down and it's safe to forcefully terminate it if
346 it's still alive.
346 it's still alive.
347
347
348 The kernel will send the reply via a function registered with Python's
348 The kernel will send the reply via a function registered with Python's
349 atexit module, ensuring it's truly done as the kernel is done with all
349 atexit module, ensuring it's truly done as the kernel is done with all
350 normal operation.
350 normal operation.
351 """
351 """
352 # Send quit message to kernel. Once we implement kernel-side setattr,
352 # Send quit message to kernel. Once we implement kernel-side setattr,
353 # this should probably be done that way, but for now this will do.
353 # this should probably be done that way, but for now this will do.
354 msg = self.session.msg('shutdown_request', {'restart':restart})
354 msg = self.session.msg('shutdown_request', {'restart':restart})
355 self._queue_send(msg)
355 self._queue_send(msg)
356 return msg['header']['msg_id']
356 return msg['header']['msg_id']
357
357
358
358
359
359
360 class SubSocketChannel(ZMQSocketChannel):
360 class SubSocketChannel(ZMQSocketChannel):
361 """The SUB channel which listens for messages that the kernel publishes.
361 """The SUB channel which listens for messages that the kernel publishes.
362 """
362 """
363
363
364 def __init__(self, context, session, address):
364 def __init__(self, context, session, address):
365 super(SubSocketChannel, self).__init__(context, session, address)
365 super(SubSocketChannel, self).__init__(context, session, address)
366 self.ioloop = ioloop.IOLoop()
366 self.ioloop = ioloop.IOLoop()
367
367
368 def run(self):
368 def run(self):
369 """The thread's main activity. Call start() instead."""
369 """The thread's main activity. Call start() instead."""
370 self.socket = self.context.socket(zmq.SUB)
370 self.socket = self.context.socket(zmq.SUB)
371 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
371 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
372 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
372 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
373 self.socket.connect('tcp://%s:%i' % self.address)
373 self.socket.connect('tcp://%s:%i' % self.address)
374 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
374 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
375 self.stream.on_recv(self._handle_recv)
375 self.stream.on_recv(self._handle_recv)
376 self._run_loop()
376 self._run_loop()
377
377
378 def stop(self):
378 def stop(self):
379 self.ioloop.stop()
379 self.ioloop.stop()
380 super(SubSocketChannel, self).stop()
380 super(SubSocketChannel, self).stop()
381
381
382 def call_handlers(self, msg):
382 def call_handlers(self, msg):
383 """This method is called in the ioloop thread when a message arrives.
383 """This method is called in the ioloop thread when a message arrives.
384
384
385 Subclasses should override this method to handle incoming messages.
385 Subclasses should override this method to handle incoming messages.
386 It is important to remember that this method is called in the thread
386 It is important to remember that this method is called in the thread
387 so that some logic must be done to ensure that the application leve
387 so that some logic must be done to ensure that the application leve
388 handlers are called in the application thread.
388 handlers are called in the application thread.
389 """
389 """
390 raise NotImplementedError('call_handlers must be defined in a subclass.')
390 raise NotImplementedError('call_handlers must be defined in a subclass.')
391
391
392 def flush(self, timeout=1.0):
392 def flush(self, timeout=1.0):
393 """Immediately processes all pending messages on the SUB channel.
393 """Immediately processes all pending messages on the SUB channel.
394
394
395 Callers should use this method to ensure that :method:`call_handlers`
395 Callers should use this method to ensure that :method:`call_handlers`
396 has been called for all messages that have been received on the
396 has been called for all messages that have been received on the
397 0MQ SUB socket of this channel.
397 0MQ SUB socket of this channel.
398
398
399 This method is thread safe.
399 This method is thread safe.
400
400
401 Parameters
401 Parameters
402 ----------
402 ----------
403 timeout : float, optional
403 timeout : float, optional
404 The maximum amount of time to spend flushing, in seconds. The
404 The maximum amount of time to spend flushing, in seconds. The
405 default is one second.
405 default is one second.
406 """
406 """
407 # We do the IOLoop callback process twice to ensure that the IOLoop
407 # We do the IOLoop callback process twice to ensure that the IOLoop
408 # gets to perform at least one full poll.
408 # gets to perform at least one full poll.
409 stop_time = time.time() + timeout
409 stop_time = time.time() + timeout
410 for i in xrange(2):
410 for i in xrange(2):
411 self._flushed = False
411 self._flushed = False
412 self.ioloop.add_callback(self._flush)
412 self.ioloop.add_callback(self._flush)
413 while not self._flushed and time.time() < stop_time:
413 while not self._flushed and time.time() < stop_time:
414 time.sleep(0.01)
414 time.sleep(0.01)
415
415
416 def _flush(self):
416 def _flush(self):
417 """Callback for :method:`self.flush`."""
417 """Callback for :method:`self.flush`."""
418 self.stream.flush()
418 self.stream.flush()
419 self._flushed = True
419 self._flushed = True
420
420
421
421
422 class StdInSocketChannel(ZMQSocketChannel):
422 class StdInSocketChannel(ZMQSocketChannel):
423 """A reply channel to handle raw_input requests that the kernel makes."""
423 """A reply channel to handle raw_input requests that the kernel makes."""
424
424
425 msg_queue = None
425 msg_queue = None
426
426
427 def __init__(self, context, session, address):
427 def __init__(self, context, session, address):
428 super(StdInSocketChannel, self).__init__(context, session, address)
428 super(StdInSocketChannel, self).__init__(context, session, address)
429 self.ioloop = ioloop.IOLoop()
429 self.ioloop = ioloop.IOLoop()
430
430
431 def run(self):
431 def run(self):
432 """The thread's main activity. Call start() instead."""
432 """The thread's main activity. Call start() instead."""
433 self.socket = self.context.socket(zmq.DEALER)
433 self.socket = self.context.socket(zmq.DEALER)
434 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
434 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
435 self.socket.connect('tcp://%s:%i' % self.address)
435 self.socket.connect('tcp://%s:%i' % self.address)
436 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
436 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
437 self.stream.on_recv(self._handle_recv)
437 self.stream.on_recv(self._handle_recv)
438 self._run_loop()
438 self._run_loop()
439
439
440 def stop(self):
440 def stop(self):
441 self.ioloop.stop()
441 self.ioloop.stop()
442 super(StdInSocketChannel, self).stop()
442 super(StdInSocketChannel, self).stop()
443
443
444 def call_handlers(self, msg):
444 def call_handlers(self, msg):
445 """This method is called in the ioloop thread when a message arrives.
445 """This method is called in the ioloop thread when a message arrives.
446
446
447 Subclasses should override this method to handle incoming messages.
447 Subclasses should override this method to handle incoming messages.
448 It is important to remember that this method is called in the thread
448 It is important to remember that this method is called in the thread
449 so that some logic must be done to ensure that the application leve
449 so that some logic must be done to ensure that the application leve
450 handlers are called in the application thread.
450 handlers are called in the application thread.
451 """
451 """
452 raise NotImplementedError('call_handlers must be defined in a subclass.')
452 raise NotImplementedError('call_handlers must be defined in a subclass.')
453
453
454 def input(self, string):
454 def input(self, string):
455 """Send a string of raw input to the kernel."""
455 """Send a string of raw input to the kernel."""
456 content = dict(value=string)
456 content = dict(value=string)
457 msg = self.session.msg('input_reply', content)
457 msg = self.session.msg('input_reply', content)
458 self._queue_send(msg)
458 self._queue_send(msg)
459
459
460
460
461 class HBSocketChannel(ZMQSocketChannel):
461 class HBSocketChannel(ZMQSocketChannel):
462 """The heartbeat channel which monitors the kernel heartbeat.
462 """The heartbeat channel which monitors the kernel heartbeat.
463
463
464 Note that the heartbeat channel is paused by default. As long as you start
464 Note that the heartbeat channel is paused by default. As long as you start
465 this channel, the kernel manager will ensure that it is paused and un-paused
465 this channel, the kernel manager will ensure that it is paused and un-paused
466 as appropriate.
466 as appropriate.
467 """
467 """
468
468
469 time_to_dead = 3.0
469 time_to_dead = 3.0
470 socket = None
470 socket = None
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)
486
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
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)
494 else:
531 continue
532
495 since_last_heartbeat = 0.0
533 since_last_heartbeat = 0.0
496 request_time = time.time()
497 try:
498 #io.rprint('Ping from HB channel') # dbg
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
499 self.socket.send(b'ping')
537 self.socket.send(b'ping')
500 except zmq.ZMQError, e:
538 request_time = time.time()
501 #io.rprint('*** HB Error:', e) # dbg
539 ready = self._poll(request_time)
502 if e.errno == zmq.EFSM:
540 if ready:
503 #io.rprint('sleep...', self.time_to_dead) # dbg
541 self._beating = True
504 time.sleep(self.time_to_dead)
542 # the poll above guarantees we have something to recv
505 self._create_socket()
543 self.socket.recv()
506 else:
544 # sleep the remainder of the cycle
507 raise
545 remainder = self.time_to_dead - (time.time() - request_time)
508 else:
546 if remainder > 0:
509 while True:
547 time.sleep(remainder)
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
548 continue
532 else:
549 else:
533 raise
550 # nothing was received within the time limit, signal heart failure
534 else:
551 self._beating = False
535 break
536
537 since_last_heartbeat = time.time()-request_time
552 since_last_heartbeat = time.time() - request_time
538 if since_last_heartbeat > self.time_to_dead:
539 self.call_handlers(since_last_heartbeat)
553 self.call_handlers(since_last_heartbeat)
540 break
554 # and close/reopen the socket, because the REQ/REP cycle has been broken
541 else:
555 self._create_socket()
542 # FIXME: We should probably log this instead.
556 continue
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."""
554 self._pause = True
560 self._pause = True
555
561
556 def unpause(self):
562 def unpause(self):
557 """Unpause the heartbeat."""
563 """Unpause the heartbeat."""
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
566
572
567 def stop(self):
573 def stop(self):
568 self._running = False
574 self._running = False
569 super(HBSocketChannel, self).stop()
575 super(HBSocketChannel, self).stop()
570
576
571 def call_handlers(self, since_last_heartbeat):
577 def call_handlers(self, since_last_heartbeat):
572 """This method is called in the ioloop thread when a message arrives.
578 """This method is called in the ioloop thread when a message arrives.
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.')
580
586
581
587
582 #-----------------------------------------------------------------------------
588 #-----------------------------------------------------------------------------
583 # Main kernel manager class
589 # Main kernel manager class
584 #-----------------------------------------------------------------------------
590 #-----------------------------------------------------------------------------
585
591
586 class KernelManager(HasTraits):
592 class KernelManager(HasTraits):
587 """ Manages a kernel for a frontend.
593 """ Manages a kernel for a frontend.
588
594
589 The SUB channel is for the frontend to receive messages published by the
595 The SUB channel is for the frontend to receive messages published by the
590 kernel.
596 kernel.
591
597
592 The REQ channel is for the frontend to make requests of the kernel.
598 The REQ channel is for the frontend to make requests of the kernel.
593
599
594 The REP channel is for the kernel to request stdin (raw_input) from the
600 The REP channel is for the kernel to request stdin (raw_input) from the
595 frontend.
601 frontend.
596 """
602 """
597 # config object for passing to child configurables
603 # config object for passing to child configurables
598 config = Instance(Config)
604 config = Instance(Config)
599
605
600 # The PyZMQ Context to use for communication with the kernel.
606 # The PyZMQ Context to use for communication with the kernel.
601 context = Instance(zmq.Context)
607 context = Instance(zmq.Context)
602 def _context_default(self):
608 def _context_default(self):
603 return zmq.Context.instance()
609 return zmq.Context.instance()
604
610
605 # The Session to use for communication with the kernel.
611 # The Session to use for communication with the kernel.
606 session = Instance(Session)
612 session = Instance(Session)
607
613
608 # The kernel process with which the KernelManager is communicating.
614 # The kernel process with which the KernelManager is communicating.
609 kernel = Instance(Popen)
615 kernel = Instance(Popen)
610
616
611 # The addresses for the communication channels.
617 # The addresses for the communication channels.
612 connection_file = Unicode('')
618 connection_file = Unicode('')
613 ip = Unicode(LOCALHOST)
619 ip = Unicode(LOCALHOST)
614 def _ip_changed(self, name, old, new):
620 def _ip_changed(self, name, old, new):
615 if new == '*':
621 if new == '*':
616 self.ip = '0.0.0.0'
622 self.ip = '0.0.0.0'
617 shell_port = Integer(0)
623 shell_port = Integer(0)
618 iopub_port = Integer(0)
624 iopub_port = Integer(0)
619 stdin_port = Integer(0)
625 stdin_port = Integer(0)
620 hb_port = Integer(0)
626 hb_port = Integer(0)
621
627
622 # The classes to use for the various channels.
628 # The classes to use for the various channels.
623 shell_channel_class = Type(ShellSocketChannel)
629 shell_channel_class = Type(ShellSocketChannel)
624 sub_channel_class = Type(SubSocketChannel)
630 sub_channel_class = Type(SubSocketChannel)
625 stdin_channel_class = Type(StdInSocketChannel)
631 stdin_channel_class = Type(StdInSocketChannel)
626 hb_channel_class = Type(HBSocketChannel)
632 hb_channel_class = Type(HBSocketChannel)
627
633
628 # Protected traits.
634 # Protected traits.
629 _launch_args = Any
635 _launch_args = Any
630 _shell_channel = Any
636 _shell_channel = Any
631 _sub_channel = Any
637 _sub_channel = Any
632 _stdin_channel = Any
638 _stdin_channel = Any
633 _hb_channel = Any
639 _hb_channel = Any
634 _connection_file_written=Bool(False)
640 _connection_file_written=Bool(False)
635
641
636 def __init__(self, **kwargs):
642 def __init__(self, **kwargs):
637 super(KernelManager, self).__init__(**kwargs)
643 super(KernelManager, self).__init__(**kwargs)
638 if self.session is None:
644 if self.session is None:
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
644 self._connection_file_written = False
645 try:
646 os.remove(self.connection_file)
647 except IOError:
648 pass
649
649
650
650
651 #--------------------------------------------------------------------------
651 #--------------------------------------------------------------------------
652 # Channel management methods:
652 # Channel management methods:
653 #--------------------------------------------------------------------------
653 #--------------------------------------------------------------------------
654
654
655 def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
655 def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
656 """Starts the channels for this kernel.
656 """Starts the channels for this kernel.
657
657
658 This will create the channels if they do not exist and then start
658 This will create the channels if they do not exist and then start
659 them. If port numbers of 0 are being used (random ports) then you
659 them. If port numbers of 0 are being used (random ports) then you
660 must first call :method:`start_kernel`. If the channels have been
660 must first call :method:`start_kernel`. If the channels have been
661 stopped and you call this, :class:`RuntimeError` will be raised.
661 stopped and you call this, :class:`RuntimeError` will be raised.
662 """
662 """
663 if shell:
663 if shell:
664 self.shell_channel.start()
664 self.shell_channel.start()
665 if sub:
665 if sub:
666 self.sub_channel.start()
666 self.sub_channel.start()
667 if stdin:
667 if stdin:
668 self.stdin_channel.start()
668 self.stdin_channel.start()
669 self.shell_channel.allow_stdin = True
669 self.shell_channel.allow_stdin = True
670 else:
670 else:
671 self.shell_channel.allow_stdin = False
671 self.shell_channel.allow_stdin = False
672 if hb:
672 if hb:
673 self.hb_channel.start()
673 self.hb_channel.start()
674
674
675 def stop_channels(self):
675 def stop_channels(self):
676 """Stops all the running channels for this kernel.
676 """Stops all the running channels for this kernel.
677 """
677 """
678 if self.shell_channel.is_alive():
678 if self.shell_channel.is_alive():
679 self.shell_channel.stop()
679 self.shell_channel.stop()
680 if self.sub_channel.is_alive():
680 if self.sub_channel.is_alive():
681 self.sub_channel.stop()
681 self.sub_channel.stop()
682 if self.stdin_channel.is_alive():
682 if self.stdin_channel.is_alive():
683 self.stdin_channel.stop()
683 self.stdin_channel.stop()
684 if self.hb_channel.is_alive():
684 if self.hb_channel.is_alive():
685 self.hb_channel.stop()
685 self.hb_channel.stop()
686
686
687 @property
687 @property
688 def channels_running(self):
688 def channels_running(self):
689 """Are any of the channels created and running?"""
689 """Are any of the channels created and running?"""
690 return (self.shell_channel.is_alive() or self.sub_channel.is_alive() or
690 return (self.shell_channel.is_alive() or self.sub_channel.is_alive() or
691 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
691 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
692
692
693 #--------------------------------------------------------------------------
693 #--------------------------------------------------------------------------
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:
700 cfg = json.loads(f.read())
713 cfg = json.loads(f.read())
701
714
702 self.ip = cfg['ip']
715 self.ip = cfg['ip']
703 self.shell_port = cfg['shell_port']
716 self.shell_port = cfg['shell_port']
704 self.stdin_port = cfg['stdin_port']
717 self.stdin_port = cfg['stdin_port']
705 self.iopub_port = cfg['iopub_port']
718 self.iopub_port = cfg['iopub_port']
706 self.hb_port = cfg['hb_port']
719 self.hb_port = cfg['hb_port']
707 self.session.key = str_to_bytes(cfg['key'])
720 self.session.key = str_to_bytes(cfg['key'])
708
721
709 def write_connection_file(self):
722 def write_connection_file(self):
710 """write connection info to JSON dict in self.connection_file"""
723 """write connection info to JSON dict in self.connection_file"""
711 if self._connection_file_written:
724 if self._connection_file_written:
712 return
725 return
713 self.connection_file,cfg = write_connection_file(self.connection_file,
726 self.connection_file,cfg = write_connection_file(self.connection_file,
714 ip=self.ip, key=self.session.key,
727 ip=self.ip, key=self.session.key,
715 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
728 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
716 shell_port=self.shell_port, hb_port=self.hb_port)
729 shell_port=self.shell_port, hb_port=self.hb_port)
717 # write_connection_file also sets default ports:
730 # write_connection_file also sets default ports:
718 self.shell_port = cfg['shell_port']
731 self.shell_port = cfg['shell_port']
719 self.stdin_port = cfg['stdin_port']
732 self.stdin_port = cfg['stdin_port']
720 self.iopub_port = cfg['iopub_port']
733 self.iopub_port = cfg['iopub_port']
721 self.hb_port = cfg['hb_port']
734 self.hb_port = cfg['hb_port']
722
735
723 self._connection_file_written = True
736 self._connection_file_written = True
724
737
725 def start_kernel(self, **kw):
738 def start_kernel(self, **kw):
726 """Starts a kernel process and configures the manager to use it.
739 """Starts a kernel process and configures the manager to use it.
727
740
728 If random ports (port=0) are being used, this method must be called
741 If random ports (port=0) are being used, this method must be called
729 before the channels are created.
742 before the channels are created.
730
743
731 Parameters:
744 Parameters:
732 -----------
745 -----------
733 ipython : bool, optional (default True)
746 ipython : bool, optional (default True)
734 Whether to use an IPython kernel instead of a plain Python kernel.
747 Whether to use an IPython kernel instead of a plain Python kernel.
735
748
736 launcher : callable, optional (default None)
749 launcher : callable, optional (default None)
737 A custom function for launching the kernel process (generally a
750 A custom function for launching the kernel process (generally a
738 wrapper around ``entry_point.base_launch_kernel``). In most cases,
751 wrapper around ``entry_point.base_launch_kernel``). In most cases,
739 it should not be necessary to use this parameter.
752 it should not be necessary to use this parameter.
740
753
741 **kw : optional
754 **kw : optional
742 See respective options for IPython and Python kernels.
755 See respective options for IPython and Python kernels.
743 """
756 """
744 if self.ip not in LOCAL_IPS:
757 if self.ip not in LOCAL_IPS:
745 raise RuntimeError("Can only launch a kernel on a local interface. "
758 raise RuntimeError("Can only launch a kernel on a local interface. "
746 "Make sure that the '*_address' attributes are "
759 "Make sure that the '*_address' attributes are "
747 "configured properly. "
760 "configured properly. "
748 "Currently valid addresses are: %s"%LOCAL_IPS
761 "Currently valid addresses are: %s"%LOCAL_IPS
749 )
762 )
750
763
751 # write connection file / get default ports
764 # write connection file / get default ports
752 self.write_connection_file()
765 self.write_connection_file()
753
766
754 self._launch_args = kw.copy()
767 self._launch_args = kw.copy()
755 launch_kernel = kw.pop('launcher', None)
768 launch_kernel = kw.pop('launcher', None)
756 if launch_kernel is None:
769 if launch_kernel is None:
757 if kw.pop('ipython', True):
770 if kw.pop('ipython', True):
758 from ipkernel import launch_kernel
771 from ipkernel import launch_kernel
759 else:
772 else:
760 from pykernel import launch_kernel
773 from pykernel import launch_kernel
761 self.kernel = launch_kernel(fname=self.connection_file, **kw)
774 self.kernel = launch_kernel(fname=self.connection_file, **kw)
762
775
763 def shutdown_kernel(self, restart=False):
776 def shutdown_kernel(self, restart=False):
764 """ Attempts to the stop the kernel process cleanly. If the kernel
777 """ Attempts to the stop the kernel process cleanly. If the kernel
765 cannot be stopped, it is killed, if possible.
778 cannot be stopped, it is killed, if possible.
766 """
779 """
767 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
780 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
768 if sys.platform == 'win32':
781 if sys.platform == 'win32':
769 self.kill_kernel()
782 self.kill_kernel()
770 return
783 return
771
784
772 # Pause the heart beat channel if it exists.
785 # Pause the heart beat channel if it exists.
773 if self._hb_channel is not None:
786 if self._hb_channel is not None:
774 self._hb_channel.pause()
787 self._hb_channel.pause()
775
788
776 # Don't send any additional kernel kill messages immediately, to give
789 # Don't send any additional kernel kill messages immediately, to give
777 # the kernel a chance to properly execute shutdown actions. Wait for at
790 # the kernel a chance to properly execute shutdown actions. Wait for at
778 # most 1s, checking every 0.1s.
791 # most 1s, checking every 0.1s.
779 self.shell_channel.shutdown(restart=restart)
792 self.shell_channel.shutdown(restart=restart)
780 for i in range(10):
793 for i in range(10):
781 if self.is_alive:
794 if self.is_alive:
782 time.sleep(0.1)
795 time.sleep(0.1)
783 else:
796 else:
784 break
797 break
785 else:
798 else:
786 # OK, we've waited long enough.
799 # OK, we've waited long enough.
787 if self.has_kernel:
800 if self.has_kernel:
788 self.kill_kernel()
801 self.kill_kernel()
789
802
790 if not restart and self._connection_file_written:
803 if not restart and self._connection_file_written:
791 # cleanup connection files on full shutdown of kernel we started
804 # cleanup connection files on full shutdown of kernel we started
792 self._connection_file_written = False
805 self._connection_file_written = False
793 try:
806 try:
794 os.remove(self.connection_file)
807 os.remove(self.connection_file)
795 except IOError:
808 except IOError:
796 pass
809 pass
797
810
798 def restart_kernel(self, now=False, **kw):
811 def restart_kernel(self, now=False, **kw):
799 """Restarts a kernel with the arguments that were used to launch it.
812 """Restarts a kernel with the arguments that were used to launch it.
800
813
801 If the old kernel was launched with random ports, the same ports will be
814 If the old kernel was launched with random ports, the same ports will be
802 used for the new kernel.
815 used for the new kernel.
803
816
804 Parameters
817 Parameters
805 ----------
818 ----------
806 now : bool, optional
819 now : bool, optional
807 If True, the kernel is forcefully restarted *immediately*, without
820 If True, the kernel is forcefully restarted *immediately*, without
808 having a chance to do any cleanup action. Otherwise the kernel is
821 having a chance to do any cleanup action. Otherwise the kernel is
809 given 1s to clean up before a forceful restart is issued.
822 given 1s to clean up before a forceful restart is issued.
810
823
811 In all cases the kernel is restarted, the only difference is whether
824 In all cases the kernel is restarted, the only difference is whether
812 it is given a chance to perform a clean shutdown or not.
825 it is given a chance to perform a clean shutdown or not.
813
826
814 **kw : optional
827 **kw : optional
815 Any options specified here will replace those used to launch the
828 Any options specified here will replace those used to launch the
816 kernel.
829 kernel.
817 """
830 """
818 if self._launch_args is None:
831 if self._launch_args is None:
819 raise RuntimeError("Cannot restart the kernel. "
832 raise RuntimeError("Cannot restart the kernel. "
820 "No previous call to 'start_kernel'.")
833 "No previous call to 'start_kernel'.")
821 else:
834 else:
822 # Stop currently running kernel.
835 # Stop currently running kernel.
823 if self.has_kernel:
836 if self.has_kernel:
824 if now:
837 if now:
825 self.kill_kernel()
838 self.kill_kernel()
826 else:
839 else:
827 self.shutdown_kernel(restart=True)
840 self.shutdown_kernel(restart=True)
828
841
829 # Start new kernel.
842 # Start new kernel.
830 self._launch_args.update(kw)
843 self._launch_args.update(kw)
831 self.start_kernel(**self._launch_args)
844 self.start_kernel(**self._launch_args)
832
845
833 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
846 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
834 # unless there is some delay here.
847 # unless there is some delay here.
835 if sys.platform == 'win32':
848 if sys.platform == 'win32':
836 time.sleep(0.2)
849 time.sleep(0.2)
837
850
838 @property
851 @property
839 def has_kernel(self):
852 def has_kernel(self):
840 """Returns whether a kernel process has been specified for the kernel
853 """Returns whether a kernel process has been specified for the kernel
841 manager.
854 manager.
842 """
855 """
843 return self.kernel is not None
856 return self.kernel is not None
844
857
845 def kill_kernel(self):
858 def kill_kernel(self):
846 """ Kill the running kernel. """
859 """ Kill the running kernel. """
847 if self.has_kernel:
860 if self.has_kernel:
848 # Pause the heart beat channel if it exists.
861 # Pause the heart beat channel if it exists.
849 if self._hb_channel is not None:
862 if self._hb_channel is not None:
850 self._hb_channel.pause()
863 self._hb_channel.pause()
851
864
852 # Attempt to kill the kernel.
865 # Attempt to kill the kernel.
853 try:
866 try:
854 self.kernel.kill()
867 self.kernel.kill()
855 except OSError, e:
868 except OSError, e:
856 # In Windows, we will get an Access Denied error if the process
869 # In Windows, we will get an Access Denied error if the process
857 # has already terminated. Ignore it.
870 # has already terminated. Ignore it.
858 if sys.platform == 'win32':
871 if sys.platform == 'win32':
859 if e.winerror != 5:
872 if e.winerror != 5:
860 raise
873 raise
861 # On Unix, we may get an ESRCH error if the process has already
874 # On Unix, we may get an ESRCH error if the process has already
862 # terminated. Ignore it.
875 # terminated. Ignore it.
863 else:
876 else:
864 from errno import ESRCH
877 from errno import ESRCH
865 if e.errno != ESRCH:
878 if e.errno != ESRCH:
866 raise
879 raise
867 self.kernel = None
880 self.kernel = None
868 else:
881 else:
869 raise RuntimeError("Cannot kill kernel. No kernel is running!")
882 raise RuntimeError("Cannot kill kernel. No kernel is running!")
870
883
871 def interrupt_kernel(self):
884 def interrupt_kernel(self):
872 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
885 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
873 well supported on all platforms.
886 well supported on all platforms.
874 """
887 """
875 if self.has_kernel:
888 if self.has_kernel:
876 if sys.platform == 'win32':
889 if sys.platform == 'win32':
877 from parentpoller import ParentPollerWindows as Poller
890 from parentpoller import ParentPollerWindows as Poller
878 Poller.send_interrupt(self.kernel.win32_interrupt_event)
891 Poller.send_interrupt(self.kernel.win32_interrupt_event)
879 else:
892 else:
880 self.kernel.send_signal(signal.SIGINT)
893 self.kernel.send_signal(signal.SIGINT)
881 else:
894 else:
882 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
895 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
883
896
884 def signal_kernel(self, signum):
897 def signal_kernel(self, signum):
885 """ Sends a signal to the kernel. Note that since only SIGTERM is
898 """ Sends a signal to the kernel. Note that since only SIGTERM is
886 supported on Windows, this function is only useful on Unix systems.
899 supported on Windows, this function is only useful on Unix systems.
887 """
900 """
888 if self.has_kernel:
901 if self.has_kernel:
889 self.kernel.send_signal(signum)
902 self.kernel.send_signal(signum)
890 else:
903 else:
891 raise RuntimeError("Cannot signal kernel. No kernel is running!")
904 raise RuntimeError("Cannot signal kernel. No kernel is running!")
892
905
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 #--------------------------------------------------------------------------
909 # Channels used for communication with the kernel:
924 # Channels used for communication with the kernel:
910 #--------------------------------------------------------------------------
925 #--------------------------------------------------------------------------
911
926
912 @property
927 @property
913 def shell_channel(self):
928 def shell_channel(self):
914 """Get the REQ socket channel object to make requests of the kernel."""
929 """Get the REQ socket channel object to make requests of the kernel."""
915 if self._shell_channel is None:
930 if self._shell_channel is None:
916 self._shell_channel = self.shell_channel_class(self.context,
931 self._shell_channel = self.shell_channel_class(self.context,
917 self.session,
932 self.session,
918 (self.ip, self.shell_port))
933 (self.ip, self.shell_port))
919 return self._shell_channel
934 return self._shell_channel
920
935
921 @property
936 @property
922 def sub_channel(self):
937 def sub_channel(self):
923 """Get the SUB socket channel object."""
938 """Get the SUB socket channel object."""
924 if self._sub_channel is None:
939 if self._sub_channel is None:
925 self._sub_channel = self.sub_channel_class(self.context,
940 self._sub_channel = self.sub_channel_class(self.context,
926 self.session,
941 self.session,
927 (self.ip, self.iopub_port))
942 (self.ip, self.iopub_port))
928 return self._sub_channel
943 return self._sub_channel
929
944
930 @property
945 @property
931 def stdin_channel(self):
946 def stdin_channel(self):
932 """Get the REP socket channel object to handle stdin (raw_input)."""
947 """Get the REP socket channel object to handle stdin (raw_input)."""
933 if self._stdin_channel is None:
948 if self._stdin_channel is None:
934 self._stdin_channel = self.stdin_channel_class(self.context,
949 self._stdin_channel = self.stdin_channel_class(self.context,
935 self.session,
950 self.session,
936 (self.ip, self.stdin_port))
951 (self.ip, self.stdin_port))
937 return self._stdin_channel
952 return self._stdin_channel
938
953
939 @property
954 @property
940 def hb_channel(self):
955 def hb_channel(self):
941 """Get the heartbeat socket channel object to check that the
956 """Get the heartbeat socket channel object to check that the
942 kernel is alive."""
957 kernel is alive."""
943 if self._hb_channel is None:
958 if self._hb_channel is None:
944 self._hb_channel = self.hb_channel_class(self.context,
959 self._hb_channel = self.hb_channel_class(self.context,
945 self.session,
960 self.session,
946 (self.ip, self.hb_port))
961 (self.ip, self.hb_port))
947 return self._hb_channel
962 return self._hb_channel
@@ -1,512 +1,509
1 """A ZMQ-based subclass of InteractiveShell.
1 """A ZMQ-based subclass of InteractiveShell.
2
2
3 This code is meant to ease the refactoring of the base InteractiveShell into
3 This code is meant to ease the refactoring of the base InteractiveShell into
4 something with a cleaner architecture for 2-process use, without actually
4 something with a cleaner architecture for 2-process use, without actually
5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
6 we subclass and override what we want to fix. Once this is working well, we
6 we subclass and override what we want to fix. Once this is working well, we
7 can go back to the base class and refactor the code for a cleaner inheritance
7 can go back to the base class and refactor the code for a cleaner inheritance
8 implementation that doesn't rely on so much monkeypatching.
8 implementation that doesn't rely on so much monkeypatching.
9
9
10 But this lets us maintain a fully working IPython as we develop the new
10 But this lets us maintain a fully working IPython as we develop the new
11 machinery. This should thus be thought of as scaffolding.
11 machinery. This should thus be thought of as scaffolding.
12 """
12 """
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Stdlib
18 # Stdlib
19 import inspect
19 import inspect
20 import os
20 import os
21 import sys
21 import sys
22 from subprocess import Popen, PIPE
22 from subprocess import Popen, PIPE
23
23
24 # Our own
24 # Our own
25 from IPython.core.interactiveshell import (
25 from IPython.core.interactiveshell import (
26 InteractiveShell, InteractiveShellABC
26 InteractiveShell, InteractiveShellABC
27 )
27 )
28 from IPython.core import page, pylabtools
28 from IPython.core import page, pylabtools
29 from IPython.core.autocall import ZMQExitAutocall
29 from IPython.core.autocall import ZMQExitAutocall
30 from IPython.core.displaypub import DisplayPublisher
30 from IPython.core.displaypub import DisplayPublisher
31 from IPython.core.macro import Macro
31 from IPython.core.macro import Macro
32 from IPython.core.magic import MacroToEdit
32 from IPython.core.magic import MacroToEdit
33 from IPython.core.payloadpage import install_payload_page
33 from IPython.core.payloadpage import install_payload_page
34 from IPython.lib.kernel import (
34 from IPython.lib.kernel import (
35 get_connection_file, get_connection_info, connect_qtconsole
35 get_connection_file, get_connection_info, connect_qtconsole
36 )
36 )
37 from IPython.utils import io
37 from IPython.utils import io
38 from IPython.utils.jsonutil import json_clean
38 from IPython.utils.jsonutil import json_clean
39 from IPython.utils.path import get_py_filename
39 from IPython.utils.path import get_py_filename
40 from IPython.utils.process import arg_split
40 from IPython.utils.process import arg_split
41 from IPython.utils.traitlets import Instance, Type, Dict, CBool
41 from IPython.utils.traitlets import Instance, Type, Dict, CBool
42 from IPython.utils.warn import warn, error
42 from IPython.utils.warn import warn, error
43 from IPython.zmq.displayhook import ZMQShellDisplayHook, _encode_binary
43 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
56 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
57
51
58 class ZMQDisplayPublisher(DisplayPublisher):
52 class ZMQDisplayPublisher(DisplayPublisher):
59 """A display publisher that publishes data using a ZeroMQ PUB socket."""
53 """A display publisher that publishes data using a ZeroMQ PUB socket."""
60
54
61 session = Instance(Session)
55 session = Instance(Session)
62 pub_socket = Instance('zmq.Socket')
56 pub_socket = Instance('zmq.Socket')
63 parent_header = Dict({})
57 parent_header = Dict({})
64
58
65 def set_parent(self, parent):
59 def set_parent(self, parent):
66 """Set the parent for outbound messages."""
60 """Set the parent for outbound messages."""
67 self.parent_header = extract_header(parent)
61 self.parent_header = extract_header(parent)
68
62
69 def publish(self, source, data, metadata=None):
63 def publish(self, source, data, metadata=None):
70 if metadata is None:
64 if metadata is None:
71 metadata = {}
65 metadata = {}
72 self._validate_data(source, data, metadata)
66 self._validate_data(source, data, metadata)
73 content = {}
67 content = {}
74 content['source'] = source
68 content['source'] = source
75 _encode_binary(data)
69 _encode_binary(data)
76 content['data'] = data
70 content['data'] = data
77 content['metadata'] = metadata
71 content['metadata'] = metadata
78 self.session.send(
72 self.session.send(
79 self.pub_socket, u'display_data', json_clean(content),
73 self.pub_socket, u'display_data', json_clean(content),
80 parent=self.parent_header
74 parent=self.parent_header
81 )
75 )
82
76
83 def clear_output(self, stdout=True, stderr=True, other=True):
77 def clear_output(self, stdout=True, stderr=True, other=True):
84 content = dict(stdout=stdout, stderr=stderr, other=other)
78 content = dict(stdout=stdout, stderr=stderr, other=other)
85 self.session.send(
79 self.session.send(
86 self.pub_socket, u'clear_output', content,
80 self.pub_socket, u'clear_output', content,
87 parent=self.parent_header
81 parent=self.parent_header
88 )
82 )
89
83
90 class ZMQInteractiveShell(InteractiveShell):
84 class ZMQInteractiveShell(InteractiveShell):
91 """A subclass of InteractiveShell for ZMQ."""
85 """A subclass of InteractiveShell for ZMQ."""
92
86
93 displayhook_class = Type(ZMQShellDisplayHook)
87 displayhook_class = Type(ZMQShellDisplayHook)
94 display_pub_class = Type(ZMQDisplayPublisher)
88 display_pub_class = Type(ZMQDisplayPublisher)
95
89
96 # Override the traitlet in the parent class, because there's no point using
90 # Override the traitlet in the parent class, because there's no point using
97 # readline for the kernel. Can be removed when the readline code is moved
91 # readline for the kernel. Can be removed when the readline code is moved
98 # to the terminal frontend.
92 # to the terminal frontend.
99 colors_force = CBool(True)
93 colors_force = CBool(True)
100 readline_use = CBool(False)
94 readline_use = CBool(False)
101 # autoindent has no meaning in a zmqshell, and attempting to enable it
95 # autoindent has no meaning in a zmqshell, and attempting to enable it
102 # will print a warning in the absence of readline.
96 # will print a warning in the absence of readline.
103 autoindent = CBool(False)
97 autoindent = CBool(False)
104
98
105 exiter = Instance(ZMQExitAutocall)
99 exiter = Instance(ZMQExitAutocall)
106 def _exiter_default(self):
100 def _exiter_default(self):
107 return ZMQExitAutocall(self)
101 return ZMQExitAutocall(self)
108
102
109 keepkernel_on_exit = None
103 keepkernel_on_exit = None
110
104
111 # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no
105 # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no
112 # interactive input being read; we provide event loop support in ipkernel
106 # interactive input being read; we provide event loop support in ipkernel
113 from .eventloops import enable_gui
107 from .eventloops import enable_gui
114 enable_gui = staticmethod(enable_gui)
108 enable_gui = staticmethod(enable_gui)
115
109
116 def init_environment(self):
110 def init_environment(self):
117 """Configure the user's environment.
111 """Configure the user's environment.
118
112
119 """
113 """
120 env = os.environ
114 env = os.environ
121 # These two ensure 'ls' produces nice coloring on BSD-derived systems
115 # These two ensure 'ls' produces nice coloring on BSD-derived systems
122 env['TERM'] = 'xterm-color'
116 env['TERM'] = 'xterm-color'
123 env['CLICOLOR'] = '1'
117 env['CLICOLOR'] = '1'
124 # Since normal pagers don't work at all (over pexpect we don't have
118 # Since normal pagers don't work at all (over pexpect we don't have
125 # single-key control of the subprocess), try to disable paging in
119 # single-key control of the subprocess), try to disable paging in
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'
129
123
124 # And install the payload version of page.
125 install_payload_page()
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.
132
129
133 FIXME: this payload is currently not correctly processed by the
130 FIXME: this payload is currently not correctly processed by the
134 frontend.
131 frontend.
135 """
132 """
136 new = self.prompt_manager.render('rewrite') + cmd
133 new = self.prompt_manager.render('rewrite') + cmd
137 payload = dict(
134 payload = dict(
138 source='IPython.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input',
135 source='IPython.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input',
139 transformed_input=new,
136 transformed_input=new,
140 )
137 )
141 self.payload_manager.write_payload(payload)
138 self.payload_manager.write_payload(payload)
142
139
143 def ask_exit(self):
140 def ask_exit(self):
144 """Engage the exit actions."""
141 """Engage the exit actions."""
145 payload = dict(
142 payload = dict(
146 source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit',
143 source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit',
147 exit=True,
144 exit=True,
148 keepkernel=self.keepkernel_on_exit,
145 keepkernel=self.keepkernel_on_exit,
149 )
146 )
150 self.payload_manager.write_payload(payload)
147 self.payload_manager.write_payload(payload)
151
148
152 def _showtraceback(self, etype, evalue, stb):
149 def _showtraceback(self, etype, evalue, stb):
153
150
154 exc_content = {
151 exc_content = {
155 u'traceback' : stb,
152 u'traceback' : stb,
156 u'ename' : unicode(etype.__name__),
153 u'ename' : unicode(etype.__name__),
157 u'evalue' : unicode(evalue)
154 u'evalue' : unicode(evalue)
158 }
155 }
159
156
160 dh = self.displayhook
157 dh = self.displayhook
161 # Send exception info over pub socket for other clients than the caller
158 # Send exception info over pub socket for other clients than the caller
162 # to pick up
159 # to pick up
163 exc_msg = dh.session.send(dh.pub_socket, u'pyerr', json_clean(exc_content), dh.parent_header)
160 exc_msg = dh.session.send(dh.pub_socket, u'pyerr', json_clean(exc_content), dh.parent_header)
164
161
165 # FIXME - Hack: store exception info in shell object. Right now, the
162 # FIXME - Hack: store exception info in shell object. Right now, the
166 # caller is reading this info after the fact, we need to fix this logic
163 # caller is reading this info after the fact, we need to fix this logic
167 # to remove this hack. Even uglier, we need to store the error status
164 # to remove this hack. Even uglier, we need to store the error status
168 # here, because in the main loop, the logic that sets it is being
165 # here, because in the main loop, the logic that sets it is being
169 # skipped because runlines swallows the exceptions.
166 # skipped because runlines swallows the exceptions.
170 exc_content[u'status'] = u'error'
167 exc_content[u'status'] = u'error'
171 self._reply_content = exc_content
168 self._reply_content = exc_content
172 # /FIXME
169 # /FIXME
173
170
174 return exc_content
171 return exc_content
175
172
176 #------------------------------------------------------------------------
173 #------------------------------------------------------------------------
177 # Magic overrides
174 # Magic overrides
178 #------------------------------------------------------------------------
175 #------------------------------------------------------------------------
179 # Once the base class stops inheriting from magic, this code needs to be
176 # Once the base class stops inheriting from magic, this code needs to be
180 # moved into a separate machinery as well. For now, at least isolate here
177 # moved into a separate machinery as well. For now, at least isolate here
181 # the magics which this class needs to implement differently from the base
178 # the magics which this class needs to implement differently from the base
182 # class, or that are unique to it.
179 # class, or that are unique to it.
183
180
184 def magic_doctest_mode(self,parameter_s=''):
181 def magic_doctest_mode(self,parameter_s=''):
185 """Toggle doctest mode on and off.
182 """Toggle doctest mode on and off.
186
183
187 This mode is intended to make IPython behave as much as possible like a
184 This mode is intended to make IPython behave as much as possible like a
188 plain Python shell, from the perspective of how its prompts, exceptions
185 plain Python shell, from the perspective of how its prompts, exceptions
189 and output look. This makes it easy to copy and paste parts of a
186 and output look. This makes it easy to copy and paste parts of a
190 session into doctests. It does so by:
187 session into doctests. It does so by:
191
188
192 - Changing the prompts to the classic ``>>>`` ones.
189 - Changing the prompts to the classic ``>>>`` ones.
193 - Changing the exception reporting mode to 'Plain'.
190 - Changing the exception reporting mode to 'Plain'.
194 - Disabling pretty-printing of output.
191 - Disabling pretty-printing of output.
195
192
196 Note that IPython also supports the pasting of code snippets that have
193 Note that IPython also supports the pasting of code snippets that have
197 leading '>>>' and '...' prompts in them. This means that you can paste
194 leading '>>>' and '...' prompts in them. This means that you can paste
198 doctests from files or docstrings (even if they have leading
195 doctests from files or docstrings (even if they have leading
199 whitespace), and the code will execute correctly. You can then use
196 whitespace), and the code will execute correctly. You can then use
200 '%history -t' to see the translated history; this will give you the
197 '%history -t' to see the translated history; this will give you the
201 input after removal of all the leading prompts and whitespace, which
198 input after removal of all the leading prompts and whitespace, which
202 can be pasted back into an editor.
199 can be pasted back into an editor.
203
200
204 With these features, you can switch into this mode easily whenever you
201 With these features, you can switch into this mode easily whenever you
205 need to do testing and changes to doctests, without having to leave
202 need to do testing and changes to doctests, without having to leave
206 your existing IPython session.
203 your existing IPython session.
207 """
204 """
208
205
209 from IPython.utils.ipstruct import Struct
206 from IPython.utils.ipstruct import Struct
210
207
211 # Shorthands
208 # Shorthands
212 shell = self.shell
209 shell = self.shell
213 disp_formatter = self.shell.display_formatter
210 disp_formatter = self.shell.display_formatter
214 ptformatter = disp_formatter.formatters['text/plain']
211 ptformatter = disp_formatter.formatters['text/plain']
215 # dstore is a data store kept in the instance metadata bag to track any
212 # dstore is a data store kept in the instance metadata bag to track any
216 # changes we make, so we can undo them later.
213 # changes we make, so we can undo them later.
217 dstore = shell.meta.setdefault('doctest_mode', Struct())
214 dstore = shell.meta.setdefault('doctest_mode', Struct())
218 save_dstore = dstore.setdefault
215 save_dstore = dstore.setdefault
219
216
220 # save a few values we'll need to recover later
217 # save a few values we'll need to recover later
221 mode = save_dstore('mode', False)
218 mode = save_dstore('mode', False)
222 save_dstore('rc_pprint', ptformatter.pprint)
219 save_dstore('rc_pprint', ptformatter.pprint)
223 save_dstore('rc_plain_text_only',disp_formatter.plain_text_only)
220 save_dstore('rc_plain_text_only',disp_formatter.plain_text_only)
224 save_dstore('xmode', shell.InteractiveTB.mode)
221 save_dstore('xmode', shell.InteractiveTB.mode)
225
222
226 if mode == False:
223 if mode == False:
227 # turn on
224 # turn on
228 ptformatter.pprint = False
225 ptformatter.pprint = False
229 disp_formatter.plain_text_only = True
226 disp_formatter.plain_text_only = True
230 shell.magic_xmode('Plain')
227 shell.magic_xmode('Plain')
231 else:
228 else:
232 # turn off
229 # turn off
233 ptformatter.pprint = dstore.rc_pprint
230 ptformatter.pprint = dstore.rc_pprint
234 disp_formatter.plain_text_only = dstore.rc_plain_text_only
231 disp_formatter.plain_text_only = dstore.rc_plain_text_only
235 shell.magic_xmode(dstore.xmode)
232 shell.magic_xmode(dstore.xmode)
236
233
237 # Store new mode and inform on console
234 # Store new mode and inform on console
238 dstore.mode = bool(1-int(mode))
235 dstore.mode = bool(1-int(mode))
239 mode_label = ['OFF','ON'][dstore.mode]
236 mode_label = ['OFF','ON'][dstore.mode]
240 print('Doctest mode is:', mode_label)
237 print('Doctest mode is:', mode_label)
241
238
242 # Send the payload back so that clients can modify their prompt display
239 # Send the payload back so that clients can modify their prompt display
243 payload = dict(
240 payload = dict(
244 source='IPython.zmq.zmqshell.ZMQInteractiveShell.magic_doctest_mode',
241 source='IPython.zmq.zmqshell.ZMQInteractiveShell.magic_doctest_mode',
245 mode=dstore.mode)
242 mode=dstore.mode)
246 self.payload_manager.write_payload(payload)
243 self.payload_manager.write_payload(payload)
247
244
248 def magic_edit(self,parameter_s='',last_call=['','']):
245 def magic_edit(self,parameter_s='',last_call=['','']):
249 """Bring up an editor and execute the resulting code.
246 """Bring up an editor and execute the resulting code.
250
247
251 Usage:
248 Usage:
252 %edit [options] [args]
249 %edit [options] [args]
253
250
254 %edit runs an external text editor. You will need to set the command for
251 %edit runs an external text editor. You will need to set the command for
255 this editor via the ``TerminalInteractiveShell.editor`` option in your
252 this editor via the ``TerminalInteractiveShell.editor`` option in your
256 configuration file before it will work.
253 configuration file before it will work.
257
254
258 This command allows you to conveniently edit multi-line code right in
255 This command allows you to conveniently edit multi-line code right in
259 your IPython session.
256 your IPython session.
260
257
261 If called without arguments, %edit opens up an empty editor with a
258 If called without arguments, %edit opens up an empty editor with a
262 temporary file and will execute the contents of this file when you
259 temporary file and will execute the contents of this file when you
263 close it (don't forget to save it!).
260 close it (don't forget to save it!).
264
261
265
262
266 Options:
263 Options:
267
264
268 -n <number>: open the editor at a specified line number. By default,
265 -n <number>: open the editor at a specified line number. By default,
269 the IPython editor hook uses the unix syntax 'editor +N filename', but
266 the IPython editor hook uses the unix syntax 'editor +N filename', but
270 you can configure this by providing your own modified hook if your
267 you can configure this by providing your own modified hook if your
271 favorite editor supports line-number specifications with a different
268 favorite editor supports line-number specifications with a different
272 syntax.
269 syntax.
273
270
274 -p: this will call the editor with the same data as the previous time
271 -p: this will call the editor with the same data as the previous time
275 it was used, regardless of how long ago (in your current session) it
272 it was used, regardless of how long ago (in your current session) it
276 was.
273 was.
277
274
278 -r: use 'raw' input. This option only applies to input taken from the
275 -r: use 'raw' input. This option only applies to input taken from the
279 user's history. By default, the 'processed' history is used, so that
276 user's history. By default, the 'processed' history is used, so that
280 magics are loaded in their transformed version to valid Python. If
277 magics are loaded in their transformed version to valid Python. If
281 this option is given, the raw input as typed as the command line is
278 this option is given, the raw input as typed as the command line is
282 used instead. When you exit the editor, it will be executed by
279 used instead. When you exit the editor, it will be executed by
283 IPython's own processor.
280 IPython's own processor.
284
281
285 -x: do not execute the edited code immediately upon exit. This is
282 -x: do not execute the edited code immediately upon exit. This is
286 mainly useful if you are editing programs which need to be called with
283 mainly useful if you are editing programs which need to be called with
287 command line arguments, which you can then do using %run.
284 command line arguments, which you can then do using %run.
288
285
289
286
290 Arguments:
287 Arguments:
291
288
292 If arguments are given, the following possibilites exist:
289 If arguments are given, the following possibilites exist:
293
290
294 - The arguments are numbers or pairs of colon-separated numbers (like
291 - The arguments are numbers or pairs of colon-separated numbers (like
295 1 4:8 9). These are interpreted as lines of previous input to be
292 1 4:8 9). These are interpreted as lines of previous input to be
296 loaded into the editor. The syntax is the same of the %macro command.
293 loaded into the editor. The syntax is the same of the %macro command.
297
294
298 - If the argument doesn't start with a number, it is evaluated as a
295 - If the argument doesn't start with a number, it is evaluated as a
299 variable and its contents loaded into the editor. You can thus edit
296 variable and its contents loaded into the editor. You can thus edit
300 any string which contains python code (including the result of
297 any string which contains python code (including the result of
301 previous edits).
298 previous edits).
302
299
303 - If the argument is the name of an object (other than a string),
300 - If the argument is the name of an object (other than a string),
304 IPython will try to locate the file where it was defined and open the
301 IPython will try to locate the file where it was defined and open the
305 editor at the point where it is defined. You can use `%edit function`
302 editor at the point where it is defined. You can use `%edit function`
306 to load an editor exactly at the point where 'function' is defined,
303 to load an editor exactly at the point where 'function' is defined,
307 edit it and have the file be executed automatically.
304 edit it and have the file be executed automatically.
308
305
309 If the object is a macro (see %macro for details), this opens up your
306 If the object is a macro (see %macro for details), this opens up your
310 specified editor with a temporary file containing the macro's data.
307 specified editor with a temporary file containing the macro's data.
311 Upon exit, the macro is reloaded with the contents of the file.
308 Upon exit, the macro is reloaded with the contents of the file.
312
309
313 Note: opening at an exact line is only supported under Unix, and some
310 Note: opening at an exact line is only supported under Unix, and some
314 editors (like kedit and gedit up to Gnome 2.8) do not understand the
311 editors (like kedit and gedit up to Gnome 2.8) do not understand the
315 '+NUMBER' parameter necessary for this feature. Good editors like
312 '+NUMBER' parameter necessary for this feature. Good editors like
316 (X)Emacs, vi, jed, pico and joe all do.
313 (X)Emacs, vi, jed, pico and joe all do.
317
314
318 - If the argument is not found as a variable, IPython will look for a
315 - If the argument is not found as a variable, IPython will look for a
319 file with that name (adding .py if necessary) and load it into the
316 file with that name (adding .py if necessary) and load it into the
320 editor. It will execute its contents with execfile() when you exit,
317 editor. It will execute its contents with execfile() when you exit,
321 loading any code in the file into your interactive namespace.
318 loading any code in the file into your interactive namespace.
322
319
323 After executing your code, %edit will return as output the code you
320 After executing your code, %edit will return as output the code you
324 typed in the editor (except when it was an existing file). This way
321 typed in the editor (except when it was an existing file). This way
325 you can reload the code in further invocations of %edit as a variable,
322 you can reload the code in further invocations of %edit as a variable,
326 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
323 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
327 the output.
324 the output.
328
325
329 Note that %edit is also available through the alias %ed.
326 Note that %edit is also available through the alias %ed.
330
327
331 This is an example of creating a simple function inside the editor and
328 This is an example of creating a simple function inside the editor and
332 then modifying it. First, start up the editor:
329 then modifying it. First, start up the editor:
333
330
334 In [1]: ed
331 In [1]: ed
335 Editing... done. Executing edited code...
332 Editing... done. Executing edited code...
336 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
333 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
337
334
338 We can then call the function foo():
335 We can then call the function foo():
339
336
340 In [2]: foo()
337 In [2]: foo()
341 foo() was defined in an editing session
338 foo() was defined in an editing session
342
339
343 Now we edit foo. IPython automatically loads the editor with the
340 Now we edit foo. IPython automatically loads the editor with the
344 (temporary) file where foo() was previously defined:
341 (temporary) file where foo() was previously defined:
345
342
346 In [3]: ed foo
343 In [3]: ed foo
347 Editing... done. Executing edited code...
344 Editing... done. Executing edited code...
348
345
349 And if we call foo() again we get the modified version:
346 And if we call foo() again we get the modified version:
350
347
351 In [4]: foo()
348 In [4]: foo()
352 foo() has now been changed!
349 foo() has now been changed!
353
350
354 Here is an example of how to edit a code snippet successive
351 Here is an example of how to edit a code snippet successive
355 times. First we call the editor:
352 times. First we call the editor:
356
353
357 In [5]: ed
354 In [5]: ed
358 Editing... done. Executing edited code...
355 Editing... done. Executing edited code...
359 hello
356 hello
360 Out[5]: "print 'hello'n"
357 Out[5]: "print 'hello'n"
361
358
362 Now we call it again with the previous output (stored in _):
359 Now we call it again with the previous output (stored in _):
363
360
364 In [6]: ed _
361 In [6]: ed _
365 Editing... done. Executing edited code...
362 Editing... done. Executing edited code...
366 hello world
363 hello world
367 Out[6]: "print 'hello world'n"
364 Out[6]: "print 'hello world'n"
368
365
369 Now we call it with the output #8 (stored in _8, also as Out[8]):
366 Now we call it with the output #8 (stored in _8, also as Out[8]):
370
367
371 In [7]: ed _8
368 In [7]: ed _8
372 Editing... done. Executing edited code...
369 Editing... done. Executing edited code...
373 hello again
370 hello again
374 Out[7]: "print 'hello again'n"
371 Out[7]: "print 'hello again'n"
375 """
372 """
376
373
377 opts,args = self.parse_options(parameter_s,'prn:')
374 opts,args = self.parse_options(parameter_s,'prn:')
378
375
379 try:
376 try:
380 filename, lineno, _ = self._find_edit_target(args, opts, last_call)
377 filename, lineno, _ = self._find_edit_target(args, opts, last_call)
381 except MacroToEdit as e:
378 except MacroToEdit as e:
382 # TODO: Implement macro editing over 2 processes.
379 # TODO: Implement macro editing over 2 processes.
383 print("Macro editing not yet implemented in 2-process model.")
380 print("Macro editing not yet implemented in 2-process model.")
384 return
381 return
385
382
386 # Make sure we send to the client an absolute path, in case the working
383 # Make sure we send to the client an absolute path, in case the working
387 # directory of client and kernel don't match
384 # directory of client and kernel don't match
388 filename = os.path.abspath(filename)
385 filename = os.path.abspath(filename)
389
386
390 payload = {
387 payload = {
391 'source' : 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic',
388 'source' : 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic',
392 'filename' : filename,
389 'filename' : filename,
393 'line_number' : lineno
390 'line_number' : lineno
394 }
391 }
395 self.payload_manager.write_payload(payload)
392 self.payload_manager.write_payload(payload)
396
393
397 # A few magics that are adapted to the specifics of using pexpect and a
394 # A few magics that are adapted to the specifics of using pexpect and a
398 # remote terminal
395 # remote terminal
399
396
400 def magic_clear(self, arg_s):
397 def magic_clear(self, arg_s):
401 """Clear the terminal."""
398 """Clear the terminal."""
402 if os.name == 'posix':
399 if os.name == 'posix':
403 self.shell.system("clear")
400 self.shell.system("clear")
404 else:
401 else:
405 self.shell.system("cls")
402 self.shell.system("cls")
406
403
407 if os.name == 'nt':
404 if os.name == 'nt':
408 # This is the usual name in windows
405 # This is the usual name in windows
409 magic_cls = magic_clear
406 magic_cls = magic_clear
410
407
411 # Terminal pagers won't work over pexpect, but we do have our own pager
408 # Terminal pagers won't work over pexpect, but we do have our own pager
412
409
413 def magic_less(self, arg_s):
410 def magic_less(self, arg_s):
414 """Show a file through the pager.
411 """Show a file through the pager.
415
412
416 Files ending in .py are syntax-highlighted."""
413 Files ending in .py are syntax-highlighted."""
417 cont = open(arg_s).read()
414 cont = open(arg_s).read()
418 if arg_s.endswith('.py'):
415 if arg_s.endswith('.py'):
419 cont = self.shell.pycolorize(cont)
416 cont = self.shell.pycolorize(cont)
420 page.page(cont)
417 page.page(cont)
421
418
422 magic_more = magic_less
419 magic_more = magic_less
423
420
424 # Man calls a pager, so we also need to redefine it
421 # Man calls a pager, so we also need to redefine it
425 if os.name == 'posix':
422 if os.name == 'posix':
426 def magic_man(self, arg_s):
423 def magic_man(self, arg_s):
427 """Find the man page for the given command and display in pager."""
424 """Find the man page for the given command and display in pager."""
428 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
425 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
429 split=False))
426 split=False))
430
427
431 # FIXME: this is specific to the GUI, so we should let the gui app load
428 # FIXME: this is specific to the GUI, so we should let the gui app load
432 # magics at startup that are only for the gui. Once the gui app has proper
429 # magics at startup that are only for the gui. Once the gui app has proper
433 # profile and configuration management, we can have it initialize a kernel
430 # profile and configuration management, we can have it initialize a kernel
434 # with a special config file that provides these.
431 # with a special config file that provides these.
435 def magic_guiref(self, arg_s):
432 def magic_guiref(self, arg_s):
436 """Show a basic reference about the GUI console."""
433 """Show a basic reference about the GUI console."""
437 from IPython.core.usage import gui_reference
434 from IPython.core.usage import gui_reference
438 page.page(gui_reference, auto_html=True)
435 page.page(gui_reference, auto_html=True)
439
436
440 def magic_connect_info(self, arg_s):
437 def magic_connect_info(self, arg_s):
441 """Print information for connecting other clients to this kernel
438 """Print information for connecting other clients to this kernel
442
439
443 It will print the contents of this session's connection file, as well as
440 It will print the contents of this session's connection file, as well as
444 shortcuts for local clients.
441 shortcuts for local clients.
445
442
446 In the simplest case, when called from the most recently launched kernel,
443 In the simplest case, when called from the most recently launched kernel,
447 secondary clients can be connected, simply with:
444 secondary clients can be connected, simply with:
448
445
449 $> ipython <app> --existing
446 $> ipython <app> --existing
450
447
451 """
448 """
452
449
453 from IPython.core.application import BaseIPythonApplication as BaseIPApp
450 from IPython.core.application import BaseIPythonApplication as BaseIPApp
454
451
455 if BaseIPApp.initialized():
452 if BaseIPApp.initialized():
456 app = BaseIPApp.instance()
453 app = BaseIPApp.instance()
457 security_dir = app.profile_dir.security_dir
454 security_dir = app.profile_dir.security_dir
458 profile = app.profile
455 profile = app.profile
459 else:
456 else:
460 profile = 'default'
457 profile = 'default'
461 security_dir = ''
458 security_dir = ''
462
459
463 try:
460 try:
464 connection_file = get_connection_file()
461 connection_file = get_connection_file()
465 info = get_connection_info(unpack=False)
462 info = get_connection_info(unpack=False)
466 except Exception as e:
463 except Exception as e:
467 error("Could not get connection info: %r" % e)
464 error("Could not get connection info: %r" % e)
468 return
465 return
469
466
470 # add profile flag for non-default profile
467 # add profile flag for non-default profile
471 profile_flag = "--profile %s" % profile if profile != 'default' else ""
468 profile_flag = "--profile %s" % profile if profile != 'default' else ""
472
469
473 # if it's in the security dir, truncate to basename
470 # if it's in the security dir, truncate to basename
474 if security_dir == os.path.dirname(connection_file):
471 if security_dir == os.path.dirname(connection_file):
475 connection_file = os.path.basename(connection_file)
472 connection_file = os.path.basename(connection_file)
476
473
477
474
478 print (info + '\n')
475 print (info + '\n')
479 print ("Paste the above JSON into a file, and connect with:\n"
476 print ("Paste the above JSON into a file, and connect with:\n"
480 " $> ipython <app> --existing <file>\n"
477 " $> ipython <app> --existing <file>\n"
481 "or, if you are local, you can connect with just:\n"
478 "or, if you are local, you can connect with just:\n"
482 " $> ipython <app> --existing {0} {1}\n"
479 " $> ipython <app> --existing {0} {1}\n"
483 "or even just:\n"
480 "or even just:\n"
484 " $> ipython <app> --existing {1}\n"
481 " $> ipython <app> --existing {1}\n"
485 "if this is the most recent IPython session you have started.".format(
482 "if this is the most recent IPython session you have started.".format(
486 connection_file, profile_flag
483 connection_file, profile_flag
487 )
484 )
488 )
485 )
489
486
490 def magic_qtconsole(self, arg_s):
487 def magic_qtconsole(self, arg_s):
491 """Open a qtconsole connected to this kernel.
488 """Open a qtconsole connected to this kernel.
492
489
493 Useful for connecting a qtconsole to running notebooks, for better
490 Useful for connecting a qtconsole to running notebooks, for better
494 debugging.
491 debugging.
495 """
492 """
496 try:
493 try:
497 p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix'))
494 p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix'))
498 except Exception as e:
495 except Exception as e:
499 error("Could not start qtconsole: %r" % e)
496 error("Could not start qtconsole: %r" % e)
500 return
497 return
501
498
502 def set_next_input(self, text):
499 def set_next_input(self, text):
503 """Send the specified text to the frontend to be presented at the next
500 """Send the specified text to the frontend to be presented at the next
504 input cell."""
501 input cell."""
505 payload = dict(
502 payload = dict(
506 source='IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input',
503 source='IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input',
507 text=text
504 text=text
508 )
505 )
509 self.payload_manager.write_payload(payload)
506 self.payload_manager.write_payload(payload)
510
507
511
508
512 InteractiveShellABC.register(ZMQInteractiveShell)
509 InteractiveShellABC.register(ZMQInteractiveShell)
General Comments 0
You need to be logged in to leave comments. Login now