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