notebookapp.py
752 lines
| 29.2 KiB
| text/x-python
|
PythonLexer
MinRK
|
r6067 | # coding: utf-8 | ||
Brian E. Granger
|
r4609 | """A tornado based IPython notebook server. | ||
Authors: | ||||
* Brian Granger | ||||
""" | ||||
Brian E. Granger
|
r4344 | #----------------------------------------------------------------------------- | ||
MinRK
|
r10557 | # Copyright (C) 2013 The IPython Development Team | ||
Brian E. Granger
|
r4348 | # | ||
# Distributed under the terms of the BSD License. The full license is in | ||||
Brian E. Granger
|
r4609 | # the file COPYING, distributed as part of this software. | ||
Brian E. Granger
|
r4348 | #----------------------------------------------------------------------------- | ||
#----------------------------------------------------------------------------- | ||||
Brian E. Granger
|
r4344 | # Imports | ||
#----------------------------------------------------------------------------- | ||||
Fernando Perez
|
r5212 | # stdlib | ||
Brian E. Granger
|
r4548 | import errno | ||
Brian E. Granger
|
r4339 | import logging | ||
import os | ||||
Bradley M. Froehle
|
r7234 | import random | ||
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 | ||
MinRK
|
r6509 | import time | ||
Thomas Kluyver
|
r5065 | import webbrowser | ||
Brian E. Granger
|
r4339 | |||
MinRK
|
r10199 | |||
Fernando Perez
|
r5212 | # Third party | ||
MinRK
|
r10201 | # check for pyzmq 2.1.11 | ||
from IPython.utils.zmqrelated import check_for_zmq | ||||
MinRK
|
r11035 | check_for_zmq('2.1.11', '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
|
r10199 | # check for tornado 2.1.0 | ||
msg = "The IPython Notebook requires tornado >= 2.1.0" | ||||
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") | ||||
if version_info < (2,1,0): | ||||
raise ImportError(msg + ", but you have %s" % tornado.version) | ||||
Brian E. Granger
|
r4339 | from tornado import httpserver | ||
from tornado import web | ||||
Fernando Perez
|
r5212 | # Our own libraries | ||
MinRK
|
r11035 | from IPython.html import DEFAULT_STATIC_FILES_PATH | ||
Brian E. Granger
|
r10642 | |||
Brian E. Granger
|
r10666 | from .services.kernels.kernelmanager import MappingKernelManager | ||
from .services.notebooks.nbmanager import NotebookManager | ||||
from .services.notebooks.filenbmanager import FileNotebookManager | ||||
from .services.clusters.clustermanager import ClusterManager | ||||
Brian E. Granger
|
r10642 | |||
Brian E. Granger
|
r10650 | from .base.handlers import AuthenticatedFileHandler, FileFindHandler | ||
Brian E. Granger
|
r4339 | |||
Fernando Perez
|
r5762 | from IPython.config.application import catch_config_error, boolean_flag | ||
Brian E. Granger
|
r4344 | from IPython.core.application import BaseIPythonApplication | ||
Fernando Perez
|
r11023 | from IPython.consoleapp import IPythonConsoleApp | ||
MinRK
|
r9376 | from IPython.kernel import swallow_argv | ||
Brian E. Granger
|
r10642 | from IPython.kernel.zmq.session import default_secure | ||
MinRK
|
r9372 | from IPython.kernel.zmq.kernelapp import ( | ||
MinRK
|
r9357 | kernel_flags, | ||
kernel_aliases, | ||||
Brian E. Granger
|
r4345 | ) | ||
Brian Granger
|
r8180 | from IPython.utils.importstring import import_item | ||
W. Trevor King
|
r9252 | from IPython.utils.localinterfaces import LOCALHOST | ||
MinRK
|
r10557 | from IPython.utils import submodule | ||
Brian Granger
|
r8180 | from IPython.utils.traitlets import ( | ||
MinRK
|
r10784 | Dict, Unicode, Integer, List, Bool, Bytes, | ||
Brian Granger
|
r8180 | DottedObjectName | ||
) | ||||
MinRK
|
r6067 | from IPython.utils import py3compat | ||
MinRK
|
r7923 | from IPython.utils.path import filefind | ||
Brian E. Granger
|
r4344 | |||
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 --pylab=inline # pylab in inline plotting mode | ||||
ipython notebook --certfile=mycert.pem # use SSL/TLS certificate | ||||
ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces | ||||
""" | ||||
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): | ||||
yield port + random.randint(-2*n, 2*n) | ||||
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 | |||
Matthias BUSSONNIER
|
r9284 | def __init__(self, ipython_app, kernel_manager, notebook_manager, | ||
Brian Granger
|
r6191 | cluster_manager, log, | ||
Andrew Straw
|
r6004 | base_project_url, settings_overrides): | ||
Brian E. Granger
|
r10647 | |||
Brian E. Granger
|
r10656 | settings = self.init_settings( | ||
ipython_app, kernel_manager, notebook_manager, cluster_manager, | ||||
log, base_project_url, settings_overrides) | ||||
handlers = self.init_handlers(settings) | ||||
super(NotebookWebApplication, self).__init__(handlers, **settings) | ||||
Timo Paulssen
|
r5664 | |||
Brian E. Granger
|
r10656 | def init_settings(self, ipython_app, kernel_manager, notebook_manager, | ||
cluster_manager, log, | ||||
base_project_url, settings_overrides): | ||||
MinRK
|
r6067 | # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and | ||
# base_project_url will always be unicode, which will in turn | ||||
# make the patterns unicode, and ultimately result in unicode | ||||
# keys in kwargs to handler._execute(**kwargs) in tornado. | ||||
# This enforces that base_project_url be ascii in that situation. | ||||
# | ||||
# Note that the URLs these patterns check against are escaped, | ||||
# and thus guaranteed to be ASCII: 'héllo' is really 'h%C3%A9llo'. | ||||
base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii') | ||||
MinRK
|
r10355 | template_path = os.path.join(os.path.dirname(__file__), "templates") | ||
Matthias BUSSONNIER
|
r7797 | settings = dict( | ||
MinRK
|
r10355 | # basics | ||
base_project_url=base_project_url, | ||||
base_kernel_url=ipython_app.base_kernel_url, | ||||
template_path=template_path, | ||||
MinRK
|
r7930 | static_path=ipython_app.static_file_path, | ||
MinRK
|
r7923 | static_handler_class = FileFindHandler, | ||
Bussonnier Matthias
|
r8938 | static_url_prefix = url_path_join(base_project_url,'/static/'), | ||
MinRK
|
r10355 | |||
# authentication | ||||
MinRK
|
r10784 | cookie_secret=ipython_app.cookie_secret, | ||
Bussonnier Matthias
|
r8938 | login_url=url_path_join(base_project_url,'/login'), | ||
MinRK
|
r10355 | read_only=ipython_app.read_only, | ||
password=ipython_app.password, | ||||
# managers | ||||
kernel_manager=kernel_manager, | ||||
notebook_manager=notebook_manager, | ||||
cluster_manager=cluster_manager, | ||||
# IPython stuff | ||||
MinRK
|
r10388 | mathjax_url=ipython_app.mathjax_url, | ||
MinRK
|
r10355 | max_msg_size=ipython_app.max_msg_size, | ||
config=ipython_app.config, | ||||
use_less=ipython_app.use_less, | ||||
jinja2_env=Environment(loader=FileSystemLoader(template_path)), | ||||
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): | ||
# Load the (URL pattern, handler) tuples for each component. | ||||
handlers = [] | ||||
handlers.extend(load_handlers('base.handlers')) | ||||
handlers.extend(load_handlers('tree.handlers')) | ||||
handlers.extend(load_handlers('auth.login')) | ||||
handlers.extend(load_handlers('auth.logout')) | ||||
Brian E. Granger
|
r10665 | handlers.extend(load_handlers('notebook.handlers')) | ||
handlers.extend(load_handlers('services.kernels.handlers')) | ||||
handlers.extend(load_handlers('services.notebooks.handlers')) | ||||
handlers.extend(load_handlers('services.clusters.handlers')) | ||||
Brian E. Granger
|
r10656 | handlers.extend([ | ||
(r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}), | ||||
]) | ||||
Andrew Straw
|
r6004 | # prepend base_project_url onto the patterns that we match | ||
new_handlers = [] | ||||
for handler in handlers: | ||||
Brian E. Granger
|
r10656 | pattern = url_path_join(settings['base_project_url'], handler[0]) | ||
MinRK
|
r10355 | new_handler = tuple([pattern] + list(handler[1:])) | ||
new_handlers.append(new_handler) | ||||
Brian E. Granger
|
r10656 | return new_handlers | ||
Brian E. Granger
|
r4339 | |||
Brian E. Granger
|
r4346 | |||
Brian E. Granger
|
r4344 | #----------------------------------------------------------------------------- | ||
# Aliases and Flags | ||||
#----------------------------------------------------------------------------- | ||||
MinRK
|
r9357 | flags = dict(kernel_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
|
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. | ||||
""" | ||||
) | ||||
MinRK
|
r5191 | flags['read-only'] = ( | ||
{'NotebookApp' : {'read_only' : True}}, | ||||
MinRK
|
r5200 | """Allow read-only access to notebooks. | ||
When using a password to protect the notebook server, this flag | ||||
allows unauthenticated clients to view the notebook list, and | ||||
individual notebooks, but not edit them, start kernels, or run | ||||
code. | ||||
MinRK
|
r5210 | If no password is set, the server will be entirely read-only. | ||
MinRK
|
r5200 | """ | ||
MinRK
|
r5191 | ) | ||
Brian E. Granger
|
r4344 | |||
Fernando Perez
|
r5762 | # Add notebook manager flags | ||
Brian Granger
|
r8183 | flags.update(boolean_flag('script', 'FileNotebookManager.save_script', | ||
Fernando Perez
|
r5762 | 'Auto-save a .py script everytime the .ipynb notebook is saved', | ||
'Do not auto-save .py scripts for every notebook')) | ||||
Fernando Perez
|
r5758 | |||
Brian E. Granger
|
r4344 | # the flags that are specific to the frontend | ||
# these must be scrubbed before being passed to the kernel, | ||||
# or it will raise an error on unrecognized flags | ||||
Fernando Perez
|
r5762 | notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script'] | ||
Brian E. Granger
|
r4344 | |||
MinRK
|
r9357 | aliases = dict(kernel_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', | ||||
Brian Granger
|
r8194 | 'notebook-dir': 'NotebookManager.notebook_dir', | ||
Paul Ivanov
|
r6116 | 'browser': 'NotebookApp.browser', | ||
Brian E. Granger
|
r4519 | }) | ||
Brian E. Granger
|
r4344 | |||
MinRK
|
r4963 | # remove ipkernel flags that are singletons, and don't make sense in | ||
# multi-kernel evironment: | ||||
aliases.pop('f', None) | ||||
Bradley M. Froehle
|
r7234 | notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile', | ||
MinRK
|
r4705 | u'notebook-dir'] | ||
Brian E. Granger
|
r4579 | |||
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' | ||
default_config_file_name='ipython_notebook_config.py' | ||||
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 | ||
Brian E. Granger
|
r4344 | |||
Brian Granger
|
r8194 | classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager, | ||
Brian Granger
|
r8183 | FileNotebookManager] | ||
Brian E. Granger
|
r4344 | flags = Dict(flags) | ||
aliases = Dict(aliases) | ||||
kernel_argv = List(Unicode) | ||||
MinRK
|
r10355 | max_msg_size = Integer(65536, config=True, help=""" | ||
The max raw message size accepted from the browser | ||||
over a WebSocket connection. | ||||
""") | ||||
def _log_level_default(self): | ||||
return logging.INFO | ||||
Brian E. Granger
|
r4345 | |||
MinRK
|
r10358 | def _log_format_default(self): | ||
"""override default log format to include time""" | ||||
MinRK
|
r10560 | return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)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 | ||
file_to_run = Unicode('') | ||||
Brian E. Granger
|
r4519 | # Network related information. | ||
Brian E. Granger
|
r4344 | ip = Unicode(LOCALHOST, config=True, | ||
help="The IP address the notebook server will listen on." | ||||
) | ||||
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 | |||
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): | ||||
return os.urandom(1024) | ||||
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 | |||
read_only = Bool(False, config=True, | ||||
help="Whether to prevent editing/execution of notebooks." | ||||
) | ||||
Bussonnier Matthias
|
r9266 | |||
Matthias BUSSONNIER
|
r9284 | use_less = Bool(False, config=True, | ||
Bussonnier Matthias
|
r9266 | help="""Wether to use Browser Side less-css parsing | ||
Matthias BUSSONNIER
|
r9284 | instead of compiled css version in templates that allows | ||
it. This is mainly convenient when working on the less | ||||
Bussonnier Matthias
|
r9266 | file to avoid a build step, or if user want to overwrite | ||
some of the less variables without having to recompile | ||||
Matthias BUSSONNIER
|
r9287 | everything. | ||
You will need to install the less.js component in the static directory | ||||
either in the source tree or in your profile folder. | ||||
""") | ||||
Matthias BUSSONNIER
|
r9284 | |||
Timo Paulssen
|
r5665 | webapp_settings = Dict(config=True, | ||
help="Supply overrides for the tornado.web.Application that the " | ||||
"IPython notebook uses.") | ||||
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 | |||
Andrew Straw
|
r6004 | base_project_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. | ||||
''') | ||||
def _base_project_url_changed(self, name, old, new): | ||||
if not new.startswith('/'): | ||||
self.base_project_url = '/'+new | ||||
elif not new.endswith('/'): | ||||
self.base_project_url = new+'/' | ||||
Andrew Straw
|
r6004 | base_kernel_url = Unicode('/', config=True, | ||
Bussonnier Matthias
|
r8938 | help='''The base URL for the kernel server | ||
Leading and trailing slashes can be omitted, | ||||
and will automatically be added. | ||||
''') | ||||
def _base_kernel_url_changed(self, name, old, new): | ||||
if not new.startswith('/'): | ||||
self.base_kernel_url = '/'+new | ||||
elif not new.endswith('/'): | ||||
self.base_kernel_url = new+'/' | ||||
MinRK
|
r10808 | websocket_url = Unicode("", config=True, | ||
help="""The base URL for the websocket server, | ||||
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] | ||||
""" | ||||
Andrew Straw
|
r6006 | ) | ||
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] | ||
Andrew Straw
|
r6004 | |||
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'' | ||||
Andrew Straw
|
r6002 | static_url_prefix = self.webapp_settings.get("static_url_prefix", | ||
MinRK
|
r11153 | url_path_join(self.base_project_url, "static") | ||
) | ||||
MinRK
|
r7923 | try: | ||
MinRK
|
r8017 | mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path) | ||
MinRK
|
r7923 | except IOError: | ||
MinRK
|
r6729 | if self.certfile: | ||
# HTTPS: load from Rackspace CDN, because SSL certificate requires it | ||||
base = u"https://c328740.ssl.cf1.rackcdn.com" | ||||
MinRK
|
r6155 | else: | ||
MinRK
|
r6729 | base = u"http://cdn.mathjax.org" | ||
MinRK
|
r6155 | |||
MinRK
|
r6729 | url = base + u"/mathjax/latest/MathJax.js" | ||
self.log.info("Using MathJax from CDN: %s", url) | ||||
return url | ||||
MinRK
|
r7923 | else: | ||
MinRK
|
r8017 | self.log.info("Using local MathJax from %s" % mathjax) | ||
MinRK
|
r11153 | return url_path_join(static_url_prefix, u"mathjax/MathJax.js") | ||
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 | |||
MinRK
|
r11035 | notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager', | ||
Brian Granger
|
r8180 | config=True, | ||
help='The notebook manager class to use.') | ||||
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" | ||||
"sent by the upstream reverse proxy. Neccesary if the proxy handles SSL") | ||||
) | ||||
Brian E. Granger
|
r4344 | def parse_command_line(self, argv=None): | ||
Brian E. Granger
|
r5104 | super(NotebookApp, self).parse_command_line(argv) | ||
Brian E. Granger
|
r4344 | if argv is None: | ||
argv = sys.argv[1:] | ||||
MinRK
|
r5620 | # Scrub frontend-specific flags | ||
self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags) | ||||
Brian E. Granger
|
r4609 | # Kernel should inherit default config file from frontend | ||
MinRK
|
r9520 | self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name) | ||
Brian E. Granger
|
r4344 | |||
Puneeth Chaganti
|
r6724 | if self.extra_args: | ||
MinRK
|
r7556 | f = os.path.abspath(self.extra_args[0]) | ||
if os.path.isdir(f): | ||||
nbdir = f | ||||
else: | ||||
self.file_to_run = f | ||||
nbdir = os.path.dirname(f) | ||||
Brian Granger
|
r8194 | self.config.NotebookManager.notebook_dir = nbdir | ||
Puneeth Chaganti
|
r6724 | |||
Brian E. Granger
|
r4494 | def init_configurables(self): | ||
MinRK
|
r4963 | # force Session default to be secure | ||
default_secure(self.config) | ||||
Brian E. Granger
|
r4545 | self.kernel_manager = MappingKernelManager( | ||
MinRK
|
r11064 | parent=self, log=self.log, kernel_argv=self.kernel_argv, | ||
MinRK
|
r4960 | connection_dir = self.profile_dir.security_dir, | ||
Brian E. Granger
|
r4494 | ) | ||
Brian Granger
|
r8180 | kls = import_item(self.notebook_manager_class) | ||
MinRK
|
r11064 | self.notebook_manager = kls(parent=self, log=self.log) | ||
Brian Granger
|
r8180 | self.notebook_manager.load_notebook_names() | ||
MinRK
|
r11064 | self.cluster_manager = ClusterManager(parent=self, log=self.log) | ||
Brian Granger
|
r6199 | self.cluster_manager.update_profiles() | ||
Brian E. Granger
|
r4344 | |||
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 | |||
# hook up tornado 3's loggers to our app handlers | ||||
for name in ('access', 'application', 'general'): | ||||
logging.getLogger('tornado.%s' % name).handlers = self.log.handlers | ||||
MinRK
|
r5799 | |||
def init_webapp(self): | ||||
"""initialize tornado webapp and httpserver""" | ||||
Brian E. Granger
|
r4347 | self.web_app = NotebookWebApplication( | ||
Brian Granger
|
r6191 | self, self.kernel_manager, self.notebook_manager, | ||
self.cluster_manager, self.log, | ||||
Andrew Straw
|
r6004 | self.base_project_url, self.webapp_settings | ||
Brian E. Granger
|
r4347 | ) | ||
Brian E. Granger
|
r4519 | if self.certfile: | ||
ssl_options = dict(certfile=self.certfile) | ||||
if self.keyfile: | ||||
ssl_options['keyfile'] = self.keyfile | ||||
else: | ||||
ssl_options = None | ||||
MinRK
|
r4705 | self.web_app.password = self.password | ||
Alberto Valverde
|
r10163 | self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options, | ||
xheaders=self.trust_xheaders) | ||||
dkua
|
r8723 | if not self.ip: | ||
dkua
|
r8724 | warning = "WARNING: The notebook server is listening on all IP addresses" | ||
dkua
|
r8723 | if ssl_options is None: | ||
Thomas Weißschuh
|
r10564 | self.log.critical(warning + " and not using encryption. This " | ||
dkua
|
r8726 | "is not recommended.") | ||
dkua
|
r8724 | if not self.password and not self.read_only: | ||
Thomas Weißschuh
|
r10564 | self.log.critical(warning + " and not using authentication. " | ||
dkua
|
r8726 | "This is highly insecure and not recommended.") | ||
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: | ||
Paul Ivanov
|
r10164 | # XXX: remove the e.errno == -9 block when we require | ||
# tornado >= 3.0 | ||||
Paul Ivanov
|
r10168 | if e.errno == -9 and tornado.version_info[0] < 3: | ||
Paul Ivanov
|
r10164 | # The flags passed to socket.getaddrinfo from | ||
# tornado.netutils.bind_sockets can cause "gaierror: | ||||
# [Errno -9] Address family for hostname not supported" | ||||
# when the interface is not associated, for example. | ||||
# Changing the flags to exclude socket.AI_ADDRCONFIG does | ||||
# not cause this error, but the only way to do this is to | ||||
# monkeypatch socket to remove the AI_ADDRCONFIG attribute | ||||
saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG | ||||
Paul Ivanov
|
r10168 | self.log.warn('Monkeypatching socket to fix tornado bug') | ||
Paul Ivanov
|
r10164 | del(socket.AI_ADDRCONFIG) | ||
try: | ||||
# retry the tornado call without AI_ADDRCONFIG flags | ||||
self.http_server.listen(port, self.ip) | ||||
except socket.error as e2: | ||||
e = e2 | ||||
else: | ||||
self.port = port | ||||
success = True | ||||
break | ||||
# restore the monekypatch | ||||
socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG | ||||
Brian E. Granger
|
r4548 | if e.errno != errno.EADDRINUSE: | ||
raise | ||||
Brian E. Granger
|
r4609 | self.log.info('The port %i is already in use, trying another random port.' % port) | ||
Brian E. Granger
|
r4548 | 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 | |||
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 | """ | ||
MinRK
|
r6509 | # FIXME: remove this delay when pyzmq dependency is >= 2.1.11 | ||
time.sleep(0.1) | ||||
Paul Ivanov
|
r9912 | info = self.log.info | ||
info('interrupted') | ||||
Paul Ivanov
|
r10019 | print self.notebook_info() | ||
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() | ||||
if line.lower().startswith('y'): | ||||
self.log.critical("Shutdown confirmed") | ||||
ioloop.IOLoop.instance().stop() | ||||
return | ||||
else: | ||||
print "No answer for 5s:", | ||||
print "resuming operation..." | ||||
# 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 | ||||
ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler) | ||||
def _signal_stop(self, sig, frame): | ||||
MinRK
|
r6498 | self.log.critical("received signal %s, stopping", sig) | ||
ioloop.IOLoop.instance().stop() | ||||
Paul Ivanov
|
r9913 | |||
def _signal_info(self, sig, frame): | ||||
Paul Ivanov
|
r10019 | 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") | ||||
MinRK
|
r5799 | @catch_config_error | ||
def initialize(self, argv=None): | ||||
MinRK
|
r6883 | self.init_logging() | ||
MinRK
|
r5799 | super(NotebookApp, self).initialize(argv) | ||
self.init_configurables() | ||||
MinRK
|
r10557 | self.init_components() | ||
MinRK
|
r5799 | self.init_webapp() | ||
MinRK
|
r6498 | self.init_signal() | ||
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" | ||||
mgr_info = self.notebook_manager.info_string() + "\n" | ||||
return mgr_info +"The IPython Notebook is running at: %s" % self._url | ||||
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.""" | ||||
Brian E. Granger
|
r4519 | ip = self.ip if self.ip else '[all ip addresses on your system]' | ||
Satrajit Ghosh
|
r4682 | proto = 'https' if self.certfile else 'http' | ||
Fernando Perez
|
r5786 | info = self.log.info | ||
Paul Ivanov
|
r9910 | self._url = "%s://%s:%i%s" % (proto, ip, self.port, | ||
self.base_project_url) | ||||
Paul Ivanov
|
r10019 | for line in self.notebook_info().split("\n"): | ||
info(line) | ||||
Fernando Perez
|
r5786 | info("Use Control-C to stop this server and shut down all kernels.") | ||
MinRK
|
r7556 | if self.open_browser or self.file_to_run: | ||
W. Trevor King
|
r9252 | ip = self.ip or LOCALHOST | ||
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 | ||||
Puneeth Chaganti
|
r6724 | |||
if self.file_to_run: | ||||
Puneeth Chaganti
|
r8162 | name, _ = os.path.splitext(os.path.basename(self.file_to_run)) | ||
url = self.notebook_manager.rev_mapping.get(name, '') | ||||
Puneeth Chaganti
|
r6724 | else: | ||
url = '' | ||||
Bradley M. Froehle
|
r7658 | if browser: | ||
b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip, | ||||
self.port, self.base_project_url, url), new=2) | ||||
threading.Thread(target=b).start() | ||||
MinRK
|
r5799 | try: | ||
ioloop.IOLoop.instance().start() | ||||
except KeyboardInterrupt: | ||||
info("Interrupted...") | ||||
finally: | ||||
self.cleanup_kernels() | ||||
Brian E. Granger
|
r4344 | |||
#----------------------------------------------------------------------------- | ||||
# Main entry point | ||||
#----------------------------------------------------------------------------- | ||||
Brian E. Granger
|
r4339 | |||
Brian E. Granger
|
r4341 | def launch_new_instance(): | ||
MinRK
|
r5799 | app = NotebookApp.instance() | ||
Brian E. Granger
|
r4344 | app.initialize() | ||
app.start() | ||||
Brian E. Granger
|
r4339 | |||