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