##// END OF EJS Templates
move utils.kernel (formerly entry_point and lib.kernel) to kernel.util
MinRK -
Show More
@@ -1,362 +1,362 b''
1 1 """ A minimal application base mixin for all ZMQ based IPython frontends.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations. This is a
5 5 refactoring of what used to be the IPython/frontend/qt/console/qtconsoleapp.py
6 6
7 7 Authors:
8 8
9 9 * Evan Patterson
10 10 * Min RK
11 11 * Erik Tollerud
12 12 * Fernando Perez
13 13 * Bussonnier Matthias
14 14 * Thomas Kluyver
15 15 * Paul Ivanov
16 16
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 # stdlib imports
24 24 import atexit
25 25 import json
26 26 import os
27 27 import shutil
28 28 import signal
29 29 import sys
30 30 import uuid
31 31
32 32
33 33 # Local imports
34 34 from IPython.config.application import boolean_flag
35 35 from IPython.config.configurable import Configurable
36 36 from IPython.core.profiledir import ProfileDir
37 37 from IPython.zmq.blockingkernelmanager import BlockingKernelManager
38 38 from IPython.zmq.kernelmanager import KernelManager
39 from IPython.utils.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
39 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
40 40 from IPython.utils.path import filefind
41 41 from IPython.utils.py3compat import str_to_bytes
42 42 from IPython.utils.traitlets import (
43 43 Dict, List, Unicode, CUnicode, Int, CBool, Any, CaselessStrEnum
44 44 )
45 45 from IPython.zmq.ipkernel import (
46 46 flags as ipkernel_flags,
47 47 aliases as ipkernel_aliases,
48 48 IPKernelApp
49 49 )
50 50 from IPython.zmq.session import Session, default_secure
51 51 from IPython.zmq.zmqshell import ZMQInteractiveShell
52 52
53 53 #-----------------------------------------------------------------------------
54 54 # Network Constants
55 55 #-----------------------------------------------------------------------------
56 56
57 57 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
58 58
59 59 #-----------------------------------------------------------------------------
60 60 # Globals
61 61 #-----------------------------------------------------------------------------
62 62
63 63
64 64 #-----------------------------------------------------------------------------
65 65 # Aliases and Flags
66 66 #-----------------------------------------------------------------------------
67 67
68 68 flags = dict(ipkernel_flags)
69 69
70 70 # the flags that are specific to the frontend
71 71 # these must be scrubbed before being passed to the kernel,
72 72 # or it will raise an error on unrecognized flags
73 73 app_flags = {
74 74 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
75 75 "Connect to an existing kernel. If no argument specified, guess most recent"),
76 76 }
77 77 app_flags.update(boolean_flag(
78 78 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
79 79 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
80 80 to force a direct exit without any confirmation.
81 81 """,
82 82 """Don't prompt the user when exiting. This will terminate the kernel
83 83 if it is owned by the frontend, and leave it alive if it is external.
84 84 """
85 85 ))
86 86 flags.update(app_flags)
87 87
88 88 aliases = dict(ipkernel_aliases)
89 89
90 90 # also scrub aliases from the frontend
91 91 app_aliases = dict(
92 92 ip = 'KernelManager.ip',
93 93 transport = 'KernelManager.transport',
94 94 hb = 'IPythonConsoleApp.hb_port',
95 95 shell = 'IPythonConsoleApp.shell_port',
96 96 iopub = 'IPythonConsoleApp.iopub_port',
97 97 stdin = 'IPythonConsoleApp.stdin_port',
98 98 existing = 'IPythonConsoleApp.existing',
99 99 f = 'IPythonConsoleApp.connection_file',
100 100
101 101
102 102 ssh = 'IPythonConsoleApp.sshserver',
103 103 )
104 104 aliases.update(app_aliases)
105 105
106 106 #-----------------------------------------------------------------------------
107 107 # Classes
108 108 #-----------------------------------------------------------------------------
109 109
110 110 #-----------------------------------------------------------------------------
111 111 # IPythonConsole
112 112 #-----------------------------------------------------------------------------
113 113
114 114 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session]
115 115
116 116 try:
117 117 from IPython.zmq.pylab.backend_inline import InlineBackend
118 118 except ImportError:
119 119 pass
120 120 else:
121 121 classes.append(InlineBackend)
122 122
123 123 class IPythonConsoleApp(Configurable):
124 124 name = 'ipython-console-mixin'
125 125 default_config_file_name='ipython_config.py'
126 126
127 127 description = """
128 128 The IPython Mixin Console.
129 129
130 130 This class contains the common portions of console client (QtConsole,
131 131 ZMQ-based terminal console, etc). It is not a full console, in that
132 132 launched terminal subprocesses will not be able to accept input.
133 133
134 134 The Console using this mixing supports various extra features beyond
135 135 the single-process Terminal IPython shell, such as connecting to
136 136 existing kernel, via:
137 137
138 138 ipython <appname> --existing
139 139
140 140 as well as tunnel via SSH
141 141
142 142 """
143 143
144 144 classes = classes
145 145 flags = Dict(flags)
146 146 aliases = Dict(aliases)
147 147 kernel_manager_class = BlockingKernelManager
148 148
149 149 kernel_argv = List(Unicode)
150 150 # frontend flags&aliases to be stripped when building kernel_argv
151 151 frontend_flags = Any(app_flags)
152 152 frontend_aliases = Any(app_aliases)
153 153
154 154 # create requested profiles by default, if they don't exist:
155 155 auto_create = CBool(True)
156 156 # connection info:
157 157
158 158 sshserver = Unicode('', config=True,
159 159 help="""The SSH server to use to connect to the kernel.""")
160 160 sshkey = Unicode('', config=True,
161 161 help="""Path to the ssh key to use for logging in to the ssh server.""")
162 162
163 163 hb_port = Int(0, config=True,
164 164 help="set the heartbeat port [default: random]")
165 165 shell_port = Int(0, config=True,
166 166 help="set the shell (ROUTER) port [default: random]")
167 167 iopub_port = Int(0, config=True,
168 168 help="set the iopub (PUB) port [default: random]")
169 169 stdin_port = Int(0, config=True,
170 170 help="set the stdin (DEALER) port [default: random]")
171 171 connection_file = Unicode('', config=True,
172 172 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
173 173
174 174 This file will contain the IP, ports, and authentication key needed to connect
175 175 clients to this kernel. By default, this file will be created in the security-dir
176 176 of the current profile, but can be specified by absolute path.
177 177 """)
178 178 def _connection_file_default(self):
179 179 return 'kernel-%i.json' % os.getpid()
180 180
181 181 existing = CUnicode('', config=True,
182 182 help="""Connect to an already running kernel""")
183 183
184 184 confirm_exit = CBool(True, config=True,
185 185 help="""
186 186 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
187 187 to force a direct exit without any confirmation.""",
188 188 )
189 189
190 190
191 191 def build_kernel_argv(self, argv=None):
192 192 """build argv to be passed to kernel subprocess"""
193 193 if argv is None:
194 194 argv = sys.argv[1:]
195 195 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
196 196 # kernel should inherit default config file from frontend
197 197 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
198 198
199 199 def init_connection_file(self):
200 200 """find the connection file, and load the info if found.
201 201
202 202 The current working directory and the current profile's security
203 203 directory will be searched for the file if it is not given by
204 204 absolute path.
205 205
206 206 When attempting to connect to an existing kernel and the `--existing`
207 207 argument does not match an existing file, it will be interpreted as a
208 208 fileglob, and the matching file in the current profile's security dir
209 209 with the latest access time will be used.
210 210
211 211 After this method is called, self.connection_file contains the *full path*
212 212 to the connection file, never just its name.
213 213 """
214 214 if self.existing:
215 215 try:
216 216 cf = find_connection_file(self.existing)
217 217 except Exception:
218 218 self.log.critical("Could not find existing kernel connection file %s", self.existing)
219 219 self.exit(1)
220 220 self.log.info("Connecting to existing kernel: %s" % cf)
221 221 self.connection_file = cf
222 222 else:
223 223 # not existing, check if we are going to write the file
224 224 # and ensure that self.connection_file is a full path, not just the shortname
225 225 try:
226 226 cf = find_connection_file(self.connection_file)
227 227 except Exception:
228 228 # file might not exist
229 229 if self.connection_file == os.path.basename(self.connection_file):
230 230 # just shortname, put it in security dir
231 231 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
232 232 else:
233 233 cf = self.connection_file
234 234 self.connection_file = cf
235 235
236 236 # should load_connection_file only be used for existing?
237 237 # as it is now, this allows reusing ports if an existing
238 238 # file is requested
239 239 try:
240 240 self.load_connection_file()
241 241 except Exception:
242 242 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
243 243 self.exit(1)
244 244
245 245 def load_connection_file(self):
246 246 """load ip/port/hmac config from JSON connection file"""
247 247 # this is identical to KernelApp.load_connection_file
248 248 # perhaps it can be centralized somewhere?
249 249 try:
250 250 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
251 251 except IOError:
252 252 self.log.debug("Connection File not found: %s", self.connection_file)
253 253 return
254 254 self.log.debug(u"Loading connection file %s", fname)
255 255 with open(fname) as f:
256 256 cfg = json.load(f)
257 257
258 258 self.config.KernelManager.transport = cfg.get('transport', 'tcp')
259 259 self.config.KernelManager.ip = cfg.get('ip', LOCALHOST)
260 260
261 261 for channel in ('hb', 'shell', 'iopub', 'stdin'):
262 262 name = channel + '_port'
263 263 if getattr(self, name) == 0 and name in cfg:
264 264 # not overridden by config or cl_args
265 265 setattr(self, name, cfg[name])
266 266 if 'key' in cfg:
267 267 self.config.Session.key = str_to_bytes(cfg['key'])
268 268
269 269 def init_ssh(self):
270 270 """set up ssh tunnels, if needed."""
271 271 if not self.existing or (not self.sshserver and not self.sshkey):
272 272 return
273 273
274 274 self.load_connection_file()
275 275
276 276 transport = self.config.KernelManager.transport
277 277 ip = self.config.KernelManager.ip
278 278
279 279 if transport != 'tcp':
280 280 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
281 281 sys.exit(-1)
282 282
283 283 if self.sshkey and not self.sshserver:
284 284 # specifying just the key implies that we are connecting directly
285 285 self.sshserver = ip
286 286 ip = LOCALHOST
287 287
288 288 # build connection dict for tunnels:
289 289 info = dict(ip=ip,
290 290 shell_port=self.shell_port,
291 291 iopub_port=self.iopub_port,
292 292 stdin_port=self.stdin_port,
293 293 hb_port=self.hb_port
294 294 )
295 295
296 296 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
297 297
298 298 # tunnels return a new set of ports, which will be on localhost:
299 299 self.config.KernelManager.ip = LOCALHOST
300 300 try:
301 301 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
302 302 except:
303 303 # even catch KeyboardInterrupt
304 304 self.log.error("Could not setup tunnels", exc_info=True)
305 305 self.exit(1)
306 306
307 307 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
308 308
309 309 cf = self.connection_file
310 310 base,ext = os.path.splitext(cf)
311 311 base = os.path.basename(base)
312 312 self.connection_file = os.path.basename(base)+'-ssh'+ext
313 313 self.log.critical("To connect another client via this tunnel, use:")
314 314 self.log.critical("--existing %s" % self.connection_file)
315 315
316 316 def _new_connection_file(self):
317 317 cf = ''
318 318 while not cf:
319 319 # we don't need a 128b id to distinguish kernels, use more readable
320 320 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
321 321 # kernels can subclass.
322 322 ident = str(uuid.uuid4()).split('-')[-1]
323 323 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
324 324 # only keep if it's actually new. Protect against unlikely collision
325 325 # in 48b random search space
326 326 cf = cf if not os.path.exists(cf) else ''
327 327 return cf
328 328
329 329 def init_kernel_manager(self):
330 330 # Don't let Qt or ZMQ swallow KeyboardInterupts.
331 331 signal.signal(signal.SIGINT, signal.SIG_DFL)
332 332
333 333 # Create a KernelManager and start a kernel.
334 334 self.kernel_manager = self.kernel_manager_class(
335 335 shell_port=self.shell_port,
336 336 iopub_port=self.iopub_port,
337 337 stdin_port=self.stdin_port,
338 338 hb_port=self.hb_port,
339 339 connection_file=self.connection_file,
340 340 config=self.config,
341 341 )
342 342 # start the kernel
343 343 if not self.existing:
344 344 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
345 345 atexit.register(self.kernel_manager.cleanup_ipc_files)
346 346 elif self.sshserver:
347 347 # ssh, write new connection file
348 348 self.kernel_manager.write_connection_file()
349 349 atexit.register(self.kernel_manager.cleanup_connection_file)
350 350 self.kernel_manager.start_channels()
351 351
352 352
353 353 def initialize(self, argv=None):
354 354 """
355 355 Classes which mix this class in should call:
356 356 IPythonConsoleApp.initialize(self,argv)
357 357 """
358 358 self.init_connection_file()
359 359 default_secure(self.config)
360 360 self.init_ssh()
361 361 self.init_kernel_manager()
362 362
@@ -1,643 +1,643 b''
1 1 # coding: utf-8
2 2 """A tornado based IPython notebook server.
3 3
4 4 Authors:
5 5
6 6 * Brian Granger
7 7 """
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # stdlib
20 20 import errno
21 21 import logging
22 22 import os
23 23 import random
24 24 import re
25 25 import select
26 26 import signal
27 27 import socket
28 28 import sys
29 29 import threading
30 30 import time
31 31 import uuid
32 32 import webbrowser
33 33
34 34 # Third party
35 35 import zmq
36 36 from jinja2 import Environment, FileSystemLoader
37 37
38 38 # Install the pyzmq ioloop. This has to be done before anything else from
39 39 # tornado is imported.
40 40 from zmq.eventloop import ioloop
41 41 ioloop.install()
42 42
43 43 from tornado import httpserver
44 44 from tornado import web
45 45
46 46 # Our own libraries
47 47 from .kernelmanager import MappingKernelManager
48 48 from .handlers import (LoginHandler, LogoutHandler,
49 49 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
50 50 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
51 51 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
52 52 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
53 53 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
54 54 FileFindHandler,
55 55 )
56 56 from .nbmanager import NotebookManager
57 57 from .filenbmanager import FileNotebookManager
58 58 from .clustermanager import ClusterManager
59 59
60 60 from IPython.config.application import catch_config_error, boolean_flag
61 61 from IPython.core.application import BaseIPythonApplication
62 62 from IPython.core.profiledir import ProfileDir
63 63 from IPython.frontend.consoleapp import IPythonConsoleApp
64 64 from IPython.zmq.session import Session, default_secure
65 65 from IPython.zmq.zmqshell import ZMQInteractiveShell
66 66 from IPython.zmq.ipkernel import (
67 67 flags as ipkernel_flags,
68 68 aliases as ipkernel_aliases,
69 69 IPKernelApp
70 70 )
71 71 from IPython.utils.importstring import import_item
72 72 from IPython.utils.localinterfaces import LOCALHOST
73 from IPython.utils.kernel import swallow_argv
73 from IPython.kernel import swallow_argv
74 74 from IPython.utils.traitlets import (
75 75 Dict, Unicode, Integer, List, Enum, Bool,
76 76 DottedObjectName
77 77 )
78 78 from IPython.utils import py3compat
79 79 from IPython.utils.path import filefind
80 80
81 81 #-----------------------------------------------------------------------------
82 82 # Module globals
83 83 #-----------------------------------------------------------------------------
84 84
85 85 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
86 86 _kernel_action_regex = r"(?P<action>restart|interrupt)"
87 87 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
88 88 _profile_regex = r"(?P<profile>[^\/]+)" # there is almost no text that is invalid
89 89 _cluster_action_regex = r"(?P<action>start|stop)"
90 90
91 91 _examples = """
92 92 ipython notebook # start the notebook
93 93 ipython notebook --profile=sympy # use the sympy profile
94 94 ipython notebook --pylab=inline # pylab in inline plotting mode
95 95 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
96 96 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
97 97 """
98 98
99 99 #-----------------------------------------------------------------------------
100 100 # Helper functions
101 101 #-----------------------------------------------------------------------------
102 102
103 103 def url_path_join(a,b):
104 104 if a.endswith('/') and b.startswith('/'):
105 105 return a[:-1]+b
106 106 else:
107 107 return a+b
108 108
109 109 def random_ports(port, n):
110 110 """Generate a list of n random ports near the given port.
111 111
112 112 The first 5 ports will be sequential, and the remaining n-5 will be
113 113 randomly selected in the range [port-2*n, port+2*n].
114 114 """
115 115 for i in range(min(5, n)):
116 116 yield port + i
117 117 for i in range(n-5):
118 118 yield port + random.randint(-2*n, 2*n)
119 119
120 120 #-----------------------------------------------------------------------------
121 121 # The Tornado web application
122 122 #-----------------------------------------------------------------------------
123 123
124 124 class NotebookWebApplication(web.Application):
125 125
126 126 def __init__(self, ipython_app, kernel_manager, notebook_manager,
127 127 cluster_manager, log,
128 128 base_project_url, settings_overrides):
129 129 handlers = [
130 130 (r"/", ProjectDashboardHandler),
131 131 (r"/login", LoginHandler),
132 132 (r"/logout", LogoutHandler),
133 133 (r"/new", NewHandler),
134 134 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
135 135 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
136 136 (r"/%s/print" % _notebook_id_regex, PrintNotebookHandler),
137 137 (r"/kernels", MainKernelHandler),
138 138 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
139 139 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
140 140 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
141 141 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
142 142 (r"/notebooks", NotebookRootHandler),
143 143 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
144 144 (r"/rstservice/render", RSTHandler),
145 145 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
146 146 (r"/clusters", MainClusterHandler),
147 147 (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler),
148 148 (r"/clusters/%s" % _profile_regex, ClusterProfileHandler),
149 149 ]
150 150
151 151 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
152 152 # base_project_url will always be unicode, which will in turn
153 153 # make the patterns unicode, and ultimately result in unicode
154 154 # keys in kwargs to handler._execute(**kwargs) in tornado.
155 155 # This enforces that base_project_url be ascii in that situation.
156 156 #
157 157 # Note that the URLs these patterns check against are escaped,
158 158 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
159 159 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
160 160
161 161 settings = dict(
162 162 template_path=os.path.join(os.path.dirname(__file__), "templates"),
163 163 static_path=ipython_app.static_file_path,
164 164 static_handler_class = FileFindHandler,
165 165 static_url_prefix = url_path_join(base_project_url,'/static/'),
166 166 cookie_secret=os.urandom(1024),
167 167 login_url=url_path_join(base_project_url,'/login'),
168 168 cookie_name='username-%s' % uuid.uuid4(),
169 169 )
170 170
171 171 # allow custom overrides for the tornado web app.
172 172 settings.update(settings_overrides)
173 173
174 174 # prepend base_project_url onto the patterns that we match
175 175 new_handlers = []
176 176 for handler in handlers:
177 177 pattern = url_path_join(base_project_url, handler[0])
178 178 new_handler = tuple([pattern]+list(handler[1:]))
179 179 new_handlers.append( new_handler )
180 180
181 181 super(NotebookWebApplication, self).__init__(new_handlers, **settings)
182 182
183 183 self.kernel_manager = kernel_manager
184 184 self.notebook_manager = notebook_manager
185 185 self.cluster_manager = cluster_manager
186 186 self.ipython_app = ipython_app
187 187 self.read_only = self.ipython_app.read_only
188 188 self.config = self.ipython_app.config
189 189 self.log = log
190 190 self.jinja2_env = Environment(loader=FileSystemLoader(os.path.join(os.path.dirname(__file__), "templates")))
191 191
192 192
193 193
194 194 #-----------------------------------------------------------------------------
195 195 # Aliases and Flags
196 196 #-----------------------------------------------------------------------------
197 197
198 198 flags = dict(ipkernel_flags)
199 199 flags['no-browser']=(
200 200 {'NotebookApp' : {'open_browser' : False}},
201 201 "Don't open the notebook in a browser after startup."
202 202 )
203 203 flags['no-mathjax']=(
204 204 {'NotebookApp' : {'enable_mathjax' : False}},
205 205 """Disable MathJax
206 206
207 207 MathJax is the javascript library IPython uses to render math/LaTeX. It is
208 208 very large, so you may want to disable it if you have a slow internet
209 209 connection, or for offline use of the notebook.
210 210
211 211 When disabled, equations etc. will appear as their untransformed TeX source.
212 212 """
213 213 )
214 214 flags['read-only'] = (
215 215 {'NotebookApp' : {'read_only' : True}},
216 216 """Allow read-only access to notebooks.
217 217
218 218 When using a password to protect the notebook server, this flag
219 219 allows unauthenticated clients to view the notebook list, and
220 220 individual notebooks, but not edit them, start kernels, or run
221 221 code.
222 222
223 223 If no password is set, the server will be entirely read-only.
224 224 """
225 225 )
226 226
227 227 # Add notebook manager flags
228 228 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
229 229 'Auto-save a .py script everytime the .ipynb notebook is saved',
230 230 'Do not auto-save .py scripts for every notebook'))
231 231
232 232 # the flags that are specific to the frontend
233 233 # these must be scrubbed before being passed to the kernel,
234 234 # or it will raise an error on unrecognized flags
235 235 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
236 236
237 237 aliases = dict(ipkernel_aliases)
238 238
239 239 aliases.update({
240 240 'ip': 'NotebookApp.ip',
241 241 'port': 'NotebookApp.port',
242 242 'port-retries': 'NotebookApp.port_retries',
243 243 'transport': 'KernelManager.transport',
244 244 'keyfile': 'NotebookApp.keyfile',
245 245 'certfile': 'NotebookApp.certfile',
246 246 'notebook-dir': 'NotebookManager.notebook_dir',
247 247 'browser': 'NotebookApp.browser',
248 248 })
249 249
250 250 # remove ipkernel flags that are singletons, and don't make sense in
251 251 # multi-kernel evironment:
252 252 aliases.pop('f', None)
253 253
254 254 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
255 255 u'notebook-dir']
256 256
257 257 #-----------------------------------------------------------------------------
258 258 # NotebookApp
259 259 #-----------------------------------------------------------------------------
260 260
261 261 class NotebookApp(BaseIPythonApplication):
262 262
263 263 name = 'ipython-notebook'
264 264 default_config_file_name='ipython_notebook_config.py'
265 265
266 266 description = """
267 267 The IPython HTML Notebook.
268 268
269 269 This launches a Tornado based HTML Notebook Server that serves up an
270 270 HTML5/Javascript Notebook client.
271 271 """
272 272 examples = _examples
273 273
274 274 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
275 275 FileNotebookManager]
276 276 flags = Dict(flags)
277 277 aliases = Dict(aliases)
278 278
279 279 kernel_argv = List(Unicode)
280 280
281 281 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
282 282 default_value=logging.INFO,
283 283 config=True,
284 284 help="Set the log level by value or name.")
285 285
286 286 # create requested profiles by default, if they don't exist:
287 287 auto_create = Bool(True)
288 288
289 289 # file to be opened in the notebook server
290 290 file_to_run = Unicode('')
291 291
292 292 # Network related information.
293 293
294 294 ip = Unicode(LOCALHOST, config=True,
295 295 help="The IP address the notebook server will listen on."
296 296 )
297 297
298 298 def _ip_changed(self, name, old, new):
299 299 if new == u'*': self.ip = u''
300 300
301 301 port = Integer(8888, config=True,
302 302 help="The port the notebook server will listen on."
303 303 )
304 304 port_retries = Integer(50, config=True,
305 305 help="The number of additional ports to try if the specified port is not available."
306 306 )
307 307
308 308 certfile = Unicode(u'', config=True,
309 309 help="""The full path to an SSL/TLS certificate file."""
310 310 )
311 311
312 312 keyfile = Unicode(u'', config=True,
313 313 help="""The full path to a private key file for usage with SSL/TLS."""
314 314 )
315 315
316 316 password = Unicode(u'', config=True,
317 317 help="""Hashed password to use for web authentication.
318 318
319 319 To generate, type in a python/IPython shell:
320 320
321 321 from IPython.lib import passwd; passwd()
322 322
323 323 The string should be of the form type:salt:hashed-password.
324 324 """
325 325 )
326 326
327 327 open_browser = Bool(True, config=True,
328 328 help="""Whether to open in a browser after starting.
329 329 The specific browser used is platform dependent and
330 330 determined by the python standard library `webbrowser`
331 331 module, unless it is overridden using the --browser
332 332 (NotebookApp.browser) configuration option.
333 333 """)
334 334
335 335 browser = Unicode(u'', config=True,
336 336 help="""Specify what command to use to invoke a web
337 337 browser when opening the notebook. If not specified, the
338 338 default browser will be determined by the `webbrowser`
339 339 standard library module, which allows setting of the
340 340 BROWSER environment variable to override it.
341 341 """)
342 342
343 343 read_only = Bool(False, config=True,
344 344 help="Whether to prevent editing/execution of notebooks."
345 345 )
346 346
347 347 webapp_settings = Dict(config=True,
348 348 help="Supply overrides for the tornado.web.Application that the "
349 349 "IPython notebook uses.")
350 350
351 351 enable_mathjax = Bool(True, config=True,
352 352 help="""Whether to enable MathJax for typesetting math/TeX
353 353
354 354 MathJax is the javascript library IPython uses to render math/LaTeX. It is
355 355 very large, so you may want to disable it if you have a slow internet
356 356 connection, or for offline use of the notebook.
357 357
358 358 When disabled, equations etc. will appear as their untransformed TeX source.
359 359 """
360 360 )
361 361 def _enable_mathjax_changed(self, name, old, new):
362 362 """set mathjax url to empty if mathjax is disabled"""
363 363 if not new:
364 364 self.mathjax_url = u''
365 365
366 366 base_project_url = Unicode('/', config=True,
367 367 help='''The base URL for the notebook server.
368 368
369 369 Leading and trailing slashes can be omitted,
370 370 and will automatically be added.
371 371 ''')
372 372 def _base_project_url_changed(self, name, old, new):
373 373 if not new.startswith('/'):
374 374 self.base_project_url = '/'+new
375 375 elif not new.endswith('/'):
376 376 self.base_project_url = new+'/'
377 377
378 378 base_kernel_url = Unicode('/', config=True,
379 379 help='''The base URL for the kernel server
380 380
381 381 Leading and trailing slashes can be omitted,
382 382 and will automatically be added.
383 383 ''')
384 384 def _base_kernel_url_changed(self, name, old, new):
385 385 if not new.startswith('/'):
386 386 self.base_kernel_url = '/'+new
387 387 elif not new.endswith('/'):
388 388 self.base_kernel_url = new+'/'
389 389
390 390 websocket_host = Unicode("", config=True,
391 391 help="""The hostname for the websocket server."""
392 392 )
393 393
394 394 extra_static_paths = List(Unicode, config=True,
395 395 help="""Extra paths to search for serving static files.
396 396
397 397 This allows adding javascript/css to be available from the notebook server machine,
398 398 or overriding individual files in the IPython"""
399 399 )
400 400 def _extra_static_paths_default(self):
401 401 return [os.path.join(self.profile_dir.location, 'static')]
402 402
403 403 @property
404 404 def static_file_path(self):
405 405 """return extra paths + the default location"""
406 406 return self.extra_static_paths + [os.path.join(os.path.dirname(__file__), "static")]
407 407
408 408 mathjax_url = Unicode("", config=True,
409 409 help="""The url for MathJax.js."""
410 410 )
411 411 def _mathjax_url_default(self):
412 412 if not self.enable_mathjax:
413 413 return u''
414 414 static_url_prefix = self.webapp_settings.get("static_url_prefix",
415 415 "/static/")
416 416 try:
417 417 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
418 418 except IOError:
419 419 if self.certfile:
420 420 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
421 421 base = u"https://c328740.ssl.cf1.rackcdn.com"
422 422 else:
423 423 base = u"http://cdn.mathjax.org"
424 424
425 425 url = base + u"/mathjax/latest/MathJax.js"
426 426 self.log.info("Using MathJax from CDN: %s", url)
427 427 return url
428 428 else:
429 429 self.log.info("Using local MathJax from %s" % mathjax)
430 430 return static_url_prefix+u"mathjax/MathJax.js"
431 431
432 432 def _mathjax_url_changed(self, name, old, new):
433 433 if new and not self.enable_mathjax:
434 434 # enable_mathjax=False overrides mathjax_url
435 435 self.mathjax_url = u''
436 436 else:
437 437 self.log.info("Using MathJax: %s", new)
438 438
439 439 notebook_manager_class = DottedObjectName('IPython.frontend.html.notebook.filenbmanager.FileNotebookManager',
440 440 config=True,
441 441 help='The notebook manager class to use.')
442 442
443 443 def parse_command_line(self, argv=None):
444 444 super(NotebookApp, self).parse_command_line(argv)
445 445 if argv is None:
446 446 argv = sys.argv[1:]
447 447
448 448 # Scrub frontend-specific flags
449 449 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
450 450 # Kernel should inherit default config file from frontend
451 451 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
452 452
453 453 if self.extra_args:
454 454 f = os.path.abspath(self.extra_args[0])
455 455 if os.path.isdir(f):
456 456 nbdir = f
457 457 else:
458 458 self.file_to_run = f
459 459 nbdir = os.path.dirname(f)
460 460 self.config.NotebookManager.notebook_dir = nbdir
461 461
462 462 def init_configurables(self):
463 463 # force Session default to be secure
464 464 default_secure(self.config)
465 465 self.kernel_manager = MappingKernelManager(
466 466 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
467 467 connection_dir = self.profile_dir.security_dir,
468 468 )
469 469 kls = import_item(self.notebook_manager_class)
470 470 self.notebook_manager = kls(config=self.config, log=self.log)
471 471 self.notebook_manager.log_info()
472 472 self.notebook_manager.load_notebook_names()
473 473 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
474 474 self.cluster_manager.update_profiles()
475 475
476 476 def init_logging(self):
477 477 # This prevents double log messages because tornado use a root logger that
478 478 # self.log is a child of. The logging module dipatches log messages to a log
479 479 # and all of its ancenstors until propagate is set to False.
480 480 self.log.propagate = False
481 481
482 482 def init_webapp(self):
483 483 """initialize tornado webapp and httpserver"""
484 484 self.web_app = NotebookWebApplication(
485 485 self, self.kernel_manager, self.notebook_manager,
486 486 self.cluster_manager, self.log,
487 487 self.base_project_url, self.webapp_settings
488 488 )
489 489 if self.certfile:
490 490 ssl_options = dict(certfile=self.certfile)
491 491 if self.keyfile:
492 492 ssl_options['keyfile'] = self.keyfile
493 493 else:
494 494 ssl_options = None
495 495 self.web_app.password = self.password
496 496 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
497 497 if not self.ip:
498 498 warning = "WARNING: The notebook server is listening on all IP addresses"
499 499 if ssl_options is None:
500 500 self.log.critical(warning + " and not using encryption. This"
501 501 "is not recommended.")
502 502 if not self.password and not self.read_only:
503 503 self.log.critical(warning + "and not using authentication."
504 504 "This is highly insecure and not recommended.")
505 505 success = None
506 506 for port in random_ports(self.port, self.port_retries+1):
507 507 try:
508 508 self.http_server.listen(port, self.ip)
509 509 except socket.error as e:
510 510 if e.errno != errno.EADDRINUSE:
511 511 raise
512 512 self.log.info('The port %i is already in use, trying another random port.' % port)
513 513 else:
514 514 self.port = port
515 515 success = True
516 516 break
517 517 if not success:
518 518 self.log.critical('ERROR: the notebook server could not be started because '
519 519 'no available port could be found.')
520 520 self.exit(1)
521 521
522 522 def init_signal(self):
523 523 # FIXME: remove this check when pyzmq dependency is >= 2.1.11
524 524 # safely extract zmq version info:
525 525 try:
526 526 zmq_v = zmq.pyzmq_version_info()
527 527 except AttributeError:
528 528 zmq_v = [ int(n) for n in re.findall(r'\d+', zmq.__version__) ]
529 529 if 'dev' in zmq.__version__:
530 530 zmq_v.append(999)
531 531 zmq_v = tuple(zmq_v)
532 532 if zmq_v >= (2,1,9) and not sys.platform.startswith('win'):
533 533 # This won't work with 2.1.7 and
534 534 # 2.1.9-10 will log ugly 'Interrupted system call' messages,
535 535 # but it will work
536 536 signal.signal(signal.SIGINT, self._handle_sigint)
537 537 signal.signal(signal.SIGTERM, self._signal_stop)
538 538
539 539 def _handle_sigint(self, sig, frame):
540 540 """SIGINT handler spawns confirmation dialog"""
541 541 # register more forceful signal handler for ^C^C case
542 542 signal.signal(signal.SIGINT, self._signal_stop)
543 543 # request confirmation dialog in bg thread, to avoid
544 544 # blocking the App
545 545 thread = threading.Thread(target=self._confirm_exit)
546 546 thread.daemon = True
547 547 thread.start()
548 548
549 549 def _restore_sigint_handler(self):
550 550 """callback for restoring original SIGINT handler"""
551 551 signal.signal(signal.SIGINT, self._handle_sigint)
552 552
553 553 def _confirm_exit(self):
554 554 """confirm shutdown on ^C
555 555
556 556 A second ^C, or answering 'y' within 5s will cause shutdown,
557 557 otherwise original SIGINT handler will be restored.
558 558
559 559 This doesn't work on Windows.
560 560 """
561 561 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
562 562 time.sleep(0.1)
563 563 sys.stdout.write("Shutdown Notebook Server (y/[n])? ")
564 564 sys.stdout.flush()
565 565 r,w,x = select.select([sys.stdin], [], [], 5)
566 566 if r:
567 567 line = sys.stdin.readline()
568 568 if line.lower().startswith('y'):
569 569 self.log.critical("Shutdown confirmed")
570 570 ioloop.IOLoop.instance().stop()
571 571 return
572 572 else:
573 573 print "No answer for 5s:",
574 574 print "resuming operation..."
575 575 # no answer, or answer is no:
576 576 # set it back to original SIGINT handler
577 577 # use IOLoop.add_callback because signal.signal must be called
578 578 # from main thread
579 579 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
580 580
581 581 def _signal_stop(self, sig, frame):
582 582 self.log.critical("received signal %s, stopping", sig)
583 583 ioloop.IOLoop.instance().stop()
584 584
585 585 @catch_config_error
586 586 def initialize(self, argv=None):
587 587 self.init_logging()
588 588 super(NotebookApp, self).initialize(argv)
589 589 self.init_configurables()
590 590 self.init_webapp()
591 591 self.init_signal()
592 592
593 593 def cleanup_kernels(self):
594 594 """Shutdown all kernels.
595 595
596 596 The kernels will shutdown themselves when this process no longer exists,
597 597 but explicit shutdown allows the KernelManagers to cleanup the connection files.
598 598 """
599 599 self.log.info('Shutting down kernels')
600 600 self.kernel_manager.shutdown_all()
601 601
602 602 def start(self):
603 603 ip = self.ip if self.ip else '[all ip addresses on your system]'
604 604 proto = 'https' if self.certfile else 'http'
605 605 info = self.log.info
606 606 info("The IPython Notebook is running at: %s://%s:%i%s" %
607 607 (proto, ip, self.port,self.base_project_url) )
608 608 info("Use Control-C to stop this server and shut down all kernels.")
609 609
610 610 if self.open_browser or self.file_to_run:
611 611 ip = self.ip or LOCALHOST
612 612 try:
613 613 browser = webbrowser.get(self.browser or None)
614 614 except webbrowser.Error as e:
615 615 self.log.warn('No web browser found: %s.' % e)
616 616 browser = None
617 617
618 618 if self.file_to_run:
619 619 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
620 620 url = self.notebook_manager.rev_mapping.get(name, '')
621 621 else:
622 622 url = ''
623 623 if browser:
624 624 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
625 625 self.port, self.base_project_url, url), new=2)
626 626 threading.Thread(target=b).start()
627 627 try:
628 628 ioloop.IOLoop.instance().start()
629 629 except KeyboardInterrupt:
630 630 info("Interrupted...")
631 631 finally:
632 632 self.cleanup_kernels()
633 633
634 634
635 635 #-----------------------------------------------------------------------------
636 636 # Main entry point
637 637 #-----------------------------------------------------------------------------
638 638
639 639 def launch_new_instance():
640 640 app = NotebookApp.instance()
641 641 app.initialize()
642 642 app.start()
643 643
@@ -1,370 +1,370 b''
1 1 """ A minimal application using the Qt console-style IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
5 5
6 6 Authors:
7 7
8 8 * Evan Patterson
9 9 * Min RK
10 10 * Erik Tollerud
11 11 * Fernando Perez
12 12 * Bussonnier Matthias
13 13 * Thomas Kluyver
14 14 * Paul Ivanov
15 15
16 16 """
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # stdlib imports
23 23 import json
24 24 import os
25 25 import signal
26 26 import sys
27 27 import uuid
28 28
29 29 # If run on Windows, install an exception hook which pops up a
30 30 # message box. Pythonw.exe hides the console, so without this
31 31 # the application silently fails to load.
32 32 #
33 33 # We always install this handler, because the expectation is for
34 34 # qtconsole to bring up a GUI even if called from the console.
35 35 # The old handler is called, so the exception is printed as well.
36 36 # If desired, check for pythonw with an additional condition
37 37 # (sys.executable.lower().find('pythonw.exe') >= 0).
38 38 if os.name == 'nt':
39 39 old_excepthook = sys.excepthook
40 40
41 41 def gui_excepthook(exctype, value, tb):
42 42 try:
43 43 import ctypes, traceback
44 44 MB_ICONERROR = 0x00000010L
45 45 title = u'Error starting IPython QtConsole'
46 46 msg = u''.join(traceback.format_exception(exctype, value, tb))
47 47 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
48 48 finally:
49 49 # Also call the old exception hook to let it do
50 50 # its thing too.
51 51 old_excepthook(exctype, value, tb)
52 52
53 53 sys.excepthook = gui_excepthook
54 54
55 55 # System library imports
56 56 from IPython.external.qt import QtCore, QtGui
57 57
58 58 # Local imports
59 59 from IPython.config.application import boolean_flag, catch_config_error
60 60 from IPython.core.application import BaseIPythonApplication
61 61 from IPython.core.profiledir import ProfileDir
62 62 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
63 63 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
64 64 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
65 65 from IPython.frontend.qt.console import styles
66 66 from IPython.frontend.qt.console.mainwindow import MainWindow
67 67 from IPython.frontend.qt.kernelmanager import QtKernelManager
68 from IPython.utils.kernel import tunnel_to_kernel, find_connection_file
68 from IPython.kernel import tunnel_to_kernel, find_connection_file
69 69 from IPython.utils.path import filefind
70 70 from IPython.utils.py3compat import str_to_bytes
71 71 from IPython.utils.traitlets import (
72 72 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
73 73 )
74 74 from IPython.zmq.ipkernel import IPKernelApp
75 75 from IPython.zmq.session import Session, default_secure
76 76 from IPython.zmq.zmqshell import ZMQInteractiveShell
77 77
78 78 from IPython.frontend.consoleapp import (
79 79 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
80 80 )
81 81
82 82 #-----------------------------------------------------------------------------
83 83 # Network Constants
84 84 #-----------------------------------------------------------------------------
85 85
86 86 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
87 87
88 88 #-----------------------------------------------------------------------------
89 89 # Globals
90 90 #-----------------------------------------------------------------------------
91 91
92 92 _examples = """
93 93 ipython qtconsole # start the qtconsole
94 94 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
95 95 """
96 96
97 97 #-----------------------------------------------------------------------------
98 98 # Aliases and Flags
99 99 #-----------------------------------------------------------------------------
100 100
101 101 # start with copy of flags
102 102 flags = dict(flags)
103 103 qt_flags = {
104 104 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
105 105 "Disable rich text support."),
106 106 }
107 107
108 108 # and app_flags from the Console Mixin
109 109 qt_flags.update(app_flags)
110 110 # add frontend flags to the full set
111 111 flags.update(qt_flags)
112 112
113 113 # start with copy of front&backend aliases list
114 114 aliases = dict(aliases)
115 115 qt_aliases = dict(
116 116 style = 'IPythonWidget.syntax_style',
117 117 stylesheet = 'IPythonQtConsoleApp.stylesheet',
118 118 colors = 'ZMQInteractiveShell.colors',
119 119
120 120 editor = 'IPythonWidget.editor',
121 121 paging = 'ConsoleWidget.paging',
122 122 )
123 123 # and app_aliases from the Console Mixin
124 124 qt_aliases.update(app_aliases)
125 125 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
126 126 # add frontend aliases to the full set
127 127 aliases.update(qt_aliases)
128 128
129 129 # get flags&aliases into sets, and remove a couple that
130 130 # shouldn't be scrubbed from backend flags:
131 131 qt_aliases = set(qt_aliases.keys())
132 132 qt_aliases.remove('colors')
133 133 qt_flags = set(qt_flags.keys())
134 134
135 135 #-----------------------------------------------------------------------------
136 136 # Classes
137 137 #-----------------------------------------------------------------------------
138 138
139 139 #-----------------------------------------------------------------------------
140 140 # IPythonQtConsole
141 141 #-----------------------------------------------------------------------------
142 142
143 143
144 144 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
145 145 name = 'ipython-qtconsole'
146 146
147 147 description = """
148 148 The IPython QtConsole.
149 149
150 150 This launches a Console-style application using Qt. It is not a full
151 151 console, in that launched terminal subprocesses will not be able to accept
152 152 input.
153 153
154 154 The QtConsole supports various extra features beyond the Terminal IPython
155 155 shell, such as inline plotting with matplotlib, via:
156 156
157 157 ipython qtconsole --pylab=inline
158 158
159 159 as well as saving your session as HTML, and printing the output.
160 160
161 161 """
162 162 examples = _examples
163 163
164 164 classes = [IPythonWidget] + IPythonConsoleApp.classes
165 165 flags = Dict(flags)
166 166 aliases = Dict(aliases)
167 167 frontend_flags = Any(qt_flags)
168 168 frontend_aliases = Any(qt_aliases)
169 169 kernel_manager_class = QtKernelManager
170 170
171 171 stylesheet = Unicode('', config=True,
172 172 help="path to a custom CSS stylesheet")
173 173
174 174 plain = CBool(False, config=True,
175 175 help="Use a plaintext widget instead of rich text (plain can't print/save).")
176 176
177 177 def _plain_changed(self, name, old, new):
178 178 kind = 'plain' if new else 'rich'
179 179 self.config.ConsoleWidget.kind = kind
180 180 if new:
181 181 self.widget_factory = IPythonWidget
182 182 else:
183 183 self.widget_factory = RichIPythonWidget
184 184
185 185 # the factory for creating a widget
186 186 widget_factory = Any(RichIPythonWidget)
187 187
188 188 def parse_command_line(self, argv=None):
189 189 super(IPythonQtConsoleApp, self).parse_command_line(argv)
190 190 self.build_kernel_argv(argv)
191 191
192 192
193 193 def new_frontend_master(self):
194 194 """ Create and return new frontend attached to new kernel, launched on localhost.
195 195 """
196 196 kernel_manager = self.kernel_manager_class(
197 197 connection_file=self._new_connection_file(),
198 198 config=self.config,
199 199 )
200 200 # start the kernel
201 201 kwargs = dict()
202 202 kwargs['extra_arguments'] = self.kernel_argv
203 203 kernel_manager.start_kernel(**kwargs)
204 204 kernel_manager.start_channels()
205 205 widget = self.widget_factory(config=self.config,
206 206 local_kernel=True)
207 207 self.init_colors(widget)
208 208 widget.kernel_manager = kernel_manager
209 209 widget._existing = False
210 210 widget._may_close = True
211 211 widget._confirm_exit = self.confirm_exit
212 212 return widget
213 213
214 214 def new_frontend_slave(self, current_widget):
215 215 """Create and return a new frontend attached to an existing kernel.
216 216
217 217 Parameters
218 218 ----------
219 219 current_widget : IPythonWidget
220 220 The IPythonWidget whose kernel this frontend is to share
221 221 """
222 222 kernel_manager = self.kernel_manager_class(
223 223 connection_file=current_widget.kernel_manager.connection_file,
224 224 config = self.config,
225 225 )
226 226 kernel_manager.load_connection_file()
227 227 kernel_manager.start_channels()
228 228 widget = self.widget_factory(config=self.config,
229 229 local_kernel=False)
230 230 self.init_colors(widget)
231 231 widget._existing = True
232 232 widget._may_close = False
233 233 widget._confirm_exit = False
234 234 widget.kernel_manager = kernel_manager
235 235 return widget
236 236
237 237 def init_qt_elements(self):
238 238 # Create the widget.
239 239 self.app = QtGui.QApplication([])
240 240
241 241 base_path = os.path.abspath(os.path.dirname(__file__))
242 242 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
243 243 self.app.icon = QtGui.QIcon(icon_path)
244 244 QtGui.QApplication.setWindowIcon(self.app.icon)
245 245
246 246 try:
247 247 ip = self.config.KernelManager.ip
248 248 except AttributeError:
249 249 ip = LOCALHOST
250 250 local_kernel = (not self.existing) or ip in LOCAL_IPS
251 251 self.widget = self.widget_factory(config=self.config,
252 252 local_kernel=local_kernel)
253 253 self.init_colors(self.widget)
254 254 self.widget._existing = self.existing
255 255 self.widget._may_close = not self.existing
256 256 self.widget._confirm_exit = self.confirm_exit
257 257
258 258 self.widget.kernel_manager = self.kernel_manager
259 259 self.window = MainWindow(self.app,
260 260 confirm_exit=self.confirm_exit,
261 261 new_frontend_factory=self.new_frontend_master,
262 262 slave_frontend_factory=self.new_frontend_slave,
263 263 )
264 264 self.window.log = self.log
265 265 self.window.add_tab_with_frontend(self.widget)
266 266 self.window.init_menu_bar()
267 267
268 268 self.window.setWindowTitle('IPython')
269 269
270 270 def init_colors(self, widget):
271 271 """Configure the coloring of the widget"""
272 272 # Note: This will be dramatically simplified when colors
273 273 # are removed from the backend.
274 274
275 275 # parse the colors arg down to current known labels
276 276 try:
277 277 colors = self.config.ZMQInteractiveShell.colors
278 278 except AttributeError:
279 279 colors = None
280 280 try:
281 281 style = self.config.IPythonWidget.syntax_style
282 282 except AttributeError:
283 283 style = None
284 284 try:
285 285 sheet = self.config.IPythonWidget.style_sheet
286 286 except AttributeError:
287 287 sheet = None
288 288
289 289 # find the value for colors:
290 290 if colors:
291 291 colors=colors.lower()
292 292 if colors in ('lightbg', 'light'):
293 293 colors='lightbg'
294 294 elif colors in ('dark', 'linux'):
295 295 colors='linux'
296 296 else:
297 297 colors='nocolor'
298 298 elif style:
299 299 if style=='bw':
300 300 colors='nocolor'
301 301 elif styles.dark_style(style):
302 302 colors='linux'
303 303 else:
304 304 colors='lightbg'
305 305 else:
306 306 colors=None
307 307
308 308 # Configure the style
309 309 if style:
310 310 widget.style_sheet = styles.sheet_from_template(style, colors)
311 311 widget.syntax_style = style
312 312 widget._syntax_style_changed()
313 313 widget._style_sheet_changed()
314 314 elif colors:
315 315 # use a default dark/light/bw style
316 316 widget.set_default_style(colors=colors)
317 317
318 318 if self.stylesheet:
319 319 # we got an explicit stylesheet
320 320 if os.path.isfile(self.stylesheet):
321 321 with open(self.stylesheet) as f:
322 322 sheet = f.read()
323 323 else:
324 324 raise IOError("Stylesheet %r not found." % self.stylesheet)
325 325 if sheet:
326 326 widget.style_sheet = sheet
327 327 widget._style_sheet_changed()
328 328
329 329
330 330 def init_signal(self):
331 331 """allow clean shutdown on sigint"""
332 332 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
333 333 # need a timer, so that QApplication doesn't block until a real
334 334 # Qt event fires (can require mouse movement)
335 335 # timer trick from http://stackoverflow.com/q/4938723/938949
336 336 timer = QtCore.QTimer()
337 337 # Let the interpreter run each 200 ms:
338 338 timer.timeout.connect(lambda: None)
339 339 timer.start(200)
340 340 # hold onto ref, so the timer doesn't get cleaned up
341 341 self._sigint_timer = timer
342 342
343 343 @catch_config_error
344 344 def initialize(self, argv=None):
345 345 super(IPythonQtConsoleApp, self).initialize(argv)
346 346 IPythonConsoleApp.initialize(self,argv)
347 347 self.init_qt_elements()
348 348 self.init_signal()
349 349
350 350 def start(self):
351 351
352 352 # draw the window
353 353 self.window.show()
354 354 self.window.raise_()
355 355
356 356 # Start the application main loop.
357 357 self.app.exec_()
358 358
359 359 #-----------------------------------------------------------------------------
360 360 # Main entry point
361 361 #-----------------------------------------------------------------------------
362 362
363 363 def main():
364 364 app = IPythonQtConsoleApp()
365 365 app.initialize()
366 366 app.start()
367 367
368 368
369 369 if __name__ == '__main__':
370 370 main()
@@ -1,10 +1,3 b''
1 """IPython.kernel has been replaced by IPython.parallel.
1 """IPython kernel bases"""
2 2
3 The previous version of IPython's parallel library was located at this
4 location (IPython.kernel). It has been moved to the IPython.parallel
5 subpackage and has been refactored to use zeromq/pyzmq instead of twisted.
6
7 Please see INSERT URL for further details.
8 """
9
10 raise ImportError(__doc__)
3 from .util import *
1 NO CONTENT: file renamed from IPython/utils/tests/test_kernel.py to IPython/kernel/tests/test_util.py
1 NO CONTENT: file renamed from IPython/utils/kernel.py to IPython/kernel/util.py
@@ -1,12 +1,12 b''
1 1 """[DEPRECATED] Utilities for connecting to kernels
2 2
3 Moved to IPython.utils.kernel, where it always belonged.
3 Moved to IPython.kernel.util
4 4 """
5 5
6 6 import warnings
7 warnings.warn("IPython.lib.kernel moved to IPython.utils.kernel in IPython 0.14",
7 warnings.warn("IPython.lib.kernel moved to IPython.kernel in IPython 0.14",
8 8 DeprecationWarning
9 9 )
10 10
11 from IPython.utils.kernel import *
11 from IPython.kernel.util import *
12 12
@@ -1,372 +1,372 b''
1 1 """An Application for launching a kernel
2 2
3 3 Authors
4 4 -------
5 5 * MinRK
6 6 """
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2011 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING.txt, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 # Standard library imports
19 19 import atexit
20 20 import json
21 21 import os
22 22 import sys
23 23 import signal
24 24
25 25 # System library imports
26 26 import zmq
27 27 from zmq.eventloop import ioloop
28 28
29 29 # IPython imports
30 30 from IPython.core.ultratb import FormattedTB
31 31 from IPython.core.application import (
32 32 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
33 33 )
34 34 from IPython.utils import io
35 35 from IPython.utils.localinterfaces import LOCALHOST
36 36 from IPython.utils.path import filefind
37 37 from IPython.utils.py3compat import str_to_bytes
38 38 from IPython.utils.traitlets import (
39 39 Any, Instance, Dict, Unicode, Integer, Bool, CaselessStrEnum,
40 40 DottedObjectName,
41 41 )
42 42 from IPython.utils.importstring import import_item
43 from IPython.utils.kernel import write_connection_file
43 from IPython.kernel import write_connection_file
44 44 # local imports
45 45 from IPython.zmq.heartbeat import Heartbeat
46 46 from IPython.zmq.parentpoller import ParentPollerUnix, ParentPollerWindows
47 47 from IPython.zmq.session import (
48 48 Session, session_flags, session_aliases, default_secure,
49 49 )
50 50
51 51
52 52 #-----------------------------------------------------------------------------
53 53 # Flags and Aliases
54 54 #-----------------------------------------------------------------------------
55 55
56 56 kernel_aliases = dict(base_aliases)
57 57 kernel_aliases.update({
58 58 'ip' : 'KernelApp.ip',
59 59 'hb' : 'KernelApp.hb_port',
60 60 'shell' : 'KernelApp.shell_port',
61 61 'iopub' : 'KernelApp.iopub_port',
62 62 'stdin' : 'KernelApp.stdin_port',
63 63 'f' : 'KernelApp.connection_file',
64 64 'parent': 'KernelApp.parent',
65 65 'transport': 'KernelApp.transport',
66 66 })
67 67 if sys.platform.startswith('win'):
68 68 kernel_aliases['interrupt'] = 'KernelApp.interrupt'
69 69
70 70 kernel_flags = dict(base_flags)
71 71 kernel_flags.update({
72 72 'no-stdout' : (
73 73 {'KernelApp' : {'no_stdout' : True}},
74 74 "redirect stdout to the null device"),
75 75 'no-stderr' : (
76 76 {'KernelApp' : {'no_stderr' : True}},
77 77 "redirect stderr to the null device"),
78 78 })
79 79
80 80 # inherit flags&aliases for Sessions
81 81 kernel_aliases.update(session_aliases)
82 82 kernel_flags.update(session_flags)
83 83
84 84
85 85
86 86 #-----------------------------------------------------------------------------
87 87 # Application class for starting a Kernel
88 88 #-----------------------------------------------------------------------------
89 89
90 90 class KernelApp(BaseIPythonApplication):
91 91 name='ipkernel'
92 92 aliases = Dict(kernel_aliases)
93 93 flags = Dict(kernel_flags)
94 94 classes = [Session]
95 95 # the kernel class, as an importstring
96 96 kernel_class = DottedObjectName('IPython.zmq.ipkernel.Kernel')
97 97 kernel = Any()
98 98 poller = Any() # don't restrict this even though current pollers are all Threads
99 99 heartbeat = Instance(Heartbeat)
100 100 session = Instance('IPython.zmq.session.Session')
101 101 ports = Dict()
102 102
103 103 # inherit config file name from parent:
104 104 parent_appname = Unicode(config=True)
105 105 def _parent_appname_changed(self, name, old, new):
106 106 if self.config_file_specified:
107 107 # it was manually specified, ignore
108 108 return
109 109 self.config_file_name = new.replace('-','_') + u'_config.py'
110 110 # don't let this count as specifying the config file
111 111 self.config_file_specified = False
112 112
113 113 # connection info:
114 114 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
115 115 ip = Unicode(config=True,
116 116 help="Set the IP or interface on which the kernel will listen.")
117 117 def _ip_default(self):
118 118 if self.transport == 'ipc':
119 119 if self.connection_file:
120 120 return os.path.splitext(self.abs_connection_file)[0] + '-ipc'
121 121 else:
122 122 return 'kernel-ipc'
123 123 else:
124 124 return LOCALHOST
125 125 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
126 126 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
127 127 iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
128 128 stdin_port = Integer(0, config=True, help="set the stdin (DEALER) port [default: random]")
129 129 connection_file = Unicode('', config=True,
130 130 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
131 131
132 132 This file will contain the IP, ports, and authentication key needed to connect
133 133 clients to this kernel. By default, this file will be created in the security dir
134 134 of the current profile, but can be specified by absolute path.
135 135 """)
136 136 @property
137 137 def abs_connection_file(self):
138 138 if os.path.basename(self.connection_file) == self.connection_file:
139 139 return os.path.join(self.profile_dir.security_dir, self.connection_file)
140 140 else:
141 141 return self.connection_file
142 142
143 143
144 144 # streams, etc.
145 145 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
146 146 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
147 147 outstream_class = DottedObjectName('IPython.zmq.iostream.OutStream',
148 148 config=True, help="The importstring for the OutStream factory")
149 149 displayhook_class = DottedObjectName('IPython.zmq.displayhook.ZMQDisplayHook',
150 150 config=True, help="The importstring for the DisplayHook factory")
151 151
152 152 # polling
153 153 parent = Integer(0, config=True,
154 154 help="""kill this process if its parent dies. On Windows, the argument
155 155 specifies the HANDLE of the parent process, otherwise it is simply boolean.
156 156 """)
157 157 interrupt = Integer(0, config=True,
158 158 help="""ONLY USED ON WINDOWS
159 159 Interrupt this process when the parent is signaled.
160 160 """)
161 161
162 162 def init_crash_handler(self):
163 163 # Install minimal exception handling
164 164 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
165 165 ostream=sys.__stdout__)
166 166
167 167 def init_poller(self):
168 168 if sys.platform == 'win32':
169 169 if self.interrupt or self.parent:
170 170 self.poller = ParentPollerWindows(self.interrupt, self.parent)
171 171 elif self.parent:
172 172 self.poller = ParentPollerUnix()
173 173
174 174 def _bind_socket(self, s, port):
175 175 iface = '%s://%s' % (self.transport, self.ip)
176 176 if self.transport == 'tcp':
177 177 if port <= 0:
178 178 port = s.bind_to_random_port(iface)
179 179 else:
180 180 s.bind("tcp://%s:%i" % (self.ip, port))
181 181 elif self.transport == 'ipc':
182 182 if port <= 0:
183 183 port = 1
184 184 path = "%s-%i" % (self.ip, port)
185 185 while os.path.exists(path):
186 186 port = port + 1
187 187 path = "%s-%i" % (self.ip, port)
188 188 else:
189 189 path = "%s-%i" % (self.ip, port)
190 190 s.bind("ipc://%s" % path)
191 191 return port
192 192
193 193 def load_connection_file(self):
194 194 """load ip/port/hmac config from JSON connection file"""
195 195 try:
196 196 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
197 197 except IOError:
198 198 self.log.debug("Connection file not found: %s", self.connection_file)
199 199 # This means I own it, so I will clean it up:
200 200 atexit.register(self.cleanup_connection_file)
201 201 return
202 202 self.log.debug(u"Loading connection file %s", fname)
203 203 with open(fname) as f:
204 204 s = f.read()
205 205 cfg = json.loads(s)
206 206 self.transport = cfg.get('transport', self.transport)
207 207 if self.ip == self._ip_default() and 'ip' in cfg:
208 208 # not overridden by config or cl_args
209 209 self.ip = cfg['ip']
210 210 for channel in ('hb', 'shell', 'iopub', 'stdin'):
211 211 name = channel + '_port'
212 212 if getattr(self, name) == 0 and name in cfg:
213 213 # not overridden by config or cl_args
214 214 setattr(self, name, cfg[name])
215 215 if 'key' in cfg:
216 216 self.config.Session.key = str_to_bytes(cfg['key'])
217 217
218 218 def write_connection_file(self):
219 219 """write connection info to JSON file"""
220 220 cf = self.abs_connection_file
221 221 self.log.debug("Writing connection file: %s", cf)
222 222 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
223 223 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
224 224 iopub_port=self.iopub_port)
225 225
226 226 def cleanup_connection_file(self):
227 227 cf = self.abs_connection_file
228 228 self.log.debug("Cleaning up connection file: %s", cf)
229 229 try:
230 230 os.remove(cf)
231 231 except (IOError, OSError):
232 232 pass
233 233
234 234 self.cleanup_ipc_files()
235 235
236 236 def cleanup_ipc_files(self):
237 237 """cleanup ipc files if we wrote them"""
238 238 if self.transport != 'ipc':
239 239 return
240 240 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
241 241 ipcfile = "%s-%i" % (self.ip, port)
242 242 try:
243 243 os.remove(ipcfile)
244 244 except (IOError, OSError):
245 245 pass
246 246
247 247 def init_connection_file(self):
248 248 if not self.connection_file:
249 249 self.connection_file = "kernel-%s.json"%os.getpid()
250 250 try:
251 251 self.load_connection_file()
252 252 except Exception:
253 253 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
254 254 self.exit(1)
255 255
256 256 def init_sockets(self):
257 257 # Create a context, a session, and the kernel sockets.
258 258 self.log.info("Starting the kernel at pid: %i", os.getpid())
259 259 context = zmq.Context.instance()
260 260 # Uncomment this to try closing the context.
261 261 # atexit.register(context.term)
262 262
263 263 self.shell_socket = context.socket(zmq.ROUTER)
264 264 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
265 265 self.log.debug("shell ROUTER Channel on port: %i"%self.shell_port)
266 266
267 267 self.iopub_socket = context.socket(zmq.PUB)
268 268 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
269 269 self.log.debug("iopub PUB Channel on port: %i"%self.iopub_port)
270 270
271 271 self.stdin_socket = context.socket(zmq.ROUTER)
272 272 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
273 273 self.log.debug("stdin ROUTER Channel on port: %i"%self.stdin_port)
274 274
275 275 def init_heartbeat(self):
276 276 """start the heart beating"""
277 277 # heartbeat doesn't share context, because it mustn't be blocked
278 278 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
279 279 hb_ctx = zmq.Context()
280 280 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
281 281 self.hb_port = self.heartbeat.port
282 282 self.log.debug("Heartbeat REP Channel on port: %i"%self.hb_port)
283 283 self.heartbeat.start()
284 284
285 285 # Helper to make it easier to connect to an existing kernel.
286 286 # set log-level to critical, to make sure it is output
287 287 self.log.critical("To connect another client to this kernel, use:")
288 288
289 289 def log_connection_info(self):
290 290 """display connection info, and store ports"""
291 291 basename = os.path.basename(self.connection_file)
292 292 if basename == self.connection_file or \
293 293 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
294 294 # use shortname
295 295 tail = basename
296 296 if self.profile != 'default':
297 297 tail += " --profile %s" % self.profile
298 298 else:
299 299 tail = self.connection_file
300 300 self.log.critical("--existing %s", tail)
301 301
302 302
303 303 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
304 304 stdin=self.stdin_port, hb=self.hb_port)
305 305
306 306 def init_session(self):
307 307 """create our session object"""
308 308 default_secure(self.config)
309 309 self.session = Session(config=self.config, username=u'kernel')
310 310
311 311 def init_blackhole(self):
312 312 """redirects stdout/stderr to devnull if necessary"""
313 313 if self.no_stdout or self.no_stderr:
314 314 blackhole = open(os.devnull, 'w')
315 315 if self.no_stdout:
316 316 sys.stdout = sys.__stdout__ = blackhole
317 317 if self.no_stderr:
318 318 sys.stderr = sys.__stderr__ = blackhole
319 319
320 320 def init_io(self):
321 321 """Redirect input streams and set a display hook."""
322 322 if self.outstream_class:
323 323 outstream_factory = import_item(str(self.outstream_class))
324 324 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
325 325 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
326 326 if self.displayhook_class:
327 327 displayhook_factory = import_item(str(self.displayhook_class))
328 328 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
329 329
330 330 def init_signal(self):
331 331 signal.signal(signal.SIGINT, signal.SIG_IGN)
332 332
333 333 def init_kernel(self):
334 334 """Create the Kernel object itself"""
335 335 kernel_factory = import_item(str(self.kernel_class))
336 336 self.kernel = kernel_factory(config=self.config, session=self.session,
337 337 shell_socket=self.shell_socket,
338 338 iopub_socket=self.iopub_socket,
339 339 stdin_socket=self.stdin_socket,
340 340 log=self.log
341 341 )
342 342 self.kernel.record_ports(self.ports)
343 343
344 344 @catch_config_error
345 345 def initialize(self, argv=None):
346 346 super(KernelApp, self).initialize(argv)
347 347 self.init_blackhole()
348 348 self.init_connection_file()
349 349 self.init_session()
350 350 self.init_poller()
351 351 self.init_sockets()
352 352 self.init_heartbeat()
353 353 # writing/displaying connection info must be *after* init_sockets/heartbeat
354 354 self.log_connection_info()
355 355 self.write_connection_file()
356 356 self.init_io()
357 357 self.init_signal()
358 358 self.init_kernel()
359 359 # flush stdout/stderr, so that anything written to these streams during
360 360 # initialization do not get associated with the first execution request
361 361 sys.stdout.flush()
362 362 sys.stderr.flush()
363 363
364 364 def start(self):
365 365 if self.poller is not None:
366 366 self.poller.start()
367 367 self.kernel.start()
368 368 try:
369 369 ioloop.IOLoop.instance().start()
370 370 except KeyboardInterrupt:
371 371 pass
372 372
@@ -1,1129 +1,1129 b''
1 1 """Base classes to manage the interaction with a running kernel.
2 2
3 3 TODO
4 4 * Create logger to handle debugging and console messages.
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2008-2011 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 # Standard library imports.
19 19 import atexit
20 20 import errno
21 21 import json
22 22 from subprocess import Popen
23 23 import os
24 24 import signal
25 25 import sys
26 26 from threading import Thread
27 27 import time
28 28
29 29 # System library imports.
30 30 import zmq
31 31 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
32 32 # during garbage collection of threads at exit:
33 33 from zmq import ZMQError
34 34 from zmq.eventloop import ioloop, zmqstream
35 35
36 36 # Local imports.
37 37 from IPython.config.configurable import Configurable
38 38 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
39 39 from IPython.utils.traitlets import (
40 40 Any, Instance, Type, Unicode, List, Integer, Bool, CaselessStrEnum
41 41 )
42 42 from IPython.utils.py3compat import str_to_bytes
43 from IPython.utils.kernel import (
43 from IPython.kernel import (
44 44 write_connection_file,
45 45 make_ipkernel_cmd,
46 46 launch_kernel,
47 47 )
48 48 from session import Session
49 49 from IPython.zmq.kernelmanagerabc import (
50 50 ShellChannelABC, IOPubChannelABC,
51 51 HBChannelABC, StdInChannelABC,
52 52 KernelManagerABC
53 53 )
54 54
55 55
56 56 #-----------------------------------------------------------------------------
57 57 # Constants and exceptions
58 58 #-----------------------------------------------------------------------------
59 59
60 60 class InvalidPortNumber(Exception):
61 61 pass
62 62
63 63 #-----------------------------------------------------------------------------
64 64 # Utility functions
65 65 #-----------------------------------------------------------------------------
66 66
67 67 # some utilities to validate message structure, these might get moved elsewhere
68 68 # if they prove to have more generic utility
69 69
70 70 def validate_string_list(lst):
71 71 """Validate that the input is a list of strings.
72 72
73 73 Raises ValueError if not."""
74 74 if not isinstance(lst, list):
75 75 raise ValueError('input %r must be a list' % lst)
76 76 for x in lst:
77 77 if not isinstance(x, basestring):
78 78 raise ValueError('element %r in list must be a string' % x)
79 79
80 80
81 81 def validate_string_dict(dct):
82 82 """Validate that the input is a dict with string keys and values.
83 83
84 84 Raises ValueError if not."""
85 85 for k,v in dct.iteritems():
86 86 if not isinstance(k, basestring):
87 87 raise ValueError('key %r in dict must be a string' % k)
88 88 if not isinstance(v, basestring):
89 89 raise ValueError('value %r in dict must be a string' % v)
90 90
91 91
92 92 #-----------------------------------------------------------------------------
93 93 # ZMQ Socket Channel classes
94 94 #-----------------------------------------------------------------------------
95 95
96 96 class ZMQSocketChannel(Thread):
97 97 """The base class for the channels that use ZMQ sockets."""
98 98 context = None
99 99 session = None
100 100 socket = None
101 101 ioloop = None
102 102 stream = None
103 103 _address = None
104 104 _exiting = False
105 105
106 106 def __init__(self, context, session, address):
107 107 """Create a channel.
108 108
109 109 Parameters
110 110 ----------
111 111 context : :class:`zmq.Context`
112 112 The ZMQ context to use.
113 113 session : :class:`session.Session`
114 114 The session to use.
115 115 address : zmq url
116 116 Standard (ip, port) tuple that the kernel is listening on.
117 117 """
118 118 super(ZMQSocketChannel, self).__init__()
119 119 self.daemon = True
120 120
121 121 self.context = context
122 122 self.session = session
123 123 if isinstance(address, tuple):
124 124 if address[1] == 0:
125 125 message = 'The port number for a channel cannot be 0.'
126 126 raise InvalidPortNumber(message)
127 127 address = "tcp://%s:%i" % address
128 128 self._address = address
129 129 atexit.register(self._notice_exit)
130 130
131 131 def _notice_exit(self):
132 132 self._exiting = True
133 133
134 134 def _run_loop(self):
135 135 """Run my loop, ignoring EINTR events in the poller"""
136 136 while True:
137 137 try:
138 138 self.ioloop.start()
139 139 except ZMQError as e:
140 140 if e.errno == errno.EINTR:
141 141 continue
142 142 else:
143 143 raise
144 144 except Exception:
145 145 if self._exiting:
146 146 break
147 147 else:
148 148 raise
149 149 else:
150 150 break
151 151
152 152 def stop(self):
153 153 """Stop the channel's event loop and join its thread.
154 154
155 155 This calls :method:`Thread.join` and returns when the thread
156 156 terminates. :class:`RuntimeError` will be raised if
157 157 :method:`self.start` is called again.
158 158 """
159 159 self.join()
160 160
161 161 @property
162 162 def address(self):
163 163 """Get the channel's address as a zmq url string.
164 164
165 165 These URLS have the form: 'tcp://127.0.0.1:5555'.
166 166 """
167 167 return self._address
168 168
169 169 def _queue_send(self, msg):
170 170 """Queue a message to be sent from the IOLoop's thread.
171 171
172 172 Parameters
173 173 ----------
174 174 msg : message to send
175 175
176 176 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
177 177 thread control of the action.
178 178 """
179 179 def thread_send():
180 180 self.session.send(self.stream, msg)
181 181 self.ioloop.add_callback(thread_send)
182 182
183 183 def _handle_recv(self, msg):
184 184 """Callback for stream.on_recv.
185 185
186 186 Unpacks message, and calls handlers with it.
187 187 """
188 188 ident,smsg = self.session.feed_identities(msg)
189 189 self.call_handlers(self.session.unserialize(smsg))
190 190
191 191
192 192
193 193 class ShellChannel(ZMQSocketChannel):
194 194 """The shell channel for issuing request/replies to the kernel."""
195 195
196 196 command_queue = None
197 197 # flag for whether execute requests should be allowed to call raw_input:
198 198 allow_stdin = True
199 199
200 200 def __init__(self, context, session, address):
201 201 super(ShellChannel, self).__init__(context, session, address)
202 202 self.ioloop = ioloop.IOLoop()
203 203
204 204 def run(self):
205 205 """The thread's main activity. Call start() instead."""
206 206 self.socket = self.context.socket(zmq.DEALER)
207 207 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
208 208 self.socket.connect(self.address)
209 209 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
210 210 self.stream.on_recv(self._handle_recv)
211 211 self._run_loop()
212 212 try:
213 213 self.socket.close()
214 214 except:
215 215 pass
216 216
217 217 def stop(self):
218 218 """Stop the channel's event loop and join its thread."""
219 219 self.ioloop.stop()
220 220 super(ShellChannel, self).stop()
221 221
222 222 def call_handlers(self, msg):
223 223 """This method is called in the ioloop thread when a message arrives.
224 224
225 225 Subclasses should override this method to handle incoming messages.
226 226 It is important to remember that this method is called in the thread
227 227 so that some logic must be done to ensure that the application leve
228 228 handlers are called in the application thread.
229 229 """
230 230 raise NotImplementedError('call_handlers must be defined in a subclass.')
231 231
232 232 def execute(self, code, silent=False, store_history=True,
233 233 user_variables=None, user_expressions=None, allow_stdin=None):
234 234 """Execute code in the kernel.
235 235
236 236 Parameters
237 237 ----------
238 238 code : str
239 239 A string of Python code.
240 240
241 241 silent : bool, optional (default False)
242 242 If set, the kernel will execute the code as quietly possible, and
243 243 will force store_history to be False.
244 244
245 245 store_history : bool, optional (default True)
246 246 If set, the kernel will store command history. This is forced
247 247 to be False if silent is True.
248 248
249 249 user_variables : list, optional
250 250 A list of variable names to pull from the user's namespace. They
251 251 will come back as a dict with these names as keys and their
252 252 :func:`repr` as values.
253 253
254 254 user_expressions : dict, optional
255 255 A dict mapping names to expressions to be evaluated in the user's
256 256 dict. The expression values are returned as strings formatted using
257 257 :func:`repr`.
258 258
259 259 allow_stdin : bool, optional (default self.allow_stdin)
260 260 Flag for whether the kernel can send stdin requests to frontends.
261 261
262 262 Some frontends (e.g. the Notebook) do not support stdin requests.
263 263 If raw_input is called from code executed from such a frontend, a
264 264 StdinNotImplementedError will be raised.
265 265
266 266 Returns
267 267 -------
268 268 The msg_id of the message sent.
269 269 """
270 270 if user_variables is None:
271 271 user_variables = []
272 272 if user_expressions is None:
273 273 user_expressions = {}
274 274 if allow_stdin is None:
275 275 allow_stdin = self.allow_stdin
276 276
277 277
278 278 # Don't waste network traffic if inputs are invalid
279 279 if not isinstance(code, basestring):
280 280 raise ValueError('code %r must be a string' % code)
281 281 validate_string_list(user_variables)
282 282 validate_string_dict(user_expressions)
283 283
284 284 # Create class for content/msg creation. Related to, but possibly
285 285 # not in Session.
286 286 content = dict(code=code, silent=silent, store_history=store_history,
287 287 user_variables=user_variables,
288 288 user_expressions=user_expressions,
289 289 allow_stdin=allow_stdin,
290 290 )
291 291 msg = self.session.msg('execute_request', content)
292 292 self._queue_send(msg)
293 293 return msg['header']['msg_id']
294 294
295 295 def complete(self, text, line, cursor_pos, block=None):
296 296 """Tab complete text in the kernel's namespace.
297 297
298 298 Parameters
299 299 ----------
300 300 text : str
301 301 The text to complete.
302 302 line : str
303 303 The full line of text that is the surrounding context for the
304 304 text to complete.
305 305 cursor_pos : int
306 306 The position of the cursor in the line where the completion was
307 307 requested.
308 308 block : str, optional
309 309 The full block of code in which the completion is being requested.
310 310
311 311 Returns
312 312 -------
313 313 The msg_id of the message sent.
314 314 """
315 315 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
316 316 msg = self.session.msg('complete_request', content)
317 317 self._queue_send(msg)
318 318 return msg['header']['msg_id']
319 319
320 320 def object_info(self, oname, detail_level=0):
321 321 """Get metadata information about an object in the kernel's namespace.
322 322
323 323 Parameters
324 324 ----------
325 325 oname : str
326 326 A string specifying the object name.
327 327 detail_level : int, optional
328 328 The level of detail for the introspection (0-2)
329 329
330 330 Returns
331 331 -------
332 332 The msg_id of the message sent.
333 333 """
334 334 content = dict(oname=oname, detail_level=detail_level)
335 335 msg = self.session.msg('object_info_request', content)
336 336 self._queue_send(msg)
337 337 return msg['header']['msg_id']
338 338
339 339 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
340 340 """Get entries from the kernel's history list.
341 341
342 342 Parameters
343 343 ----------
344 344 raw : bool
345 345 If True, return the raw input.
346 346 output : bool
347 347 If True, then return the output as well.
348 348 hist_access_type : str
349 349 'range' (fill in session, start and stop params), 'tail' (fill in n)
350 350 or 'search' (fill in pattern param).
351 351
352 352 session : int
353 353 For a range request, the session from which to get lines. Session
354 354 numbers are positive integers; negative ones count back from the
355 355 current session.
356 356 start : int
357 357 The first line number of a history range.
358 358 stop : int
359 359 The final (excluded) line number of a history range.
360 360
361 361 n : int
362 362 The number of lines of history to get for a tail request.
363 363
364 364 pattern : str
365 365 The glob-syntax pattern for a search request.
366 366
367 367 Returns
368 368 -------
369 369 The msg_id of the message sent.
370 370 """
371 371 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
372 372 **kwargs)
373 373 msg = self.session.msg('history_request', content)
374 374 self._queue_send(msg)
375 375 return msg['header']['msg_id']
376 376
377 377 def kernel_info(self):
378 378 """Request kernel info."""
379 379 msg = self.session.msg('kernel_info_request')
380 380 self._queue_send(msg)
381 381 return msg['header']['msg_id']
382 382
383 383 def shutdown(self, restart=False):
384 384 """Request an immediate kernel shutdown.
385 385
386 386 Upon receipt of the (empty) reply, client code can safely assume that
387 387 the kernel has shut down and it's safe to forcefully terminate it if
388 388 it's still alive.
389 389
390 390 The kernel will send the reply via a function registered with Python's
391 391 atexit module, ensuring it's truly done as the kernel is done with all
392 392 normal operation.
393 393 """
394 394 # Send quit message to kernel. Once we implement kernel-side setattr,
395 395 # this should probably be done that way, but for now this will do.
396 396 msg = self.session.msg('shutdown_request', {'restart':restart})
397 397 self._queue_send(msg)
398 398 return msg['header']['msg_id']
399 399
400 400
401 401
402 402 class IOPubChannel(ZMQSocketChannel):
403 403 """The iopub channel which listens for messages that the kernel publishes.
404 404
405 405 This channel is where all output is published to frontends.
406 406 """
407 407
408 408 def __init__(self, context, session, address):
409 409 super(IOPubChannel, self).__init__(context, session, address)
410 410 self.ioloop = ioloop.IOLoop()
411 411
412 412 def run(self):
413 413 """The thread's main activity. Call start() instead."""
414 414 self.socket = self.context.socket(zmq.SUB)
415 415 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
416 416 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
417 417 self.socket.connect(self.address)
418 418 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
419 419 self.stream.on_recv(self._handle_recv)
420 420 self._run_loop()
421 421 try:
422 422 self.socket.close()
423 423 except:
424 424 pass
425 425
426 426 def stop(self):
427 427 """Stop the channel's event loop and join its thread."""
428 428 self.ioloop.stop()
429 429 super(IOPubChannel, self).stop()
430 430
431 431 def call_handlers(self, msg):
432 432 """This method is called in the ioloop thread when a message arrives.
433 433
434 434 Subclasses should override this method to handle incoming messages.
435 435 It is important to remember that this method is called in the thread
436 436 so that some logic must be done to ensure that the application leve
437 437 handlers are called in the application thread.
438 438 """
439 439 raise NotImplementedError('call_handlers must be defined in a subclass.')
440 440
441 441 def flush(self, timeout=1.0):
442 442 """Immediately processes all pending messages on the iopub channel.
443 443
444 444 Callers should use this method to ensure that :method:`call_handlers`
445 445 has been called for all messages that have been received on the
446 446 0MQ SUB socket of this channel.
447 447
448 448 This method is thread safe.
449 449
450 450 Parameters
451 451 ----------
452 452 timeout : float, optional
453 453 The maximum amount of time to spend flushing, in seconds. The
454 454 default is one second.
455 455 """
456 456 # We do the IOLoop callback process twice to ensure that the IOLoop
457 457 # gets to perform at least one full poll.
458 458 stop_time = time.time() + timeout
459 459 for i in xrange(2):
460 460 self._flushed = False
461 461 self.ioloop.add_callback(self._flush)
462 462 while not self._flushed and time.time() < stop_time:
463 463 time.sleep(0.01)
464 464
465 465 def _flush(self):
466 466 """Callback for :method:`self.flush`."""
467 467 self.stream.flush()
468 468 self._flushed = True
469 469
470 470
471 471 class StdInChannel(ZMQSocketChannel):
472 472 """The stdin channel to handle raw_input requests that the kernel makes."""
473 473
474 474 msg_queue = None
475 475
476 476 def __init__(self, context, session, address):
477 477 super(StdInChannel, self).__init__(context, session, address)
478 478 self.ioloop = ioloop.IOLoop()
479 479
480 480 def run(self):
481 481 """The thread's main activity. Call start() instead."""
482 482 self.socket = self.context.socket(zmq.DEALER)
483 483 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
484 484 self.socket.connect(self.address)
485 485 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
486 486 self.stream.on_recv(self._handle_recv)
487 487 self._run_loop()
488 488 try:
489 489 self.socket.close()
490 490 except:
491 491 pass
492 492
493 493 def stop(self):
494 494 """Stop the channel's event loop and join its thread."""
495 495 self.ioloop.stop()
496 496 super(StdInChannel, self).stop()
497 497
498 498 def call_handlers(self, msg):
499 499 """This method is called in the ioloop thread when a message arrives.
500 500
501 501 Subclasses should override this method to handle incoming messages.
502 502 It is important to remember that this method is called in the thread
503 503 so that some logic must be done to ensure that the application leve
504 504 handlers are called in the application thread.
505 505 """
506 506 raise NotImplementedError('call_handlers must be defined in a subclass.')
507 507
508 508 def input(self, string):
509 509 """Send a string of raw input to the kernel."""
510 510 content = dict(value=string)
511 511 msg = self.session.msg('input_reply', content)
512 512 self._queue_send(msg)
513 513
514 514
515 515 class HBChannel(ZMQSocketChannel):
516 516 """The heartbeat channel which monitors the kernel heartbeat.
517 517
518 518 Note that the heartbeat channel is paused by default. As long as you start
519 519 this channel, the kernel manager will ensure that it is paused and un-paused
520 520 as appropriate.
521 521 """
522 522
523 523 time_to_dead = 3.0
524 524 socket = None
525 525 poller = None
526 526 _running = None
527 527 _pause = None
528 528 _beating = None
529 529
530 530 def __init__(self, context, session, address):
531 531 super(HBChannel, self).__init__(context, session, address)
532 532 self._running = False
533 533 self._pause =True
534 534 self.poller = zmq.Poller()
535 535
536 536 def _create_socket(self):
537 537 if self.socket is not None:
538 538 # close previous socket, before opening a new one
539 539 self.poller.unregister(self.socket)
540 540 self.socket.close()
541 541 self.socket = self.context.socket(zmq.REQ)
542 542 self.socket.setsockopt(zmq.LINGER, 0)
543 543 self.socket.connect(self.address)
544 544
545 545 self.poller.register(self.socket, zmq.POLLIN)
546 546
547 547 def _poll(self, start_time):
548 548 """poll for heartbeat replies until we reach self.time_to_dead.
549 549
550 550 Ignores interrupts, and returns the result of poll(), which
551 551 will be an empty list if no messages arrived before the timeout,
552 552 or the event tuple if there is a message to receive.
553 553 """
554 554
555 555 until_dead = self.time_to_dead - (time.time() - start_time)
556 556 # ensure poll at least once
557 557 until_dead = max(until_dead, 1e-3)
558 558 events = []
559 559 while True:
560 560 try:
561 561 events = self.poller.poll(1000 * until_dead)
562 562 except ZMQError as e:
563 563 if e.errno == errno.EINTR:
564 564 # ignore interrupts during heartbeat
565 565 # this may never actually happen
566 566 until_dead = self.time_to_dead - (time.time() - start_time)
567 567 until_dead = max(until_dead, 1e-3)
568 568 pass
569 569 else:
570 570 raise
571 571 except Exception:
572 572 if self._exiting:
573 573 break
574 574 else:
575 575 raise
576 576 else:
577 577 break
578 578 return events
579 579
580 580 def run(self):
581 581 """The thread's main activity. Call start() instead."""
582 582 self._create_socket()
583 583 self._running = True
584 584 self._beating = True
585 585
586 586 while self._running:
587 587 if self._pause:
588 588 # just sleep, and skip the rest of the loop
589 589 time.sleep(self.time_to_dead)
590 590 continue
591 591
592 592 since_last_heartbeat = 0.0
593 593 # io.rprint('Ping from HB channel') # dbg
594 594 # no need to catch EFSM here, because the previous event was
595 595 # either a recv or connect, which cannot be followed by EFSM
596 596 self.socket.send(b'ping')
597 597 request_time = time.time()
598 598 ready = self._poll(request_time)
599 599 if ready:
600 600 self._beating = True
601 601 # the poll above guarantees we have something to recv
602 602 self.socket.recv()
603 603 # sleep the remainder of the cycle
604 604 remainder = self.time_to_dead - (time.time() - request_time)
605 605 if remainder > 0:
606 606 time.sleep(remainder)
607 607 continue
608 608 else:
609 609 # nothing was received within the time limit, signal heart failure
610 610 self._beating = False
611 611 since_last_heartbeat = time.time() - request_time
612 612 self.call_handlers(since_last_heartbeat)
613 613 # and close/reopen the socket, because the REQ/REP cycle has been broken
614 614 self._create_socket()
615 615 continue
616 616 try:
617 617 self.socket.close()
618 618 except:
619 619 pass
620 620
621 621 def pause(self):
622 622 """Pause the heartbeat."""
623 623 self._pause = True
624 624
625 625 def unpause(self):
626 626 """Unpause the heartbeat."""
627 627 self._pause = False
628 628
629 629 def is_beating(self):
630 630 """Is the heartbeat running and responsive (and not paused)."""
631 631 if self.is_alive() and not self._pause and self._beating:
632 632 return True
633 633 else:
634 634 return False
635 635
636 636 def stop(self):
637 637 """Stop the channel's event loop and join its thread."""
638 638 self._running = False
639 639 super(HBChannel, self).stop()
640 640
641 641 def call_handlers(self, since_last_heartbeat):
642 642 """This method is called in the ioloop thread when a message arrives.
643 643
644 644 Subclasses should override this method to handle incoming messages.
645 645 It is important to remember that this method is called in the thread
646 646 so that some logic must be done to ensure that the application level
647 647 handlers are called in the application thread.
648 648 """
649 649 raise NotImplementedError('call_handlers must be defined in a subclass.')
650 650
651 651
652 652 #-----------------------------------------------------------------------------
653 653 # Main kernel manager class
654 654 #-----------------------------------------------------------------------------
655 655
656 656 class KernelManager(Configurable):
657 657 """Manages a single kernel on this host along with its channels.
658 658
659 659 There are four channels associated with each kernel:
660 660
661 661 * shell: for request/reply calls to the kernel.
662 662 * iopub: for the kernel to publish results to frontends.
663 663 * hb: for monitoring the kernel's heartbeat.
664 664 * stdin: for frontends to reply to raw_input calls in the kernel.
665 665
666 666 The usage of the channels that this class manages is optional. It is
667 667 entirely possible to connect to the kernels directly using ZeroMQ
668 668 sockets. These channels are useful primarily for talking to a kernel
669 669 whose :class:`KernelManager` is in the same process.
670 670
671 671 This version manages kernels started using Popen.
672 672 """
673 673 # The PyZMQ Context to use for communication with the kernel.
674 674 context = Instance(zmq.Context)
675 675 def _context_default(self):
676 676 return zmq.Context.instance()
677 677
678 678 # The Session to use for communication with the kernel.
679 679 session = Instance(Session)
680 680 def _session_default(self):
681 681 return Session(config=self.config)
682 682
683 683 # The kernel process with which the KernelManager is communicating.
684 684 # generally a Popen instance
685 685 kernel = Any()
686 686
687 687 kernel_cmd = List(Unicode, config=True,
688 688 help="""The Popen Command to launch the kernel.
689 689 Override this if you have a custom
690 690 """
691 691 )
692 692 def _kernel_cmd_changed(self, name, old, new):
693 693 print 'kernel cmd changed', new
694 694 self.ipython_kernel = False
695 695
696 696 ipython_kernel = Bool(True)
697 697
698 698
699 699 # The addresses for the communication channels.
700 700 connection_file = Unicode('')
701 701
702 702 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
703 703
704 704 ip = Unicode(LOCALHOST, config=True,
705 705 help="""Set the kernel\'s IP address [default localhost].
706 706 If the IP address is something other than localhost, then
707 707 Consoles on other machines will be able to connect
708 708 to the Kernel, so be careful!"""
709 709 )
710 710 def _ip_default(self):
711 711 if self.transport == 'ipc':
712 712 if self.connection_file:
713 713 return os.path.splitext(self.connection_file)[0] + '-ipc'
714 714 else:
715 715 return 'kernel-ipc'
716 716 else:
717 717 return LOCALHOST
718 718 def _ip_changed(self, name, old, new):
719 719 if new == '*':
720 720 self.ip = '0.0.0.0'
721 721 shell_port = Integer(0)
722 722 iopub_port = Integer(0)
723 723 stdin_port = Integer(0)
724 724 hb_port = Integer(0)
725 725
726 726 # The classes to use for the various channels.
727 727 shell_channel_class = Type(ShellChannel)
728 728 iopub_channel_class = Type(IOPubChannel)
729 729 stdin_channel_class = Type(StdInChannel)
730 730 hb_channel_class = Type(HBChannel)
731 731
732 732 # Protected traits.
733 733 _launch_args = Any
734 734 _shell_channel = Any
735 735 _iopub_channel = Any
736 736 _stdin_channel = Any
737 737 _hb_channel = Any
738 738 _connection_file_written=Bool(False)
739 739
740 740 def __del__(self):
741 741 self.cleanup_connection_file()
742 742
743 743 #--------------------------------------------------------------------------
744 744 # Channel management methods:
745 745 #--------------------------------------------------------------------------
746 746
747 747 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
748 748 """Starts the channels for this kernel.
749 749
750 750 This will create the channels if they do not exist and then start
751 751 them (their activity runs in a thread). If port numbers of 0 are
752 752 being used (random ports) then you must first call
753 753 :method:`start_kernel`. If the channels have been stopped and you
754 754 call this, :class:`RuntimeError` will be raised.
755 755 """
756 756 if shell:
757 757 self.shell_channel.start()
758 758 if iopub:
759 759 self.iopub_channel.start()
760 760 if stdin:
761 761 self.stdin_channel.start()
762 762 self.shell_channel.allow_stdin = True
763 763 else:
764 764 self.shell_channel.allow_stdin = False
765 765 if hb:
766 766 self.hb_channel.start()
767 767
768 768 def stop_channels(self):
769 769 """Stops all the running channels for this kernel.
770 770
771 771 This stops their event loops and joins their threads.
772 772 """
773 773 if self.shell_channel.is_alive():
774 774 self.shell_channel.stop()
775 775 if self.iopub_channel.is_alive():
776 776 self.iopub_channel.stop()
777 777 if self.stdin_channel.is_alive():
778 778 self.stdin_channel.stop()
779 779 if self.hb_channel.is_alive():
780 780 self.hb_channel.stop()
781 781
782 782 @property
783 783 def channels_running(self):
784 784 """Are any of the channels created and running?"""
785 785 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
786 786 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
787 787
788 788 def _make_url(self, port):
789 789 """Make a zmq url with a port.
790 790
791 791 There are two cases that this handles:
792 792
793 793 * tcp: tcp://ip:port
794 794 * ipc: ipc://ip-port
795 795 """
796 796 if self.transport == 'tcp':
797 797 return "tcp://%s:%i" % (self.ip, port)
798 798 else:
799 799 return "%s://%s-%s" % (self.transport, self.ip, port)
800 800
801 801 @property
802 802 def shell_channel(self):
803 803 """Get the shell channel object for this kernel."""
804 804 if self._shell_channel is None:
805 805 self._shell_channel = self.shell_channel_class(
806 806 self.context, self.session, self._make_url(self.shell_port)
807 807 )
808 808 return self._shell_channel
809 809
810 810 @property
811 811 def iopub_channel(self):
812 812 """Get the iopub channel object for this kernel."""
813 813 if self._iopub_channel is None:
814 814 self._iopub_channel = self.iopub_channel_class(
815 815 self.context, self.session, self._make_url(self.iopub_port)
816 816 )
817 817 return self._iopub_channel
818 818
819 819 @property
820 820 def stdin_channel(self):
821 821 """Get the stdin channel object for this kernel."""
822 822 if self._stdin_channel is None:
823 823 self._stdin_channel = self.stdin_channel_class(
824 824 self.context, self.session, self._make_url(self.stdin_port)
825 825 )
826 826 return self._stdin_channel
827 827
828 828 @property
829 829 def hb_channel(self):
830 830 """Get the hb channel object for this kernel."""
831 831 if self._hb_channel is None:
832 832 self._hb_channel = self.hb_channel_class(
833 833 self.context, self.session, self._make_url(self.hb_port)
834 834 )
835 835 return self._hb_channel
836 836
837 837 #--------------------------------------------------------------------------
838 838 # Connection and ipc file management
839 839 #--------------------------------------------------------------------------
840 840
841 841 def cleanup_connection_file(self):
842 842 """Cleanup connection file *if we wrote it*
843 843
844 844 Will not raise if the connection file was already removed somehow.
845 845 """
846 846 if self._connection_file_written:
847 847 # cleanup connection files on full shutdown of kernel we started
848 848 self._connection_file_written = False
849 849 try:
850 850 os.remove(self.connection_file)
851 851 except (IOError, OSError):
852 852 pass
853 853
854 854 def cleanup_ipc_files(self):
855 855 """Cleanup ipc files if we wrote them."""
856 856 if self.transport != 'ipc':
857 857 return
858 858 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
859 859 ipcfile = "%s-%i" % (self.ip, port)
860 860 try:
861 861 os.remove(ipcfile)
862 862 except (IOError, OSError):
863 863 pass
864 864
865 865 def load_connection_file(self):
866 866 """Load connection info from JSON dict in self.connection_file."""
867 867 with open(self.connection_file) as f:
868 868 cfg = json.loads(f.read())
869 869
870 870 from pprint import pprint
871 871 pprint(cfg)
872 872 self.transport = cfg.get('transport', 'tcp')
873 873 self.ip = cfg['ip']
874 874 self.shell_port = cfg['shell_port']
875 875 self.stdin_port = cfg['stdin_port']
876 876 self.iopub_port = cfg['iopub_port']
877 877 self.hb_port = cfg['hb_port']
878 878 self.session.key = str_to_bytes(cfg['key'])
879 879
880 880 def write_connection_file(self):
881 881 """Write connection info to JSON dict in self.connection_file."""
882 882 if self._connection_file_written:
883 883 return
884 884 self.connection_file,cfg = write_connection_file(self.connection_file,
885 885 transport=self.transport, ip=self.ip, key=self.session.key,
886 886 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
887 887 shell_port=self.shell_port, hb_port=self.hb_port)
888 888 # write_connection_file also sets default ports:
889 889 self.shell_port = cfg['shell_port']
890 890 self.stdin_port = cfg['stdin_port']
891 891 self.iopub_port = cfg['iopub_port']
892 892 self.hb_port = cfg['hb_port']
893 893
894 894 self._connection_file_written = True
895 895
896 896 #--------------------------------------------------------------------------
897 897 # Kernel management
898 898 #--------------------------------------------------------------------------
899 899
900 900 def format_kernel_cmd(self, **kw):
901 901 """format templated args (e.g. {connection_file})"""
902 902 if self.kernel_cmd:
903 903 cmd = self.kernel_cmd
904 904 else:
905 905 cmd = make_ipkernel_cmd(
906 906 'from IPython.zmq.ipkernel import main; main()',
907 907 **kw
908 908 )
909 909 ns = dict(connection_file=self.connection_file)
910 910 ns.update(self._launch_args)
911 911 return [ c.format(**ns) for c in cmd ]
912 912
913 913 def _launch_kernel(self, kernel_cmd, **kw):
914 914 """actually launch the kernel
915 915
916 916 override in a subclass to launch kernel subprocesses differently
917 917 """
918 918 return launch_kernel(kernel_cmd, **kw)
919 919
920 920 def start_kernel(self, **kw):
921 921 """Starts a kernel on this host in a separate process.
922 922
923 923 If random ports (port=0) are being used, this method must be called
924 924 before the channels are created.
925 925
926 926 Parameters:
927 927 -----------
928 928 **kw : optional
929 929 keyword arguments that are passed down to build the kernel_cmd
930 930 and launching the kernel (e.g. Popen kwargs).
931 931 """
932 932 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
933 933 raise RuntimeError("Can only launch a kernel on a local interface. "
934 934 "Make sure that the '*_address' attributes are "
935 935 "configured properly. "
936 936 "Currently valid addresses are: %s"%LOCAL_IPS
937 937 )
938 938
939 939 # write connection file / get default ports
940 940 self.write_connection_file()
941 941
942 942 # save kwargs for use in restart
943 943 self._launch_args = kw.copy()
944 944 # build the Popen cmd
945 945 kernel_cmd = self.format_kernel_cmd(**kw)
946 946 # launch the kernel subprocess
947 947 self.kernel = self._launch_kernel(kernel_cmd,
948 948 ipython_kernel=self.ipython_kernel,
949 949 **kw)
950 950
951 951 def shutdown_kernel(self, now=False, restart=False):
952 952 """Attempts to the stop the kernel process cleanly.
953 953
954 954 This attempts to shutdown the kernels cleanly by:
955 955
956 956 1. Sending it a shutdown message over the shell channel.
957 957 2. If that fails, the kernel is shutdown forcibly by sending it
958 958 a signal.
959 959
960 960 Parameters:
961 961 -----------
962 962 now : bool
963 963 Should the kernel be forcible killed *now*. This skips the
964 964 first, nice shutdown attempt.
965 965 restart: bool
966 966 Will this kernel be restarted after it is shutdown. When this
967 967 is True, connection files will not be cleaned up.
968 968 """
969 969 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
970 970 if sys.platform == 'win32':
971 971 self._kill_kernel()
972 972 return
973 973
974 974 # Pause the heart beat channel if it exists.
975 975 if self._hb_channel is not None:
976 976 self._hb_channel.pause()
977 977
978 978 if now:
979 979 if self.has_kernel:
980 980 self._kill_kernel()
981 981 else:
982 982 # Don't send any additional kernel kill messages immediately, to give
983 983 # the kernel a chance to properly execute shutdown actions. Wait for at
984 984 # most 1s, checking every 0.1s.
985 985 self.shell_channel.shutdown(restart=restart)
986 986 for i in range(10):
987 987 if self.is_alive:
988 988 time.sleep(0.1)
989 989 else:
990 990 break
991 991 else:
992 992 # OK, we've waited long enough.
993 993 if self.has_kernel:
994 994 self._kill_kernel()
995 995
996 996 if not restart:
997 997 self.cleanup_connection_file()
998 998 self.cleanup_ipc_files()
999 999 else:
1000 1000 self.cleanup_ipc_files()
1001 1001
1002 1002 def restart_kernel(self, now=False, **kw):
1003 1003 """Restarts a kernel with the arguments that were used to launch it.
1004 1004
1005 1005 If the old kernel was launched with random ports, the same ports will be
1006 1006 used for the new kernel. The same connection file is used again.
1007 1007
1008 1008 Parameters
1009 1009 ----------
1010 1010 now : bool, optional
1011 1011 If True, the kernel is forcefully restarted *immediately*, without
1012 1012 having a chance to do any cleanup action. Otherwise the kernel is
1013 1013 given 1s to clean up before a forceful restart is issued.
1014 1014
1015 1015 In all cases the kernel is restarted, the only difference is whether
1016 1016 it is given a chance to perform a clean shutdown or not.
1017 1017
1018 1018 **kw : optional
1019 1019 Any options specified here will overwrite those used to launch the
1020 1020 kernel.
1021 1021 """
1022 1022 if self._launch_args is None:
1023 1023 raise RuntimeError("Cannot restart the kernel. "
1024 1024 "No previous call to 'start_kernel'.")
1025 1025 else:
1026 1026 # Stop currently running kernel.
1027 1027 self.shutdown_kernel(now=now, restart=True)
1028 1028
1029 1029 # Start new kernel.
1030 1030 self._launch_args.update(kw)
1031 1031 self.start_kernel(**self._launch_args)
1032 1032
1033 1033 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
1034 1034 # unless there is some delay here.
1035 1035 if sys.platform == 'win32':
1036 1036 time.sleep(0.2)
1037 1037
1038 1038 @property
1039 1039 def has_kernel(self):
1040 1040 """Has a kernel been started that we are managing."""
1041 1041 return self.kernel is not None
1042 1042
1043 1043 def _kill_kernel(self):
1044 1044 """Kill the running kernel.
1045 1045
1046 1046 This is a private method, callers should use shutdown_kernel(now=True).
1047 1047 """
1048 1048 if self.has_kernel:
1049 1049 # Pause the heart beat channel if it exists.
1050 1050 if self._hb_channel is not None:
1051 1051 self._hb_channel.pause()
1052 1052
1053 1053 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1054 1054 # TerminateProcess() on Win32).
1055 1055 try:
1056 1056 self.kernel.kill()
1057 1057 except OSError as e:
1058 1058 # In Windows, we will get an Access Denied error if the process
1059 1059 # has already terminated. Ignore it.
1060 1060 if sys.platform == 'win32':
1061 1061 if e.winerror != 5:
1062 1062 raise
1063 1063 # On Unix, we may get an ESRCH error if the process has already
1064 1064 # terminated. Ignore it.
1065 1065 else:
1066 1066 from errno import ESRCH
1067 1067 if e.errno != ESRCH:
1068 1068 raise
1069 1069
1070 1070 # Block until the kernel terminates.
1071 1071 self.kernel.wait()
1072 1072 self.kernel = None
1073 1073 else:
1074 1074 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1075 1075
1076 1076 def interrupt_kernel(self):
1077 1077 """Interrupts the kernel by sending it a signal.
1078 1078
1079 1079 Unlike ``signal_kernel``, this operation is well supported on all
1080 1080 platforms.
1081 1081 """
1082 1082 if self.has_kernel:
1083 1083 if sys.platform == 'win32':
1084 1084 from parentpoller import ParentPollerWindows as Poller
1085 1085 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1086 1086 else:
1087 1087 self.kernel.send_signal(signal.SIGINT)
1088 1088 else:
1089 1089 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1090 1090
1091 1091 def signal_kernel(self, signum):
1092 1092 """Sends a signal to the kernel.
1093 1093
1094 1094 Note that since only SIGTERM is supported on Windows, this function is
1095 1095 only useful on Unix systems.
1096 1096 """
1097 1097 if self.has_kernel:
1098 1098 self.kernel.send_signal(signum)
1099 1099 else:
1100 1100 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1101 1101
1102 1102 @property
1103 1103 def is_alive(self):
1104 1104 """Is the kernel process still running?"""
1105 1105 if self.has_kernel:
1106 1106 if self.kernel.poll() is None:
1107 1107 return True
1108 1108 else:
1109 1109 return False
1110 1110 elif self._hb_channel is not None:
1111 1111 # We didn't start the kernel with this KernelManager so we
1112 1112 # use the heartbeat.
1113 1113 return self._hb_channel.is_beating()
1114 1114 else:
1115 1115 # no heartbeat and not local, we can't tell if it's running,
1116 1116 # so naively return True
1117 1117 return True
1118 1118
1119 1119
1120 1120 #-----------------------------------------------------------------------------
1121 1121 # ABC Registration
1122 1122 #-----------------------------------------------------------------------------
1123 1123
1124 1124 ShellChannelABC.register(ShellChannel)
1125 1125 IOPubChannelABC.register(IOPubChannel)
1126 1126 HBChannelABC.register(HBChannel)
1127 1127 StdInChannelABC.register(StdInChannel)
1128 1128 KernelManagerABC.register(KernelManager)
1129 1129
@@ -1,591 +1,591 b''
1 1 """A ZMQ-based subclass of InteractiveShell.
2 2
3 3 This code is meant to ease the refactoring of the base InteractiveShell into
4 4 something with a cleaner architecture for 2-process use, without actually
5 5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
6 6 we subclass and override what we want to fix. Once this is working well, we
7 7 can go back to the base class and refactor the code for a cleaner inheritance
8 8 implementation that doesn't rely on so much monkeypatching.
9 9
10 10 But this lets us maintain a fully working IPython as we develop the new
11 11 machinery. This should thus be thought of as scaffolding.
12 12 """
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Stdlib
19 19 import os
20 20 import sys
21 21 import time
22 22
23 23 # System library imports
24 24 from zmq.eventloop import ioloop
25 25
26 26 # Our own
27 27 from IPython.core.interactiveshell import (
28 28 InteractiveShell, InteractiveShellABC
29 29 )
30 30 from IPython.core import page
31 31 from IPython.core.autocall import ZMQExitAutocall
32 32 from IPython.core.displaypub import DisplayPublisher
33 33 from IPython.core.error import UsageError
34 34 from IPython.core.magics import MacroToEdit, CodeMagics
35 35 from IPython.core.magic import magics_class, line_magic, Magics
36 36 from IPython.core.payloadpage import install_payload_page
37 37 from IPython.inprocess.socket import SocketABC
38 from IPython.utils.kernel import (
38 from IPython.kernel import (
39 39 get_connection_file, get_connection_info, connect_qtconsole
40 40 )
41 41 from IPython.testing.skipdoctest import skip_doctest
42 42 from IPython.utils import io, openpy
43 43 from IPython.utils.jsonutil import json_clean, encode_images
44 44 from IPython.utils.process import arg_split
45 45 from IPython.utils import py3compat
46 46 from IPython.utils.traitlets import Instance, Type, Dict, CBool, CBytes
47 47 from IPython.utils.warn import warn, error
48 48 from IPython.zmq.displayhook import ZMQShellDisplayHook
49 49 from IPython.zmq.datapub import ZMQDataPublisher
50 50 from IPython.zmq.session import extract_header
51 51 from session import Session
52 52
53 53 #-----------------------------------------------------------------------------
54 54 # Functions and classes
55 55 #-----------------------------------------------------------------------------
56 56
57 57 class ZMQDisplayPublisher(DisplayPublisher):
58 58 """A display publisher that publishes data using a ZeroMQ PUB socket."""
59 59
60 60 session = Instance(Session)
61 61 pub_socket = Instance(SocketABC)
62 62 parent_header = Dict({})
63 63 topic = CBytes(b'displaypub')
64 64
65 65 def set_parent(self, parent):
66 66 """Set the parent for outbound messages."""
67 67 self.parent_header = extract_header(parent)
68 68
69 69 def _flush_streams(self):
70 70 """flush IO Streams prior to display"""
71 71 sys.stdout.flush()
72 72 sys.stderr.flush()
73 73
74 74 def publish(self, source, data, metadata=None):
75 75 self._flush_streams()
76 76 if metadata is None:
77 77 metadata = {}
78 78 self._validate_data(source, data, metadata)
79 79 content = {}
80 80 content['source'] = source
81 81 content['data'] = encode_images(data)
82 82 content['metadata'] = metadata
83 83 self.session.send(
84 84 self.pub_socket, u'display_data', json_clean(content),
85 85 parent=self.parent_header, ident=self.topic,
86 86 )
87 87
88 88 def clear_output(self, stdout=True, stderr=True, other=True):
89 89 content = dict(stdout=stdout, stderr=stderr, other=other)
90 90
91 91 if stdout:
92 92 print('\r', file=sys.stdout, end='')
93 93 if stderr:
94 94 print('\r', file=sys.stderr, end='')
95 95
96 96 self._flush_streams()
97 97
98 98 self.session.send(
99 99 self.pub_socket, u'clear_output', content,
100 100 parent=self.parent_header, ident=self.topic,
101 101 )
102 102
103 103 @magics_class
104 104 class KernelMagics(Magics):
105 105 #------------------------------------------------------------------------
106 106 # Magic overrides
107 107 #------------------------------------------------------------------------
108 108 # Once the base class stops inheriting from magic, this code needs to be
109 109 # moved into a separate machinery as well. For now, at least isolate here
110 110 # the magics which this class needs to implement differently from the base
111 111 # class, or that are unique to it.
112 112
113 113 @line_magic
114 114 def doctest_mode(self, parameter_s=''):
115 115 """Toggle doctest mode on and off.
116 116
117 117 This mode is intended to make IPython behave as much as possible like a
118 118 plain Python shell, from the perspective of how its prompts, exceptions
119 119 and output look. This makes it easy to copy and paste parts of a
120 120 session into doctests. It does so by:
121 121
122 122 - Changing the prompts to the classic ``>>>`` ones.
123 123 - Changing the exception reporting mode to 'Plain'.
124 124 - Disabling pretty-printing of output.
125 125
126 126 Note that IPython also supports the pasting of code snippets that have
127 127 leading '>>>' and '...' prompts in them. This means that you can paste
128 128 doctests from files or docstrings (even if they have leading
129 129 whitespace), and the code will execute correctly. You can then use
130 130 '%history -t' to see the translated history; this will give you the
131 131 input after removal of all the leading prompts and whitespace, which
132 132 can be pasted back into an editor.
133 133
134 134 With these features, you can switch into this mode easily whenever you
135 135 need to do testing and changes to doctests, without having to leave
136 136 your existing IPython session.
137 137 """
138 138
139 139 from IPython.utils.ipstruct import Struct
140 140
141 141 # Shorthands
142 142 shell = self.shell
143 143 disp_formatter = self.shell.display_formatter
144 144 ptformatter = disp_formatter.formatters['text/plain']
145 145 # dstore is a data store kept in the instance metadata bag to track any
146 146 # changes we make, so we can undo them later.
147 147 dstore = shell.meta.setdefault('doctest_mode', Struct())
148 148 save_dstore = dstore.setdefault
149 149
150 150 # save a few values we'll need to recover later
151 151 mode = save_dstore('mode', False)
152 152 save_dstore('rc_pprint', ptformatter.pprint)
153 153 save_dstore('rc_plain_text_only',disp_formatter.plain_text_only)
154 154 save_dstore('xmode', shell.InteractiveTB.mode)
155 155
156 156 if mode == False:
157 157 # turn on
158 158 ptformatter.pprint = False
159 159 disp_formatter.plain_text_only = True
160 160 shell.magic('xmode Plain')
161 161 else:
162 162 # turn off
163 163 ptformatter.pprint = dstore.rc_pprint
164 164 disp_formatter.plain_text_only = dstore.rc_plain_text_only
165 165 shell.magic("xmode " + dstore.xmode)
166 166
167 167 # Store new mode and inform on console
168 168 dstore.mode = bool(1-int(mode))
169 169 mode_label = ['OFF','ON'][dstore.mode]
170 170 print('Doctest mode is:', mode_label)
171 171
172 172 # Send the payload back so that clients can modify their prompt display
173 173 payload = dict(
174 174 source='IPython.zmq.zmqshell.ZMQInteractiveShell.doctest_mode',
175 175 mode=dstore.mode)
176 176 shell.payload_manager.write_payload(payload)
177 177
178 178
179 179 _find_edit_target = CodeMagics._find_edit_target
180 180
181 181 @skip_doctest
182 182 @line_magic
183 183 def edit(self, parameter_s='', last_call=['','']):
184 184 """Bring up an editor and execute the resulting code.
185 185
186 186 Usage:
187 187 %edit [options] [args]
188 188
189 189 %edit runs an external text editor. You will need to set the command for
190 190 this editor via the ``TerminalInteractiveShell.editor`` option in your
191 191 configuration file before it will work.
192 192
193 193 This command allows you to conveniently edit multi-line code right in
194 194 your IPython session.
195 195
196 196 If called without arguments, %edit opens up an empty editor with a
197 197 temporary file and will execute the contents of this file when you
198 198 close it (don't forget to save it!).
199 199
200 200
201 201 Options:
202 202
203 203 -n <number>: open the editor at a specified line number. By default,
204 204 the IPython editor hook uses the unix syntax 'editor +N filename', but
205 205 you can configure this by providing your own modified hook if your
206 206 favorite editor supports line-number specifications with a different
207 207 syntax.
208 208
209 209 -p: this will call the editor with the same data as the previous time
210 210 it was used, regardless of how long ago (in your current session) it
211 211 was.
212 212
213 213 -r: use 'raw' input. This option only applies to input taken from the
214 214 user's history. By default, the 'processed' history is used, so that
215 215 magics are loaded in their transformed version to valid Python. If
216 216 this option is given, the raw input as typed as the command line is
217 217 used instead. When you exit the editor, it will be executed by
218 218 IPython's own processor.
219 219
220 220 -x: do not execute the edited code immediately upon exit. This is
221 221 mainly useful if you are editing programs which need to be called with
222 222 command line arguments, which you can then do using %run.
223 223
224 224
225 225 Arguments:
226 226
227 227 If arguments are given, the following possibilites exist:
228 228
229 229 - The arguments are numbers or pairs of colon-separated numbers (like
230 230 1 4:8 9). These are interpreted as lines of previous input to be
231 231 loaded into the editor. The syntax is the same of the %macro command.
232 232
233 233 - If the argument doesn't start with a number, it is evaluated as a
234 234 variable and its contents loaded into the editor. You can thus edit
235 235 any string which contains python code (including the result of
236 236 previous edits).
237 237
238 238 - If the argument is the name of an object (other than a string),
239 239 IPython will try to locate the file where it was defined and open the
240 240 editor at the point where it is defined. You can use `%edit function`
241 241 to load an editor exactly at the point where 'function' is defined,
242 242 edit it and have the file be executed automatically.
243 243
244 244 If the object is a macro (see %macro for details), this opens up your
245 245 specified editor with a temporary file containing the macro's data.
246 246 Upon exit, the macro is reloaded with the contents of the file.
247 247
248 248 Note: opening at an exact line is only supported under Unix, and some
249 249 editors (like kedit and gedit up to Gnome 2.8) do not understand the
250 250 '+NUMBER' parameter necessary for this feature. Good editors like
251 251 (X)Emacs, vi, jed, pico and joe all do.
252 252
253 253 - If the argument is not found as a variable, IPython will look for a
254 254 file with that name (adding .py if necessary) and load it into the
255 255 editor. It will execute its contents with execfile() when you exit,
256 256 loading any code in the file into your interactive namespace.
257 257
258 258 After executing your code, %edit will return as output the code you
259 259 typed in the editor (except when it was an existing file). This way
260 260 you can reload the code in further invocations of %edit as a variable,
261 261 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
262 262 the output.
263 263
264 264 Note that %edit is also available through the alias %ed.
265 265
266 266 This is an example of creating a simple function inside the editor and
267 267 then modifying it. First, start up the editor:
268 268
269 269 In [1]: ed
270 270 Editing... done. Executing edited code...
271 271 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
272 272
273 273 We can then call the function foo():
274 274
275 275 In [2]: foo()
276 276 foo() was defined in an editing session
277 277
278 278 Now we edit foo. IPython automatically loads the editor with the
279 279 (temporary) file where foo() was previously defined:
280 280
281 281 In [3]: ed foo
282 282 Editing... done. Executing edited code...
283 283
284 284 And if we call foo() again we get the modified version:
285 285
286 286 In [4]: foo()
287 287 foo() has now been changed!
288 288
289 289 Here is an example of how to edit a code snippet successive
290 290 times. First we call the editor:
291 291
292 292 In [5]: ed
293 293 Editing... done. Executing edited code...
294 294 hello
295 295 Out[5]: "print 'hello'n"
296 296
297 297 Now we call it again with the previous output (stored in _):
298 298
299 299 In [6]: ed _
300 300 Editing... done. Executing edited code...
301 301 hello world
302 302 Out[6]: "print 'hello world'n"
303 303
304 304 Now we call it with the output #8 (stored in _8, also as Out[8]):
305 305
306 306 In [7]: ed _8
307 307 Editing... done. Executing edited code...
308 308 hello again
309 309 Out[7]: "print 'hello again'n"
310 310 """
311 311
312 312 opts,args = self.parse_options(parameter_s,'prn:')
313 313
314 314 try:
315 315 filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call)
316 316 except MacroToEdit as e:
317 317 # TODO: Implement macro editing over 2 processes.
318 318 print("Macro editing not yet implemented in 2-process model.")
319 319 return
320 320
321 321 # Make sure we send to the client an absolute path, in case the working
322 322 # directory of client and kernel don't match
323 323 filename = os.path.abspath(filename)
324 324
325 325 payload = {
326 326 'source' : 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic',
327 327 'filename' : filename,
328 328 'line_number' : lineno
329 329 }
330 330 self.shell.payload_manager.write_payload(payload)
331 331
332 332 # A few magics that are adapted to the specifics of using pexpect and a
333 333 # remote terminal
334 334
335 335 @line_magic
336 336 def clear(self, arg_s):
337 337 """Clear the terminal."""
338 338 if os.name == 'posix':
339 339 self.shell.system("clear")
340 340 else:
341 341 self.shell.system("cls")
342 342
343 343 if os.name == 'nt':
344 344 # This is the usual name in windows
345 345 cls = line_magic('cls')(clear)
346 346
347 347 # Terminal pagers won't work over pexpect, but we do have our own pager
348 348
349 349 @line_magic
350 350 def less(self, arg_s):
351 351 """Show a file through the pager.
352 352
353 353 Files ending in .py are syntax-highlighted."""
354 354 if not arg_s:
355 355 raise UsageError('Missing filename.')
356 356
357 357 cont = open(arg_s).read()
358 358 if arg_s.endswith('.py'):
359 359 cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False))
360 360 else:
361 361 cont = open(arg_s).read()
362 362 page.page(cont)
363 363
364 364 more = line_magic('more')(less)
365 365
366 366 # Man calls a pager, so we also need to redefine it
367 367 if os.name == 'posix':
368 368 @line_magic
369 369 def man(self, arg_s):
370 370 """Find the man page for the given command and display in pager."""
371 371 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
372 372 split=False))
373 373
374 374 @line_magic
375 375 def connect_info(self, arg_s):
376 376 """Print information for connecting other clients to this kernel
377 377
378 378 It will print the contents of this session's connection file, as well as
379 379 shortcuts for local clients.
380 380
381 381 In the simplest case, when called from the most recently launched kernel,
382 382 secondary clients can be connected, simply with:
383 383
384 384 $> ipython <app> --existing
385 385
386 386 """
387 387
388 388 from IPython.core.application import BaseIPythonApplication as BaseIPApp
389 389
390 390 if BaseIPApp.initialized():
391 391 app = BaseIPApp.instance()
392 392 security_dir = app.profile_dir.security_dir
393 393 profile = app.profile
394 394 else:
395 395 profile = 'default'
396 396 security_dir = ''
397 397
398 398 try:
399 399 connection_file = get_connection_file()
400 400 info = get_connection_info(unpack=False)
401 401 except Exception as e:
402 402 error("Could not get connection info: %r" % e)
403 403 return
404 404
405 405 # add profile flag for non-default profile
406 406 profile_flag = "--profile %s" % profile if profile != 'default' else ""
407 407
408 408 # if it's in the security dir, truncate to basename
409 409 if security_dir == os.path.dirname(connection_file):
410 410 connection_file = os.path.basename(connection_file)
411 411
412 412
413 413 print (info + '\n')
414 414 print ("Paste the above JSON into a file, and connect with:\n"
415 415 " $> ipython <app> --existing <file>\n"
416 416 "or, if you are local, you can connect with just:\n"
417 417 " $> ipython <app> --existing {0} {1}\n"
418 418 "or even just:\n"
419 419 " $> ipython <app> --existing {1}\n"
420 420 "if this is the most recent IPython session you have started.".format(
421 421 connection_file, profile_flag
422 422 )
423 423 )
424 424
425 425 @line_magic
426 426 def qtconsole(self, arg_s):
427 427 """Open a qtconsole connected to this kernel.
428 428
429 429 Useful for connecting a qtconsole to running notebooks, for better
430 430 debugging.
431 431 """
432 432
433 433 # %qtconsole should imply bind_kernel for engines:
434 434 try:
435 435 from IPython.parallel import bind_kernel
436 436 except ImportError:
437 437 # technically possible, because parallel has higher pyzmq min-version
438 438 pass
439 439 else:
440 440 bind_kernel()
441 441
442 442 try:
443 443 p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix'))
444 444 except Exception as e:
445 445 error("Could not start qtconsole: %r" % e)
446 446 return
447 447
448 448 def safe_unicode(e):
449 449 """unicode(e) with various fallbacks. Used for exceptions, which may not be
450 450 safe to call unicode() on.
451 451 """
452 452 try:
453 453 return unicode(e)
454 454 except UnicodeError:
455 455 pass
456 456
457 457 try:
458 458 return py3compat.str_to_unicode(str(e))
459 459 except UnicodeError:
460 460 pass
461 461
462 462 try:
463 463 return py3compat.str_to_unicode(repr(e))
464 464 except UnicodeError:
465 465 pass
466 466
467 467 return u'Unrecoverably corrupt evalue'
468 468
469 469
470 470 class ZMQInteractiveShell(InteractiveShell):
471 471 """A subclass of InteractiveShell for ZMQ."""
472 472
473 473 displayhook_class = Type(ZMQShellDisplayHook)
474 474 display_pub_class = Type(ZMQDisplayPublisher)
475 475 data_pub_class = Type(ZMQDataPublisher)
476 476
477 477 # Override the traitlet in the parent class, because there's no point using
478 478 # readline for the kernel. Can be removed when the readline code is moved
479 479 # to the terminal frontend.
480 480 colors_force = CBool(True)
481 481 readline_use = CBool(False)
482 482 # autoindent has no meaning in a zmqshell, and attempting to enable it
483 483 # will print a warning in the absence of readline.
484 484 autoindent = CBool(False)
485 485
486 486 exiter = Instance(ZMQExitAutocall)
487 487 def _exiter_default(self):
488 488 return ZMQExitAutocall(self)
489 489
490 490 def _exit_now_changed(self, name, old, new):
491 491 """stop eventloop when exit_now fires"""
492 492 if new:
493 493 loop = ioloop.IOLoop.instance()
494 494 loop.add_timeout(time.time()+0.1, loop.stop)
495 495
496 496 keepkernel_on_exit = None
497 497
498 498 # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no
499 499 # interactive input being read; we provide event loop support in ipkernel
500 500 from .eventloops import enable_gui
501 501 enable_gui = staticmethod(enable_gui)
502 502
503 503 def init_environment(self):
504 504 """Configure the user's environment.
505 505
506 506 """
507 507 env = os.environ
508 508 # These two ensure 'ls' produces nice coloring on BSD-derived systems
509 509 env['TERM'] = 'xterm-color'
510 510 env['CLICOLOR'] = '1'
511 511 # Since normal pagers don't work at all (over pexpect we don't have
512 512 # single-key control of the subprocess), try to disable paging in
513 513 # subprocesses as much as possible.
514 514 env['PAGER'] = 'cat'
515 515 env['GIT_PAGER'] = 'cat'
516 516
517 517 # And install the payload version of page.
518 518 install_payload_page()
519 519
520 520 def auto_rewrite_input(self, cmd):
521 521 """Called to show the auto-rewritten input for autocall and friends.
522 522
523 523 FIXME: this payload is currently not correctly processed by the
524 524 frontend.
525 525 """
526 526 new = self.prompt_manager.render('rewrite') + cmd
527 527 payload = dict(
528 528 source='IPython.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input',
529 529 transformed_input=new,
530 530 )
531 531 self.payload_manager.write_payload(payload)
532 532
533 533 def ask_exit(self):
534 534 """Engage the exit actions."""
535 535 self.exit_now = True
536 536 payload = dict(
537 537 source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit',
538 538 exit=True,
539 539 keepkernel=self.keepkernel_on_exit,
540 540 )
541 541 self.payload_manager.write_payload(payload)
542 542
543 543 def _showtraceback(self, etype, evalue, stb):
544 544
545 545 exc_content = {
546 546 u'traceback' : stb,
547 547 u'ename' : unicode(etype.__name__),
548 548 u'evalue' : safe_unicode(evalue)
549 549 }
550 550
551 551 dh = self.displayhook
552 552 # Send exception info over pub socket for other clients than the caller
553 553 # to pick up
554 554 topic = None
555 555 if dh.topic:
556 556 topic = dh.topic.replace(b'pyout', b'pyerr')
557 557
558 558 exc_msg = dh.session.send(dh.pub_socket, u'pyerr', json_clean(exc_content), dh.parent_header, ident=topic)
559 559
560 560 # FIXME - Hack: store exception info in shell object. Right now, the
561 561 # caller is reading this info after the fact, we need to fix this logic
562 562 # to remove this hack. Even uglier, we need to store the error status
563 563 # here, because in the main loop, the logic that sets it is being
564 564 # skipped because runlines swallows the exceptions.
565 565 exc_content[u'status'] = u'error'
566 566 self._reply_content = exc_content
567 567 # /FIXME
568 568
569 569 return exc_content
570 570
571 571 def set_next_input(self, text):
572 572 """Send the specified text to the frontend to be presented at the next
573 573 input cell."""
574 574 payload = dict(
575 575 source='IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input',
576 576 text=text
577 577 )
578 578 self.payload_manager.write_payload(payload)
579 579
580 580 #-------------------------------------------------------------------------
581 581 # Things related to magics
582 582 #-------------------------------------------------------------------------
583 583
584 584 def init_magics(self):
585 585 super(ZMQInteractiveShell, self).init_magics()
586 586 self.register_magics(KernelMagics)
587 587 self.magics_manager.register_alias('ed', 'edit')
588 588
589 589
590 590
591 591 InteractiveShellABC.register(ZMQInteractiveShell)
General Comments 0
You need to be logged in to leave comments. Login now