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