notebookapp.py
1137 lines
| 42.0 KiB
| text/x-python
|
PythonLexer
MinRK
|
r6067 | # coding: utf-8 | ||
MinRK
|
r16374 | """A tornado based IPython notebook server.""" | ||
Brian E. Granger
|
r4609 | |||
MinRK
|
r16374 | # Copyright (c) IPython Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||||
Brian E. Granger
|
r4609 | |||
Thomas Kluyver
|
r13348 | from __future__ import print_function | ||
MinRK
|
r16375 | |||
MinRK
|
r17141 | import base64 | ||
Min RK
|
r19069 | import datetime | ||
Brian E. Granger
|
r4548 | import errno | ||
Thomas Kluyver
|
r19456 | import importlib | ||
Thomas Kluyver
|
r14063 | import io | ||
import json | ||||
Brian E. Granger
|
r4339 | import logging | ||
import os | ||||
Bradley M. Froehle
|
r7234 | import random | ||
MinRK
|
r17106 | import re | ||
MinRK
|
r6508 | import select | ||
Brian E. Granger
|
r4345 | import signal | ||
Brian E. Granger
|
r4548 | import socket | ||
Brian E. Granger
|
r4345 | import sys | ||
Fernando Perez
|
r5212 | import threading | ||
Thomas Kluyver
|
r5065 | import webbrowser | ||
Brian E. Granger
|
r4339 | |||
MinRK
|
r10199 | |||
Min RK
|
r20354 | # check for pyzmq | ||
MinRK
|
r10201 | from IPython.utils.zmqrelated import check_for_zmq | ||
Min RK
|
r20354 | check_for_zmq('13', 'IPython.html') | ||
MinRK
|
r10199 | |||
Cameron Bates
|
r8840 | from jinja2 import Environment, FileSystemLoader | ||
Brian E. Granger
|
r4339 | |||
# Install the pyzmq ioloop. This has to be done before anything else from | ||||
# tornado is imported. | ||||
from zmq.eventloop import ioloop | ||||
MinRK
|
r6631 | ioloop.install() | ||
Brian E. Granger
|
r4339 | |||
MinRK
|
r13313 | # check for tornado 3.1.0 | ||
Min RK
|
r18739 | msg = "The IPython Notebook requires tornado >= 4.0" | ||
MinRK
|
r10199 | try: | ||
import tornado | ||||
except ImportError: | ||||
raise ImportError(msg) | ||||
try: | ||||
version_info = tornado.version_info | ||||
except AttributeError: | ||||
raise ImportError(msg + ", but you have < 1.1.0") | ||||
Min RK
|
r18739 | if version_info < (4,0): | ||
MinRK
|
r10199 | raise ImportError(msg + ", but you have %s" % tornado.version) | ||
Brian E. Granger
|
r4339 | from tornado import httpserver | ||
from tornado import web | ||||
MinRK
|
r18117 | from tornado.log import LogFormatter, app_log, access_log, gen_log | ||
Brian E. Granger
|
r4339 | |||
Scott Sanderson
|
r18693 | from IPython.html import ( | ||
DEFAULT_STATIC_FILES_PATH, | ||||
Scott Sanderson
|
r18695 | DEFAULT_TEMPLATE_PATH_LIST, | ||
Scott Sanderson
|
r18693 | ) | ||
MinRK
|
r13939 | from .base.handlers import Template404 | ||
MinRK
|
r14645 | from .log import log_request | ||
Brian E. Granger
|
r10666 | from .services.kernels.kernelmanager import MappingKernelManager | ||
Scott Sanderson
|
r19636 | from .services.config import ConfigManager | ||
MinRK
|
r17524 | from .services.contents.manager import ContentsManager | ||
from .services.contents.filemanager import FileContentsManager | ||||
Brian E. Granger
|
r10666 | from .services.clusters.clustermanager import ClusterManager | ||
Zachary Sailer
|
r12982 | from .services.sessions.sessionmanager import SessionManager | ||
Brian E. Granger
|
r10642 | |||
Scott Sanderson
|
r19637 | from .auth.login import LoginHandler | ||
from .auth.logout import LogoutHandler | ||||
Scott Sanderson
|
r19639 | from .base.handlers import IPythonHandler, FileFindHandler | ||
Brian E. Granger
|
r4339 | |||
Thomas Kluyver
|
r16041 | from IPython.config import Config | ||
Fernando Perez
|
r5762 | from IPython.config.application import catch_config_error, boolean_flag | ||
MinRK
|
r16375 | from IPython.core.application import ( | ||
BaseIPythonApplication, base_flags, base_aliases, | ||||
Brian E. Granger
|
r4345 | ) | ||
MinRK
|
r16375 | from IPython.core.profiledir import ProfileDir | ||
from IPython.kernel import KernelManager | ||||
Thomas Kluyver
|
r16684 | from IPython.kernel.kernelspec import KernelSpecManager | ||
Min RK
|
r20570 | from IPython.kernel.zmq.session import Session | ||
MinRK
|
r15791 | from IPython.nbformat.sign import NotebookNotary | ||
Brian Granger
|
r8180 | from IPython.utils.importstring import import_item | ||
MinRK
|
r10557 | from IPython.utils import submodule | ||
Thomas Kluyver
|
r17181 | from IPython.utils.process import check_pid | ||
Brian Granger
|
r8180 | from IPython.utils.traitlets import ( | ||
Thomas Kluyver
|
r16684 | Dict, Unicode, Integer, List, Bool, Bytes, Instance, | ||
Scott Sanderson
|
r19637 | TraitError, Type, | ||
Brian Granger
|
r8180 | ) | ||
MinRK
|
r6067 | from IPython.utils import py3compat | ||
MinRK
|
r12800 | from IPython.utils.path import filefind, get_ipython_dir | ||
Min RK
|
r19069 | from IPython.utils.sysinfo import get_sys_info | ||
Brian E. Granger
|
r4344 | |||
Min RK
|
r19854 | from .nbextensions import SYSTEM_NBEXTENSIONS_DIRS | ||
Brian E. Granger
|
r10642 | from .utils import url_path_join | ||
Brian E. Granger
|
r4344 | #----------------------------------------------------------------------------- | ||
# Module globals | ||||
#----------------------------------------------------------------------------- | ||||
Brian E. Granger
|
r4339 | |||
Brian E. Granger
|
r4519 | _examples = """ | ||
ipython notebook # start the notebook | ||||
ipython notebook --profile=sympy # use the sympy profile | ||||
ipython notebook --certfile=mycert.pem # use SSL/TLS certificate | ||||
""" | ||||
Brian E. Granger
|
r4344 | #----------------------------------------------------------------------------- | ||
Andrew Straw
|
r6004 | # Helper functions | ||
#----------------------------------------------------------------------------- | ||||
Bradley M. Froehle
|
r7234 | def random_ports(port, n): | ||
"""Generate a list of n random ports near the given port. | ||||
The first 5 ports will be sequential, and the remaining n-5 will be | ||||
randomly selected in the range [port-2*n, port+2*n]. | ||||
""" | ||||
for i in range(min(5, n)): | ||||
yield port + i | ||||
for i in range(n-5): | ||||
MinRK
|
r12843 | yield max(1, port + random.randint(-2*n, 2*n)) | ||
Bradley M. Froehle
|
r7234 | |||
Brian E. Granger
|
r10647 | def load_handlers(name): | ||
"""Load the (URL pattern, handler) tuples for each component.""" | ||||
MinRK
|
r11035 | name = 'IPython.html.' + name | ||
Brian E. Granger
|
r10647 | mod = __import__(name, fromlist=['default_handlers']) | ||
return mod.default_handlers | ||||
Andrew Straw
|
r6004 | #----------------------------------------------------------------------------- | ||
Brian E. Granger
|
r4344 | # The Tornado web application | ||
#----------------------------------------------------------------------------- | ||||
Brian E. Granger
|
r4339 | |||
Brian E. Granger
|
r4342 | class NotebookWebApplication(web.Application): | ||
Brian E. Granger
|
r4339 | |||
MinRK
|
r17524 | def __init__(self, ipython_app, kernel_manager, contents_manager, | ||
Thomas Kluyver
|
r19083 | cluster_manager, session_manager, kernel_spec_manager, | ||
config_manager, log, | ||||
MinRK
|
r17778 | base_url, default_url, settings_overrides, jinja_env_options): | ||
Brian E. Granger
|
r10647 | |||
Brian E. Granger
|
r10656 | settings = self.init_settings( | ||
MinRK
|
r17524 | ipython_app, kernel_manager, contents_manager, cluster_manager, | ||
Thomas Kluyver
|
r19083 | session_manager, kernel_spec_manager, config_manager, log, base_url, | ||
default_url, settings_overrides, jinja_env_options) | ||||
Brian E. Granger
|
r10656 | handlers = self.init_handlers(settings) | ||
super(NotebookWebApplication, self).__init__(handlers, **settings) | ||||
Timo Paulssen
|
r5664 | |||
MinRK
|
r17524 | def init_settings(self, ipython_app, kernel_manager, contents_manager, | ||
Thomas Kluyver
|
r16684 | cluster_manager, session_manager, kernel_spec_manager, | ||
Thomas Kluyver
|
r19083 | config_manager, | ||
MinRK
|
r17778 | log, base_url, default_url, settings_overrides, | ||
Thomas Kluyver
|
r16684 | jinja_env_options=None): | ||
Matthias Bussonnier
|
r18038 | |||
Scott Sanderson
|
r18693 | _template_path = settings_overrides.get( | ||
"template_path", | ||||
ipython_app.template_file_path, | ||||
) | ||||
Matthias BUSSONNIER
|
r17896 | if isinstance(_template_path, str): | ||
_template_path = (_template_path,) | ||||
template_path = [os.path.expanduser(path) for path in _template_path] | ||||
Matthias BUSSONNIER
|
r15449 | jenv_opt = jinja_env_options if jinja_env_options else {} | ||
Matthias BUSSONNIER
|
r17896 | env = Environment(loader=FileSystemLoader(template_path), **jenv_opt) | ||
Min RK
|
r19069 | |||
sys_info = get_sys_info() | ||||
if sys_info['commit_source'] == 'repository': | ||||
# don't cache (rely on 304) when working from master | ||||
version_hash = '' | ||||
else: | ||||
# reset the cache on server restart | ||||
version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S") | ||||
Matthias BUSSONNIER
|
r7797 | settings = dict( | ||
MinRK
|
r10355 | # basics | ||
MinRK
|
r14645 | log_function=log_request, | ||
MinRK
|
r15238 | base_url=base_url, | ||
MinRK
|
r17778 | default_url=default_url, | ||
MinRK
|
r10355 | template_path=template_path, | ||
MinRK
|
r7930 | static_path=ipython_app.static_file_path, | ||
MinRK
|
r7923 | static_handler_class = FileFindHandler, | ||
MinRK
|
r15238 | static_url_prefix = url_path_join(base_url,'/static/'), | ||
Min RK
|
r19070 | static_handler_args = { | ||
# don't cache custom.js | ||||
'no_cache_paths': [url_path_join(base_url, 'static', 'custom')], | ||||
}, | ||||
Min RK
|
r19069 | version_hash=version_hash, | ||
MinRK
|
r10355 | |||
# authentication | ||||
MinRK
|
r10784 | cookie_secret=ipython_app.cookie_secret, | ||
MinRK
|
r15238 | login_url=url_path_join(base_url,'/login'), | ||
Phil Elson
|
r19322 | login_handler_class=ipython_app.login_handler_class, | ||
Min RK
|
r19326 | logout_handler_class=ipython_app.logout_handler_class, | ||
MinRK
|
r10355 | password=ipython_app.password, | ||
Phil Elson
|
r19322 | |||
MinRK
|
r10355 | # managers | ||
kernel_manager=kernel_manager, | ||||
MinRK
|
r17524 | contents_manager=contents_manager, | ||
MinRK
|
r10355 | cluster_manager=cluster_manager, | ||
Zachary Sailer
|
r12982 | session_manager=session_manager, | ||
Thomas Kluyver
|
r16684 | kernel_spec_manager=kernel_spec_manager, | ||
Thomas Kluyver
|
r19083 | config_manager=config_manager, | ||
Zachary Sailer
|
r12982 | |||
MinRK
|
r10355 | # IPython stuff | ||
Phil Elson
|
r19322 | nbextensions_path=ipython_app.nbextensions_path, | ||
MinRK
|
r17303 | websocket_url=ipython_app.websocket_url, | ||
MinRK
|
r10388 | mathjax_url=ipython_app.mathjax_url, | ||
MinRK
|
r10355 | config=ipython_app.config, | ||
Matthias BUSSONNIER
|
r15449 | jinja2_env=env, | ||
Thomas Kluyver
|
r18557 | terminals_available=False, # Set later if terminals are available | ||
Matthias BUSSONNIER
|
r7797 | ) | ||
# allow custom overrides for the tornado web app. | ||||
settings.update(settings_overrides) | ||||
Brian E. Granger
|
r10656 | return settings | ||
Matthias BUSSONNIER
|
r7797 | |||
Brian E. Granger
|
r10656 | def init_handlers(self, settings): | ||
Min RK
|
r18758 | """Load the (URL pattern, handler) tuples for each component.""" | ||
# Order matters. The first handler to match the URL will handle the request. | ||||
Brian E. Granger
|
r10656 | handlers = [] | ||
handlers.extend(load_handlers('tree.handlers')) | ||||
Phil Elson
|
r19322 | handlers.extend([(r"/login", settings['login_handler_class'])]) | ||
Min RK
|
r19326 | handlers.extend([(r"/logout", settings['logout_handler_class'])]) | ||
MinRK
|
r18363 | handlers.extend(load_handlers('files.handlers')) | ||
Brian E. Granger
|
r10665 | handlers.extend(load_handlers('notebook.handlers')) | ||
Thomas Kluyver
|
r13827 | handlers.extend(load_handlers('nbconvert.handlers')) | ||
Thomas Kluyver
|
r16706 | handlers.extend(load_handlers('kernelspecs.handlers')) | ||
Thomas Kluyver
|
r19074 | handlers.extend(load_handlers('edit.handlers')) | ||
Thomas Kluyver
|
r18703 | handlers.extend(load_handlers('services.config.handlers')) | ||
Brian E. Granger
|
r10665 | handlers.extend(load_handlers('services.kernels.handlers')) | ||
MinRK
|
r17524 | handlers.extend(load_handlers('services.contents.handlers')) | ||
Brian E. Granger
|
r10665 | handlers.extend(load_handlers('services.clusters.handlers')) | ||
Zachary Sailer
|
r12982 | handlers.extend(load_handlers('services.sessions.handlers')) | ||
Thomas Kluyver
|
r13837 | handlers.extend(load_handlers('services.nbconvert.handlers')) | ||
Thomas Kluyver
|
r16684 | handlers.extend(load_handlers('services.kernelspecs.handlers')) | ||
Kyle Kelley
|
r19145 | handlers.extend(load_handlers('services.security.handlers')) | ||
Manuel Riel
|
r18138 | handlers.append( | ||
Min RK
|
r19070 | (r"/nbextensions/(.*)", FileFindHandler, { | ||
'path': settings['nbextensions_path'], | ||||
'no_cache_paths': ['/'], # don't cache anything in nbextensions | ||||
}), | ||||
MinRK
|
r17524 | ) | ||
Min RK
|
r18758 | # register base handlers last | ||
MinRK
|
r18749 | handlers.extend(load_handlers('base.handlers')) | ||
MinRK
|
r17778 | # set the URL that will be redirected from `/` | ||
handlers.append( | ||||
(r'/?', web.RedirectHandler, { | ||||
Min RK
|
r20212 | 'url' : settings['default_url'], | ||
MinRK
|
r17778 | 'permanent': False, # want 302, not 301 | ||
}) | ||||
) | ||||
MinRK
|
r15238 | # prepend base_url onto the patterns that we match | ||
Andrew Straw
|
r6004 | new_handlers = [] | ||
for handler in handlers: | ||||
MinRK
|
r15238 | pattern = url_path_join(settings['base_url'], handler[0]) | ||
MinRK
|
r10355 | new_handler = tuple([pattern] + list(handler[1:])) | ||
new_handlers.append(new_handler) | ||||
MinRK
|
r13939 | # add 404 on the end, which will catch everything that falls through | ||
new_handlers.append((r'(.*)', Template404)) | ||||
Brian E. Granger
|
r10656 | return new_handlers | ||
Brian E. Granger
|
r4339 | |||
Thomas Kluyver
|
r14177 | class NbserverListApp(BaseIPythonApplication): | ||
description="List currently running notebook servers in this profile." | ||||
flags = dict( | ||||
json=({'NbserverListApp': {'json': True}}, | ||||
"Produce machine-readable JSON output."), | ||||
) | ||||
json = Bool(False, config=True, | ||||
help="If True, each line of output will be a JSON object with the " | ||||
"details from the server info file.") | ||||
def start(self): | ||||
if not self.json: | ||||
print("Currently running servers:") | ||||
Thomas Kluyver
|
r14179 | for serverinfo in list_running_servers(self.profile): | ||
Thomas Kluyver
|
r14177 | if self.json: | ||
print(json.dumps(serverinfo)) | ||||
else: | ||||
Thomas Kluyver
|
r14179 | print(serverinfo['url'], "::", serverinfo['notebook_dir']) | ||
Brian E. Granger
|
r4346 | |||
Brian E. Granger
|
r4344 | #----------------------------------------------------------------------------- | ||
# Aliases and Flags | ||||
#----------------------------------------------------------------------------- | ||||
MinRK
|
r16375 | flags = dict(base_flags) | ||
Thomas Kluyver
|
r5065 | flags['no-browser']=( | ||
MinRK
|
r5128 | {'NotebookApp' : {'open_browser' : False}}, | ||
Thomas Kluyver
|
r5065 | "Don't open the notebook in a browser after startup." | ||
) | ||||
MinRK
|
r16375 | flags['pylab']=( | ||
{'NotebookApp' : {'pylab' : 'warn'}}, | ||||
"DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib." | ||||
) | ||||
MinRK
|
r5547 | flags['no-mathjax']=( | ||
{'NotebookApp' : {'enable_mathjax' : False}}, | ||||
"""Disable MathJax | ||||
MathJax is the javascript library IPython uses to render math/LaTeX. It is | ||||
very large, so you may want to disable it if you have a slow internet | ||||
connection, or for offline use of the notebook. | ||||
When disabled, equations etc. will appear as their untransformed TeX source. | ||||
""" | ||||
) | ||||
Brian E. Granger
|
r4344 | |||
MinRK
|
r17537 | # Add notebook manager flags | ||
flags.update(boolean_flag('script', 'FileContentsManager.save_script', | ||||
'DEPRECATED, IGNORED', | ||||
'DEPRECATED, IGNORED')) | ||||
MinRK
|
r16375 | aliases = dict(base_aliases) | ||
Brian E. Granger
|
r4344 | |||
Brian E. Granger
|
r4519 | aliases.update({ | ||
Brian E. Granger
|
r5104 | 'ip': 'NotebookApp.ip', | ||
'port': 'NotebookApp.port', | ||||
Bradley M. Froehle
|
r7234 | 'port-retries': 'NotebookApp.port_retries', | ||
MinRK
|
r9177 | 'transport': 'KernelManager.transport', | ||
Brian E. Granger
|
r5104 | 'keyfile': 'NotebookApp.keyfile', | ||
'certfile': 'NotebookApp.certfile', | ||||
MinRK
|
r15420 | 'notebook-dir': 'NotebookApp.notebook_dir', | ||
Paul Ivanov
|
r6116 | 'browser': 'NotebookApp.browser', | ||
MinRK
|
r16375 | 'pylab': 'NotebookApp.pylab', | ||
Brian E. Granger
|
r4519 | }) | ||
Brian E. Granger
|
r4344 | |||
#----------------------------------------------------------------------------- | ||||
Brian E. Granger
|
r5104 | # NotebookApp | ||
Brian E. Granger
|
r4344 | #----------------------------------------------------------------------------- | ||
Brian E. Granger
|
r5104 | class NotebookApp(BaseIPythonApplication): | ||
Brian E. Granger
|
r4519 | |||
Brian E. Granger
|
r4344 | name = 'ipython-notebook' | ||
description = """ | ||||
The IPython HTML Notebook. | ||||
This launches a Tornado based HTML Notebook Server that serves up an | ||||
HTML5/Javascript Notebook client. | ||||
""" | ||||
Brian E. Granger
|
r4519 | examples = _examples | ||
MinRK
|
r16375 | aliases = aliases | ||
flags = flags | ||||
Brian E. Granger
|
r4344 | |||
MinRK
|
r16375 | classes = [ | ||
KernelManager, ProfileDir, Session, MappingKernelManager, | ||||
MinRK
|
r17524 | ContentsManager, FileContentsManager, NotebookNotary, | ||
Min RK
|
r20316 | KernelSpecManager, | ||
MinRK
|
r16375 | ] | ||
Brian E. Granger
|
r4344 | flags = Dict(flags) | ||
aliases = Dict(aliases) | ||||
Thomas Kluyver
|
r14177 | |||
subcommands = dict( | ||||
list=(NbserverListApp, NbserverListApp.description.splitlines()[0]), | ||||
) | ||||
Brian E. Granger
|
r4344 | |||
Min RK
|
r18933 | ipython_kernel_argv = List(Unicode) | ||
MinRK
|
r16358 | |||
_log_formatter_cls = LogFormatter | ||||
Brian E. Granger
|
r4344 | |||
MinRK
|
r10355 | def _log_level_default(self): | ||
return logging.INFO | ||||
Brian E. Granger
|
r4345 | |||
MinRK
|
r16358 | def _log_datefmt_default(self): | ||
"""Exclude date from default date format""" | ||||
return "%H:%M:%S" | ||||
MinRK
|
r10358 | def _log_format_default(self): | ||
"""override default log format to include time""" | ||||
MinRK
|
r16358 | return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s" | ||
MinRK
|
r10358 | |||
Paul Ivanov
|
r5847 | # create requested profiles by default, if they don't exist: | ||
auto_create = Bool(True) | ||||
Puneeth Chaganti
|
r6724 | # file to be opened in the notebook server | ||
MinRK
|
r15461 | file_to_run = Unicode('', config=True) | ||
Puneeth Chaganti
|
r6724 | |||
MinRK
|
r17106 | # Network related information | ||
MinRK
|
r17116 | allow_origin = Unicode('', config=True, | ||
MinRK
|
r17106 | help="""Set the Access-Control-Allow-Origin header | ||
Use '*' to allow any origin to access your server. | ||||
MinRK
|
r17116 | Takes precedence over allow_origin_pat. | ||
MinRK
|
r17106 | """ | ||
) | ||||
MinRK
|
r17116 | allow_origin_pat = Unicode('', config=True, | ||
MinRK
|
r17106 | help="""Use a regular expression for the Access-Control-Allow-Origin header | ||
Requests from an origin matching the expression will get replies with: | ||||
Access-Control-Allow-Origin: origin | ||||
where `origin` is the origin of the request. | ||||
MinRK
|
r17116 | Ignored if allow_origin is set. | ||
MinRK
|
r17106 | """ | ||
) | ||||
MinRK
|
r17116 | allow_credentials = Bool(False, config=True, | ||
MinRK
|
r17106 | help="Set the Access-Control-Allow-Credentials: true header" | ||
) | ||||
MinRK
|
r17778 | default_url = Unicode('/tree', config=True, | ||
help="The default URL to redirect to from `/`" | ||||
) | ||||
MinRK
|
r16334 | ip = Unicode('localhost', config=True, | ||
Brian E. Granger
|
r4344 | help="The IP address the notebook server will listen on." | ||
) | ||||
Min RK
|
r20593 | def _ip_default(self): | ||
"""Return localhost if available, 127.0.0.1 otherwise. | ||||
On some (horribly broken) systems, localhost cannot be bound. | ||||
""" | ||||
s = socket.socket() | ||||
try: | ||||
s.bind(('localhost', 0)) | ||||
Min RK
|
r20594 | except socket.error as e: | ||
self.log.warn("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s", e) | ||||
Min RK
|
r20593 | return '127.0.0.1' | ||
else: | ||||
s.close() | ||||
return 'localhost' | ||||
Brian E. Granger
|
r4344 | |||
Brian E. Granger
|
r4519 | def _ip_changed(self, name, old, new): | ||
if new == u'*': self.ip = u'' | ||||
MinRK
|
r5344 | port = Integer(8888, config=True, | ||
Brian E. Granger
|
r4344 | help="The port the notebook server will listen on." | ||
) | ||||
Bradley M. Froehle
|
r7234 | port_retries = Integer(50, config=True, | ||
help="The number of additional ports to try if the specified port is not available." | ||||
) | ||||
Brian E. Granger
|
r4344 | |||
Brian E. Granger
|
r4519 | certfile = Unicode(u'', config=True, | ||
help="""The full path to an SSL/TLS certificate file.""" | ||||
) | ||||
keyfile = Unicode(u'', config=True, | ||||
help="""The full path to a private key file for usage with SSL/TLS.""" | ||||
) | ||||
MinRK
|
r10784 | |||
MinRK
|
r17141 | cookie_secret_file = Unicode(config=True, | ||
help="""The file where the cookie secret is stored.""" | ||||
) | ||||
def _cookie_secret_file_default(self): | ||||
if self.profile_dir is None: | ||||
return '' | ||||
return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret') | ||||
MinRK
|
r10784 | cookie_secret = Bytes(b'', config=True, | ||
help="""The random bytes used to secure cookies. | ||||
By default this is a new random number every time you start the Notebook. | ||||
Set it to a value in a config file to enable logins to persist across server sessions. | ||||
MinRK
|
r10863 | |||
Note: Cookie secrets should be kept private, do not share config files with | ||||
cookie_secret stored in plaintext (you can read the value from a file). | ||||
MinRK
|
r10784 | """ | ||
) | ||||
def _cookie_secret_default(self): | ||||
MinRK
|
r17141 | if os.path.exists(self.cookie_secret_file): | ||
with io.open(self.cookie_secret_file, 'rb') as f: | ||||
return f.read() | ||||
else: | ||||
secret = base64.encodestring(os.urandom(1024)) | ||||
self._write_cookie_secret_file(secret) | ||||
return secret | ||||
def _write_cookie_secret_file(self, secret): | ||||
"""write my secret to my secret_file""" | ||||
self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file) | ||||
with io.open(self.cookie_secret_file, 'wb') as f: | ||||
f.write(secret) | ||||
try: | ||||
os.chmod(self.cookie_secret_file, 0o600) | ||||
except OSError: | ||||
self.log.warn( | ||||
"Could not set permissions on %s", | ||||
self.cookie_secret_file | ||||
) | ||||
Brian E. Granger
|
r4344 | |||
MinRK
|
r4705 | password = Unicode(u'', config=True, | ||
Stefan van der Walt
|
r5321 | help="""Hashed password to use for web authentication. | ||
Fernando Perez
|
r5337 | To generate, type in a python/IPython shell: | ||
Stefan van der Walt
|
r5321 | |||
Fernando Perez
|
r5337 | from IPython.lib import passwd; passwd() | ||
Stefan van der Walt
|
r5321 | |||
The string should be of the form type:salt:hashed-password. | ||||
""" | ||||
Satrajit Ghosh
|
r4690 | ) | ||
Paul Ivanov
|
r6116 | |||
Thomas Kluyver
|
r5065 | open_browser = Bool(True, config=True, | ||
Paul Ivanov
|
r6115 | help="""Whether to open in a browser after starting. | ||
The specific browser used is platform dependent and | ||||
determined by the python standard library `webbrowser` | ||||
Paul Ivanov
|
r6116 | module, unless it is overridden using the --browser | ||
(NotebookApp.browser) configuration option. | ||||
Paul Ivanov
|
r6115 | """) | ||
Paul Ivanov
|
r6116 | |||
browser = Unicode(u'', config=True, | ||||
Paul Ivanov
|
r6117 | help="""Specify what command to use to invoke a web | ||
browser when opening the notebook. If not specified, the | ||||
default browser will be determined by the `webbrowser` | ||||
standard library module, which allows setting of the | ||||
BROWSER environment variable to override it. | ||||
Paul Ivanov
|
r6116 | """) | ||
MinRK
|
r5191 | |||
Timo Paulssen
|
r5665 | webapp_settings = Dict(config=True, | ||
MinRK
|
r17888 | help="DEPRECATED, use tornado_settings" | ||
) | ||||
def _webapp_settings_changed(self, name, old, new): | ||||
self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n") | ||||
self.tornado_settings = new | ||||
tornado_settings = Dict(config=True, | ||||
Timo Paulssen
|
r5665 | help="Supply overrides for the tornado.web.Application that the " | ||
"IPython notebook uses.") | ||||
Min RK
|
r20529 | |||
ssl_options = Dict(config=True, | ||||
help="""Supply SSL options for the tornado HTTPServer. | ||||
See the tornado docs for details.""") | ||||
Matthias BUSSONNIER
|
r15449 | jinja_environment_options = Dict(config=True, | ||
help="Supply extra arguments that will be passed to Jinja environment.") | ||||
Timo Paulssen
|
r5665 | |||
MinRK
|
r5547 | enable_mathjax = Bool(True, config=True, | ||
help="""Whether to enable MathJax for typesetting math/TeX | ||||
MathJax is the javascript library IPython uses to render math/LaTeX. It is | ||||
very large, so you may want to disable it if you have a slow internet | ||||
connection, or for offline use of the notebook. | ||||
When disabled, equations etc. will appear as their untransformed TeX source. | ||||
""" | ||||
) | ||||
MinRK
|
r5557 | def _enable_mathjax_changed(self, name, old, new): | ||
"""set mathjax url to empty if mathjax is disabled""" | ||||
if not new: | ||||
self.mathjax_url = u'' | ||||
Andrew Straw
|
r6006 | |||
MinRK
|
r15238 | base_url = Unicode('/', config=True, | ||
Bussonnier Matthias
|
r8938 | help='''The base URL for the notebook server. | ||
Leading and trailing slashes can be omitted, | ||||
and will automatically be added. | ||||
''') | ||||
MinRK
|
r15238 | def _base_url_changed(self, name, old, new): | ||
Bussonnier Matthias
|
r8938 | if not new.startswith('/'): | ||
MinRK
|
r15238 | self.base_url = '/'+new | ||
Bussonnier Matthias
|
r8938 | elif not new.endswith('/'): | ||
MinRK
|
r15238 | self.base_url = new+'/' | ||
base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""") | ||||
def _base_project_url_changed(self, name, old, new): | ||||
self.log.warn("base_project_url is deprecated, use base_url") | ||||
self.base_url = new | ||||
Bussonnier Matthias
|
r8938 | |||
MinRK
|
r7923 | extra_static_paths = List(Unicode, config=True, | ||
help="""Extra paths to search for serving static files. | ||||
This allows adding javascript/css to be available from the notebook server machine, | ||||
or overriding individual files in the IPython""" | ||||
) | ||||
def _extra_static_paths_default(self): | ||||
return [os.path.join(self.profile_dir.location, 'static')] | ||||
@property | ||||
def static_file_path(self): | ||||
"""return extra paths + the default location""" | ||||
Thomas Kluyver
|
r9681 | return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH] | ||
Scott Sanderson
|
r18693 | |||
extra_template_paths = List(Unicode, config=True, | ||||
help="""Extra paths to search for serving jinja templates. | ||||
Can be used to override templates from IPython.html.templates.""" | ||||
) | ||||
def _extra_template_paths_default(self): | ||||
return [] | ||||
@property | ||||
def template_file_path(self): | ||||
Scott Sanderson
|
r18695 | """return extra paths + the default locations""" | ||
return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST | ||||
Scott Sanderson
|
r18693 | |||
Min RK
|
r19854 | extra_nbextensions_path = List(Unicode, config=True, | ||
help="""extra paths to look for Javascript notebook extensions""" | ||||
MinRK
|
r12800 | ) | ||
Min RK
|
r19854 | |||
@property | ||||
def nbextensions_path(self): | ||||
"""The path to look for Javascript notebook extensions""" | ||||
return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS | ||||
Andrew Straw
|
r6004 | |||
MinRK
|
r17303 | websocket_url = Unicode("", config=True, | ||
help="""The base URL for websockets, | ||||
if it differs from the HTTP server (hint: it almost certainly doesn't). | ||||
Should be in the form of an HTTP origin: ws[s]://hostname[:port] | ||||
""" | ||||
) | ||||
MinRK
|
r5557 | mathjax_url = Unicode("", config=True, | ||
help="""The url for MathJax.js.""" | ||||
) | ||||
def _mathjax_url_default(self): | ||||
if not self.enable_mathjax: | ||||
return u'' | ||||
MinRK
|
r17916 | static_url_prefix = self.tornado_settings.get("static_url_prefix", | ||
MinRK
|
r15238 | url_path_join(self.base_url, "static") | ||
MinRK
|
r11153 | ) | ||
MinRK
|
r12810 | |||
MinRK
|
r12811 | # try local mathjax, either in nbextensions/mathjax or static/mathjax | ||
MinRK
|
r12810 | for (url_prefix, search_path) in [ | ||
MinRK
|
r15238 | (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path), | ||
MinRK
|
r12810 | (static_url_prefix, self.static_file_path), | ||
]: | ||||
self.log.debug("searching for local mathjax in %s", search_path) | ||||
try: | ||||
mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path) | ||||
except IOError: | ||||
continue | ||||
MinRK
|
r6155 | else: | ||
MinRK
|
r12810 | url = url_path_join(url_prefix, u"mathjax/MathJax.js") | ||
self.log.info("Serving local MathJax from %s at %s", mathjax, url) | ||||
return url | ||||
# no local mathjax, serve from CDN | ||||
MinRK
|
r17539 | url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js" | ||
MinRK
|
r12810 | self.log.info("Using MathJax from CDN: %s", url) | ||
return url | ||||
MinRK
|
r5557 | |||
def _mathjax_url_changed(self, name, old, new): | ||||
if new and not self.enable_mathjax: | ||||
# enable_mathjax=False overrides mathjax_url | ||||
self.mathjax_url = u'' | ||||
else: | ||||
self.log.info("Using MathJax: %s", new) | ||||
Satrajit Ghosh
|
r4690 | |||
Scott Sanderson
|
r19636 | contents_manager_class = Type( | ||
default_value=FileContentsManager, | ||||
klass=ContentsManager, | ||||
Brian Granger
|
r8180 | config=True, | ||
MinRK
|
r16423 | help='The notebook manager class to use.' | ||
) | ||||
Scott Sanderson
|
r19636 | kernel_manager_class = Type( | ||
default_value=MappingKernelManager, | ||||
MinRK
|
r16423 | config=True, | ||
help='The kernel manager class to use.' | ||||
) | ||||
Scott Sanderson
|
r19636 | session_manager_class = Type( | ||
default_value=SessionManager, | ||||
MinRK
|
r16423 | config=True, | ||
help='The session manager class to use.' | ||||
) | ||||
Scott Sanderson
|
r19636 | cluster_manager_class = Type( | ||
default_value=ClusterManager, | ||||
MinRK
|
r16423 | config=True, | ||
help='The cluster manager class to use.' | ||||
) | ||||
Brian Granger
|
r8180 | |||
Scott Sanderson
|
r19636 | config_manager_class = Type( | ||
default_value=ConfigManager, | ||||
Thomas Kluyver
|
r19083 | config = True, | ||
help='The config manager class to use' | ||||
) | ||||
Thomas Kluyver
|
r16684 | kernel_spec_manager = Instance(KernelSpecManager) | ||
Scott Sanderson
|
r19636 | kernel_spec_manager_class = Type( | ||
default_value=KernelSpecManager, | ||||
config=True, | ||||
help=""" | ||||
The kernel spec manager class to use. Should be a subclass | ||||
of `IPython.kernel.kernelspec.KernelSpecManager`. | ||||
Bussonnier Matthias
|
r19071 | |||
Scott Sanderson
|
r19636 | The Api of KernelSpecManager is provisional and might change | ||
without warning between this version of IPython and the next stable one. | ||||
""" | ||||
) | ||||
Bussonnier Matthias
|
r19071 | |||
Scott Sanderson
|
r19669 | login_handler_class = Type( | ||
Scott Sanderson
|
r19637 | default_value=LoginHandler, | ||
Scott Sanderson
|
r19669 | klass=web.RequestHandler, | ||
Phil Elson
|
r19322 | config=True, | ||
Scott Sanderson
|
r19637 | help='The login handler class to use.', | ||
) | ||||
Phil Elson
|
r19322 | |||
Scott Sanderson
|
r19669 | logout_handler_class = Type( | ||
Scott Sanderson
|
r19637 | default_value=LogoutHandler, | ||
Scott Sanderson
|
r19669 | klass=web.RequestHandler, | ||
Min RK
|
r19326 | config=True, | ||
Scott Sanderson
|
r19637 | help='The logout handler class to use.', | ||
) | ||||
Min RK
|
r19326 | |||
Alberto Valverde
|
r10163 | trust_xheaders = Bool(False, config=True, | ||
help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers" | ||||
MinRK
|
r14108 | "sent by the upstream reverse proxy. Necessary if the proxy handles SSL") | ||
Alberto Valverde
|
r10163 | ) | ||
Bussonnier Matthias
|
r19071 | |||
Thomas Kluyver
|
r14063 | info_file = Unicode() | ||
def _info_file_default(self): | ||||
info_file = "nbserver-%s.json"%os.getpid() | ||||
return os.path.join(self.profile_dir.security_dir, info_file) | ||||
MinRK
|
r15420 | |||
MinRK
|
r16375 | pylab = Unicode('disabled', config=True, | ||
help=""" | ||||
DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib. | ||||
""" | ||||
) | ||||
def _pylab_changed(self, name, old, new): | ||||
MinRK
|
r16380 | """when --pylab is specified, display a warning and exit""" | ||
MinRK
|
r16375 | if new != 'warn': | ||
backend = ' %s' % new | ||||
else: | ||||
backend = '' | ||||
MinRK
|
r16380 | self.log.error("Support for specifying --pylab on the command line has been removed.") | ||
self.log.error( | ||||
MinRK
|
r16375 | "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend) | ||
) | ||||
MinRK
|
r16380 | self.exit(1) | ||
MinRK
|
r15420 | |||
Thomas Kluyver
|
r18970 | notebook_dir = Unicode(config=True, | ||
help="The directory to use for notebooks and kernels." | ||||
) | ||||
def _notebook_dir_default(self): | ||||
if self.file_to_run: | ||||
return os.path.dirname(os.path.abspath(self.file_to_run)) | ||||
else: | ||||
return py3compat.getcwd() | ||||
MinRK
|
r15420 | def _notebook_dir_changed(self, name, old, new): | ||
"""Do a bit of validation of the notebook dir.""" | ||||
if not os.path.isabs(new): | ||||
# If we receive a non-absolute path, make it absolute. | ||||
self.notebook_dir = os.path.abspath(new) | ||||
return | ||||
MinRK
|
r15423 | if not os.path.isdir(new): | ||
raise TraitError("No such notebook dir: %r" % new) | ||||
MinRK
|
r15420 | |||
MinRK
|
r15422 | # setting App.notebook_dir implies setting notebook and kernel dirs as well | ||
MinRK
|
r17524 | self.config.FileContentsManager.root_dir = new | ||
MinRK
|
r15422 | self.config.MappingKernelManager.root_dir = new | ||
Thomas Kluyver
|
r14063 | |||
Thomas Kluyver
|
r19457 | server_extensions = List(Unicode(), config=True, | ||
Thomas Kluyver
|
r19458 | help=("Python modules to load as notebook server extensions. " | ||
"This is an experimental API, and may change in future releases.") | ||||
Thomas Kluyver
|
r19456 | ) | ||
Scott Sanderson
|
r20643 | reraise_server_extension_failures = Bool( | ||
False, | ||||
config=True, | ||||
help="Reraise exceptions encountered loading server extensions?", | ||||
) | ||||
Brian E. Granger
|
r4344 | def parse_command_line(self, argv=None): | ||
Brian E. Granger
|
r5104 | super(NotebookApp, self).parse_command_line(argv) | ||
MinRK
|
r12284 | |||
Puneeth Chaganti
|
r6724 | if self.extra_args: | ||
MinRK
|
r13158 | arg0 = self.extra_args[0] | ||
f = os.path.abspath(arg0) | ||||
self.argv.remove(arg0) | ||||
MinRK
|
r13118 | if not os.path.exists(f): | ||
self.log.critical("No such file or directory: %s", f) | ||||
self.exit(1) | ||||
MinRK
|
r15461 | |||
# Use config here, to ensure that it takes higher priority than | ||||
# anything that comes from the profile. | ||||
Thomas Kluyver
|
r16041 | c = Config() | ||
MinRK
|
r7556 | if os.path.isdir(f): | ||
Thomas Kluyver
|
r16041 | c.NotebookApp.notebook_dir = f | ||
Zachary Sailer
|
r13019 | elif os.path.isfile(f): | ||
Thomas Kluyver
|
r16041 | c.NotebookApp.file_to_run = f | ||
self.update_config(c) | ||||
Puneeth Chaganti
|
r6724 | |||
MinRK
|
r12284 | def init_kernel_argv(self): | ||
Min RK
|
r18933 | """add the profile-dir to arguments to be passed to IPython kernels""" | ||
# FIXME: remove special treatment of IPython kernels | ||||
MinRK
|
r12284 | # Kernel should get *absolute* path to profile directory | ||
Min RK
|
r18933 | self.ipython_kernel_argv = ["--profile-dir", self.profile_dir.location] | ||
MinRK
|
r12284 | |||
Brian E. Granger
|
r4494 | def init_configurables(self): | ||
Scott Sanderson
|
r19636 | self.kernel_spec_manager = self.kernel_spec_manager_class( | ||
Min RK
|
r20316 | parent=self, | ||
Scott Sanderson
|
r19636 | ipython_dir=self.ipython_dir, | ||
) | ||||
self.kernel_manager = self.kernel_manager_class( | ||||
parent=self, | ||||
log=self.log, | ||||
ipython_kernel_argv=self.ipython_kernel_argv, | ||||
connection_dir=self.profile_dir.security_dir, | ||||
) | ||||
self.contents_manager = self.contents_manager_class( | ||||
parent=self, | ||||
log=self.log, | ||||
) | ||||
self.session_manager = self.session_manager_class( | ||||
parent=self, | ||||
log=self.log, | ||||
kernel_manager=self.kernel_manager, | ||||
contents_manager=self.contents_manager, | ||||
) | ||||
self.cluster_manager = self.cluster_manager_class( | ||||
parent=self, | ||||
log=self.log, | ||||
Brian E. Granger
|
r4494 | ) | ||
Scott Sanderson
|
r19637 | |||
Scott Sanderson
|
r19636 | self.config_manager = self.config_manager_class( | ||
parent=self, | ||||
log=self.log, | ||||
profile_dir=self.profile_dir.location, | ||||
) | ||||
Thomas Kluyver
|
r19083 | |||
Brian E. Granger
|
r4345 | def init_logging(self): | ||
# This prevents double log messages because tornado use a root logger that | ||||
# self.log is a child of. The logging module dipatches log messages to a log | ||||
# and all of its ancenstors until propagate is set to False. | ||||
self.log.propagate = False | ||||
MinRK
|
r10358 | |||
MinRK
|
r18117 | for log in app_log, access_log, gen_log: | ||
# consistent log output name (NotebookApp instead of tornado.access, etc.) | ||||
log.name = self.log.name | ||||
MinRK
|
r10358 | # hook up tornado 3's loggers to our app handlers | ||
MinRK
|
r16358 | logger = logging.getLogger('tornado') | ||
logger.propagate = True | ||||
logger.parent = self.log | ||||
logger.setLevel(self.log.level) | ||||
MinRK
|
r5799 | |||
def init_webapp(self): | ||||
"""initialize tornado webapp and httpserver""" | ||||
MinRK
|
r17916 | self.tornado_settings['allow_origin'] = self.allow_origin | ||
MinRK
|
r17184 | if self.allow_origin_pat: | ||
MinRK
|
r17916 | self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat) | ||
self.tornado_settings['allow_credentials'] = self.allow_credentials | ||||
Min RK
|
r20212 | # ensure default_url starts with base_url | ||
if not self.default_url.startswith(self.base_url): | ||||
self.default_url = url_path_join(self.base_url, self.default_url) | ||||
MinRK
|
r17106 | |||
Brian E. Granger
|
r4347 | self.web_app = NotebookWebApplication( | ||
MinRK
|
r17524 | self, self.kernel_manager, self.contents_manager, | ||
Thomas Kluyver
|
r16684 | self.cluster_manager, self.session_manager, self.kernel_spec_manager, | ||
Thomas Kluyver
|
r19083 | self.config_manager, | ||
MinRK
|
r17916 | self.log, self.base_url, self.default_url, self.tornado_settings, | ||
Matthias BUSSONNIER
|
r15449 | self.jinja_environment_options | ||
Brian E. Granger
|
r4347 | ) | ||
Min RK
|
r20529 | ssl_options = self.ssl_options | ||
Brian E. Granger
|
r4519 | if self.certfile: | ||
Min RK
|
r20529 | ssl_options['certfile'] = self.certfile | ||
if self.keyfile: | ||||
ssl_options['keyfile'] = self.keyfile | ||||
if not ssl_options: | ||||
# None indicates no SSL config | ||||
Brian E. Granger
|
r4519 | ssl_options = None | ||
Min RK
|
r19333 | self.login_handler_class.validate_security(self, ssl_options=ssl_options) | ||
Alberto Valverde
|
r10163 | self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options, | ||
xheaders=self.trust_xheaders) | ||||
Phil Elson
|
r19322 | |||
Bradley M. Froehle
|
r7234 | success = None | ||
for port in random_ports(self.port, self.port_retries+1): | ||||
Brian E. Granger
|
r4548 | try: | ||
self.http_server.listen(port, self.ip) | ||||
Matthias BUSSONNIER
|
r7787 | except socket.error as e: | ||
MinRK
|
r12845 | if e.errno == errno.EADDRINUSE: | ||
self.log.info('The port %i is already in use, trying another random port.' % port) | ||||
continue | ||||
elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)): | ||||
self.log.warn("Permission to listen on port %i denied" % port) | ||||
continue | ||||
else: | ||||
Brian E. Granger
|
r4548 | raise | ||
else: | ||||
self.port = port | ||||
Bradley M. Froehle
|
r7234 | success = True | ||
Brian E. Granger
|
r4548 | break | ||
Bradley M. Froehle
|
r7234 | if not success: | ||
self.log.critical('ERROR: the notebook server could not be started because ' | ||||
'no available port could be found.') | ||||
Bradley M. Froehle
|
r7240 | self.exit(1) | ||
MinRK
|
r5799 | |||
Thomas Kluyver
|
r14063 | @property | ||
def display_url(self): | ||||
ip = self.ip if self.ip else '[all ip addresses on your system]' | ||||
return self._url(ip) | ||||
@property | ||||
def connection_url(self): | ||||
MinRK
|
r16334 | ip = self.ip if self.ip else 'localhost' | ||
Thomas Kluyver
|
r14063 | return self._url(ip) | ||
def _url(self, ip): | ||||
proto = 'https' if self.certfile else 'http' | ||||
MinRK
|
r15238 | return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url) | ||
Thomas Kluyver
|
r14063 | |||
Thomas Kluyver
|
r18482 | def init_terminals(self): | ||
try: | ||||
from .terminal import initialize | ||||
initialize(self.web_app) | ||||
Thomas Kluyver
|
r18557 | self.web_app.settings['terminals_available'] = True | ||
Thomas Kluyver
|
r18482 | except ImportError as e: | ||
Min RK
|
r19981 | log = self.log.debug if sys.platform == 'win32' else self.log.warn | ||
log("Terminals not available (error was %s)", e) | ||||
Thomas Kluyver
|
r18482 | |||
MinRK
|
r6498 | def init_signal(self): | ||
MinRK
|
r10200 | if not sys.platform.startswith('win'): | ||
MinRK
|
r6509 | signal.signal(signal.SIGINT, self._handle_sigint) | ||
MinRK
|
r6508 | signal.signal(signal.SIGTERM, self._signal_stop) | ||
Paul Ivanov
|
r10031 | if hasattr(signal, 'SIGUSR1'): | ||
# Windows doesn't support SIGUSR1 | ||||
signal.signal(signal.SIGUSR1, self._signal_info) | ||||
Paul Ivanov
|
r9913 | if hasattr(signal, 'SIGINFO'): | ||
# only on BSD-based systems | ||||
signal.signal(signal.SIGINFO, self._signal_info) | ||||
MinRK
|
r6498 | |||
MinRK
|
r6508 | def _handle_sigint(self, sig, frame): | ||
"""SIGINT handler spawns confirmation dialog""" | ||||
# register more forceful signal handler for ^C^C case | ||||
signal.signal(signal.SIGINT, self._signal_stop) | ||||
# request confirmation dialog in bg thread, to avoid | ||||
# blocking the App | ||||
thread = threading.Thread(target=self._confirm_exit) | ||||
thread.daemon = True | ||||
thread.start() | ||||
def _restore_sigint_handler(self): | ||||
"""callback for restoring original SIGINT handler""" | ||||
signal.signal(signal.SIGINT, self._handle_sigint) | ||||
def _confirm_exit(self): | ||||
"""confirm shutdown on ^C | ||||
A second ^C, or answering 'y' within 5s will cause shutdown, | ||||
otherwise original SIGINT handler will be restored. | ||||
MinRK
|
r7689 | |||
This doesn't work on Windows. | ||||
MinRK
|
r6508 | """ | ||
Paul Ivanov
|
r9912 | info = self.log.info | ||
info('interrupted') | ||||
Thomas Kluyver
|
r13348 | print(self.notebook_info()) | ||
Paul Ivanov
|
r10019 | sys.stdout.write("Shutdown this notebook server (y/[n])? ") | ||
MinRK
|
r6508 | sys.stdout.flush() | ||
r,w,x = select.select([sys.stdin], [], [], 5) | ||||
if r: | ||||
line = sys.stdin.readline() | ||||
Renaud Richardet
|
r16211 | if line.lower().startswith('y') and 'n' not in line.lower(): | ||
MinRK
|
r6508 | self.log.critical("Shutdown confirmed") | ||
MinRK
|
r19347 | ioloop.IOLoop.current().stop() | ||
MinRK
|
r6508 | return | ||
else: | ||||
Thomas Kluyver
|
r13348 | print("No answer for 5s:", end=' ') | ||
print("resuming operation...") | ||||
MinRK
|
r6508 | # no answer, or answer is no: | ||
# set it back to original SIGINT handler | ||||
# use IOLoop.add_callback because signal.signal must be called | ||||
# from main thread | ||||
MinRK
|
r19347 | ioloop.IOLoop.current().add_callback(self._restore_sigint_handler) | ||
MinRK
|
r6508 | |||
def _signal_stop(self, sig, frame): | ||||
MinRK
|
r6498 | self.log.critical("received signal %s, stopping", sig) | ||
MinRK
|
r19347 | ioloop.IOLoop.current().stop() | ||
Paul Ivanov
|
r9913 | |||
def _signal_info(self, sig, frame): | ||||
Thomas Kluyver
|
r13348 | print(self.notebook_info()) | ||
MinRK
|
r6498 | |||
MinRK
|
r10557 | def init_components(self): | ||
"""Check the components submodule, and warn if it's unclean""" | ||||
status = submodule.check_submodule_status() | ||||
if status == 'missing': | ||||
self.log.warn("components submodule missing, running `git submodule update`") | ||||
submodule.update_submodules(submodule.ipython_parent()) | ||||
elif status == 'unclean': | ||||
self.log.warn("components submodule unclean, you may see 404s on static/components") | ||||
self.log.warn("run `setup.py submodule` or `git submodule update` to update") | ||||
Thomas Kluyver
|
r19456 | |||
Thomas Kluyver
|
r19457 | def init_server_extensions(self): | ||
Thomas Kluyver
|
r19456 | """Load any extensions specified by config. | ||
Import the module, then call the load_jupyter_server_extension function, | ||||
if one exists. | ||||
Thomas Kluyver
|
r19458 | |||
The extension API is experimental, and may change in future releases. | ||||
Thomas Kluyver
|
r19456 | """ | ||
Thomas Kluyver
|
r19457 | for modulename in self.server_extensions: | ||
Thomas Kluyver
|
r19456 | try: | ||
mod = importlib.import_module(modulename) | ||||
func = getattr(mod, 'load_jupyter_server_extension', None) | ||||
if func is not None: | ||||
func(self) | ||||
except Exception: | ||||
Scott Sanderson
|
r20643 | if self.reraise_server_extension_failures: | ||
raise | ||||
Thomas Kluyver
|
r19456 | self.log.warn("Error loading server extension %s", modulename, | ||
exc_info=True) | ||||
MinRK
|
r10557 | |||
MinRK
|
r5799 | @catch_config_error | ||
def initialize(self, argv=None): | ||||
super(NotebookApp, self).initialize(argv) | ||||
MinRK
|
r13307 | self.init_logging() | ||
MinRK
|
r12284 | self.init_kernel_argv() | ||
MinRK
|
r5799 | self.init_configurables() | ||
MinRK
|
r10557 | self.init_components() | ||
MinRK
|
r5799 | self.init_webapp() | ||
Thomas Kluyver
|
r18482 | self.init_terminals() | ||
MinRK
|
r6498 | self.init_signal() | ||
Thomas Kluyver
|
r19457 | self.init_server_extensions() | ||
MinRK
|
r5799 | |||
def cleanup_kernels(self): | ||||
Brian E. Granger
|
r9112 | """Shutdown all kernels. | ||
MinRK
|
r5799 | |||
The kernels will shutdown themselves when this process no longer exists, | ||||
but explicit shutdown allows the KernelManagers to cleanup the connection files. | ||||
""" | ||||
self.log.info('Shutting down kernels') | ||||
Brian E. Granger
|
r9112 | self.kernel_manager.shutdown_all() | ||
Brian E. Granger
|
r4344 | |||
Paul Ivanov
|
r10019 | def notebook_info(self): | ||
"Return the current working directory and the server url information" | ||||
MinRK
|
r17524 | info = self.contents_manager.info_string() + "\n" | ||
Paul Ivanov
|
r13261 | info += "%d active kernels \n" % len(self.kernel_manager._kernels) | ||
Thomas Kluyver
|
r14063 | return info + "The IPython Notebook is running at: %s" % self.display_url | ||
def server_info(self): | ||||
"""Return a JSONable dict of information about this server.""" | ||||
return {'url': self.connection_url, | ||||
'hostname': self.ip if self.ip else 'localhost', | ||||
'port': self.port, | ||||
'secure': bool(self.certfile), | ||||
MinRK
|
r15238 | 'base_url': self.base_url, | ||
MinRK
|
r15420 | 'notebook_dir': os.path.abspath(self.notebook_dir), | ||
Thomas Kluyver
|
r17181 | 'pid': os.getpid() | ||
Thomas Kluyver
|
r14063 | } | ||
def write_server_info_file(self): | ||||
"""Write the result of server_info() to the JSON file info_file.""" | ||||
Thomas Kluyver
|
r14079 | with open(self.info_file, 'w') as f: | ||
json.dump(self.server_info(), f, indent=2) | ||||
Thomas Kluyver
|
r14063 | |||
def remove_server_info_file(self): | ||||
"""Remove the nbserver-<pid>.json file created for this server. | ||||
Ignores the error raised when the file has already been removed. | ||||
""" | ||||
try: | ||||
os.unlink(self.info_file) | ||||
except OSError as e: | ||||
if e.errno != errno.ENOENT: | ||||
raise | ||||
Paul Ivanov
|
r9912 | |||
Brian E. Granger
|
r4344 | def start(self): | ||
MinRK
|
r10358 | """ Start the IPython Notebook server app, after initialization | ||
Paul Ivanov
|
r9910 | |||
This method takes no arguments so all configuration and initialization | ||||
must be done prior to calling this method.""" | ||||
Thomas Kluyver
|
r14177 | if self.subapp is not None: | ||
return self.subapp.start() | ||||
Fernando Perez
|
r5786 | info = self.log.info | ||
Paul Ivanov
|
r10019 | for line in self.notebook_info().split("\n"): | ||
info(line) | ||||
MinRK
|
r11228 | info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).") | ||
Fernando Perez
|
r5786 | |||
Thomas Kluyver
|
r14063 | self.write_server_info_file() | ||
MinRK
|
r7556 | if self.open_browser or self.file_to_run: | ||
Bradley M. Froehle
|
r7658 | try: | ||
browser = webbrowser.get(self.browser or None) | ||||
except webbrowser.Error as e: | ||||
self.log.warn('No web browser found: %s.' % e) | ||||
browser = None | ||||
MinRK
|
r13118 | |||
MinRK
|
r15460 | if self.file_to_run: | ||
Thomas Kluyver
|
r18970 | if not os.path.exists(self.file_to_run): | ||
self.log.critical("%s does not exist" % self.file_to_run) | ||||
MinRK
|
r15460 | self.exit(1) | ||
Thomas Kluyver
|
r18970 | |||
relpath = os.path.relpath(self.file_to_run, self.notebook_dir) | ||||
uri = url_path_join('notebooks', *relpath.split(os.sep)) | ||||
Puneeth Chaganti
|
r6724 | else: | ||
MinRK
|
r15460 | uri = 'tree' | ||
Bradley M. Froehle
|
r7658 | if browser: | ||
MinRK
|
r15460 | b = lambda : browser.open(url_path_join(self.connection_url, uri), | ||
Thomas Kluyver
|
r14063 | new=2) | ||
Bradley M. Froehle
|
r7658 | threading.Thread(target=b).start() | ||
MinRK
|
r19347 | |||
self.io_loop = ioloop.IOLoop.current() | ||||
Min RK
|
r20401 | if sys.platform.startswith('win'): | ||
# add no-op to wake every 5s | ||||
# to handle signals that may be ignored by the inner loop | ||||
pc = ioloop.PeriodicCallback(lambda : None, 5000) | ||||
pc.start() | ||||
MinRK
|
r5799 | try: | ||
MinRK
|
r19347 | self.io_loop.start() | ||
MinRK
|
r5799 | except KeyboardInterrupt: | ||
info("Interrupted...") | ||||
finally: | ||||
self.cleanup_kernels() | ||||
Thomas Kluyver
|
r14063 | self.remove_server_info_file() | ||
MinRK
|
r19347 | |||
def stop(self): | ||||
def _stop(): | ||||
self.http_server.stop() | ||||
self.io_loop.stop() | ||||
self.io_loop.add_callback(_stop) | ||||
Thomas Kluyver
|
r14063 | |||
Thomas Kluyver
|
r14177 | def list_running_servers(profile='default'): | ||
Thomas Kluyver
|
r14063 | """Iterate over the server info files of running notebook servers. | ||
MinRK
|
r5799 | |||
Thomas Kluyver
|
r14063 | Given a profile name, find nbserver-* files in the security directory of | ||
that profile, and yield dicts of their information, each one pertaining to | ||||
a currently running notebook server instance. | ||||
""" | ||||
pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile) | ||||
for file in os.listdir(pd.security_dir): | ||||
if file.startswith('nbserver-'): | ||||
with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f: | ||||
Thomas Kluyver
|
r17181 | info = json.load(f) | ||
Brian E. Granger
|
r4344 | |||
Thomas Kluyver
|
r17181 | # Simple check whether that process is really still running | ||
Thomas Kluyver
|
r19296 | # Also remove leftover files from IPython 2.x without a pid field | ||
if ('pid' in info) and check_pid(info['pid']): | ||||
Thomas Kluyver
|
r17181 | yield info | ||
else: | ||||
# If the process has died, try to delete its info file | ||||
try: | ||||
os.unlink(file) | ||||
except OSError: | ||||
pass # TODO: This should warn or log or something | ||||
Brian E. Granger
|
r4344 | #----------------------------------------------------------------------------- | ||
# Main entry point | ||||
#----------------------------------------------------------------------------- | ||||
Brian E. Granger
|
r4339 | |||
MinRK
|
r11176 | launch_new_instance = NotebookApp.launch_instance | ||
Brian E. Granger
|
r4339 | |||