##// END OF EJS Templates
Remove unused imports in IPython.html
Thomas Kluyver -
Show More
@@ -1,753 +1,751 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) 2013 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 select
25 25 import signal
26 26 import socket
27 27 import sys
28 28 import threading
29 29 import time
30 import uuid
31 30 import webbrowser
32 31
33 32
34 33 # Third party
35 34 # check for pyzmq 2.1.11
36 35 from IPython.utils.zmqrelated import check_for_zmq
37 36 check_for_zmq('2.1.11', 'IPython.html')
38 37
39 import zmq
40 38 from jinja2 import Environment, FileSystemLoader
41 39
42 40 # Install the pyzmq ioloop. This has to be done before anything else from
43 41 # tornado is imported.
44 42 from zmq.eventloop import ioloop
45 43 ioloop.install()
46 44
47 45 # check for tornado 2.1.0
48 46 msg = "The IPython Notebook requires tornado >= 2.1.0"
49 47 try:
50 48 import tornado
51 49 except ImportError:
52 50 raise ImportError(msg)
53 51 try:
54 52 version_info = tornado.version_info
55 53 except AttributeError:
56 54 raise ImportError(msg + ", but you have < 1.1.0")
57 55 if version_info < (2,1,0):
58 56 raise ImportError(msg + ", but you have %s" % tornado.version)
59 57
60 58 from tornado import httpserver
61 59 from tornado import web
62 60
63 61 # Our own libraries
64 62 from IPython.html import DEFAULT_STATIC_FILES_PATH
65 63
66 64 from .services.kernels.kernelmanager import MappingKernelManager
67 65 from .services.notebooks.nbmanager import NotebookManager
68 66 from .services.notebooks.filenbmanager import FileNotebookManager
69 67 from .services.clusters.clustermanager import ClusterManager
70 68
71 69 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
72 70
73 71 from IPython.config.application import catch_config_error, boolean_flag
74 72 from IPython.core.application import BaseIPythonApplication
75 73 from IPython.consoleapp import IPythonConsoleApp
76 74 from IPython.kernel import swallow_argv
77 75 from IPython.kernel.zmq.session import default_secure
78 76 from IPython.kernel.zmq.kernelapp import (
79 77 kernel_flags,
80 78 kernel_aliases,
81 79 )
82 80 from IPython.utils.importstring import import_item
83 81 from IPython.utils.localinterfaces import LOCALHOST
84 82 from IPython.utils import submodule
85 83 from IPython.utils.traitlets import (
86 84 Dict, Unicode, Integer, List, Bool, Bytes,
87 85 DottedObjectName
88 86 )
89 87 from IPython.utils import py3compat
90 88 from IPython.utils.path import filefind
91 89
92 90 from .utils import url_path_join
93 91
94 92 #-----------------------------------------------------------------------------
95 93 # Module globals
96 94 #-----------------------------------------------------------------------------
97 95
98 96 _examples = """
99 97 ipython notebook # start the notebook
100 98 ipython notebook --profile=sympy # use the sympy profile
101 99 ipython notebook --pylab=inline # pylab in inline plotting mode
102 100 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
103 101 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
104 102 """
105 103
106 104 #-----------------------------------------------------------------------------
107 105 # Helper functions
108 106 #-----------------------------------------------------------------------------
109 107
110 108 def random_ports(port, n):
111 109 """Generate a list of n random ports near the given port.
112 110
113 111 The first 5 ports will be sequential, and the remaining n-5 will be
114 112 randomly selected in the range [port-2*n, port+2*n].
115 113 """
116 114 for i in range(min(5, n)):
117 115 yield port + i
118 116 for i in range(n-5):
119 117 yield port + random.randint(-2*n, 2*n)
120 118
121 119 def load_handlers(name):
122 120 """Load the (URL pattern, handler) tuples for each component."""
123 121 name = 'IPython.html.' + name
124 122 mod = __import__(name, fromlist=['default_handlers'])
125 123 return mod.default_handlers
126 124
127 125 #-----------------------------------------------------------------------------
128 126 # The Tornado web application
129 127 #-----------------------------------------------------------------------------
130 128
131 129 class NotebookWebApplication(web.Application):
132 130
133 131 def __init__(self, ipython_app, kernel_manager, notebook_manager,
134 132 cluster_manager, log,
135 133 base_project_url, settings_overrides):
136 134
137 135 settings = self.init_settings(
138 136 ipython_app, kernel_manager, notebook_manager, cluster_manager,
139 137 log, base_project_url, settings_overrides)
140 138 handlers = self.init_handlers(settings)
141 139
142 140 super(NotebookWebApplication, self).__init__(handlers, **settings)
143 141
144 142 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
145 143 cluster_manager, log,
146 144 base_project_url, settings_overrides):
147 145 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
148 146 # base_project_url will always be unicode, which will in turn
149 147 # make the patterns unicode, and ultimately result in unicode
150 148 # keys in kwargs to handler._execute(**kwargs) in tornado.
151 149 # This enforces that base_project_url be ascii in that situation.
152 150 #
153 151 # Note that the URLs these patterns check against are escaped,
154 152 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
155 153 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
156 154 template_path = os.path.join(os.path.dirname(__file__), "templates")
157 155 settings = dict(
158 156 # basics
159 157 base_project_url=base_project_url,
160 158 base_kernel_url=ipython_app.base_kernel_url,
161 159 template_path=template_path,
162 160 static_path=ipython_app.static_file_path,
163 161 static_handler_class = FileFindHandler,
164 162 static_url_prefix = url_path_join(base_project_url,'/static/'),
165 163
166 164 # authentication
167 165 cookie_secret=ipython_app.cookie_secret,
168 166 login_url=url_path_join(base_project_url,'/login'),
169 167 read_only=ipython_app.read_only,
170 168 password=ipython_app.password,
171 169
172 170 # managers
173 171 kernel_manager=kernel_manager,
174 172 notebook_manager=notebook_manager,
175 173 cluster_manager=cluster_manager,
176 174
177 175 # IPython stuff
178 176 mathjax_url=ipython_app.mathjax_url,
179 177 max_msg_size=ipython_app.max_msg_size,
180 178 config=ipython_app.config,
181 179 use_less=ipython_app.use_less,
182 180 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
183 181 )
184 182
185 183 # allow custom overrides for the tornado web app.
186 184 settings.update(settings_overrides)
187 185 return settings
188 186
189 187 def init_handlers(self, settings):
190 188 # Load the (URL pattern, handler) tuples for each component.
191 189 handlers = []
192 190 handlers.extend(load_handlers('base.handlers'))
193 191 handlers.extend(load_handlers('tree.handlers'))
194 192 handlers.extend(load_handlers('auth.login'))
195 193 handlers.extend(load_handlers('auth.logout'))
196 194 handlers.extend(load_handlers('notebook.handlers'))
197 195 handlers.extend(load_handlers('services.kernels.handlers'))
198 196 handlers.extend(load_handlers('services.notebooks.handlers'))
199 197 handlers.extend(load_handlers('services.clusters.handlers'))
200 198 handlers.extend([
201 199 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
202 200 ])
203 201 # prepend base_project_url onto the patterns that we match
204 202 new_handlers = []
205 203 for handler in handlers:
206 204 pattern = url_path_join(settings['base_project_url'], handler[0])
207 205 new_handler = tuple([pattern] + list(handler[1:]))
208 206 new_handlers.append(new_handler)
209 207 return new_handlers
210 208
211 209
212 210
213 211 #-----------------------------------------------------------------------------
214 212 # Aliases and Flags
215 213 #-----------------------------------------------------------------------------
216 214
217 215 flags = dict(kernel_flags)
218 216 flags['no-browser']=(
219 217 {'NotebookApp' : {'open_browser' : False}},
220 218 "Don't open the notebook in a browser after startup."
221 219 )
222 220 flags['no-mathjax']=(
223 221 {'NotebookApp' : {'enable_mathjax' : False}},
224 222 """Disable MathJax
225 223
226 224 MathJax is the javascript library IPython uses to render math/LaTeX. It is
227 225 very large, so you may want to disable it if you have a slow internet
228 226 connection, or for offline use of the notebook.
229 227
230 228 When disabled, equations etc. will appear as their untransformed TeX source.
231 229 """
232 230 )
233 231 flags['read-only'] = (
234 232 {'NotebookApp' : {'read_only' : True}},
235 233 """Allow read-only access to notebooks.
236 234
237 235 When using a password to protect the notebook server, this flag
238 236 allows unauthenticated clients to view the notebook list, and
239 237 individual notebooks, but not edit them, start kernels, or run
240 238 code.
241 239
242 240 If no password is set, the server will be entirely read-only.
243 241 """
244 242 )
245 243
246 244 # Add notebook manager flags
247 245 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
248 246 'Auto-save a .py script everytime the .ipynb notebook is saved',
249 247 'Do not auto-save .py scripts for every notebook'))
250 248
251 249 # the flags that are specific to the frontend
252 250 # these must be scrubbed before being passed to the kernel,
253 251 # or it will raise an error on unrecognized flags
254 252 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
255 253
256 254 aliases = dict(kernel_aliases)
257 255
258 256 aliases.update({
259 257 'ip': 'NotebookApp.ip',
260 258 'port': 'NotebookApp.port',
261 259 'port-retries': 'NotebookApp.port_retries',
262 260 'transport': 'KernelManager.transport',
263 261 'keyfile': 'NotebookApp.keyfile',
264 262 'certfile': 'NotebookApp.certfile',
265 263 'notebook-dir': 'NotebookManager.notebook_dir',
266 264 'browser': 'NotebookApp.browser',
267 265 })
268 266
269 267 # remove ipkernel flags that are singletons, and don't make sense in
270 268 # multi-kernel evironment:
271 269 aliases.pop('f', None)
272 270
273 271 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
274 272 u'notebook-dir']
275 273
276 274 #-----------------------------------------------------------------------------
277 275 # NotebookApp
278 276 #-----------------------------------------------------------------------------
279 277
280 278 class NotebookApp(BaseIPythonApplication):
281 279
282 280 name = 'ipython-notebook'
283 281 default_config_file_name='ipython_notebook_config.py'
284 282
285 283 description = """
286 284 The IPython HTML Notebook.
287 285
288 286 This launches a Tornado based HTML Notebook Server that serves up an
289 287 HTML5/Javascript Notebook client.
290 288 """
291 289 examples = _examples
292 290
293 291 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
294 292 FileNotebookManager]
295 293 flags = Dict(flags)
296 294 aliases = Dict(aliases)
297 295
298 296 kernel_argv = List(Unicode)
299 297
300 298 max_msg_size = Integer(65536, config=True, help="""
301 299 The max raw message size accepted from the browser
302 300 over a WebSocket connection.
303 301 """)
304 302
305 303 def _log_level_default(self):
306 304 return logging.INFO
307 305
308 306 def _log_format_default(self):
309 307 """override default log format to include time"""
310 308 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
311 309
312 310 # create requested profiles by default, if they don't exist:
313 311 auto_create = Bool(True)
314 312
315 313 # file to be opened in the notebook server
316 314 file_to_run = Unicode('')
317 315
318 316 # Network related information.
319 317
320 318 ip = Unicode(LOCALHOST, config=True,
321 319 help="The IP address the notebook server will listen on."
322 320 )
323 321
324 322 def _ip_changed(self, name, old, new):
325 323 if new == u'*': self.ip = u''
326 324
327 325 port = Integer(8888, config=True,
328 326 help="The port the notebook server will listen on."
329 327 )
330 328 port_retries = Integer(50, config=True,
331 329 help="The number of additional ports to try if the specified port is not available."
332 330 )
333 331
334 332 certfile = Unicode(u'', config=True,
335 333 help="""The full path to an SSL/TLS certificate file."""
336 334 )
337 335
338 336 keyfile = Unicode(u'', config=True,
339 337 help="""The full path to a private key file for usage with SSL/TLS."""
340 338 )
341 339
342 340 cookie_secret = Bytes(b'', config=True,
343 341 help="""The random bytes used to secure cookies.
344 342 By default this is a new random number every time you start the Notebook.
345 343 Set it to a value in a config file to enable logins to persist across server sessions.
346 344
347 345 Note: Cookie secrets should be kept private, do not share config files with
348 346 cookie_secret stored in plaintext (you can read the value from a file).
349 347 """
350 348 )
351 349 def _cookie_secret_default(self):
352 350 return os.urandom(1024)
353 351
354 352 password = Unicode(u'', config=True,
355 353 help="""Hashed password to use for web authentication.
356 354
357 355 To generate, type in a python/IPython shell:
358 356
359 357 from IPython.lib import passwd; passwd()
360 358
361 359 The string should be of the form type:salt:hashed-password.
362 360 """
363 361 )
364 362
365 363 open_browser = Bool(True, config=True,
366 364 help="""Whether to open in a browser after starting.
367 365 The specific browser used is platform dependent and
368 366 determined by the python standard library `webbrowser`
369 367 module, unless it is overridden using the --browser
370 368 (NotebookApp.browser) configuration option.
371 369 """)
372 370
373 371 browser = Unicode(u'', config=True,
374 372 help="""Specify what command to use to invoke a web
375 373 browser when opening the notebook. If not specified, the
376 374 default browser will be determined by the `webbrowser`
377 375 standard library module, which allows setting of the
378 376 BROWSER environment variable to override it.
379 377 """)
380 378
381 379 read_only = Bool(False, config=True,
382 380 help="Whether to prevent editing/execution of notebooks."
383 381 )
384 382
385 383 use_less = Bool(False, config=True,
386 384 help="""Wether to use Browser Side less-css parsing
387 385 instead of compiled css version in templates that allows
388 386 it. This is mainly convenient when working on the less
389 387 file to avoid a build step, or if user want to overwrite
390 388 some of the less variables without having to recompile
391 389 everything.
392 390
393 391 You will need to install the less.js component in the static directory
394 392 either in the source tree or in your profile folder.
395 393 """)
396 394
397 395 webapp_settings = Dict(config=True,
398 396 help="Supply overrides for the tornado.web.Application that the "
399 397 "IPython notebook uses.")
400 398
401 399 enable_mathjax = Bool(True, config=True,
402 400 help="""Whether to enable MathJax for typesetting math/TeX
403 401
404 402 MathJax is the javascript library IPython uses to render math/LaTeX. It is
405 403 very large, so you may want to disable it if you have a slow internet
406 404 connection, or for offline use of the notebook.
407 405
408 406 When disabled, equations etc. will appear as their untransformed TeX source.
409 407 """
410 408 )
411 409 def _enable_mathjax_changed(self, name, old, new):
412 410 """set mathjax url to empty if mathjax is disabled"""
413 411 if not new:
414 412 self.mathjax_url = u''
415 413
416 414 base_project_url = Unicode('/', config=True,
417 415 help='''The base URL for the notebook server.
418 416
419 417 Leading and trailing slashes can be omitted,
420 418 and will automatically be added.
421 419 ''')
422 420 def _base_project_url_changed(self, name, old, new):
423 421 if not new.startswith('/'):
424 422 self.base_project_url = '/'+new
425 423 elif not new.endswith('/'):
426 424 self.base_project_url = new+'/'
427 425
428 426 base_kernel_url = Unicode('/', config=True,
429 427 help='''The base URL for the kernel server
430 428
431 429 Leading and trailing slashes can be omitted,
432 430 and will automatically be added.
433 431 ''')
434 432 def _base_kernel_url_changed(self, name, old, new):
435 433 if not new.startswith('/'):
436 434 self.base_kernel_url = '/'+new
437 435 elif not new.endswith('/'):
438 436 self.base_kernel_url = new+'/'
439 437
440 438 websocket_url = Unicode("", config=True,
441 439 help="""The base URL for the websocket server,
442 440 if it differs from the HTTP server (hint: it almost certainly doesn't).
443 441
444 442 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
445 443 """
446 444 )
447 445
448 446 extra_static_paths = List(Unicode, config=True,
449 447 help="""Extra paths to search for serving static files.
450 448
451 449 This allows adding javascript/css to be available from the notebook server machine,
452 450 or overriding individual files in the IPython"""
453 451 )
454 452 def _extra_static_paths_default(self):
455 453 return [os.path.join(self.profile_dir.location, 'static')]
456 454
457 455 @property
458 456 def static_file_path(self):
459 457 """return extra paths + the default location"""
460 458 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
461 459
462 460 mathjax_url = Unicode("", config=True,
463 461 help="""The url for MathJax.js."""
464 462 )
465 463 def _mathjax_url_default(self):
466 464 if not self.enable_mathjax:
467 465 return u''
468 466 static_url_prefix = self.webapp_settings.get("static_url_prefix",
469 467 "/static/")
470 468 try:
471 469 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
472 470 except IOError:
473 471 if self.certfile:
474 472 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
475 473 base = u"https://c328740.ssl.cf1.rackcdn.com"
476 474 else:
477 475 base = u"http://cdn.mathjax.org"
478 476
479 477 url = base + u"/mathjax/latest/MathJax.js"
480 478 self.log.info("Using MathJax from CDN: %s", url)
481 479 return url
482 480 else:
483 481 self.log.info("Using local MathJax from %s" % mathjax)
484 482 return static_url_prefix+u"mathjax/MathJax.js"
485 483
486 484 def _mathjax_url_changed(self, name, old, new):
487 485 if new and not self.enable_mathjax:
488 486 # enable_mathjax=False overrides mathjax_url
489 487 self.mathjax_url = u''
490 488 else:
491 489 self.log.info("Using MathJax: %s", new)
492 490
493 491 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
494 492 config=True,
495 493 help='The notebook manager class to use.')
496 494
497 495 trust_xheaders = Bool(False, config=True,
498 496 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
499 497 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
500 498 )
501 499
502 500 def parse_command_line(self, argv=None):
503 501 super(NotebookApp, self).parse_command_line(argv)
504 502 if argv is None:
505 503 argv = sys.argv[1:]
506 504
507 505 # Scrub frontend-specific flags
508 506 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
509 507 # Kernel should inherit default config file from frontend
510 508 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
511 509
512 510 if self.extra_args:
513 511 f = os.path.abspath(self.extra_args[0])
514 512 if os.path.isdir(f):
515 513 nbdir = f
516 514 else:
517 515 self.file_to_run = f
518 516 nbdir = os.path.dirname(f)
519 517 self.config.NotebookManager.notebook_dir = nbdir
520 518
521 519 def init_configurables(self):
522 520 # force Session default to be secure
523 521 default_secure(self.config)
524 522 self.kernel_manager = MappingKernelManager(
525 523 parent=self, log=self.log, kernel_argv=self.kernel_argv,
526 524 connection_dir = self.profile_dir.security_dir,
527 525 )
528 526 kls = import_item(self.notebook_manager_class)
529 527 self.notebook_manager = kls(parent=self, log=self.log)
530 528 self.notebook_manager.load_notebook_names()
531 529 self.cluster_manager = ClusterManager(parent=self, log=self.log)
532 530 self.cluster_manager.update_profiles()
533 531
534 532 def init_logging(self):
535 533 # This prevents double log messages because tornado use a root logger that
536 534 # self.log is a child of. The logging module dipatches log messages to a log
537 535 # and all of its ancenstors until propagate is set to False.
538 536 self.log.propagate = False
539 537
540 538 # hook up tornado 3's loggers to our app handlers
541 539 for name in ('access', 'application', 'general'):
542 540 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
543 541
544 542 def init_webapp(self):
545 543 """initialize tornado webapp and httpserver"""
546 544 self.web_app = NotebookWebApplication(
547 545 self, self.kernel_manager, self.notebook_manager,
548 546 self.cluster_manager, self.log,
549 547 self.base_project_url, self.webapp_settings
550 548 )
551 549 if self.certfile:
552 550 ssl_options = dict(certfile=self.certfile)
553 551 if self.keyfile:
554 552 ssl_options['keyfile'] = self.keyfile
555 553 else:
556 554 ssl_options = None
557 555 self.web_app.password = self.password
558 556 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
559 557 xheaders=self.trust_xheaders)
560 558 if not self.ip:
561 559 warning = "WARNING: The notebook server is listening on all IP addresses"
562 560 if ssl_options is None:
563 561 self.log.critical(warning + " and not using encryption. This "
564 562 "is not recommended.")
565 563 if not self.password and not self.read_only:
566 564 self.log.critical(warning + " and not using authentication. "
567 565 "This is highly insecure and not recommended.")
568 566 success = None
569 567 for port in random_ports(self.port, self.port_retries+1):
570 568 try:
571 569 self.http_server.listen(port, self.ip)
572 570 except socket.error as e:
573 571 # XXX: remove the e.errno == -9 block when we require
574 572 # tornado >= 3.0
575 573 if e.errno == -9 and tornado.version_info[0] < 3:
576 574 # The flags passed to socket.getaddrinfo from
577 575 # tornado.netutils.bind_sockets can cause "gaierror:
578 576 # [Errno -9] Address family for hostname not supported"
579 577 # when the interface is not associated, for example.
580 578 # Changing the flags to exclude socket.AI_ADDRCONFIG does
581 579 # not cause this error, but the only way to do this is to
582 580 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
583 581 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
584 582 self.log.warn('Monkeypatching socket to fix tornado bug')
585 583 del(socket.AI_ADDRCONFIG)
586 584 try:
587 585 # retry the tornado call without AI_ADDRCONFIG flags
588 586 self.http_server.listen(port, self.ip)
589 587 except socket.error as e2:
590 588 e = e2
591 589 else:
592 590 self.port = port
593 591 success = True
594 592 break
595 593 # restore the monekypatch
596 594 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
597 595 if e.errno != errno.EADDRINUSE:
598 596 raise
599 597 self.log.info('The port %i is already in use, trying another random port.' % port)
600 598 else:
601 599 self.port = port
602 600 success = True
603 601 break
604 602 if not success:
605 603 self.log.critical('ERROR: the notebook server could not be started because '
606 604 'no available port could be found.')
607 605 self.exit(1)
608 606
609 607 def init_signal(self):
610 608 if not sys.platform.startswith('win'):
611 609 signal.signal(signal.SIGINT, self._handle_sigint)
612 610 signal.signal(signal.SIGTERM, self._signal_stop)
613 611 if hasattr(signal, 'SIGUSR1'):
614 612 # Windows doesn't support SIGUSR1
615 613 signal.signal(signal.SIGUSR1, self._signal_info)
616 614 if hasattr(signal, 'SIGINFO'):
617 615 # only on BSD-based systems
618 616 signal.signal(signal.SIGINFO, self._signal_info)
619 617
620 618 def _handle_sigint(self, sig, frame):
621 619 """SIGINT handler spawns confirmation dialog"""
622 620 # register more forceful signal handler for ^C^C case
623 621 signal.signal(signal.SIGINT, self._signal_stop)
624 622 # request confirmation dialog in bg thread, to avoid
625 623 # blocking the App
626 624 thread = threading.Thread(target=self._confirm_exit)
627 625 thread.daemon = True
628 626 thread.start()
629 627
630 628 def _restore_sigint_handler(self):
631 629 """callback for restoring original SIGINT handler"""
632 630 signal.signal(signal.SIGINT, self._handle_sigint)
633 631
634 632 def _confirm_exit(self):
635 633 """confirm shutdown on ^C
636 634
637 635 A second ^C, or answering 'y' within 5s will cause shutdown,
638 636 otherwise original SIGINT handler will be restored.
639 637
640 638 This doesn't work on Windows.
641 639 """
642 640 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
643 641 time.sleep(0.1)
644 642 info = self.log.info
645 643 info('interrupted')
646 644 print self.notebook_info()
647 645 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
648 646 sys.stdout.flush()
649 647 r,w,x = select.select([sys.stdin], [], [], 5)
650 648 if r:
651 649 line = sys.stdin.readline()
652 650 if line.lower().startswith('y'):
653 651 self.log.critical("Shutdown confirmed")
654 652 ioloop.IOLoop.instance().stop()
655 653 return
656 654 else:
657 655 print "No answer for 5s:",
658 656 print "resuming operation..."
659 657 # no answer, or answer is no:
660 658 # set it back to original SIGINT handler
661 659 # use IOLoop.add_callback because signal.signal must be called
662 660 # from main thread
663 661 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
664 662
665 663 def _signal_stop(self, sig, frame):
666 664 self.log.critical("received signal %s, stopping", sig)
667 665 ioloop.IOLoop.instance().stop()
668 666
669 667 def _signal_info(self, sig, frame):
670 668 print self.notebook_info()
671 669
672 670 def init_components(self):
673 671 """Check the components submodule, and warn if it's unclean"""
674 672 status = submodule.check_submodule_status()
675 673 if status == 'missing':
676 674 self.log.warn("components submodule missing, running `git submodule update`")
677 675 submodule.update_submodules(submodule.ipython_parent())
678 676 elif status == 'unclean':
679 677 self.log.warn("components submodule unclean, you may see 404s on static/components")
680 678 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
681 679
682 680
683 681 @catch_config_error
684 682 def initialize(self, argv=None):
685 683 self.init_logging()
686 684 super(NotebookApp, self).initialize(argv)
687 685 self.init_configurables()
688 686 self.init_components()
689 687 self.init_webapp()
690 688 self.init_signal()
691 689
692 690 def cleanup_kernels(self):
693 691 """Shutdown all kernels.
694 692
695 693 The kernels will shutdown themselves when this process no longer exists,
696 694 but explicit shutdown allows the KernelManagers to cleanup the connection files.
697 695 """
698 696 self.log.info('Shutting down kernels')
699 697 self.kernel_manager.shutdown_all()
700 698
701 699 def notebook_info(self):
702 700 "Return the current working directory and the server url information"
703 701 mgr_info = self.notebook_manager.info_string() + "\n"
704 702 return mgr_info +"The IPython Notebook is running at: %s" % self._url
705 703
706 704 def start(self):
707 705 """ Start the IPython Notebook server app, after initialization
708 706
709 707 This method takes no arguments so all configuration and initialization
710 708 must be done prior to calling this method."""
711 709 ip = self.ip if self.ip else '[all ip addresses on your system]'
712 710 proto = 'https' if self.certfile else 'http'
713 711 info = self.log.info
714 712 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
715 713 self.base_project_url)
716 714 for line in self.notebook_info().split("\n"):
717 715 info(line)
718 716 info("Use Control-C to stop this server and shut down all kernels.")
719 717
720 718 if self.open_browser or self.file_to_run:
721 719 ip = self.ip or LOCALHOST
722 720 try:
723 721 browser = webbrowser.get(self.browser or None)
724 722 except webbrowser.Error as e:
725 723 self.log.warn('No web browser found: %s.' % e)
726 724 browser = None
727 725
728 726 if self.file_to_run:
729 727 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
730 728 url = self.notebook_manager.rev_mapping.get(name, '')
731 729 else:
732 730 url = ''
733 731 if browser:
734 732 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
735 733 self.port, self.base_project_url, url), new=2)
736 734 threading.Thread(target=b).start()
737 735 try:
738 736 ioloop.IOLoop.instance().start()
739 737 except KeyboardInterrupt:
740 738 info("Interrupted...")
741 739 finally:
742 740 self.cleanup_kernels()
743 741
744 742
745 743 #-----------------------------------------------------------------------------
746 744 # Main entry point
747 745 #-----------------------------------------------------------------------------
748 746
749 747 def launch_new_instance():
750 748 app = NotebookApp.instance()
751 749 app.initialize()
752 750 app.start()
753 751
@@ -1,172 +1,170 b''
1 1 """Manage IPython.parallel clusters in the notebook.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
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 import os
20 20
21 21 from tornado import web
22 22 from zmq.eventloop import ioloop
23 23
24 24 from IPython.config.configurable import LoggingConfigurable
25 from IPython.config.loader import load_pyconfig_files
26 25 from IPython.utils.traitlets import Dict, Instance, CFloat
27 26 from IPython.parallel.apps.ipclusterapp import IPClusterStart
28 27 from IPython.core.profileapp import list_profiles_in
29 28 from IPython.core.profiledir import ProfileDir
30 29 from IPython.utils.path import get_ipython_dir
31 from IPython.utils.sysinfo import num_cpus
32 30
33 31
34 32 #-----------------------------------------------------------------------------
35 33 # Classes
36 34 #-----------------------------------------------------------------------------
37 35
38 36
39 37 class DummyIPClusterStart(IPClusterStart):
40 38 """Dummy subclass to skip init steps that conflict with global app.
41 39
42 40 Instantiating and initializing this class should result in fully configured
43 41 launchers, but no other side effects or state.
44 42 """
45 43
46 44 def init_signal(self):
47 45 pass
48 46 def reinit_logging(self):
49 47 pass
50 48
51 49
52 50 class ClusterManager(LoggingConfigurable):
53 51
54 52 profiles = Dict()
55 53
56 54 delay = CFloat(1., config=True,
57 55 help="delay (in s) between starting the controller and the engines")
58 56
59 57 loop = Instance('zmq.eventloop.ioloop.IOLoop')
60 58 def _loop_default(self):
61 59 from zmq.eventloop.ioloop import IOLoop
62 60 return IOLoop.instance()
63 61
64 62 def build_launchers(self, profile_dir):
65 63 starter = DummyIPClusterStart(log=self.log)
66 64 starter.initialize(['--profile-dir', profile_dir])
67 65 cl = starter.controller_launcher
68 66 esl = starter.engine_launcher
69 67 n = starter.n
70 68 return cl, esl, n
71 69
72 70 def get_profile_dir(self, name, path):
73 71 p = ProfileDir.find_profile_dir_by_name(path,name=name)
74 72 return p.location
75 73
76 74 def update_profiles(self):
77 75 """List all profiles in the ipython_dir and cwd.
78 76 """
79 77 for path in [get_ipython_dir(), os.getcwdu()]:
80 78 for profile in list_profiles_in(path):
81 79 pd = self.get_profile_dir(profile, path)
82 80 if profile not in self.profiles:
83 81 self.log.debug("Adding cluster profile '%s'" % profile)
84 82 self.profiles[profile] = {
85 83 'profile': profile,
86 84 'profile_dir': pd,
87 85 'status': 'stopped'
88 86 }
89 87
90 88 def list_profiles(self):
91 89 self.update_profiles()
92 90 result = [self.profile_info(p) for p in sorted(self.profiles.keys())]
93 91 return result
94 92
95 93 def check_profile(self, profile):
96 94 if profile not in self.profiles:
97 95 raise web.HTTPError(404, u'profile not found')
98 96
99 97 def profile_info(self, profile):
100 98 self.check_profile(profile)
101 99 result = {}
102 100 data = self.profiles.get(profile)
103 101 result['profile'] = profile
104 102 result['profile_dir'] = data['profile_dir']
105 103 result['status'] = data['status']
106 104 if 'n' in data:
107 105 result['n'] = data['n']
108 106 return result
109 107
110 108 def start_cluster(self, profile, n=None):
111 109 """Start a cluster for a given profile."""
112 110 self.check_profile(profile)
113 111 data = self.profiles[profile]
114 112 if data['status'] == 'running':
115 113 raise web.HTTPError(409, u'cluster already running')
116 114 cl, esl, default_n = self.build_launchers(data['profile_dir'])
117 115 n = n if n is not None else default_n
118 116 def clean_data():
119 117 data.pop('controller_launcher',None)
120 118 data.pop('engine_set_launcher',None)
121 119 data.pop('n',None)
122 120 data['status'] = 'stopped'
123 121 def engines_stopped(r):
124 122 self.log.debug('Engines stopped')
125 123 if cl.running:
126 124 cl.stop()
127 125 clean_data()
128 126 esl.on_stop(engines_stopped)
129 127 def controller_stopped(r):
130 128 self.log.debug('Controller stopped')
131 129 if esl.running:
132 130 esl.stop()
133 131 clean_data()
134 132 cl.on_stop(controller_stopped)
135 133
136 134 dc = ioloop.DelayedCallback(lambda: cl.start(), 0, self.loop)
137 135 dc.start()
138 136 dc = ioloop.DelayedCallback(lambda: esl.start(n), 1000*self.delay, self.loop)
139 137 dc.start()
140 138
141 139 self.log.debug('Cluster started')
142 140 data['controller_launcher'] = cl
143 141 data['engine_set_launcher'] = esl
144 142 data['n'] = n
145 143 data['status'] = 'running'
146 144 return self.profile_info(profile)
147 145
148 146 def stop_cluster(self, profile):
149 147 """Stop a cluster for a given profile."""
150 148 self.check_profile(profile)
151 149 data = self.profiles[profile]
152 150 if data['status'] == 'stopped':
153 151 raise web.HTTPError(409, u'cluster not running')
154 152 data = self.profiles[profile]
155 153 cl = data['controller_launcher']
156 154 esl = data['engine_set_launcher']
157 155 if cl.running:
158 156 cl.stop()
159 157 if esl.running:
160 158 esl.stop()
161 159 # Return a temp info dict, the real one is updated in the on_stop
162 160 # logic above.
163 161 result = {
164 162 'profile': data['profile'],
165 163 'profile_dir': data['profile_dir'],
166 164 'status': 'stopped'
167 165 }
168 166 return result
169 167
170 168 def stop_all_clusters(self):
171 169 for p in self.profiles.keys():
172 170 self.stop_cluster(p)
@@ -1,110 +1,110 b''
1 1 """A kernel manager relating notebooks and kernels
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2013 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 from tornado import web
20 20
21 21 from IPython.kernel.multikernelmanager import MultiKernelManager
22 22 from IPython.utils.traitlets import (
23 Dict, List, Unicode, Integer,
23 Dict, List, Unicode,
24 24 )
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Classes
28 28 #-----------------------------------------------------------------------------
29 29
30 30
31 31 class MappingKernelManager(MultiKernelManager):
32 32 """A KernelManager that handles notebook mapping and HTTP error handling"""
33 33
34 34 def _kernel_manager_class_default(self):
35 35 return "IPython.kernel.ioloop.IOLoopKernelManager"
36 36
37 37 kernel_argv = List(Unicode)
38 38
39 39 _notebook_mapping = Dict()
40 40
41 41 #-------------------------------------------------------------------------
42 42 # Methods for managing kernels and sessions
43 43 #-------------------------------------------------------------------------
44 44
45 45 def kernel_for_notebook(self, notebook_id):
46 46 """Return the kernel_id for a notebook_id or None."""
47 47 return self._notebook_mapping.get(notebook_id)
48 48
49 49 def set_kernel_for_notebook(self, notebook_id, kernel_id):
50 50 """Associate a notebook with a kernel."""
51 51 if notebook_id is not None:
52 52 self._notebook_mapping[notebook_id] = kernel_id
53 53
54 54 def notebook_for_kernel(self, kernel_id):
55 55 """Return the notebook_id for a kernel_id or None."""
56 56 for notebook_id, kid in self._notebook_mapping.iteritems():
57 57 if kernel_id == kid:
58 58 return notebook_id
59 59 return None
60 60
61 61 def delete_mapping_for_kernel(self, kernel_id):
62 62 """Remove the kernel/notebook mapping for kernel_id."""
63 63 notebook_id = self.notebook_for_kernel(kernel_id)
64 64 if notebook_id is not None:
65 65 del self._notebook_mapping[notebook_id]
66 66
67 67 def _handle_kernel_died(self, kernel_id):
68 68 """notice that a kernel died"""
69 69 self.log.warn("Kernel %s died, removing from map.", kernel_id)
70 70 self.delete_mapping_for_kernel(kernel_id)
71 71 self.remove_kernel(kernel_id)
72 72
73 73 def start_kernel(self, notebook_id=None, **kwargs):
74 74 """Start a kernel for a notebook an return its kernel_id.
75 75
76 76 Parameters
77 77 ----------
78 78 notebook_id : uuid
79 79 The uuid of the notebook to associate the new kernel with. If this
80 80 is not None, this kernel will be persistent whenever the notebook
81 81 requests a kernel.
82 82 """
83 83 kernel_id = self.kernel_for_notebook(notebook_id)
84 84 if kernel_id is None:
85 85 kwargs['extra_arguments'] = self.kernel_argv
86 86 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
87 87 self.set_kernel_for_notebook(notebook_id, kernel_id)
88 88 self.log.info("Kernel started: %s" % kernel_id)
89 89 self.log.debug("Kernel args: %r" % kwargs)
90 90 # register callback for failed auto-restart
91 91 self.add_restart_callback(kernel_id,
92 92 lambda : self._handle_kernel_died(kernel_id),
93 93 'dead',
94 94 )
95 95 else:
96 96 self.log.info("Using existing kernel: %s" % kernel_id)
97 97
98 98 return kernel_id
99 99
100 100 def shutdown_kernel(self, kernel_id, now=False):
101 101 """Shutdown a kernel by kernel_id"""
102 102 super(MappingKernelManager, self).shutdown_kernel(kernel_id, now=now)
103 103 self.delete_mapping_for_kernel(kernel_id)
104 104
105 105 # override _check_kernel_id to raise 404 instead of KeyError
106 106 def _check_kernel_id(self, kernel_id):
107 107 """Check a that a kernel_id exists and raise 404 if not."""
108 108 if kernel_id not in self:
109 109 raise web.HTTPError(404, u'Kernel does not exist: %s' % kernel_id)
110 110
General Comments 0
You need to be logged in to leave comments. Login now