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