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