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