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