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