##// END OF EJS Templates
don't specify kernel profile in notebook...
Min RK -
Show More
@@ -1,1137 +1,1127 b''
1 1 # coding: utf-8
2 2 """A tornado based IPython notebook server."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from __future__ import print_function
8 8
9 9 import base64
10 10 import datetime
11 11 import errno
12 12 import importlib
13 13 import io
14 14 import json
15 15 import logging
16 16 import os
17 17 import random
18 18 import re
19 19 import select
20 20 import signal
21 21 import socket
22 22 import sys
23 23 import threading
24 24 import webbrowser
25 25
26 26
27 27 # check for pyzmq
28 28 from IPython.utils.zmqrelated import check_for_zmq
29 29 check_for_zmq('13', 'IPython.html')
30 30
31 31 from jinja2 import Environment, FileSystemLoader
32 32
33 33 # Install the pyzmq ioloop. This has to be done before anything else from
34 34 # tornado is imported.
35 35 from zmq.eventloop import ioloop
36 36 ioloop.install()
37 37
38 38 # check for tornado 3.1.0
39 39 msg = "The IPython Notebook requires tornado >= 4.0"
40 40 try:
41 41 import tornado
42 42 except ImportError:
43 43 raise ImportError(msg)
44 44 try:
45 45 version_info = tornado.version_info
46 46 except AttributeError:
47 47 raise ImportError(msg + ", but you have < 1.1.0")
48 48 if version_info < (4,0):
49 49 raise ImportError(msg + ", but you have %s" % tornado.version)
50 50
51 51 from tornado import httpserver
52 52 from tornado import web
53 53 from tornado.log import LogFormatter, app_log, access_log, gen_log
54 54
55 55 from IPython.html import (
56 56 DEFAULT_STATIC_FILES_PATH,
57 57 DEFAULT_TEMPLATE_PATH_LIST,
58 58 )
59 59 from .base.handlers import Template404
60 60 from .log import log_request
61 61 from .services.kernels.kernelmanager import MappingKernelManager
62 62 from .services.config import ConfigManager
63 63 from .services.contents.manager import ContentsManager
64 64 from .services.contents.filemanager import FileContentsManager
65 65 from .services.clusters.clustermanager import ClusterManager
66 66 from .services.sessions.sessionmanager import SessionManager
67 67
68 68 from .auth.login import LoginHandler
69 69 from .auth.logout import LogoutHandler
70 70 from .base.handlers import IPythonHandler, FileFindHandler
71 71
72 72 from IPython.config import Config
73 73 from IPython.config.application import catch_config_error, boolean_flag
74 74 from IPython.core.application import (
75 75 BaseIPythonApplication, base_flags, base_aliases,
76 76 )
77 77 from IPython.core.profiledir import ProfileDir
78 78 from IPython.kernel import KernelManager
79 79 from IPython.kernel.kernelspec import KernelSpecManager
80 80 from IPython.kernel.zmq.session import Session
81 81 from IPython.nbformat.sign import NotebookNotary
82 82 from IPython.utils.importstring import import_item
83 83 from IPython.utils import submodule
84 84 from IPython.utils.process import check_pid
85 85 from IPython.utils.traitlets import (
86 86 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
87 87 TraitError, Type,
88 88 )
89 89 from IPython.utils import py3compat
90 90 from IPython.utils.path import filefind, get_ipython_dir
91 91 from IPython.utils.sysinfo import get_sys_info
92 92
93 93 from .nbextensions import SYSTEM_NBEXTENSIONS_DIRS
94 94 from .utils import url_path_join
95 95
96 96 #-----------------------------------------------------------------------------
97 97 # Module globals
98 98 #-----------------------------------------------------------------------------
99 99
100 100 _examples = """
101 101 ipython notebook # start the notebook
102 102 ipython notebook --profile=sympy # use the sympy profile
103 103 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
104 104 """
105 105
106 106 #-----------------------------------------------------------------------------
107 107 # Helper functions
108 108 #-----------------------------------------------------------------------------
109 109
110 110 def random_ports(port, n):
111 111 """Generate a list of n random ports near the given port.
112 112
113 113 The first 5 ports will be sequential, and the remaining n-5 will be
114 114 randomly selected in the range [port-2*n, port+2*n].
115 115 """
116 116 for i in range(min(5, n)):
117 117 yield port + i
118 118 for i in range(n-5):
119 119 yield max(1, port + random.randint(-2*n, 2*n))
120 120
121 121 def load_handlers(name):
122 122 """Load the (URL pattern, handler) tuples for each component."""
123 123 name = 'IPython.html.' + name
124 124 mod = __import__(name, fromlist=['default_handlers'])
125 125 return mod.default_handlers
126 126
127 127 #-----------------------------------------------------------------------------
128 128 # The Tornado web application
129 129 #-----------------------------------------------------------------------------
130 130
131 131 class NotebookWebApplication(web.Application):
132 132
133 133 def __init__(self, ipython_app, kernel_manager, contents_manager,
134 134 cluster_manager, session_manager, kernel_spec_manager,
135 135 config_manager, log,
136 136 base_url, default_url, settings_overrides, jinja_env_options):
137 137
138 138 settings = self.init_settings(
139 139 ipython_app, kernel_manager, contents_manager, cluster_manager,
140 140 session_manager, kernel_spec_manager, config_manager, log, base_url,
141 141 default_url, settings_overrides, jinja_env_options)
142 142 handlers = self.init_handlers(settings)
143 143
144 144 super(NotebookWebApplication, self).__init__(handlers, **settings)
145 145
146 146 def init_settings(self, ipython_app, kernel_manager, contents_manager,
147 147 cluster_manager, session_manager, kernel_spec_manager,
148 148 config_manager,
149 149 log, base_url, default_url, settings_overrides,
150 150 jinja_env_options=None):
151 151
152 152 _template_path = settings_overrides.get(
153 153 "template_path",
154 154 ipython_app.template_file_path,
155 155 )
156 156 if isinstance(_template_path, str):
157 157 _template_path = (_template_path,)
158 158 template_path = [os.path.expanduser(path) for path in _template_path]
159 159
160 160 jenv_opt = jinja_env_options if jinja_env_options else {}
161 161 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
162 162
163 163 sys_info = get_sys_info()
164 164 if sys_info['commit_source'] == 'repository':
165 165 # don't cache (rely on 304) when working from master
166 166 version_hash = ''
167 167 else:
168 168 # reset the cache on server restart
169 169 version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
170 170
171 171 settings = dict(
172 172 # basics
173 173 log_function=log_request,
174 174 base_url=base_url,
175 175 default_url=default_url,
176 176 template_path=template_path,
177 177 static_path=ipython_app.static_file_path,
178 178 static_handler_class = FileFindHandler,
179 179 static_url_prefix = url_path_join(base_url,'/static/'),
180 180 static_handler_args = {
181 181 # don't cache custom.js
182 182 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
183 183 },
184 184 version_hash=version_hash,
185 185
186 186 # authentication
187 187 cookie_secret=ipython_app.cookie_secret,
188 188 login_url=url_path_join(base_url,'/login'),
189 189 login_handler_class=ipython_app.login_handler_class,
190 190 logout_handler_class=ipython_app.logout_handler_class,
191 191 password=ipython_app.password,
192 192
193 193 # managers
194 194 kernel_manager=kernel_manager,
195 195 contents_manager=contents_manager,
196 196 cluster_manager=cluster_manager,
197 197 session_manager=session_manager,
198 198 kernel_spec_manager=kernel_spec_manager,
199 199 config_manager=config_manager,
200 200
201 201 # IPython stuff
202 202 nbextensions_path=ipython_app.nbextensions_path,
203 203 websocket_url=ipython_app.websocket_url,
204 204 mathjax_url=ipython_app.mathjax_url,
205 205 config=ipython_app.config,
206 206 jinja2_env=env,
207 207 terminals_available=False, # Set later if terminals are available
208 208 )
209 209
210 210 # allow custom overrides for the tornado web app.
211 211 settings.update(settings_overrides)
212 212 return settings
213 213
214 214 def init_handlers(self, settings):
215 215 """Load the (URL pattern, handler) tuples for each component."""
216 216
217 217 # Order matters. The first handler to match the URL will handle the request.
218 218 handlers = []
219 219 handlers.extend(load_handlers('tree.handlers'))
220 220 handlers.extend([(r"/login", settings['login_handler_class'])])
221 221 handlers.extend([(r"/logout", settings['logout_handler_class'])])
222 222 handlers.extend(load_handlers('files.handlers'))
223 223 handlers.extend(load_handlers('notebook.handlers'))
224 224 handlers.extend(load_handlers('nbconvert.handlers'))
225 225 handlers.extend(load_handlers('kernelspecs.handlers'))
226 226 handlers.extend(load_handlers('edit.handlers'))
227 227 handlers.extend(load_handlers('services.config.handlers'))
228 228 handlers.extend(load_handlers('services.kernels.handlers'))
229 229 handlers.extend(load_handlers('services.contents.handlers'))
230 230 handlers.extend(load_handlers('services.clusters.handlers'))
231 231 handlers.extend(load_handlers('services.sessions.handlers'))
232 232 handlers.extend(load_handlers('services.nbconvert.handlers'))
233 233 handlers.extend(load_handlers('services.kernelspecs.handlers'))
234 234 handlers.extend(load_handlers('services.security.handlers'))
235 235 handlers.append(
236 236 (r"/nbextensions/(.*)", FileFindHandler, {
237 237 'path': settings['nbextensions_path'],
238 238 'no_cache_paths': ['/'], # don't cache anything in nbextensions
239 239 }),
240 240 )
241 241 # register base handlers last
242 242 handlers.extend(load_handlers('base.handlers'))
243 243 # set the URL that will be redirected from `/`
244 244 handlers.append(
245 245 (r'/?', web.RedirectHandler, {
246 246 'url' : settings['default_url'],
247 247 'permanent': False, # want 302, not 301
248 248 })
249 249 )
250 250 # prepend base_url onto the patterns that we match
251 251 new_handlers = []
252 252 for handler in handlers:
253 253 pattern = url_path_join(settings['base_url'], handler[0])
254 254 new_handler = tuple([pattern] + list(handler[1:]))
255 255 new_handlers.append(new_handler)
256 256 # add 404 on the end, which will catch everything that falls through
257 257 new_handlers.append((r'(.*)', Template404))
258 258 return new_handlers
259 259
260 260
261 261 class NbserverListApp(BaseIPythonApplication):
262 262
263 263 description="List currently running notebook servers in this profile."
264 264
265 265 flags = dict(
266 266 json=({'NbserverListApp': {'json': True}},
267 267 "Produce machine-readable JSON output."),
268 268 )
269 269
270 270 json = Bool(False, config=True,
271 271 help="If True, each line of output will be a JSON object with the "
272 272 "details from the server info file.")
273 273
274 274 def start(self):
275 275 if not self.json:
276 276 print("Currently running servers:")
277 277 for serverinfo in list_running_servers(self.profile):
278 278 if self.json:
279 279 print(json.dumps(serverinfo))
280 280 else:
281 281 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
282 282
283 283 #-----------------------------------------------------------------------------
284 284 # Aliases and Flags
285 285 #-----------------------------------------------------------------------------
286 286
287 287 flags = dict(base_flags)
288 288 flags['no-browser']=(
289 289 {'NotebookApp' : {'open_browser' : False}},
290 290 "Don't open the notebook in a browser after startup."
291 291 )
292 292 flags['pylab']=(
293 293 {'NotebookApp' : {'pylab' : 'warn'}},
294 294 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
295 295 )
296 296 flags['no-mathjax']=(
297 297 {'NotebookApp' : {'enable_mathjax' : False}},
298 298 """Disable MathJax
299 299
300 300 MathJax is the javascript library IPython uses to render math/LaTeX. It is
301 301 very large, so you may want to disable it if you have a slow internet
302 302 connection, or for offline use of the notebook.
303 303
304 304 When disabled, equations etc. will appear as their untransformed TeX source.
305 305 """
306 306 )
307 307
308 308 # Add notebook manager flags
309 309 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
310 310 'DEPRECATED, IGNORED',
311 311 'DEPRECATED, IGNORED'))
312 312
313 313 aliases = dict(base_aliases)
314 314
315 315 aliases.update({
316 316 'ip': 'NotebookApp.ip',
317 317 'port': 'NotebookApp.port',
318 318 'port-retries': 'NotebookApp.port_retries',
319 319 'transport': 'KernelManager.transport',
320 320 'keyfile': 'NotebookApp.keyfile',
321 321 'certfile': 'NotebookApp.certfile',
322 322 'notebook-dir': 'NotebookApp.notebook_dir',
323 323 'browser': 'NotebookApp.browser',
324 324 'pylab': 'NotebookApp.pylab',
325 325 })
326 326
327 327 #-----------------------------------------------------------------------------
328 328 # NotebookApp
329 329 #-----------------------------------------------------------------------------
330 330
331 331 class NotebookApp(BaseIPythonApplication):
332 332
333 333 name = 'ipython-notebook'
334 334
335 335 description = """
336 336 The IPython HTML Notebook.
337 337
338 338 This launches a Tornado based HTML Notebook Server that serves up an
339 339 HTML5/Javascript Notebook client.
340 340 """
341 341 examples = _examples
342 342 aliases = aliases
343 343 flags = flags
344 344
345 345 classes = [
346 346 KernelManager, ProfileDir, Session, MappingKernelManager,
347 347 ContentsManager, FileContentsManager, NotebookNotary,
348 348 KernelSpecManager,
349 349 ]
350 350 flags = Dict(flags)
351 351 aliases = Dict(aliases)
352 352
353 353 subcommands = dict(
354 354 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
355 355 )
356 356
357 ipython_kernel_argv = List(Unicode)
358
359 357 _log_formatter_cls = LogFormatter
360 358
361 359 def _log_level_default(self):
362 360 return logging.INFO
363 361
364 362 def _log_datefmt_default(self):
365 363 """Exclude date from default date format"""
366 364 return "%H:%M:%S"
367 365
368 366 def _log_format_default(self):
369 367 """override default log format to include time"""
370 368 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
371 369
372 370 # create requested profiles by default, if they don't exist:
373 371 auto_create = Bool(True)
374 372
375 373 # file to be opened in the notebook server
376 374 file_to_run = Unicode('', config=True)
377 375
378 376 # Network related information
379 377
380 378 allow_origin = Unicode('', config=True,
381 379 help="""Set the Access-Control-Allow-Origin header
382 380
383 381 Use '*' to allow any origin to access your server.
384 382
385 383 Takes precedence over allow_origin_pat.
386 384 """
387 385 )
388 386
389 387 allow_origin_pat = Unicode('', config=True,
390 388 help="""Use a regular expression for the Access-Control-Allow-Origin header
391 389
392 390 Requests from an origin matching the expression will get replies with:
393 391
394 392 Access-Control-Allow-Origin: origin
395 393
396 394 where `origin` is the origin of the request.
397 395
398 396 Ignored if allow_origin is set.
399 397 """
400 398 )
401 399
402 400 allow_credentials = Bool(False, config=True,
403 401 help="Set the Access-Control-Allow-Credentials: true header"
404 402 )
405 403
406 404 default_url = Unicode('/tree', config=True,
407 405 help="The default URL to redirect to from `/`"
408 406 )
409 407
410 408 ip = Unicode('localhost', config=True,
411 409 help="The IP address the notebook server will listen on."
412 410 )
413 411 def _ip_default(self):
414 412 """Return localhost if available, 127.0.0.1 otherwise.
415 413
416 414 On some (horribly broken) systems, localhost cannot be bound.
417 415 """
418 416 s = socket.socket()
419 417 try:
420 418 s.bind(('localhost', 0))
421 419 except socket.error as e:
422 420 self.log.warn("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s", e)
423 421 return '127.0.0.1'
424 422 else:
425 423 s.close()
426 424 return 'localhost'
427 425
428 426 def _ip_changed(self, name, old, new):
429 427 if new == u'*': self.ip = u''
430 428
431 429 port = Integer(8888, config=True,
432 430 help="The port the notebook server will listen on."
433 431 )
434 432 port_retries = Integer(50, config=True,
435 433 help="The number of additional ports to try if the specified port is not available."
436 434 )
437 435
438 436 certfile = Unicode(u'', config=True,
439 437 help="""The full path to an SSL/TLS certificate file."""
440 438 )
441 439
442 440 keyfile = Unicode(u'', config=True,
443 441 help="""The full path to a private key file for usage with SSL/TLS."""
444 442 )
445 443
446 444 cookie_secret_file = Unicode(config=True,
447 445 help="""The file where the cookie secret is stored."""
448 446 )
449 447 def _cookie_secret_file_default(self):
450 448 if self.profile_dir is None:
451 449 return ''
452 450 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
453 451
454 452 cookie_secret = Bytes(b'', config=True,
455 453 help="""The random bytes used to secure cookies.
456 454 By default this is a new random number every time you start the Notebook.
457 455 Set it to a value in a config file to enable logins to persist across server sessions.
458 456
459 457 Note: Cookie secrets should be kept private, do not share config files with
460 458 cookie_secret stored in plaintext (you can read the value from a file).
461 459 """
462 460 )
463 461 def _cookie_secret_default(self):
464 462 if os.path.exists(self.cookie_secret_file):
465 463 with io.open(self.cookie_secret_file, 'rb') as f:
466 464 return f.read()
467 465 else:
468 466 secret = base64.encodestring(os.urandom(1024))
469 467 self._write_cookie_secret_file(secret)
470 468 return secret
471 469
472 470 def _write_cookie_secret_file(self, secret):
473 471 """write my secret to my secret_file"""
474 472 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
475 473 with io.open(self.cookie_secret_file, 'wb') as f:
476 474 f.write(secret)
477 475 try:
478 476 os.chmod(self.cookie_secret_file, 0o600)
479 477 except OSError:
480 478 self.log.warn(
481 479 "Could not set permissions on %s",
482 480 self.cookie_secret_file
483 481 )
484 482
485 483 password = Unicode(u'', config=True,
486 484 help="""Hashed password to use for web authentication.
487 485
488 486 To generate, type in a python/IPython shell:
489 487
490 488 from IPython.lib import passwd; passwd()
491 489
492 490 The string should be of the form type:salt:hashed-password.
493 491 """
494 492 )
495 493
496 494 open_browser = Bool(True, config=True,
497 495 help="""Whether to open in a browser after starting.
498 496 The specific browser used is platform dependent and
499 497 determined by the python standard library `webbrowser`
500 498 module, unless it is overridden using the --browser
501 499 (NotebookApp.browser) configuration option.
502 500 """)
503 501
504 502 browser = Unicode(u'', config=True,
505 503 help="""Specify what command to use to invoke a web
506 504 browser when opening the notebook. If not specified, the
507 505 default browser will be determined by the `webbrowser`
508 506 standard library module, which allows setting of the
509 507 BROWSER environment variable to override it.
510 508 """)
511 509
512 510 webapp_settings = Dict(config=True,
513 511 help="DEPRECATED, use tornado_settings"
514 512 )
515 513 def _webapp_settings_changed(self, name, old, new):
516 514 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
517 515 self.tornado_settings = new
518 516
519 517 tornado_settings = Dict(config=True,
520 518 help="Supply overrides for the tornado.web.Application that the "
521 519 "IPython notebook uses.")
522 520
523 521 ssl_options = Dict(config=True,
524 522 help="""Supply SSL options for the tornado HTTPServer.
525 523 See the tornado docs for details.""")
526 524
527 525 jinja_environment_options = Dict(config=True,
528 526 help="Supply extra arguments that will be passed to Jinja environment.")
529 527
530 528 enable_mathjax = Bool(True, config=True,
531 529 help="""Whether to enable MathJax for typesetting math/TeX
532 530
533 531 MathJax is the javascript library IPython uses to render math/LaTeX. It is
534 532 very large, so you may want to disable it if you have a slow internet
535 533 connection, or for offline use of the notebook.
536 534
537 535 When disabled, equations etc. will appear as their untransformed TeX source.
538 536 """
539 537 )
540 538 def _enable_mathjax_changed(self, name, old, new):
541 539 """set mathjax url to empty if mathjax is disabled"""
542 540 if not new:
543 541 self.mathjax_url = u''
544 542
545 543 base_url = Unicode('/', config=True,
546 544 help='''The base URL for the notebook server.
547 545
548 546 Leading and trailing slashes can be omitted,
549 547 and will automatically be added.
550 548 ''')
551 549 def _base_url_changed(self, name, old, new):
552 550 if not new.startswith('/'):
553 551 self.base_url = '/'+new
554 552 elif not new.endswith('/'):
555 553 self.base_url = new+'/'
556 554
557 555 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
558 556 def _base_project_url_changed(self, name, old, new):
559 557 self.log.warn("base_project_url is deprecated, use base_url")
560 558 self.base_url = new
561 559
562 560 extra_static_paths = List(Unicode, config=True,
563 561 help="""Extra paths to search for serving static files.
564 562
565 563 This allows adding javascript/css to be available from the notebook server machine,
566 564 or overriding individual files in the IPython"""
567 565 )
568 566 def _extra_static_paths_default(self):
569 567 return [os.path.join(self.profile_dir.location, 'static')]
570 568
571 569 @property
572 570 def static_file_path(self):
573 571 """return extra paths + the default location"""
574 572 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
575 573
576 574 extra_template_paths = List(Unicode, config=True,
577 575 help="""Extra paths to search for serving jinja templates.
578 576
579 577 Can be used to override templates from IPython.html.templates."""
580 578 )
581 579 def _extra_template_paths_default(self):
582 580 return []
583 581
584 582 @property
585 583 def template_file_path(self):
586 584 """return extra paths + the default locations"""
587 585 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
588 586
589 587 extra_nbextensions_path = List(Unicode, config=True,
590 588 help="""extra paths to look for Javascript notebook extensions"""
591 589 )
592 590
593 591 @property
594 592 def nbextensions_path(self):
595 593 """The path to look for Javascript notebook extensions"""
596 594 return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS
597 595
598 596 websocket_url = Unicode("", config=True,
599 597 help="""The base URL for websockets,
600 598 if it differs from the HTTP server (hint: it almost certainly doesn't).
601 599
602 600 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
603 601 """
604 602 )
605 603 mathjax_url = Unicode("", config=True,
606 604 help="""The url for MathJax.js."""
607 605 )
608 606 def _mathjax_url_default(self):
609 607 if not self.enable_mathjax:
610 608 return u''
611 609 static_url_prefix = self.tornado_settings.get("static_url_prefix",
612 610 url_path_join(self.base_url, "static")
613 611 )
614 612
615 613 # try local mathjax, either in nbextensions/mathjax or static/mathjax
616 614 for (url_prefix, search_path) in [
617 615 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
618 616 (static_url_prefix, self.static_file_path),
619 617 ]:
620 618 self.log.debug("searching for local mathjax in %s", search_path)
621 619 try:
622 620 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
623 621 except IOError:
624 622 continue
625 623 else:
626 624 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
627 625 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
628 626 return url
629 627
630 628 # no local mathjax, serve from CDN
631 629 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
632 630 self.log.info("Using MathJax from CDN: %s", url)
633 631 return url
634 632
635 633 def _mathjax_url_changed(self, name, old, new):
636 634 if new and not self.enable_mathjax:
637 635 # enable_mathjax=False overrides mathjax_url
638 636 self.mathjax_url = u''
639 637 else:
640 638 self.log.info("Using MathJax: %s", new)
641 639
642 640 contents_manager_class = Type(
643 641 default_value=FileContentsManager,
644 642 klass=ContentsManager,
645 643 config=True,
646 644 help='The notebook manager class to use.'
647 645 )
648 646 kernel_manager_class = Type(
649 647 default_value=MappingKernelManager,
650 648 config=True,
651 649 help='The kernel manager class to use.'
652 650 )
653 651 session_manager_class = Type(
654 652 default_value=SessionManager,
655 653 config=True,
656 654 help='The session manager class to use.'
657 655 )
658 656 cluster_manager_class = Type(
659 657 default_value=ClusterManager,
660 658 config=True,
661 659 help='The cluster manager class to use.'
662 660 )
663 661
664 662 config_manager_class = Type(
665 663 default_value=ConfigManager,
666 664 config = True,
667 665 help='The config manager class to use'
668 666 )
669 667
670 668 kernel_spec_manager = Instance(KernelSpecManager)
671 669
672 670 kernel_spec_manager_class = Type(
673 671 default_value=KernelSpecManager,
674 672 config=True,
675 673 help="""
676 674 The kernel spec manager class to use. Should be a subclass
677 675 of `IPython.kernel.kernelspec.KernelSpecManager`.
678 676
679 677 The Api of KernelSpecManager is provisional and might change
680 678 without warning between this version of IPython and the next stable one.
681 679 """
682 680 )
683 681
684 682 login_handler_class = Type(
685 683 default_value=LoginHandler,
686 684 klass=web.RequestHandler,
687 685 config=True,
688 686 help='The login handler class to use.',
689 687 )
690 688
691 689 logout_handler_class = Type(
692 690 default_value=LogoutHandler,
693 691 klass=web.RequestHandler,
694 692 config=True,
695 693 help='The logout handler class to use.',
696 694 )
697 695
698 696 trust_xheaders = Bool(False, config=True,
699 697 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
700 698 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
701 699 )
702 700
703 701 info_file = Unicode()
704 702
705 703 def _info_file_default(self):
706 704 info_file = "nbserver-%s.json"%os.getpid()
707 705 return os.path.join(self.profile_dir.security_dir, info_file)
708 706
709 707 pylab = Unicode('disabled', config=True,
710 708 help="""
711 709 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
712 710 """
713 711 )
714 712 def _pylab_changed(self, name, old, new):
715 713 """when --pylab is specified, display a warning and exit"""
716 714 if new != 'warn':
717 715 backend = ' %s' % new
718 716 else:
719 717 backend = ''
720 718 self.log.error("Support for specifying --pylab on the command line has been removed.")
721 719 self.log.error(
722 720 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
723 721 )
724 722 self.exit(1)
725 723
726 724 notebook_dir = Unicode(config=True,
727 725 help="The directory to use for notebooks and kernels."
728 726 )
729 727
730 728 def _notebook_dir_default(self):
731 729 if self.file_to_run:
732 730 return os.path.dirname(os.path.abspath(self.file_to_run))
733 731 else:
734 732 return py3compat.getcwd()
735 733
736 734 def _notebook_dir_changed(self, name, old, new):
737 735 """Do a bit of validation of the notebook dir."""
738 736 if not os.path.isabs(new):
739 737 # If we receive a non-absolute path, make it absolute.
740 738 self.notebook_dir = os.path.abspath(new)
741 739 return
742 740 if not os.path.isdir(new):
743 741 raise TraitError("No such notebook dir: %r" % new)
744 742
745 743 # setting App.notebook_dir implies setting notebook and kernel dirs as well
746 744 self.config.FileContentsManager.root_dir = new
747 745 self.config.MappingKernelManager.root_dir = new
748 746
749 747 server_extensions = List(Unicode(), config=True,
750 748 help=("Python modules to load as notebook server extensions. "
751 749 "This is an experimental API, and may change in future releases.")
752 750 )
753 751
754 752 reraise_server_extension_failures = Bool(
755 753 False,
756 754 config=True,
757 755 help="Reraise exceptions encountered loading server extensions?",
758 756 )
759 757
760 758 def parse_command_line(self, argv=None):
761 759 super(NotebookApp, self).parse_command_line(argv)
762 760
763 761 if self.extra_args:
764 762 arg0 = self.extra_args[0]
765 763 f = os.path.abspath(arg0)
766 764 self.argv.remove(arg0)
767 765 if not os.path.exists(f):
768 766 self.log.critical("No such file or directory: %s", f)
769 767 self.exit(1)
770 768
771 769 # Use config here, to ensure that it takes higher priority than
772 770 # anything that comes from the profile.
773 771 c = Config()
774 772 if os.path.isdir(f):
775 773 c.NotebookApp.notebook_dir = f
776 774 elif os.path.isfile(f):
777 775 c.NotebookApp.file_to_run = f
778 776 self.update_config(c)
779 777
780 def init_kernel_argv(self):
781 """add the profile-dir to arguments to be passed to IPython kernels"""
782 # FIXME: remove special treatment of IPython kernels
783 # Kernel should get *absolute* path to profile directory
784 self.ipython_kernel_argv = ["--profile-dir", self.profile_dir.location]
785
786 778 def init_configurables(self):
787 779 self.kernel_spec_manager = self.kernel_spec_manager_class(
788 780 parent=self,
789 781 ipython_dir=self.ipython_dir,
790 782 )
791 783 self.kernel_manager = self.kernel_manager_class(
792 784 parent=self,
793 785 log=self.log,
794 ipython_kernel_argv=self.ipython_kernel_argv,
795 786 connection_dir=self.profile_dir.security_dir,
796 787 )
797 788 self.contents_manager = self.contents_manager_class(
798 789 parent=self,
799 790 log=self.log,
800 791 )
801 792 self.session_manager = self.session_manager_class(
802 793 parent=self,
803 794 log=self.log,
804 795 kernel_manager=self.kernel_manager,
805 796 contents_manager=self.contents_manager,
806 797 )
807 798 self.cluster_manager = self.cluster_manager_class(
808 799 parent=self,
809 800 log=self.log,
810 801 )
811 802
812 803 self.config_manager = self.config_manager_class(
813 804 parent=self,
814 805 log=self.log,
815 806 profile_dir=self.profile_dir.location,
816 807 )
817 808
818 809 def init_logging(self):
819 810 # This prevents double log messages because tornado use a root logger that
820 811 # self.log is a child of. The logging module dipatches log messages to a log
821 812 # and all of its ancenstors until propagate is set to False.
822 813 self.log.propagate = False
823 814
824 815 for log in app_log, access_log, gen_log:
825 816 # consistent log output name (NotebookApp instead of tornado.access, etc.)
826 817 log.name = self.log.name
827 818 # hook up tornado 3's loggers to our app handlers
828 819 logger = logging.getLogger('tornado')
829 820 logger.propagate = True
830 821 logger.parent = self.log
831 822 logger.setLevel(self.log.level)
832 823
833 824 def init_webapp(self):
834 825 """initialize tornado webapp and httpserver"""
835 826 self.tornado_settings['allow_origin'] = self.allow_origin
836 827 if self.allow_origin_pat:
837 828 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
838 829 self.tornado_settings['allow_credentials'] = self.allow_credentials
839 830 # ensure default_url starts with base_url
840 831 if not self.default_url.startswith(self.base_url):
841 832 self.default_url = url_path_join(self.base_url, self.default_url)
842 833
843 834 self.web_app = NotebookWebApplication(
844 835 self, self.kernel_manager, self.contents_manager,
845 836 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
846 837 self.config_manager,
847 838 self.log, self.base_url, self.default_url, self.tornado_settings,
848 839 self.jinja_environment_options
849 840 )
850 841 ssl_options = self.ssl_options
851 842 if self.certfile:
852 843 ssl_options['certfile'] = self.certfile
853 844 if self.keyfile:
854 845 ssl_options['keyfile'] = self.keyfile
855 846 if not ssl_options:
856 847 # None indicates no SSL config
857 848 ssl_options = None
858 849 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
859 850 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
860 851 xheaders=self.trust_xheaders)
861 852
862 853 success = None
863 854 for port in random_ports(self.port, self.port_retries+1):
864 855 try:
865 856 self.http_server.listen(port, self.ip)
866 857 except socket.error as e:
867 858 if e.errno == errno.EADDRINUSE:
868 859 self.log.info('The port %i is already in use, trying another random port.' % port)
869 860 continue
870 861 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
871 862 self.log.warn("Permission to listen on port %i denied" % port)
872 863 continue
873 864 else:
874 865 raise
875 866 else:
876 867 self.port = port
877 868 success = True
878 869 break
879 870 if not success:
880 871 self.log.critical('ERROR: the notebook server could not be started because '
881 872 'no available port could be found.')
882 873 self.exit(1)
883 874
884 875 @property
885 876 def display_url(self):
886 877 ip = self.ip if self.ip else '[all ip addresses on your system]'
887 878 return self._url(ip)
888 879
889 880 @property
890 881 def connection_url(self):
891 882 ip = self.ip if self.ip else 'localhost'
892 883 return self._url(ip)
893 884
894 885 def _url(self, ip):
895 886 proto = 'https' if self.certfile else 'http'
896 887 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
897 888
898 889 def init_terminals(self):
899 890 try:
900 891 from .terminal import initialize
901 892 initialize(self.web_app)
902 893 self.web_app.settings['terminals_available'] = True
903 894 except ImportError as e:
904 895 log = self.log.debug if sys.platform == 'win32' else self.log.warn
905 896 log("Terminals not available (error was %s)", e)
906 897
907 898 def init_signal(self):
908 899 if not sys.platform.startswith('win'):
909 900 signal.signal(signal.SIGINT, self._handle_sigint)
910 901 signal.signal(signal.SIGTERM, self._signal_stop)
911 902 if hasattr(signal, 'SIGUSR1'):
912 903 # Windows doesn't support SIGUSR1
913 904 signal.signal(signal.SIGUSR1, self._signal_info)
914 905 if hasattr(signal, 'SIGINFO'):
915 906 # only on BSD-based systems
916 907 signal.signal(signal.SIGINFO, self._signal_info)
917 908
918 909 def _handle_sigint(self, sig, frame):
919 910 """SIGINT handler spawns confirmation dialog"""
920 911 # register more forceful signal handler for ^C^C case
921 912 signal.signal(signal.SIGINT, self._signal_stop)
922 913 # request confirmation dialog in bg thread, to avoid
923 914 # blocking the App
924 915 thread = threading.Thread(target=self._confirm_exit)
925 916 thread.daemon = True
926 917 thread.start()
927 918
928 919 def _restore_sigint_handler(self):
929 920 """callback for restoring original SIGINT handler"""
930 921 signal.signal(signal.SIGINT, self._handle_sigint)
931 922
932 923 def _confirm_exit(self):
933 924 """confirm shutdown on ^C
934 925
935 926 A second ^C, or answering 'y' within 5s will cause shutdown,
936 927 otherwise original SIGINT handler will be restored.
937 928
938 929 This doesn't work on Windows.
939 930 """
940 931 info = self.log.info
941 932 info('interrupted')
942 933 print(self.notebook_info())
943 934 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
944 935 sys.stdout.flush()
945 936 r,w,x = select.select([sys.stdin], [], [], 5)
946 937 if r:
947 938 line = sys.stdin.readline()
948 939 if line.lower().startswith('y') and 'n' not in line.lower():
949 940 self.log.critical("Shutdown confirmed")
950 941 ioloop.IOLoop.current().stop()
951 942 return
952 943 else:
953 944 print("No answer for 5s:", end=' ')
954 945 print("resuming operation...")
955 946 # no answer, or answer is no:
956 947 # set it back to original SIGINT handler
957 948 # use IOLoop.add_callback because signal.signal must be called
958 949 # from main thread
959 950 ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
960 951
961 952 def _signal_stop(self, sig, frame):
962 953 self.log.critical("received signal %s, stopping", sig)
963 954 ioloop.IOLoop.current().stop()
964 955
965 956 def _signal_info(self, sig, frame):
966 957 print(self.notebook_info())
967 958
968 959 def init_components(self):
969 960 """Check the components submodule, and warn if it's unclean"""
970 961 status = submodule.check_submodule_status()
971 962 if status == 'missing':
972 963 self.log.warn("components submodule missing, running `git submodule update`")
973 964 submodule.update_submodules(submodule.ipython_parent())
974 965 elif status == 'unclean':
975 966 self.log.warn("components submodule unclean, you may see 404s on static/components")
976 967 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
977 968
978 969 def init_server_extensions(self):
979 970 """Load any extensions specified by config.
980 971
981 972 Import the module, then call the load_jupyter_server_extension function,
982 973 if one exists.
983 974
984 975 The extension API is experimental, and may change in future releases.
985 976 """
986 977 for modulename in self.server_extensions:
987 978 try:
988 979 mod = importlib.import_module(modulename)
989 980 func = getattr(mod, 'load_jupyter_server_extension', None)
990 981 if func is not None:
991 982 func(self)
992 983 except Exception:
993 984 if self.reraise_server_extension_failures:
994 985 raise
995 986 self.log.warn("Error loading server extension %s", modulename,
996 987 exc_info=True)
997 988
998 989 @catch_config_error
999 990 def initialize(self, argv=None):
1000 991 super(NotebookApp, self).initialize(argv)
1001 992 self.init_logging()
1002 self.init_kernel_argv()
1003 993 self.init_configurables()
1004 994 self.init_components()
1005 995 self.init_webapp()
1006 996 self.init_terminals()
1007 997 self.init_signal()
1008 998 self.init_server_extensions()
1009 999
1010 1000 def cleanup_kernels(self):
1011 1001 """Shutdown all kernels.
1012 1002
1013 1003 The kernels will shutdown themselves when this process no longer exists,
1014 1004 but explicit shutdown allows the KernelManagers to cleanup the connection files.
1015 1005 """
1016 1006 self.log.info('Shutting down kernels')
1017 1007 self.kernel_manager.shutdown_all()
1018 1008
1019 1009 def notebook_info(self):
1020 1010 "Return the current working directory and the server url information"
1021 1011 info = self.contents_manager.info_string() + "\n"
1022 1012 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
1023 1013 return info + "The IPython Notebook is running at: %s" % self.display_url
1024 1014
1025 1015 def server_info(self):
1026 1016 """Return a JSONable dict of information about this server."""
1027 1017 return {'url': self.connection_url,
1028 1018 'hostname': self.ip if self.ip else 'localhost',
1029 1019 'port': self.port,
1030 1020 'secure': bool(self.certfile),
1031 1021 'base_url': self.base_url,
1032 1022 'notebook_dir': os.path.abspath(self.notebook_dir),
1033 1023 'pid': os.getpid()
1034 1024 }
1035 1025
1036 1026 def write_server_info_file(self):
1037 1027 """Write the result of server_info() to the JSON file info_file."""
1038 1028 with open(self.info_file, 'w') as f:
1039 1029 json.dump(self.server_info(), f, indent=2)
1040 1030
1041 1031 def remove_server_info_file(self):
1042 1032 """Remove the nbserver-<pid>.json file created for this server.
1043 1033
1044 1034 Ignores the error raised when the file has already been removed.
1045 1035 """
1046 1036 try:
1047 1037 os.unlink(self.info_file)
1048 1038 except OSError as e:
1049 1039 if e.errno != errno.ENOENT:
1050 1040 raise
1051 1041
1052 1042 def start(self):
1053 1043 """ Start the IPython Notebook server app, after initialization
1054 1044
1055 1045 This method takes no arguments so all configuration and initialization
1056 1046 must be done prior to calling this method."""
1057 1047 if self.subapp is not None:
1058 1048 return self.subapp.start()
1059 1049
1060 1050 info = self.log.info
1061 1051 for line in self.notebook_info().split("\n"):
1062 1052 info(line)
1063 1053 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
1064 1054
1065 1055 self.write_server_info_file()
1066 1056
1067 1057 if self.open_browser or self.file_to_run:
1068 1058 try:
1069 1059 browser = webbrowser.get(self.browser or None)
1070 1060 except webbrowser.Error as e:
1071 1061 self.log.warn('No web browser found: %s.' % e)
1072 1062 browser = None
1073 1063
1074 1064 if self.file_to_run:
1075 1065 if not os.path.exists(self.file_to_run):
1076 1066 self.log.critical("%s does not exist" % self.file_to_run)
1077 1067 self.exit(1)
1078 1068
1079 1069 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
1080 1070 uri = url_path_join('notebooks', *relpath.split(os.sep))
1081 1071 else:
1082 1072 uri = 'tree'
1083 1073 if browser:
1084 1074 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1085 1075 new=2)
1086 1076 threading.Thread(target=b).start()
1087 1077
1088 1078 self.io_loop = ioloop.IOLoop.current()
1089 1079 if sys.platform.startswith('win'):
1090 1080 # add no-op to wake every 5s
1091 1081 # to handle signals that may be ignored by the inner loop
1092 1082 pc = ioloop.PeriodicCallback(lambda : None, 5000)
1093 1083 pc.start()
1094 1084 try:
1095 1085 self.io_loop.start()
1096 1086 except KeyboardInterrupt:
1097 1087 info("Interrupted...")
1098 1088 finally:
1099 1089 self.cleanup_kernels()
1100 1090 self.remove_server_info_file()
1101 1091
1102 1092 def stop(self):
1103 1093 def _stop():
1104 1094 self.http_server.stop()
1105 1095 self.io_loop.stop()
1106 1096 self.io_loop.add_callback(_stop)
1107 1097
1108 1098
1109 1099 def list_running_servers(profile='default'):
1110 1100 """Iterate over the server info files of running notebook servers.
1111 1101
1112 1102 Given a profile name, find nbserver-* files in the security directory of
1113 1103 that profile, and yield dicts of their information, each one pertaining to
1114 1104 a currently running notebook server instance.
1115 1105 """
1116 1106 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1117 1107 for file in os.listdir(pd.security_dir):
1118 1108 if file.startswith('nbserver-'):
1119 1109 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1120 1110 info = json.load(f)
1121 1111
1122 1112 # Simple check whether that process is really still running
1123 1113 # Also remove leftover files from IPython 2.x without a pid field
1124 1114 if ('pid' in info) and check_pid(info['pid']):
1125 1115 yield info
1126 1116 else:
1127 1117 # If the process has died, try to delete its info file
1128 1118 try:
1129 1119 os.unlink(file)
1130 1120 except OSError:
1131 1121 pass # TODO: This should warn or log or something
1132 1122 #-----------------------------------------------------------------------------
1133 1123 # Main entry point
1134 1124 #-----------------------------------------------------------------------------
1135 1125
1136 1126 launch_new_instance = NotebookApp.launch_instance
1137 1127
General Comments 0
You need to be logged in to leave comments. Login now