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