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