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