##// END OF EJS Templates
check for SIGUSR1 before using it, closes #3074...
Paul Ivanov -
Show More
@@ -1,679 +1,681
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)
553 if hasattr(signal, 'SIGUSR1'):
554 # Windows doesn't support SIGUSR1
555 signal.signal(signal.SIGUSR1, self._signal_info)
554 556 if hasattr(signal, 'SIGINFO'):
555 557 # only on BSD-based systems
556 558 signal.signal(signal.SIGINFO, self._signal_info)
557 559
558 560 def _handle_sigint(self, sig, frame):
559 561 """SIGINT handler spawns confirmation dialog"""
560 562 # register more forceful signal handler for ^C^C case
561 563 signal.signal(signal.SIGINT, self._signal_stop)
562 564 # request confirmation dialog in bg thread, to avoid
563 565 # blocking the App
564 566 thread = threading.Thread(target=self._confirm_exit)
565 567 thread.daemon = True
566 568 thread.start()
567 569
568 570 def _restore_sigint_handler(self):
569 571 """callback for restoring original SIGINT handler"""
570 572 signal.signal(signal.SIGINT, self._handle_sigint)
571 573
572 574 def _confirm_exit(self):
573 575 """confirm shutdown on ^C
574 576
575 577 A second ^C, or answering 'y' within 5s will cause shutdown,
576 578 otherwise original SIGINT handler will be restored.
577 579
578 580 This doesn't work on Windows.
579 581 """
580 582 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
581 583 time.sleep(0.1)
582 584 info = self.log.info
583 585 info('interrupted')
584 586 print self.notebook_info()
585 587 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
586 588 sys.stdout.flush()
587 589 r,w,x = select.select([sys.stdin], [], [], 5)
588 590 if r:
589 591 line = sys.stdin.readline()
590 592 if line.lower().startswith('y'):
591 593 self.log.critical("Shutdown confirmed")
592 594 ioloop.IOLoop.instance().stop()
593 595 return
594 596 else:
595 597 print "No answer for 5s:",
596 598 print "resuming operation..."
597 599 # no answer, or answer is no:
598 600 # set it back to original SIGINT handler
599 601 # use IOLoop.add_callback because signal.signal must be called
600 602 # from main thread
601 603 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
602 604
603 605 def _signal_stop(self, sig, frame):
604 606 self.log.critical("received signal %s, stopping", sig)
605 607 ioloop.IOLoop.instance().stop()
606 608
607 609 def _signal_info(self, sig, frame):
608 610 print self.notebook_info()
609 611
610 612 @catch_config_error
611 613 def initialize(self, argv=None):
612 614 self.init_logging()
613 615 super(NotebookApp, self).initialize(argv)
614 616 self.init_configurables()
615 617 self.init_webapp()
616 618 self.init_signal()
617 619
618 620 def cleanup_kernels(self):
619 621 """Shutdown all kernels.
620 622
621 623 The kernels will shutdown themselves when this process no longer exists,
622 624 but explicit shutdown allows the KernelManagers to cleanup the connection files.
623 625 """
624 626 self.log.info('Shutting down kernels')
625 627 self.kernel_manager.shutdown_all()
626 628
627 629 def notebook_info(self):
628 630 "Return the current working directory and the server url information"
629 631 mgr_info = self.notebook_manager.info_string() + "\n"
630 632 return mgr_info +"The IPython Notebook is running at: %s" % self._url
631 633
632 634 def start(self):
633 635 """ Start the IPython Notebok server app, after initialization
634 636
635 637 This method takes no arguments so all configuration and initialization
636 638 must be done prior to calling this method."""
637 639 ip = self.ip if self.ip else '[all ip addresses on your system]'
638 640 proto = 'https' if self.certfile else 'http'
639 641 info = self.log.info
640 642 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
641 643 self.base_project_url)
642 644 for line in self.notebook_info().split("\n"):
643 645 info(line)
644 646 info("Use Control-C to stop this server and shut down all kernels.")
645 647
646 648 if self.open_browser or self.file_to_run:
647 649 ip = self.ip or LOCALHOST
648 650 try:
649 651 browser = webbrowser.get(self.browser or None)
650 652 except webbrowser.Error as e:
651 653 self.log.warn('No web browser found: %s.' % e)
652 654 browser = None
653 655
654 656 if self.file_to_run:
655 657 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
656 658 url = self.notebook_manager.rev_mapping.get(name, '')
657 659 else:
658 660 url = ''
659 661 if browser:
660 662 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
661 663 self.port, self.base_project_url, url), new=2)
662 664 threading.Thread(target=b).start()
663 665 try:
664 666 ioloop.IOLoop.instance().start()
665 667 except KeyboardInterrupt:
666 668 info("Interrupted...")
667 669 finally:
668 670 self.cleanup_kernels()
669 671
670 672
671 673 #-----------------------------------------------------------------------------
672 674 # Main entry point
673 675 #-----------------------------------------------------------------------------
674 676
675 677 def launch_new_instance():
676 678 app = NotebookApp.instance()
677 679 app.initialize()
678 680 app.start()
679 681
General Comments 0
You need to be logged in to leave comments. Login now