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