##// END OF EJS Templates
Merge pull request #7694 from minrk/check-pyzmq...
Matthias Bussonnier -
r20356:88c7f454 merge
parent child Browse files
Show More
@@ -1,1107 +1,1107 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 2.1.11
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('2.1.11', '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 default_secure, Session
80 from IPython.kernel.zmq.session import default_secure, 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
413
414 def _ip_changed(self, name, old, new):
414 def _ip_changed(self, name, old, new):
415 if new == u'*': self.ip = u''
415 if new == u'*': self.ip = u''
416
416
417 port = Integer(8888, config=True,
417 port = Integer(8888, config=True,
418 help="The port the notebook server will listen on."
418 help="The port the notebook server will listen on."
419 )
419 )
420 port_retries = Integer(50, config=True,
420 port_retries = Integer(50, config=True,
421 help="The number of additional ports to try if the specified port is not available."
421 help="The number of additional ports to try if the specified port is not available."
422 )
422 )
423
423
424 certfile = Unicode(u'', config=True,
424 certfile = Unicode(u'', config=True,
425 help="""The full path to an SSL/TLS certificate file."""
425 help="""The full path to an SSL/TLS certificate file."""
426 )
426 )
427
427
428 keyfile = Unicode(u'', config=True,
428 keyfile = Unicode(u'', config=True,
429 help="""The full path to a private key file for usage with SSL/TLS."""
429 help="""The full path to a private key file for usage with SSL/TLS."""
430 )
430 )
431
431
432 cookie_secret_file = Unicode(config=True,
432 cookie_secret_file = Unicode(config=True,
433 help="""The file where the cookie secret is stored."""
433 help="""The file where the cookie secret is stored."""
434 )
434 )
435 def _cookie_secret_file_default(self):
435 def _cookie_secret_file_default(self):
436 if self.profile_dir is None:
436 if self.profile_dir is None:
437 return ''
437 return ''
438 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
438 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
439
439
440 cookie_secret = Bytes(b'', config=True,
440 cookie_secret = Bytes(b'', config=True,
441 help="""The random bytes used to secure cookies.
441 help="""The random bytes used to secure cookies.
442 By default this is a new random number every time you start the Notebook.
442 By default this is a new random number every time you start the Notebook.
443 Set it to a value in a config file to enable logins to persist across server sessions.
443 Set it to a value in a config file to enable logins to persist across server sessions.
444
444
445 Note: Cookie secrets should be kept private, do not share config files with
445 Note: Cookie secrets should be kept private, do not share config files with
446 cookie_secret stored in plaintext (you can read the value from a file).
446 cookie_secret stored in plaintext (you can read the value from a file).
447 """
447 """
448 )
448 )
449 def _cookie_secret_default(self):
449 def _cookie_secret_default(self):
450 if os.path.exists(self.cookie_secret_file):
450 if os.path.exists(self.cookie_secret_file):
451 with io.open(self.cookie_secret_file, 'rb') as f:
451 with io.open(self.cookie_secret_file, 'rb') as f:
452 return f.read()
452 return f.read()
453 else:
453 else:
454 secret = base64.encodestring(os.urandom(1024))
454 secret = base64.encodestring(os.urandom(1024))
455 self._write_cookie_secret_file(secret)
455 self._write_cookie_secret_file(secret)
456 return secret
456 return secret
457
457
458 def _write_cookie_secret_file(self, secret):
458 def _write_cookie_secret_file(self, secret):
459 """write my secret to my secret_file"""
459 """write my secret to my secret_file"""
460 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
460 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
461 with io.open(self.cookie_secret_file, 'wb') as f:
461 with io.open(self.cookie_secret_file, 'wb') as f:
462 f.write(secret)
462 f.write(secret)
463 try:
463 try:
464 os.chmod(self.cookie_secret_file, 0o600)
464 os.chmod(self.cookie_secret_file, 0o600)
465 except OSError:
465 except OSError:
466 self.log.warn(
466 self.log.warn(
467 "Could not set permissions on %s",
467 "Could not set permissions on %s",
468 self.cookie_secret_file
468 self.cookie_secret_file
469 )
469 )
470
470
471 password = Unicode(u'', config=True,
471 password = Unicode(u'', config=True,
472 help="""Hashed password to use for web authentication.
472 help="""Hashed password to use for web authentication.
473
473
474 To generate, type in a python/IPython shell:
474 To generate, type in a python/IPython shell:
475
475
476 from IPython.lib import passwd; passwd()
476 from IPython.lib import passwd; passwd()
477
477
478 The string should be of the form type:salt:hashed-password.
478 The string should be of the form type:salt:hashed-password.
479 """
479 """
480 )
480 )
481
481
482 open_browser = Bool(True, config=True,
482 open_browser = Bool(True, config=True,
483 help="""Whether to open in a browser after starting.
483 help="""Whether to open in a browser after starting.
484 The specific browser used is platform dependent and
484 The specific browser used is platform dependent and
485 determined by the python standard library `webbrowser`
485 determined by the python standard library `webbrowser`
486 module, unless it is overridden using the --browser
486 module, unless it is overridden using the --browser
487 (NotebookApp.browser) configuration option.
487 (NotebookApp.browser) configuration option.
488 """)
488 """)
489
489
490 browser = Unicode(u'', config=True,
490 browser = Unicode(u'', config=True,
491 help="""Specify what command to use to invoke a web
491 help="""Specify what command to use to invoke a web
492 browser when opening the notebook. If not specified, the
492 browser when opening the notebook. If not specified, the
493 default browser will be determined by the `webbrowser`
493 default browser will be determined by the `webbrowser`
494 standard library module, which allows setting of the
494 standard library module, which allows setting of the
495 BROWSER environment variable to override it.
495 BROWSER environment variable to override it.
496 """)
496 """)
497
497
498 webapp_settings = Dict(config=True,
498 webapp_settings = Dict(config=True,
499 help="DEPRECATED, use tornado_settings"
499 help="DEPRECATED, use tornado_settings"
500 )
500 )
501 def _webapp_settings_changed(self, name, old, new):
501 def _webapp_settings_changed(self, name, old, new):
502 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
502 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
503 self.tornado_settings = new
503 self.tornado_settings = new
504
504
505 tornado_settings = Dict(config=True,
505 tornado_settings = Dict(config=True,
506 help="Supply overrides for the tornado.web.Application that the "
506 help="Supply overrides for the tornado.web.Application that the "
507 "IPython notebook uses.")
507 "IPython notebook uses.")
508
508
509 jinja_environment_options = Dict(config=True,
509 jinja_environment_options = Dict(config=True,
510 help="Supply extra arguments that will be passed to Jinja environment.")
510 help="Supply extra arguments that will be passed to Jinja environment.")
511
511
512 enable_mathjax = Bool(True, config=True,
512 enable_mathjax = Bool(True, config=True,
513 help="""Whether to enable MathJax for typesetting math/TeX
513 help="""Whether to enable MathJax for typesetting math/TeX
514
514
515 MathJax is the javascript library IPython uses to render math/LaTeX. It is
515 MathJax is the javascript library IPython uses to render math/LaTeX. It is
516 very large, so you may want to disable it if you have a slow internet
516 very large, so you may want to disable it if you have a slow internet
517 connection, or for offline use of the notebook.
517 connection, or for offline use of the notebook.
518
518
519 When disabled, equations etc. will appear as their untransformed TeX source.
519 When disabled, equations etc. will appear as their untransformed TeX source.
520 """
520 """
521 )
521 )
522 def _enable_mathjax_changed(self, name, old, new):
522 def _enable_mathjax_changed(self, name, old, new):
523 """set mathjax url to empty if mathjax is disabled"""
523 """set mathjax url to empty if mathjax is disabled"""
524 if not new:
524 if not new:
525 self.mathjax_url = u''
525 self.mathjax_url = u''
526
526
527 base_url = Unicode('/', config=True,
527 base_url = Unicode('/', config=True,
528 help='''The base URL for the notebook server.
528 help='''The base URL for the notebook server.
529
529
530 Leading and trailing slashes can be omitted,
530 Leading and trailing slashes can be omitted,
531 and will automatically be added.
531 and will automatically be added.
532 ''')
532 ''')
533 def _base_url_changed(self, name, old, new):
533 def _base_url_changed(self, name, old, new):
534 if not new.startswith('/'):
534 if not new.startswith('/'):
535 self.base_url = '/'+new
535 self.base_url = '/'+new
536 elif not new.endswith('/'):
536 elif not new.endswith('/'):
537 self.base_url = new+'/'
537 self.base_url = new+'/'
538
538
539 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
539 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
540 def _base_project_url_changed(self, name, old, new):
540 def _base_project_url_changed(self, name, old, new):
541 self.log.warn("base_project_url is deprecated, use base_url")
541 self.log.warn("base_project_url is deprecated, use base_url")
542 self.base_url = new
542 self.base_url = new
543
543
544 extra_static_paths = List(Unicode, config=True,
544 extra_static_paths = List(Unicode, config=True,
545 help="""Extra paths to search for serving static files.
545 help="""Extra paths to search for serving static files.
546
546
547 This allows adding javascript/css to be available from the notebook server machine,
547 This allows adding javascript/css to be available from the notebook server machine,
548 or overriding individual files in the IPython"""
548 or overriding individual files in the IPython"""
549 )
549 )
550 def _extra_static_paths_default(self):
550 def _extra_static_paths_default(self):
551 return [os.path.join(self.profile_dir.location, 'static')]
551 return [os.path.join(self.profile_dir.location, 'static')]
552
552
553 @property
553 @property
554 def static_file_path(self):
554 def static_file_path(self):
555 """return extra paths + the default location"""
555 """return extra paths + the default location"""
556 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
556 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
557
557
558 extra_template_paths = List(Unicode, config=True,
558 extra_template_paths = List(Unicode, config=True,
559 help="""Extra paths to search for serving jinja templates.
559 help="""Extra paths to search for serving jinja templates.
560
560
561 Can be used to override templates from IPython.html.templates."""
561 Can be used to override templates from IPython.html.templates."""
562 )
562 )
563 def _extra_template_paths_default(self):
563 def _extra_template_paths_default(self):
564 return []
564 return []
565
565
566 @property
566 @property
567 def template_file_path(self):
567 def template_file_path(self):
568 """return extra paths + the default locations"""
568 """return extra paths + the default locations"""
569 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
569 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
570
570
571 extra_nbextensions_path = List(Unicode, config=True,
571 extra_nbextensions_path = List(Unicode, config=True,
572 help="""extra paths to look for Javascript notebook extensions"""
572 help="""extra paths to look for Javascript notebook extensions"""
573 )
573 )
574
574
575 @property
575 @property
576 def nbextensions_path(self):
576 def nbextensions_path(self):
577 """The path to look for Javascript notebook extensions"""
577 """The path to look for Javascript notebook extensions"""
578 return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS
578 return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS
579
579
580 websocket_url = Unicode("", config=True,
580 websocket_url = Unicode("", config=True,
581 help="""The base URL for websockets,
581 help="""The base URL for websockets,
582 if it differs from the HTTP server (hint: it almost certainly doesn't).
582 if it differs from the HTTP server (hint: it almost certainly doesn't).
583
583
584 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
584 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
585 """
585 """
586 )
586 )
587 mathjax_url = Unicode("", config=True,
587 mathjax_url = Unicode("", config=True,
588 help="""The url for MathJax.js."""
588 help="""The url for MathJax.js."""
589 )
589 )
590 def _mathjax_url_default(self):
590 def _mathjax_url_default(self):
591 if not self.enable_mathjax:
591 if not self.enable_mathjax:
592 return u''
592 return u''
593 static_url_prefix = self.tornado_settings.get("static_url_prefix",
593 static_url_prefix = self.tornado_settings.get("static_url_prefix",
594 url_path_join(self.base_url, "static")
594 url_path_join(self.base_url, "static")
595 )
595 )
596
596
597 # try local mathjax, either in nbextensions/mathjax or static/mathjax
597 # try local mathjax, either in nbextensions/mathjax or static/mathjax
598 for (url_prefix, search_path) in [
598 for (url_prefix, search_path) in [
599 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
599 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
600 (static_url_prefix, self.static_file_path),
600 (static_url_prefix, self.static_file_path),
601 ]:
601 ]:
602 self.log.debug("searching for local mathjax in %s", search_path)
602 self.log.debug("searching for local mathjax in %s", search_path)
603 try:
603 try:
604 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
604 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
605 except IOError:
605 except IOError:
606 continue
606 continue
607 else:
607 else:
608 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
608 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
609 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
609 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
610 return url
610 return url
611
611
612 # no local mathjax, serve from CDN
612 # no local mathjax, serve from CDN
613 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
613 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
614 self.log.info("Using MathJax from CDN: %s", url)
614 self.log.info("Using MathJax from CDN: %s", url)
615 return url
615 return url
616
616
617 def _mathjax_url_changed(self, name, old, new):
617 def _mathjax_url_changed(self, name, old, new):
618 if new and not self.enable_mathjax:
618 if new and not self.enable_mathjax:
619 # enable_mathjax=False overrides mathjax_url
619 # enable_mathjax=False overrides mathjax_url
620 self.mathjax_url = u''
620 self.mathjax_url = u''
621 else:
621 else:
622 self.log.info("Using MathJax: %s", new)
622 self.log.info("Using MathJax: %s", new)
623
623
624 contents_manager_class = Type(
624 contents_manager_class = Type(
625 default_value=FileContentsManager,
625 default_value=FileContentsManager,
626 klass=ContentsManager,
626 klass=ContentsManager,
627 config=True,
627 config=True,
628 help='The notebook manager class to use.'
628 help='The notebook manager class to use.'
629 )
629 )
630 kernel_manager_class = Type(
630 kernel_manager_class = Type(
631 default_value=MappingKernelManager,
631 default_value=MappingKernelManager,
632 config=True,
632 config=True,
633 help='The kernel manager class to use.'
633 help='The kernel manager class to use.'
634 )
634 )
635 session_manager_class = Type(
635 session_manager_class = Type(
636 default_value=SessionManager,
636 default_value=SessionManager,
637 config=True,
637 config=True,
638 help='The session manager class to use.'
638 help='The session manager class to use.'
639 )
639 )
640 cluster_manager_class = Type(
640 cluster_manager_class = Type(
641 default_value=ClusterManager,
641 default_value=ClusterManager,
642 config=True,
642 config=True,
643 help='The cluster manager class to use.'
643 help='The cluster manager class to use.'
644 )
644 )
645
645
646 config_manager_class = Type(
646 config_manager_class = Type(
647 default_value=ConfigManager,
647 default_value=ConfigManager,
648 config = True,
648 config = True,
649 help='The config manager class to use'
649 help='The config manager class to use'
650 )
650 )
651
651
652 kernel_spec_manager = Instance(KernelSpecManager)
652 kernel_spec_manager = Instance(KernelSpecManager)
653
653
654 kernel_spec_manager_class = Type(
654 kernel_spec_manager_class = Type(
655 default_value=KernelSpecManager,
655 default_value=KernelSpecManager,
656 config=True,
656 config=True,
657 help="""
657 help="""
658 The kernel spec manager class to use. Should be a subclass
658 The kernel spec manager class to use. Should be a subclass
659 of `IPython.kernel.kernelspec.KernelSpecManager`.
659 of `IPython.kernel.kernelspec.KernelSpecManager`.
660
660
661 The Api of KernelSpecManager is provisional and might change
661 The Api of KernelSpecManager is provisional and might change
662 without warning between this version of IPython and the next stable one.
662 without warning between this version of IPython and the next stable one.
663 """
663 """
664 )
664 )
665
665
666 login_handler_class = Type(
666 login_handler_class = Type(
667 default_value=LoginHandler,
667 default_value=LoginHandler,
668 klass=web.RequestHandler,
668 klass=web.RequestHandler,
669 config=True,
669 config=True,
670 help='The login handler class to use.',
670 help='The login handler class to use.',
671 )
671 )
672
672
673 logout_handler_class = Type(
673 logout_handler_class = Type(
674 default_value=LogoutHandler,
674 default_value=LogoutHandler,
675 klass=web.RequestHandler,
675 klass=web.RequestHandler,
676 config=True,
676 config=True,
677 help='The logout handler class to use.',
677 help='The logout handler class to use.',
678 )
678 )
679
679
680 trust_xheaders = Bool(False, config=True,
680 trust_xheaders = Bool(False, config=True,
681 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
681 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
682 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
682 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
683 )
683 )
684
684
685 info_file = Unicode()
685 info_file = Unicode()
686
686
687 def _info_file_default(self):
687 def _info_file_default(self):
688 info_file = "nbserver-%s.json"%os.getpid()
688 info_file = "nbserver-%s.json"%os.getpid()
689 return os.path.join(self.profile_dir.security_dir, info_file)
689 return os.path.join(self.profile_dir.security_dir, info_file)
690
690
691 pylab = Unicode('disabled', config=True,
691 pylab = Unicode('disabled', config=True,
692 help="""
692 help="""
693 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
693 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
694 """
694 """
695 )
695 )
696 def _pylab_changed(self, name, old, new):
696 def _pylab_changed(self, name, old, new):
697 """when --pylab is specified, display a warning and exit"""
697 """when --pylab is specified, display a warning and exit"""
698 if new != 'warn':
698 if new != 'warn':
699 backend = ' %s' % new
699 backend = ' %s' % new
700 else:
700 else:
701 backend = ''
701 backend = ''
702 self.log.error("Support for specifying --pylab on the command line has been removed.")
702 self.log.error("Support for specifying --pylab on the command line has been removed.")
703 self.log.error(
703 self.log.error(
704 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
704 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
705 )
705 )
706 self.exit(1)
706 self.exit(1)
707
707
708 notebook_dir = Unicode(config=True,
708 notebook_dir = Unicode(config=True,
709 help="The directory to use for notebooks and kernels."
709 help="The directory to use for notebooks and kernels."
710 )
710 )
711
711
712 def _notebook_dir_default(self):
712 def _notebook_dir_default(self):
713 if self.file_to_run:
713 if self.file_to_run:
714 return os.path.dirname(os.path.abspath(self.file_to_run))
714 return os.path.dirname(os.path.abspath(self.file_to_run))
715 else:
715 else:
716 return py3compat.getcwd()
716 return py3compat.getcwd()
717
717
718 def _notebook_dir_changed(self, name, old, new):
718 def _notebook_dir_changed(self, name, old, new):
719 """Do a bit of validation of the notebook dir."""
719 """Do a bit of validation of the notebook dir."""
720 if not os.path.isabs(new):
720 if not os.path.isabs(new):
721 # If we receive a non-absolute path, make it absolute.
721 # If we receive a non-absolute path, make it absolute.
722 self.notebook_dir = os.path.abspath(new)
722 self.notebook_dir = os.path.abspath(new)
723 return
723 return
724 if not os.path.isdir(new):
724 if not os.path.isdir(new):
725 raise TraitError("No such notebook dir: %r" % new)
725 raise TraitError("No such notebook dir: %r" % new)
726
726
727 # setting App.notebook_dir implies setting notebook and kernel dirs as well
727 # setting App.notebook_dir implies setting notebook and kernel dirs as well
728 self.config.FileContentsManager.root_dir = new
728 self.config.FileContentsManager.root_dir = new
729 self.config.MappingKernelManager.root_dir = new
729 self.config.MappingKernelManager.root_dir = new
730
730
731 server_extensions = List(Unicode(), config=True,
731 server_extensions = List(Unicode(), config=True,
732 help=("Python modules to load as notebook server extensions. "
732 help=("Python modules to load as notebook server extensions. "
733 "This is an experimental API, and may change in future releases.")
733 "This is an experimental API, and may change in future releases.")
734 )
734 )
735
735
736 def parse_command_line(self, argv=None):
736 def parse_command_line(self, argv=None):
737 super(NotebookApp, self).parse_command_line(argv)
737 super(NotebookApp, self).parse_command_line(argv)
738
738
739 if self.extra_args:
739 if self.extra_args:
740 arg0 = self.extra_args[0]
740 arg0 = self.extra_args[0]
741 f = os.path.abspath(arg0)
741 f = os.path.abspath(arg0)
742 self.argv.remove(arg0)
742 self.argv.remove(arg0)
743 if not os.path.exists(f):
743 if not os.path.exists(f):
744 self.log.critical("No such file or directory: %s", f)
744 self.log.critical("No such file or directory: %s", f)
745 self.exit(1)
745 self.exit(1)
746
746
747 # Use config here, to ensure that it takes higher priority than
747 # Use config here, to ensure that it takes higher priority than
748 # anything that comes from the profile.
748 # anything that comes from the profile.
749 c = Config()
749 c = Config()
750 if os.path.isdir(f):
750 if os.path.isdir(f):
751 c.NotebookApp.notebook_dir = f
751 c.NotebookApp.notebook_dir = f
752 elif os.path.isfile(f):
752 elif os.path.isfile(f):
753 c.NotebookApp.file_to_run = f
753 c.NotebookApp.file_to_run = f
754 self.update_config(c)
754 self.update_config(c)
755
755
756 def init_kernel_argv(self):
756 def init_kernel_argv(self):
757 """add the profile-dir to arguments to be passed to IPython kernels"""
757 """add the profile-dir to arguments to be passed to IPython kernels"""
758 # FIXME: remove special treatment of IPython kernels
758 # FIXME: remove special treatment of IPython kernels
759 # Kernel should get *absolute* path to profile directory
759 # Kernel should get *absolute* path to profile directory
760 self.ipython_kernel_argv = ["--profile-dir", self.profile_dir.location]
760 self.ipython_kernel_argv = ["--profile-dir", self.profile_dir.location]
761
761
762 def init_configurables(self):
762 def init_configurables(self):
763 # force Session default to be secure
763 # force Session default to be secure
764 default_secure(self.config)
764 default_secure(self.config)
765
765
766 self.kernel_spec_manager = self.kernel_spec_manager_class(
766 self.kernel_spec_manager = self.kernel_spec_manager_class(
767 parent=self,
767 parent=self,
768 ipython_dir=self.ipython_dir,
768 ipython_dir=self.ipython_dir,
769 )
769 )
770 self.kernel_manager = self.kernel_manager_class(
770 self.kernel_manager = self.kernel_manager_class(
771 parent=self,
771 parent=self,
772 log=self.log,
772 log=self.log,
773 ipython_kernel_argv=self.ipython_kernel_argv,
773 ipython_kernel_argv=self.ipython_kernel_argv,
774 connection_dir=self.profile_dir.security_dir,
774 connection_dir=self.profile_dir.security_dir,
775 )
775 )
776 self.contents_manager = self.contents_manager_class(
776 self.contents_manager = self.contents_manager_class(
777 parent=self,
777 parent=self,
778 log=self.log,
778 log=self.log,
779 )
779 )
780 self.session_manager = self.session_manager_class(
780 self.session_manager = self.session_manager_class(
781 parent=self,
781 parent=self,
782 log=self.log,
782 log=self.log,
783 kernel_manager=self.kernel_manager,
783 kernel_manager=self.kernel_manager,
784 contents_manager=self.contents_manager,
784 contents_manager=self.contents_manager,
785 )
785 )
786 self.cluster_manager = self.cluster_manager_class(
786 self.cluster_manager = self.cluster_manager_class(
787 parent=self,
787 parent=self,
788 log=self.log,
788 log=self.log,
789 )
789 )
790
790
791 self.config_manager = self.config_manager_class(
791 self.config_manager = self.config_manager_class(
792 parent=self,
792 parent=self,
793 log=self.log,
793 log=self.log,
794 profile_dir=self.profile_dir.location,
794 profile_dir=self.profile_dir.location,
795 )
795 )
796
796
797 def init_logging(self):
797 def init_logging(self):
798 # This prevents double log messages because tornado use a root logger that
798 # This prevents double log messages because tornado use a root logger that
799 # self.log is a child of. The logging module dipatches log messages to a log
799 # self.log is a child of. The logging module dipatches log messages to a log
800 # and all of its ancenstors until propagate is set to False.
800 # and all of its ancenstors until propagate is set to False.
801 self.log.propagate = False
801 self.log.propagate = False
802
802
803 for log in app_log, access_log, gen_log:
803 for log in app_log, access_log, gen_log:
804 # consistent log output name (NotebookApp instead of tornado.access, etc.)
804 # consistent log output name (NotebookApp instead of tornado.access, etc.)
805 log.name = self.log.name
805 log.name = self.log.name
806 # hook up tornado 3's loggers to our app handlers
806 # hook up tornado 3's loggers to our app handlers
807 logger = logging.getLogger('tornado')
807 logger = logging.getLogger('tornado')
808 logger.propagate = True
808 logger.propagate = True
809 logger.parent = self.log
809 logger.parent = self.log
810 logger.setLevel(self.log.level)
810 logger.setLevel(self.log.level)
811
811
812 def init_webapp(self):
812 def init_webapp(self):
813 """initialize tornado webapp and httpserver"""
813 """initialize tornado webapp and httpserver"""
814 self.tornado_settings['allow_origin'] = self.allow_origin
814 self.tornado_settings['allow_origin'] = self.allow_origin
815 if self.allow_origin_pat:
815 if self.allow_origin_pat:
816 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
816 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
817 self.tornado_settings['allow_credentials'] = self.allow_credentials
817 self.tornado_settings['allow_credentials'] = self.allow_credentials
818 # ensure default_url starts with base_url
818 # ensure default_url starts with base_url
819 if not self.default_url.startswith(self.base_url):
819 if not self.default_url.startswith(self.base_url):
820 self.default_url = url_path_join(self.base_url, self.default_url)
820 self.default_url = url_path_join(self.base_url, self.default_url)
821
821
822 self.web_app = NotebookWebApplication(
822 self.web_app = NotebookWebApplication(
823 self, self.kernel_manager, self.contents_manager,
823 self, self.kernel_manager, self.contents_manager,
824 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
824 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
825 self.config_manager,
825 self.config_manager,
826 self.log, self.base_url, self.default_url, self.tornado_settings,
826 self.log, self.base_url, self.default_url, self.tornado_settings,
827 self.jinja_environment_options
827 self.jinja_environment_options
828 )
828 )
829 if self.certfile:
829 if self.certfile:
830 ssl_options = dict(certfile=self.certfile)
830 ssl_options = dict(certfile=self.certfile)
831 if self.keyfile:
831 if self.keyfile:
832 ssl_options['keyfile'] = self.keyfile
832 ssl_options['keyfile'] = self.keyfile
833 else:
833 else:
834 ssl_options = None
834 ssl_options = None
835 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
835 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
836 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
836 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
837 xheaders=self.trust_xheaders)
837 xheaders=self.trust_xheaders)
838
838
839 success = None
839 success = None
840 for port in random_ports(self.port, self.port_retries+1):
840 for port in random_ports(self.port, self.port_retries+1):
841 try:
841 try:
842 self.http_server.listen(port, self.ip)
842 self.http_server.listen(port, self.ip)
843 except socket.error as e:
843 except socket.error as e:
844 if e.errno == errno.EADDRINUSE:
844 if e.errno == errno.EADDRINUSE:
845 self.log.info('The port %i is already in use, trying another random port.' % port)
845 self.log.info('The port %i is already in use, trying another random port.' % port)
846 continue
846 continue
847 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
847 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
848 self.log.warn("Permission to listen on port %i denied" % port)
848 self.log.warn("Permission to listen on port %i denied" % port)
849 continue
849 continue
850 else:
850 else:
851 raise
851 raise
852 else:
852 else:
853 self.port = port
853 self.port = port
854 success = True
854 success = True
855 break
855 break
856 if not success:
856 if not success:
857 self.log.critical('ERROR: the notebook server could not be started because '
857 self.log.critical('ERROR: the notebook server could not be started because '
858 'no available port could be found.')
858 'no available port could be found.')
859 self.exit(1)
859 self.exit(1)
860
860
861 @property
861 @property
862 def display_url(self):
862 def display_url(self):
863 ip = self.ip if self.ip else '[all ip addresses on your system]'
863 ip = self.ip if self.ip else '[all ip addresses on your system]'
864 return self._url(ip)
864 return self._url(ip)
865
865
866 @property
866 @property
867 def connection_url(self):
867 def connection_url(self):
868 ip = self.ip if self.ip else 'localhost'
868 ip = self.ip if self.ip else 'localhost'
869 return self._url(ip)
869 return self._url(ip)
870
870
871 def _url(self, ip):
871 def _url(self, ip):
872 proto = 'https' if self.certfile else 'http'
872 proto = 'https' if self.certfile else 'http'
873 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
873 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
874
874
875 def init_terminals(self):
875 def init_terminals(self):
876 try:
876 try:
877 from .terminal import initialize
877 from .terminal import initialize
878 initialize(self.web_app)
878 initialize(self.web_app)
879 self.web_app.settings['terminals_available'] = True
879 self.web_app.settings['terminals_available'] = True
880 except ImportError as e:
880 except ImportError as e:
881 log = self.log.debug if sys.platform == 'win32' else self.log.warn
881 log = self.log.debug if sys.platform == 'win32' else self.log.warn
882 log("Terminals not available (error was %s)", e)
882 log("Terminals not available (error was %s)", e)
883
883
884 def init_signal(self):
884 def init_signal(self):
885 if not sys.platform.startswith('win'):
885 if not sys.platform.startswith('win'):
886 signal.signal(signal.SIGINT, self._handle_sigint)
886 signal.signal(signal.SIGINT, self._handle_sigint)
887 signal.signal(signal.SIGTERM, self._signal_stop)
887 signal.signal(signal.SIGTERM, self._signal_stop)
888 if hasattr(signal, 'SIGUSR1'):
888 if hasattr(signal, 'SIGUSR1'):
889 # Windows doesn't support SIGUSR1
889 # Windows doesn't support SIGUSR1
890 signal.signal(signal.SIGUSR1, self._signal_info)
890 signal.signal(signal.SIGUSR1, self._signal_info)
891 if hasattr(signal, 'SIGINFO'):
891 if hasattr(signal, 'SIGINFO'):
892 # only on BSD-based systems
892 # only on BSD-based systems
893 signal.signal(signal.SIGINFO, self._signal_info)
893 signal.signal(signal.SIGINFO, self._signal_info)
894
894
895 def _handle_sigint(self, sig, frame):
895 def _handle_sigint(self, sig, frame):
896 """SIGINT handler spawns confirmation dialog"""
896 """SIGINT handler spawns confirmation dialog"""
897 # register more forceful signal handler for ^C^C case
897 # register more forceful signal handler for ^C^C case
898 signal.signal(signal.SIGINT, self._signal_stop)
898 signal.signal(signal.SIGINT, self._signal_stop)
899 # request confirmation dialog in bg thread, to avoid
899 # request confirmation dialog in bg thread, to avoid
900 # blocking the App
900 # blocking the App
901 thread = threading.Thread(target=self._confirm_exit)
901 thread = threading.Thread(target=self._confirm_exit)
902 thread.daemon = True
902 thread.daemon = True
903 thread.start()
903 thread.start()
904
904
905 def _restore_sigint_handler(self):
905 def _restore_sigint_handler(self):
906 """callback for restoring original SIGINT handler"""
906 """callback for restoring original SIGINT handler"""
907 signal.signal(signal.SIGINT, self._handle_sigint)
907 signal.signal(signal.SIGINT, self._handle_sigint)
908
908
909 def _confirm_exit(self):
909 def _confirm_exit(self):
910 """confirm shutdown on ^C
910 """confirm shutdown on ^C
911
911
912 A second ^C, or answering 'y' within 5s will cause shutdown,
912 A second ^C, or answering 'y' within 5s will cause shutdown,
913 otherwise original SIGINT handler will be restored.
913 otherwise original SIGINT handler will be restored.
914
914
915 This doesn't work on Windows.
915 This doesn't work on Windows.
916 """
916 """
917 info = self.log.info
917 info = self.log.info
918 info('interrupted')
918 info('interrupted')
919 print(self.notebook_info())
919 print(self.notebook_info())
920 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
920 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
921 sys.stdout.flush()
921 sys.stdout.flush()
922 r,w,x = select.select([sys.stdin], [], [], 5)
922 r,w,x = select.select([sys.stdin], [], [], 5)
923 if r:
923 if r:
924 line = sys.stdin.readline()
924 line = sys.stdin.readline()
925 if line.lower().startswith('y') and 'n' not in line.lower():
925 if line.lower().startswith('y') and 'n' not in line.lower():
926 self.log.critical("Shutdown confirmed")
926 self.log.critical("Shutdown confirmed")
927 ioloop.IOLoop.current().stop()
927 ioloop.IOLoop.current().stop()
928 return
928 return
929 else:
929 else:
930 print("No answer for 5s:", end=' ')
930 print("No answer for 5s:", end=' ')
931 print("resuming operation...")
931 print("resuming operation...")
932 # no answer, or answer is no:
932 # no answer, or answer is no:
933 # set it back to original SIGINT handler
933 # set it back to original SIGINT handler
934 # use IOLoop.add_callback because signal.signal must be called
934 # use IOLoop.add_callback because signal.signal must be called
935 # from main thread
935 # from main thread
936 ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
936 ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
937
937
938 def _signal_stop(self, sig, frame):
938 def _signal_stop(self, sig, frame):
939 self.log.critical("received signal %s, stopping", sig)
939 self.log.critical("received signal %s, stopping", sig)
940 ioloop.IOLoop.current().stop()
940 ioloop.IOLoop.current().stop()
941
941
942 def _signal_info(self, sig, frame):
942 def _signal_info(self, sig, frame):
943 print(self.notebook_info())
943 print(self.notebook_info())
944
944
945 def init_components(self):
945 def init_components(self):
946 """Check the components submodule, and warn if it's unclean"""
946 """Check the components submodule, and warn if it's unclean"""
947 status = submodule.check_submodule_status()
947 status = submodule.check_submodule_status()
948 if status == 'missing':
948 if status == 'missing':
949 self.log.warn("components submodule missing, running `git submodule update`")
949 self.log.warn("components submodule missing, running `git submodule update`")
950 submodule.update_submodules(submodule.ipython_parent())
950 submodule.update_submodules(submodule.ipython_parent())
951 elif status == 'unclean':
951 elif status == 'unclean':
952 self.log.warn("components submodule unclean, you may see 404s on static/components")
952 self.log.warn("components submodule unclean, you may see 404s on static/components")
953 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
953 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
954
954
955 def init_server_extensions(self):
955 def init_server_extensions(self):
956 """Load any extensions specified by config.
956 """Load any extensions specified by config.
957
957
958 Import the module, then call the load_jupyter_server_extension function,
958 Import the module, then call the load_jupyter_server_extension function,
959 if one exists.
959 if one exists.
960
960
961 The extension API is experimental, and may change in future releases.
961 The extension API is experimental, and may change in future releases.
962 """
962 """
963 for modulename in self.server_extensions:
963 for modulename in self.server_extensions:
964 try:
964 try:
965 mod = importlib.import_module(modulename)
965 mod = importlib.import_module(modulename)
966 func = getattr(mod, 'load_jupyter_server_extension', None)
966 func = getattr(mod, 'load_jupyter_server_extension', None)
967 if func is not None:
967 if func is not None:
968 func(self)
968 func(self)
969 except Exception:
969 except Exception:
970 self.log.warn("Error loading server extension %s", modulename,
970 self.log.warn("Error loading server extension %s", modulename,
971 exc_info=True)
971 exc_info=True)
972
972
973 @catch_config_error
973 @catch_config_error
974 def initialize(self, argv=None):
974 def initialize(self, argv=None):
975 super(NotebookApp, self).initialize(argv)
975 super(NotebookApp, self).initialize(argv)
976 self.init_logging()
976 self.init_logging()
977 self.init_kernel_argv()
977 self.init_kernel_argv()
978 self.init_configurables()
978 self.init_configurables()
979 self.init_components()
979 self.init_components()
980 self.init_webapp()
980 self.init_webapp()
981 self.init_terminals()
981 self.init_terminals()
982 self.init_signal()
982 self.init_signal()
983 self.init_server_extensions()
983 self.init_server_extensions()
984
984
985 def cleanup_kernels(self):
985 def cleanup_kernels(self):
986 """Shutdown all kernels.
986 """Shutdown all kernels.
987
987
988 The kernels will shutdown themselves when this process no longer exists,
988 The kernels will shutdown themselves when this process no longer exists,
989 but explicit shutdown allows the KernelManagers to cleanup the connection files.
989 but explicit shutdown allows the KernelManagers to cleanup the connection files.
990 """
990 """
991 self.log.info('Shutting down kernels')
991 self.log.info('Shutting down kernels')
992 self.kernel_manager.shutdown_all()
992 self.kernel_manager.shutdown_all()
993
993
994 def notebook_info(self):
994 def notebook_info(self):
995 "Return the current working directory and the server url information"
995 "Return the current working directory and the server url information"
996 info = self.contents_manager.info_string() + "\n"
996 info = self.contents_manager.info_string() + "\n"
997 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
997 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
998 return info + "The IPython Notebook is running at: %s" % self.display_url
998 return info + "The IPython Notebook is running at: %s" % self.display_url
999
999
1000 def server_info(self):
1000 def server_info(self):
1001 """Return a JSONable dict of information about this server."""
1001 """Return a JSONable dict of information about this server."""
1002 return {'url': self.connection_url,
1002 return {'url': self.connection_url,
1003 'hostname': self.ip if self.ip else 'localhost',
1003 'hostname': self.ip if self.ip else 'localhost',
1004 'port': self.port,
1004 'port': self.port,
1005 'secure': bool(self.certfile),
1005 'secure': bool(self.certfile),
1006 'base_url': self.base_url,
1006 'base_url': self.base_url,
1007 'notebook_dir': os.path.abspath(self.notebook_dir),
1007 'notebook_dir': os.path.abspath(self.notebook_dir),
1008 'pid': os.getpid()
1008 'pid': os.getpid()
1009 }
1009 }
1010
1010
1011 def write_server_info_file(self):
1011 def write_server_info_file(self):
1012 """Write the result of server_info() to the JSON file info_file."""
1012 """Write the result of server_info() to the JSON file info_file."""
1013 with open(self.info_file, 'w') as f:
1013 with open(self.info_file, 'w') as f:
1014 json.dump(self.server_info(), f, indent=2)
1014 json.dump(self.server_info(), f, indent=2)
1015
1015
1016 def remove_server_info_file(self):
1016 def remove_server_info_file(self):
1017 """Remove the nbserver-<pid>.json file created for this server.
1017 """Remove the nbserver-<pid>.json file created for this server.
1018
1018
1019 Ignores the error raised when the file has already been removed.
1019 Ignores the error raised when the file has already been removed.
1020 """
1020 """
1021 try:
1021 try:
1022 os.unlink(self.info_file)
1022 os.unlink(self.info_file)
1023 except OSError as e:
1023 except OSError as e:
1024 if e.errno != errno.ENOENT:
1024 if e.errno != errno.ENOENT:
1025 raise
1025 raise
1026
1026
1027 def start(self):
1027 def start(self):
1028 """ Start the IPython Notebook server app, after initialization
1028 """ Start the IPython Notebook server app, after initialization
1029
1029
1030 This method takes no arguments so all configuration and initialization
1030 This method takes no arguments so all configuration and initialization
1031 must be done prior to calling this method."""
1031 must be done prior to calling this method."""
1032 if self.subapp is not None:
1032 if self.subapp is not None:
1033 return self.subapp.start()
1033 return self.subapp.start()
1034
1034
1035 info = self.log.info
1035 info = self.log.info
1036 for line in self.notebook_info().split("\n"):
1036 for line in self.notebook_info().split("\n"):
1037 info(line)
1037 info(line)
1038 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
1038 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
1039
1039
1040 self.write_server_info_file()
1040 self.write_server_info_file()
1041
1041
1042 if self.open_browser or self.file_to_run:
1042 if self.open_browser or self.file_to_run:
1043 try:
1043 try:
1044 browser = webbrowser.get(self.browser or None)
1044 browser = webbrowser.get(self.browser or None)
1045 except webbrowser.Error as e:
1045 except webbrowser.Error as e:
1046 self.log.warn('No web browser found: %s.' % e)
1046 self.log.warn('No web browser found: %s.' % e)
1047 browser = None
1047 browser = None
1048
1048
1049 if self.file_to_run:
1049 if self.file_to_run:
1050 if not os.path.exists(self.file_to_run):
1050 if not os.path.exists(self.file_to_run):
1051 self.log.critical("%s does not exist" % self.file_to_run)
1051 self.log.critical("%s does not exist" % self.file_to_run)
1052 self.exit(1)
1052 self.exit(1)
1053
1053
1054 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
1054 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
1055 uri = url_path_join('notebooks', *relpath.split(os.sep))
1055 uri = url_path_join('notebooks', *relpath.split(os.sep))
1056 else:
1056 else:
1057 uri = 'tree'
1057 uri = 'tree'
1058 if browser:
1058 if browser:
1059 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1059 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1060 new=2)
1060 new=2)
1061 threading.Thread(target=b).start()
1061 threading.Thread(target=b).start()
1062
1062
1063 self.io_loop = ioloop.IOLoop.current()
1063 self.io_loop = ioloop.IOLoop.current()
1064 try:
1064 try:
1065 self.io_loop.start()
1065 self.io_loop.start()
1066 except KeyboardInterrupt:
1066 except KeyboardInterrupt:
1067 info("Interrupted...")
1067 info("Interrupted...")
1068 finally:
1068 finally:
1069 self.cleanup_kernels()
1069 self.cleanup_kernels()
1070 self.remove_server_info_file()
1070 self.remove_server_info_file()
1071
1071
1072 def stop(self):
1072 def stop(self):
1073 def _stop():
1073 def _stop():
1074 self.http_server.stop()
1074 self.http_server.stop()
1075 self.io_loop.stop()
1075 self.io_loop.stop()
1076 self.io_loop.add_callback(_stop)
1076 self.io_loop.add_callback(_stop)
1077
1077
1078
1078
1079 def list_running_servers(profile='default'):
1079 def list_running_servers(profile='default'):
1080 """Iterate over the server info files of running notebook servers.
1080 """Iterate over the server info files of running notebook servers.
1081
1081
1082 Given a profile name, find nbserver-* files in the security directory of
1082 Given a profile name, find nbserver-* files in the security directory of
1083 that profile, and yield dicts of their information, each one pertaining to
1083 that profile, and yield dicts of their information, each one pertaining to
1084 a currently running notebook server instance.
1084 a currently running notebook server instance.
1085 """
1085 """
1086 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1086 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1087 for file in os.listdir(pd.security_dir):
1087 for file in os.listdir(pd.security_dir):
1088 if file.startswith('nbserver-'):
1088 if file.startswith('nbserver-'):
1089 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1089 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1090 info = json.load(f)
1090 info = json.load(f)
1091
1091
1092 # Simple check whether that process is really still running
1092 # Simple check whether that process is really still running
1093 # Also remove leftover files from IPython 2.x without a pid field
1093 # Also remove leftover files from IPython 2.x without a pid field
1094 if ('pid' in info) and check_pid(info['pid']):
1094 if ('pid' in info) and check_pid(info['pid']):
1095 yield info
1095 yield info
1096 else:
1096 else:
1097 # If the process has died, try to delete its info file
1097 # If the process has died, try to delete its info file
1098 try:
1098 try:
1099 os.unlink(file)
1099 os.unlink(file)
1100 except OSError:
1100 except OSError:
1101 pass # TODO: This should warn or log or something
1101 pass # TODO: This should warn or log or something
1102 #-----------------------------------------------------------------------------
1102 #-----------------------------------------------------------------------------
1103 # Main entry point
1103 # Main entry point
1104 #-----------------------------------------------------------------------------
1104 #-----------------------------------------------------------------------------
1105
1105
1106 launch_new_instance = NotebookApp.launch_instance
1106 launch_new_instance = NotebookApp.launch_instance
1107
1107
@@ -1,17 +1,11 b''
1 #-----------------------------------------------------------------------------
1 # Copyright (c) IPython Development Team.
2 # Copyright (C) 2013 The IPython Development Team
2 # Distributed under the terms of the Modified BSD License.
3 #
4 # Distributed under the terms of the BSD License. The full license is in
5 # the file COPYING.txt, distributed as part of this software.
6 #-----------------------------------------------------------------------------
7
3
8 #-----------------------------------------------------------------------------
4 # Verify zmq version dependency
9 # Verify zmq version dependency >= 2.1.11
10 #-----------------------------------------------------------------------------
11
5
12 from IPython.utils.zmqrelated import check_for_zmq
6 from IPython.utils.zmqrelated import check_for_zmq
13
7
14 check_for_zmq('2.1.11', 'IPython.kernel.zmq')
8 check_for_zmq('13', 'IPython.kernel.zmq')
15
9
16 from .session import Session
10 from .session import Session
17
11
@@ -1,519 +1,519 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """IPython Test Suite Runner.
2 """IPython Test Suite Runner.
3
3
4 This module provides a main entry point to a user script to test IPython
4 This module provides a main entry point to a user script to test IPython
5 itself from the command line. There are two ways of running this script:
5 itself from the command line. There are two ways of running this script:
6
6
7 1. With the syntax `iptest all`. This runs our entire test suite by
7 1. With the syntax `iptest all`. This runs our entire test suite by
8 calling this script (with different arguments) recursively. This
8 calling this script (with different arguments) recursively. This
9 causes modules and package to be tested in different processes, using nose
9 causes modules and package to be tested in different processes, using nose
10 or trial where appropriate.
10 or trial where appropriate.
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 the script simply calls nose, but with special command line flags and
12 the script simply calls nose, but with special command line flags and
13 plugins loaded.
13 plugins loaded.
14
14
15 """
15 """
16
16
17 # Copyright (c) IPython Development Team.
17 # Copyright (c) IPython Development Team.
18 # Distributed under the terms of the Modified BSD License.
18 # Distributed under the terms of the Modified BSD License.
19
19
20 from __future__ import print_function
20 from __future__ import print_function
21
21
22 import glob
22 import glob
23 from io import BytesIO
23 from io import BytesIO
24 import os
24 import os
25 import os.path as path
25 import os.path as path
26 import sys
26 import sys
27 from threading import Thread, Lock, Event
27 from threading import Thread, Lock, Event
28 import warnings
28 import warnings
29
29
30 import nose.plugins.builtin
30 import nose.plugins.builtin
31 from nose.plugins.xunit import Xunit
31 from nose.plugins.xunit import Xunit
32 from nose import SkipTest
32 from nose import SkipTest
33 from nose.core import TestProgram
33 from nose.core import TestProgram
34 from nose.plugins import Plugin
34 from nose.plugins import Plugin
35 from nose.util import safe_str
35 from nose.util import safe_str
36
36
37 from IPython.utils.process import is_cmd_found
37 from IPython.utils.process import is_cmd_found
38 from IPython.utils.py3compat import bytes_to_str
38 from IPython.utils.py3compat import bytes_to_str
39 from IPython.utils.importstring import import_item
39 from IPython.utils.importstring import import_item
40 from IPython.testing.plugin.ipdoctest import IPythonDoctest
40 from IPython.testing.plugin.ipdoctest import IPythonDoctest
41 from IPython.external.decorators import KnownFailure, knownfailureif
41 from IPython.external.decorators import KnownFailure, knownfailureif
42
42
43 pjoin = path.join
43 pjoin = path.join
44
44
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Globals
47 # Globals
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49
49
50
50
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52 # Warnings control
52 # Warnings control
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54
54
55 # Twisted generates annoying warnings with Python 2.6, as will do other code
55 # Twisted generates annoying warnings with Python 2.6, as will do other code
56 # that imports 'sets' as of today
56 # that imports 'sets' as of today
57 warnings.filterwarnings('ignore', 'the sets module is deprecated',
57 warnings.filterwarnings('ignore', 'the sets module is deprecated',
58 DeprecationWarning )
58 DeprecationWarning )
59
59
60 # This one also comes from Twisted
60 # This one also comes from Twisted
61 warnings.filterwarnings('ignore', 'the sha module is deprecated',
61 warnings.filterwarnings('ignore', 'the sha module is deprecated',
62 DeprecationWarning)
62 DeprecationWarning)
63
63
64 # Wx on Fedora11 spits these out
64 # Wx on Fedora11 spits these out
65 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
65 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
66 UserWarning)
66 UserWarning)
67
67
68 # ------------------------------------------------------------------------------
68 # ------------------------------------------------------------------------------
69 # Monkeypatch Xunit to count known failures as skipped.
69 # Monkeypatch Xunit to count known failures as skipped.
70 # ------------------------------------------------------------------------------
70 # ------------------------------------------------------------------------------
71 def monkeypatch_xunit():
71 def monkeypatch_xunit():
72 try:
72 try:
73 knownfailureif(True)(lambda: None)()
73 knownfailureif(True)(lambda: None)()
74 except Exception as e:
74 except Exception as e:
75 KnownFailureTest = type(e)
75 KnownFailureTest = type(e)
76
76
77 def addError(self, test, err, capt=None):
77 def addError(self, test, err, capt=None):
78 if issubclass(err[0], KnownFailureTest):
78 if issubclass(err[0], KnownFailureTest):
79 err = (SkipTest,) + err[1:]
79 err = (SkipTest,) + err[1:]
80 return self.orig_addError(test, err, capt)
80 return self.orig_addError(test, err, capt)
81
81
82 Xunit.orig_addError = Xunit.addError
82 Xunit.orig_addError = Xunit.addError
83 Xunit.addError = addError
83 Xunit.addError = addError
84
84
85 #-----------------------------------------------------------------------------
85 #-----------------------------------------------------------------------------
86 # Check which dependencies are installed and greater than minimum version.
86 # Check which dependencies are installed and greater than minimum version.
87 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
88 def extract_version(mod):
88 def extract_version(mod):
89 return mod.__version__
89 return mod.__version__
90
90
91 def test_for(item, min_version=None, callback=extract_version):
91 def test_for(item, min_version=None, callback=extract_version):
92 """Test to see if item is importable, and optionally check against a minimum
92 """Test to see if item is importable, and optionally check against a minimum
93 version.
93 version.
94
94
95 If min_version is given, the default behavior is to check against the
95 If min_version is given, the default behavior is to check against the
96 `__version__` attribute of the item, but specifying `callback` allows you to
96 `__version__` attribute of the item, but specifying `callback` allows you to
97 extract the value you are interested in. e.g::
97 extract the value you are interested in. e.g::
98
98
99 In [1]: import sys
99 In [1]: import sys
100
100
101 In [2]: from IPython.testing.iptest import test_for
101 In [2]: from IPython.testing.iptest import test_for
102
102
103 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
103 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
104 Out[3]: True
104 Out[3]: True
105
105
106 """
106 """
107 try:
107 try:
108 check = import_item(item)
108 check = import_item(item)
109 except (ImportError, RuntimeError):
109 except (ImportError, RuntimeError):
110 # GTK reports Runtime error if it can't be initialized even if it's
110 # GTK reports Runtime error if it can't be initialized even if it's
111 # importable.
111 # importable.
112 return False
112 return False
113 else:
113 else:
114 if min_version:
114 if min_version:
115 if callback:
115 if callback:
116 # extra processing step to get version to compare
116 # extra processing step to get version to compare
117 check = callback(check)
117 check = callback(check)
118
118
119 return check >= min_version
119 return check >= min_version
120 else:
120 else:
121 return True
121 return True
122
122
123 # Global dict where we can store information on what we have and what we don't
123 # Global dict where we can store information on what we have and what we don't
124 # have available at test run time
124 # have available at test run time
125 have = {}
125 have = {}
126
126
127 have['curses'] = test_for('_curses')
127 have['curses'] = test_for('_curses')
128 have['matplotlib'] = test_for('matplotlib')
128 have['matplotlib'] = test_for('matplotlib')
129 have['numpy'] = test_for('numpy')
129 have['numpy'] = test_for('numpy')
130 have['pexpect'] = test_for('IPython.external.pexpect')
130 have['pexpect'] = test_for('IPython.external.pexpect')
131 have['pymongo'] = test_for('pymongo')
131 have['pymongo'] = test_for('pymongo')
132 have['pygments'] = test_for('pygments')
132 have['pygments'] = test_for('pygments')
133 have['qt'] = test_for('IPython.external.qt')
133 have['qt'] = test_for('IPython.external.qt')
134 have['sqlite3'] = test_for('sqlite3')
134 have['sqlite3'] = test_for('sqlite3')
135 have['tornado'] = test_for('tornado.version_info', (4,0), callback=None)
135 have['tornado'] = test_for('tornado.version_info', (4,0), callback=None)
136 have['jinja2'] = test_for('jinja2')
136 have['jinja2'] = test_for('jinja2')
137 have['mistune'] = test_for('mistune')
137 have['mistune'] = test_for('mistune')
138 have['requests'] = test_for('requests')
138 have['requests'] = test_for('requests')
139 have['sphinx'] = test_for('sphinx')
139 have['sphinx'] = test_for('sphinx')
140 have['jsonschema'] = test_for('jsonschema')
140 have['jsonschema'] = test_for('jsonschema')
141 have['terminado'] = test_for('terminado')
141 have['terminado'] = test_for('terminado')
142 have['casperjs'] = is_cmd_found('casperjs')
142 have['casperjs'] = is_cmd_found('casperjs')
143 have['phantomjs'] = is_cmd_found('phantomjs')
143 have['phantomjs'] = is_cmd_found('phantomjs')
144 have['slimerjs'] = is_cmd_found('slimerjs')
144 have['slimerjs'] = is_cmd_found('slimerjs')
145
145
146 min_zmq = (2,1,11)
146 min_zmq = (13,)
147
147
148 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
148 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
149
149
150 #-----------------------------------------------------------------------------
150 #-----------------------------------------------------------------------------
151 # Test suite definitions
151 # Test suite definitions
152 #-----------------------------------------------------------------------------
152 #-----------------------------------------------------------------------------
153
153
154 test_group_names = ['parallel', 'kernel', 'kernel.inprocess', 'config', 'core',
154 test_group_names = ['parallel', 'kernel', 'kernel.inprocess', 'config', 'core',
155 'extensions', 'lib', 'terminal', 'testing', 'utils',
155 'extensions', 'lib', 'terminal', 'testing', 'utils',
156 'nbformat', 'qt', 'html', 'nbconvert'
156 'nbformat', 'qt', 'html', 'nbconvert'
157 ]
157 ]
158
158
159 class TestSection(object):
159 class TestSection(object):
160 def __init__(self, name, includes):
160 def __init__(self, name, includes):
161 self.name = name
161 self.name = name
162 self.includes = includes
162 self.includes = includes
163 self.excludes = []
163 self.excludes = []
164 self.dependencies = []
164 self.dependencies = []
165 self.enabled = True
165 self.enabled = True
166
166
167 def exclude(self, module):
167 def exclude(self, module):
168 if not module.startswith('IPython'):
168 if not module.startswith('IPython'):
169 module = self.includes[0] + "." + module
169 module = self.includes[0] + "." + module
170 self.excludes.append(module.replace('.', os.sep))
170 self.excludes.append(module.replace('.', os.sep))
171
171
172 def requires(self, *packages):
172 def requires(self, *packages):
173 self.dependencies.extend(packages)
173 self.dependencies.extend(packages)
174
174
175 @property
175 @property
176 def will_run(self):
176 def will_run(self):
177 return self.enabled and all(have[p] for p in self.dependencies)
177 return self.enabled and all(have[p] for p in self.dependencies)
178
178
179 # Name -> (include, exclude, dependencies_met)
179 # Name -> (include, exclude, dependencies_met)
180 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
180 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
181
181
182 # Exclusions and dependencies
182 # Exclusions and dependencies
183 # ---------------------------
183 # ---------------------------
184
184
185 # core:
185 # core:
186 sec = test_sections['core']
186 sec = test_sections['core']
187 if not have['sqlite3']:
187 if not have['sqlite3']:
188 sec.exclude('tests.test_history')
188 sec.exclude('tests.test_history')
189 sec.exclude('history')
189 sec.exclude('history')
190 if not have['matplotlib']:
190 if not have['matplotlib']:
191 sec.exclude('pylabtools'),
191 sec.exclude('pylabtools'),
192 sec.exclude('tests.test_pylabtools')
192 sec.exclude('tests.test_pylabtools')
193
193
194 # lib:
194 # lib:
195 sec = test_sections['lib']
195 sec = test_sections['lib']
196 if not have['zmq']:
196 if not have['zmq']:
197 sec.exclude('kernel')
197 sec.exclude('kernel')
198 # We do this unconditionally, so that the test suite doesn't import
198 # We do this unconditionally, so that the test suite doesn't import
199 # gtk, changing the default encoding and masking some unicode bugs.
199 # gtk, changing the default encoding and masking some unicode bugs.
200 sec.exclude('inputhookgtk')
200 sec.exclude('inputhookgtk')
201 # We also do this unconditionally, because wx can interfere with Unix signals.
201 # We also do this unconditionally, because wx can interfere with Unix signals.
202 # There are currently no tests for it anyway.
202 # There are currently no tests for it anyway.
203 sec.exclude('inputhookwx')
203 sec.exclude('inputhookwx')
204 # Testing inputhook will need a lot of thought, to figure out
204 # Testing inputhook will need a lot of thought, to figure out
205 # how to have tests that don't lock up with the gui event
205 # how to have tests that don't lock up with the gui event
206 # loops in the picture
206 # loops in the picture
207 sec.exclude('inputhook')
207 sec.exclude('inputhook')
208
208
209 # testing:
209 # testing:
210 sec = test_sections['testing']
210 sec = test_sections['testing']
211 # These have to be skipped on win32 because they use echo, rm, cd, etc.
211 # These have to be skipped on win32 because they use echo, rm, cd, etc.
212 # See ticket https://github.com/ipython/ipython/issues/87
212 # See ticket https://github.com/ipython/ipython/issues/87
213 if sys.platform == 'win32':
213 if sys.platform == 'win32':
214 sec.exclude('plugin.test_exampleip')
214 sec.exclude('plugin.test_exampleip')
215 sec.exclude('plugin.dtexample')
215 sec.exclude('plugin.dtexample')
216
216
217 # terminal:
217 # terminal:
218 if (not have['pexpect']) or (not have['zmq']):
218 if (not have['pexpect']) or (not have['zmq']):
219 test_sections['terminal'].exclude('console')
219 test_sections['terminal'].exclude('console')
220
220
221 # parallel
221 # parallel
222 sec = test_sections['parallel']
222 sec = test_sections['parallel']
223 sec.requires('zmq')
223 sec.requires('zmq')
224 if not have['pymongo']:
224 if not have['pymongo']:
225 sec.exclude('controller.mongodb')
225 sec.exclude('controller.mongodb')
226 sec.exclude('tests.test_mongodb')
226 sec.exclude('tests.test_mongodb')
227
227
228 # kernel:
228 # kernel:
229 sec = test_sections['kernel']
229 sec = test_sections['kernel']
230 sec.requires('zmq')
230 sec.requires('zmq')
231 # The in-process kernel tests are done in a separate section
231 # The in-process kernel tests are done in a separate section
232 sec.exclude('inprocess')
232 sec.exclude('inprocess')
233 # importing gtk sets the default encoding, which we want to avoid
233 # importing gtk sets the default encoding, which we want to avoid
234 sec.exclude('zmq.gui.gtkembed')
234 sec.exclude('zmq.gui.gtkembed')
235 sec.exclude('zmq.gui.gtk3embed')
235 sec.exclude('zmq.gui.gtk3embed')
236 if not have['matplotlib']:
236 if not have['matplotlib']:
237 sec.exclude('zmq.pylab')
237 sec.exclude('zmq.pylab')
238
238
239 # kernel.inprocess:
239 # kernel.inprocess:
240 test_sections['kernel.inprocess'].requires('zmq')
240 test_sections['kernel.inprocess'].requires('zmq')
241
241
242 # extensions:
242 # extensions:
243 sec = test_sections['extensions']
243 sec = test_sections['extensions']
244 # This is deprecated in favour of rpy2
244 # This is deprecated in favour of rpy2
245 sec.exclude('rmagic')
245 sec.exclude('rmagic')
246 # autoreload does some strange stuff, so move it to its own test section
246 # autoreload does some strange stuff, so move it to its own test section
247 sec.exclude('autoreload')
247 sec.exclude('autoreload')
248 sec.exclude('tests.test_autoreload')
248 sec.exclude('tests.test_autoreload')
249 test_sections['autoreload'] = TestSection('autoreload',
249 test_sections['autoreload'] = TestSection('autoreload',
250 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
250 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
251 test_group_names.append('autoreload')
251 test_group_names.append('autoreload')
252
252
253 # qt:
253 # qt:
254 test_sections['qt'].requires('zmq', 'qt', 'pygments')
254 test_sections['qt'].requires('zmq', 'qt', 'pygments')
255
255
256 # html:
256 # html:
257 sec = test_sections['html']
257 sec = test_sections['html']
258 sec.requires('zmq', 'tornado', 'requests', 'sqlite3', 'jsonschema')
258 sec.requires('zmq', 'tornado', 'requests', 'sqlite3', 'jsonschema')
259 # The notebook 'static' directory contains JS, css and other
259 # The notebook 'static' directory contains JS, css and other
260 # files for web serving. Occasionally projects may put a .py
260 # files for web serving. Occasionally projects may put a .py
261 # file in there (MathJax ships a conf.py), so we might as
261 # file in there (MathJax ships a conf.py), so we might as
262 # well play it safe and skip the whole thing.
262 # well play it safe and skip the whole thing.
263 sec.exclude('static')
263 sec.exclude('static')
264 sec.exclude('tasks')
264 sec.exclude('tasks')
265 if not have['jinja2']:
265 if not have['jinja2']:
266 sec.exclude('notebookapp')
266 sec.exclude('notebookapp')
267 if not have['pygments'] or not have['jinja2']:
267 if not have['pygments'] or not have['jinja2']:
268 sec.exclude('nbconvert')
268 sec.exclude('nbconvert')
269 if not have['terminado']:
269 if not have['terminado']:
270 sec.exclude('terminal')
270 sec.exclude('terminal')
271
271
272 # config:
272 # config:
273 # Config files aren't really importable stand-alone
273 # Config files aren't really importable stand-alone
274 test_sections['config'].exclude('profile')
274 test_sections['config'].exclude('profile')
275
275
276 # nbconvert:
276 # nbconvert:
277 sec = test_sections['nbconvert']
277 sec = test_sections['nbconvert']
278 sec.requires('pygments', 'jinja2', 'jsonschema', 'mistune')
278 sec.requires('pygments', 'jinja2', 'jsonschema', 'mistune')
279 # Exclude nbconvert directories containing config files used to test.
279 # Exclude nbconvert directories containing config files used to test.
280 # Executing the config files with iptest would cause an exception.
280 # Executing the config files with iptest would cause an exception.
281 sec.exclude('tests.files')
281 sec.exclude('tests.files')
282 sec.exclude('exporters.tests.files')
282 sec.exclude('exporters.tests.files')
283 if not have['tornado']:
283 if not have['tornado']:
284 sec.exclude('nbconvert.post_processors.serve')
284 sec.exclude('nbconvert.post_processors.serve')
285 sec.exclude('nbconvert.post_processors.tests.test_serve')
285 sec.exclude('nbconvert.post_processors.tests.test_serve')
286
286
287 # nbformat:
287 # nbformat:
288 test_sections['nbformat'].requires('jsonschema')
288 test_sections['nbformat'].requires('jsonschema')
289
289
290 #-----------------------------------------------------------------------------
290 #-----------------------------------------------------------------------------
291 # Functions and classes
291 # Functions and classes
292 #-----------------------------------------------------------------------------
292 #-----------------------------------------------------------------------------
293
293
294 def check_exclusions_exist():
294 def check_exclusions_exist():
295 from IPython.utils.path import get_ipython_package_dir
295 from IPython.utils.path import get_ipython_package_dir
296 from IPython.utils.warn import warn
296 from IPython.utils.warn import warn
297 parent = os.path.dirname(get_ipython_package_dir())
297 parent = os.path.dirname(get_ipython_package_dir())
298 for sec in test_sections:
298 for sec in test_sections:
299 for pattern in sec.exclusions:
299 for pattern in sec.exclusions:
300 fullpath = pjoin(parent, pattern)
300 fullpath = pjoin(parent, pattern)
301 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
301 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
302 warn("Excluding nonexistent file: %r" % pattern)
302 warn("Excluding nonexistent file: %r" % pattern)
303
303
304
304
305 class ExclusionPlugin(Plugin):
305 class ExclusionPlugin(Plugin):
306 """A nose plugin to effect our exclusions of files and directories.
306 """A nose plugin to effect our exclusions of files and directories.
307 """
307 """
308 name = 'exclusions'
308 name = 'exclusions'
309 score = 3000 # Should come before any other plugins
309 score = 3000 # Should come before any other plugins
310
310
311 def __init__(self, exclude_patterns=None):
311 def __init__(self, exclude_patterns=None):
312 """
312 """
313 Parameters
313 Parameters
314 ----------
314 ----------
315
315
316 exclude_patterns : sequence of strings, optional
316 exclude_patterns : sequence of strings, optional
317 Filenames containing these patterns (as raw strings, not as regular
317 Filenames containing these patterns (as raw strings, not as regular
318 expressions) are excluded from the tests.
318 expressions) are excluded from the tests.
319 """
319 """
320 self.exclude_patterns = exclude_patterns or []
320 self.exclude_patterns = exclude_patterns or []
321 super(ExclusionPlugin, self).__init__()
321 super(ExclusionPlugin, self).__init__()
322
322
323 def options(self, parser, env=os.environ):
323 def options(self, parser, env=os.environ):
324 Plugin.options(self, parser, env)
324 Plugin.options(self, parser, env)
325
325
326 def configure(self, options, config):
326 def configure(self, options, config):
327 Plugin.configure(self, options, config)
327 Plugin.configure(self, options, config)
328 # Override nose trying to disable plugin.
328 # Override nose trying to disable plugin.
329 self.enabled = True
329 self.enabled = True
330
330
331 def wantFile(self, filename):
331 def wantFile(self, filename):
332 """Return whether the given filename should be scanned for tests.
332 """Return whether the given filename should be scanned for tests.
333 """
333 """
334 if any(pat in filename for pat in self.exclude_patterns):
334 if any(pat in filename for pat in self.exclude_patterns):
335 return False
335 return False
336 return None
336 return None
337
337
338 def wantDirectory(self, directory):
338 def wantDirectory(self, directory):
339 """Return whether the given directory should be scanned for tests.
339 """Return whether the given directory should be scanned for tests.
340 """
340 """
341 if any(pat in directory for pat in self.exclude_patterns):
341 if any(pat in directory for pat in self.exclude_patterns):
342 return False
342 return False
343 return None
343 return None
344
344
345
345
346 class StreamCapturer(Thread):
346 class StreamCapturer(Thread):
347 daemon = True # Don't hang if main thread crashes
347 daemon = True # Don't hang if main thread crashes
348 started = False
348 started = False
349 def __init__(self, echo=False):
349 def __init__(self, echo=False):
350 super(StreamCapturer, self).__init__()
350 super(StreamCapturer, self).__init__()
351 self.echo = echo
351 self.echo = echo
352 self.streams = []
352 self.streams = []
353 self.buffer = BytesIO()
353 self.buffer = BytesIO()
354 self.readfd, self.writefd = os.pipe()
354 self.readfd, self.writefd = os.pipe()
355 self.buffer_lock = Lock()
355 self.buffer_lock = Lock()
356 self.stop = Event()
356 self.stop = Event()
357
357
358 def run(self):
358 def run(self):
359 self.started = True
359 self.started = True
360
360
361 while not self.stop.is_set():
361 while not self.stop.is_set():
362 chunk = os.read(self.readfd, 1024)
362 chunk = os.read(self.readfd, 1024)
363
363
364 with self.buffer_lock:
364 with self.buffer_lock:
365 self.buffer.write(chunk)
365 self.buffer.write(chunk)
366 if self.echo:
366 if self.echo:
367 sys.stdout.write(bytes_to_str(chunk))
367 sys.stdout.write(bytes_to_str(chunk))
368
368
369 os.close(self.readfd)
369 os.close(self.readfd)
370 os.close(self.writefd)
370 os.close(self.writefd)
371
371
372 def reset_buffer(self):
372 def reset_buffer(self):
373 with self.buffer_lock:
373 with self.buffer_lock:
374 self.buffer.truncate(0)
374 self.buffer.truncate(0)
375 self.buffer.seek(0)
375 self.buffer.seek(0)
376
376
377 def get_buffer(self):
377 def get_buffer(self):
378 with self.buffer_lock:
378 with self.buffer_lock:
379 return self.buffer.getvalue()
379 return self.buffer.getvalue()
380
380
381 def ensure_started(self):
381 def ensure_started(self):
382 if not self.started:
382 if not self.started:
383 self.start()
383 self.start()
384
384
385 def halt(self):
385 def halt(self):
386 """Safely stop the thread."""
386 """Safely stop the thread."""
387 if not self.started:
387 if not self.started:
388 return
388 return
389
389
390 self.stop.set()
390 self.stop.set()
391 os.write(self.writefd, b'\0') # Ensure we're not locked in a read()
391 os.write(self.writefd, b'\0') # Ensure we're not locked in a read()
392 self.join()
392 self.join()
393
393
394 class SubprocessStreamCapturePlugin(Plugin):
394 class SubprocessStreamCapturePlugin(Plugin):
395 name='subprocstreams'
395 name='subprocstreams'
396 def __init__(self):
396 def __init__(self):
397 Plugin.__init__(self)
397 Plugin.__init__(self)
398 self.stream_capturer = StreamCapturer()
398 self.stream_capturer = StreamCapturer()
399 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
399 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
400 # This is ugly, but distant parts of the test machinery need to be able
400 # This is ugly, but distant parts of the test machinery need to be able
401 # to redirect streams, so we make the object globally accessible.
401 # to redirect streams, so we make the object globally accessible.
402 nose.iptest_stdstreams_fileno = self.get_write_fileno
402 nose.iptest_stdstreams_fileno = self.get_write_fileno
403
403
404 def get_write_fileno(self):
404 def get_write_fileno(self):
405 if self.destination == 'capture':
405 if self.destination == 'capture':
406 self.stream_capturer.ensure_started()
406 self.stream_capturer.ensure_started()
407 return self.stream_capturer.writefd
407 return self.stream_capturer.writefd
408 elif self.destination == 'discard':
408 elif self.destination == 'discard':
409 return os.open(os.devnull, os.O_WRONLY)
409 return os.open(os.devnull, os.O_WRONLY)
410 else:
410 else:
411 return sys.__stdout__.fileno()
411 return sys.__stdout__.fileno()
412
412
413 def configure(self, options, config):
413 def configure(self, options, config):
414 Plugin.configure(self, options, config)
414 Plugin.configure(self, options, config)
415 # Override nose trying to disable plugin.
415 # Override nose trying to disable plugin.
416 if self.destination == 'capture':
416 if self.destination == 'capture':
417 self.enabled = True
417 self.enabled = True
418
418
419 def startTest(self, test):
419 def startTest(self, test):
420 # Reset log capture
420 # Reset log capture
421 self.stream_capturer.reset_buffer()
421 self.stream_capturer.reset_buffer()
422
422
423 def formatFailure(self, test, err):
423 def formatFailure(self, test, err):
424 # Show output
424 # Show output
425 ec, ev, tb = err
425 ec, ev, tb = err
426 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
426 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
427 if captured.strip():
427 if captured.strip():
428 ev = safe_str(ev)
428 ev = safe_str(ev)
429 out = [ev, '>> begin captured subprocess output <<',
429 out = [ev, '>> begin captured subprocess output <<',
430 captured,
430 captured,
431 '>> end captured subprocess output <<']
431 '>> end captured subprocess output <<']
432 return ec, '\n'.join(out), tb
432 return ec, '\n'.join(out), tb
433
433
434 return err
434 return err
435
435
436 formatError = formatFailure
436 formatError = formatFailure
437
437
438 def finalize(self, result):
438 def finalize(self, result):
439 self.stream_capturer.halt()
439 self.stream_capturer.halt()
440
440
441
441
442 def run_iptest():
442 def run_iptest():
443 """Run the IPython test suite using nose.
443 """Run the IPython test suite using nose.
444
444
445 This function is called when this script is **not** called with the form
445 This function is called when this script is **not** called with the form
446 `iptest all`. It simply calls nose with appropriate command line flags
446 `iptest all`. It simply calls nose with appropriate command line flags
447 and accepts all of the standard nose arguments.
447 and accepts all of the standard nose arguments.
448 """
448 """
449 # Apply our monkeypatch to Xunit
449 # Apply our monkeypatch to Xunit
450 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
450 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
451 monkeypatch_xunit()
451 monkeypatch_xunit()
452
452
453 warnings.filterwarnings('ignore',
453 warnings.filterwarnings('ignore',
454 'This will be removed soon. Use IPython.testing.util instead')
454 'This will be removed soon. Use IPython.testing.util instead')
455
455
456 arg1 = sys.argv[1]
456 arg1 = sys.argv[1]
457 if arg1 in test_sections:
457 if arg1 in test_sections:
458 section = test_sections[arg1]
458 section = test_sections[arg1]
459 sys.argv[1:2] = section.includes
459 sys.argv[1:2] = section.includes
460 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
460 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
461 section = test_sections[arg1[8:]]
461 section = test_sections[arg1[8:]]
462 sys.argv[1:2] = section.includes
462 sys.argv[1:2] = section.includes
463 else:
463 else:
464 section = TestSection(arg1, includes=[arg1])
464 section = TestSection(arg1, includes=[arg1])
465
465
466
466
467 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
467 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
468
468
469 '--with-ipdoctest',
469 '--with-ipdoctest',
470 '--ipdoctest-tests','--ipdoctest-extension=txt',
470 '--ipdoctest-tests','--ipdoctest-extension=txt',
471
471
472 # We add --exe because of setuptools' imbecility (it
472 # We add --exe because of setuptools' imbecility (it
473 # blindly does chmod +x on ALL files). Nose does the
473 # blindly does chmod +x on ALL files). Nose does the
474 # right thing and it tries to avoid executables,
474 # right thing and it tries to avoid executables,
475 # setuptools unfortunately forces our hand here. This
475 # setuptools unfortunately forces our hand here. This
476 # has been discussed on the distutils list and the
476 # has been discussed on the distutils list and the
477 # setuptools devs refuse to fix this problem!
477 # setuptools devs refuse to fix this problem!
478 '--exe',
478 '--exe',
479 ]
479 ]
480 if '-a' not in argv and '-A' not in argv:
480 if '-a' not in argv and '-A' not in argv:
481 argv = argv + ['-a', '!crash']
481 argv = argv + ['-a', '!crash']
482
482
483 if nose.__version__ >= '0.11':
483 if nose.__version__ >= '0.11':
484 # I don't fully understand why we need this one, but depending on what
484 # I don't fully understand why we need this one, but depending on what
485 # directory the test suite is run from, if we don't give it, 0 tests
485 # directory the test suite is run from, if we don't give it, 0 tests
486 # get run. Specifically, if the test suite is run from the source dir
486 # get run. Specifically, if the test suite is run from the source dir
487 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
487 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
488 # even if the same call done in this directory works fine). It appears
488 # even if the same call done in this directory works fine). It appears
489 # that if the requested package is in the current dir, nose bails early
489 # that if the requested package is in the current dir, nose bails early
490 # by default. Since it's otherwise harmless, leave it in by default
490 # by default. Since it's otherwise harmless, leave it in by default
491 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
491 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
492 argv.append('--traverse-namespace')
492 argv.append('--traverse-namespace')
493
493
494 # use our plugin for doctesting. It will remove the standard doctest plugin
494 # use our plugin for doctesting. It will remove the standard doctest plugin
495 # if it finds it enabled
495 # if it finds it enabled
496 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure(),
496 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure(),
497 SubprocessStreamCapturePlugin() ]
497 SubprocessStreamCapturePlugin() ]
498
498
499 # Use working directory set by parent process (see iptestcontroller)
499 # Use working directory set by parent process (see iptestcontroller)
500 if 'IPTEST_WORKING_DIR' in os.environ:
500 if 'IPTEST_WORKING_DIR' in os.environ:
501 os.chdir(os.environ['IPTEST_WORKING_DIR'])
501 os.chdir(os.environ['IPTEST_WORKING_DIR'])
502
502
503 # We need a global ipython running in this process, but the special
503 # We need a global ipython running in this process, but the special
504 # in-process group spawns its own IPython kernels, so for *that* group we
504 # in-process group spawns its own IPython kernels, so for *that* group we
505 # must avoid also opening the global one (otherwise there's a conflict of
505 # must avoid also opening the global one (otherwise there's a conflict of
506 # singletons). Ultimately the solution to this problem is to refactor our
506 # singletons). Ultimately the solution to this problem is to refactor our
507 # assumptions about what needs to be a singleton and what doesn't (app
507 # assumptions about what needs to be a singleton and what doesn't (app
508 # objects should, individual shells shouldn't). But for now, this
508 # objects should, individual shells shouldn't). But for now, this
509 # workaround allows the test suite for the inprocess module to complete.
509 # workaround allows the test suite for the inprocess module to complete.
510 if 'kernel.inprocess' not in section.name:
510 if 'kernel.inprocess' not in section.name:
511 from IPython.testing import globalipapp
511 from IPython.testing import globalipapp
512 globalipapp.start_ipython()
512 globalipapp.start_ipython()
513
513
514 # Now nose can run
514 # Now nose can run
515 TestProgram(argv=argv, addplugins=plugins)
515 TestProgram(argv=argv, addplugins=plugins)
516
516
517 if __name__ == '__main__':
517 if __name__ == '__main__':
518 run_iptest()
518 run_iptest()
519
519
@@ -1,46 +1,19 b''
1 """Utilities for checking zmq versions."""
1 """Utilities for checking zmq versions."""
2 #-----------------------------------------------------------------------------
2 # Copyright (c) IPython Development Team.
3 # Copyright (C) 2013 The IPython Development Team
3 # Distributed under the terms of the Modified BSD License.
4 #
5 # Distributed under the terms of the BSD License. The full license is in
6 # the file COPYING.txt, distributed as part of this software.
7 #-----------------------------------------------------------------------------
8
9 #-----------------------------------------------------------------------------
10 # Verify zmq version dependency >= 2.1.11
11 #-----------------------------------------------------------------------------
12
4
13 from IPython.utils.version import check_version
5 from IPython.utils.version import check_version
14
6
15
7
16 def patch_pyzmq():
17 """backport a few patches from newer pyzmq
18
19 These can be removed as we bump our minimum pyzmq version
20 """
21
22 import zmq
23
24 # fallback on stdlib json if jsonlib is selected, because jsonlib breaks things.
25 # jsonlib support is removed from pyzmq >= 2.2.0
26
27 from zmq.utils import jsonapi
28 if jsonapi.jsonmod.__name__ == 'jsonlib':
29 import json
30 jsonapi.jsonmod = json
31
32
33 def check_for_zmq(minimum_version, required_by='Someone'):
8 def check_for_zmq(minimum_version, required_by='Someone'):
34 try:
9 try:
35 import zmq
10 import zmq
36 except ImportError:
11 except ImportError:
37 raise ImportError("%s requires pyzmq >= %s"%(required_by, minimum_version))
12 raise ImportError("%s requires pyzmq >= %s"%(required_by, minimum_version))
38
13
39 patch_pyzmq()
40
41 pyzmq_version = zmq.__version__
14 pyzmq_version = zmq.__version__
42
15
43 if not check_version(pyzmq_version, minimum_version):
16 if not check_version(pyzmq_version, minimum_version):
44 raise ImportError("%s requires pyzmq >= %s, but you have %s"%(
17 raise ImportError("%s requires pyzmq >= %s, but you have %s"%(
45 required_by, minimum_version, pyzmq_version))
18 required_by, minimum_version, pyzmq_version))
46
19
@@ -1,341 +1,342 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2 # -*- coding: utf-8 -*-
3 """Setup script for IPython.
3 """Setup script for IPython.
4
4
5 Under Posix environments it works like a typical setup.py script.
5 Under Posix environments it works like a typical setup.py script.
6 Under Windows, the command sdist is not supported, since IPython
6 Under Windows, the command sdist is not supported, since IPython
7 requires utilities which are not available under Windows."""
7 requires utilities which are not available under Windows."""
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (c) 2008-2011, IPython Development Team.
10 # Copyright (c) 2008-2011, IPython Development Team.
11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
14 #
14 #
15 # Distributed under the terms of the Modified BSD License.
15 # Distributed under the terms of the Modified BSD License.
16 #
16 #
17 # The full license is in the file COPYING.rst, distributed with this software.
17 # The full license is in the file COPYING.rst, distributed with this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Minimal Python version sanity check
21 # Minimal Python version sanity check
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 from __future__ import print_function
23 from __future__ import print_function
24
24
25 import sys
25 import sys
26
26
27 # This check is also made in IPython/__init__, don't forget to update both when
27 # This check is also made in IPython/__init__, don't forget to update both when
28 # changing Python version requirements.
28 # changing Python version requirements.
29 v = sys.version_info
29 v = sys.version_info
30 if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,3)):
30 if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,3)):
31 error = "ERROR: IPython requires Python version 2.7 or 3.3 or above."
31 error = "ERROR: IPython requires Python version 2.7 or 3.3 or above."
32 print(error, file=sys.stderr)
32 print(error, file=sys.stderr)
33 sys.exit(1)
33 sys.exit(1)
34
34
35 PY3 = (sys.version_info[0] >= 3)
35 PY3 = (sys.version_info[0] >= 3)
36
36
37 # At least we're on the python version we need, move on.
37 # At least we're on the python version we need, move on.
38
38
39 #-------------------------------------------------------------------------------
39 #-------------------------------------------------------------------------------
40 # Imports
40 # Imports
41 #-------------------------------------------------------------------------------
41 #-------------------------------------------------------------------------------
42
42
43 # Stdlib imports
43 # Stdlib imports
44 import os
44 import os
45 import shutil
45 import shutil
46
46
47 from glob import glob
47 from glob import glob
48
48
49 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
49 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
50 # update it when the contents of directories change.
50 # update it when the contents of directories change.
51 if os.path.exists('MANIFEST'): os.remove('MANIFEST')
51 if os.path.exists('MANIFEST'): os.remove('MANIFEST')
52
52
53 from distutils.core import setup
53 from distutils.core import setup
54
54
55 # Our own imports
55 # Our own imports
56 from setupbase import target_update
56 from setupbase import target_update
57
57
58 from setupbase import (
58 from setupbase import (
59 setup_args,
59 setup_args,
60 find_packages,
60 find_packages,
61 find_package_data,
61 find_package_data,
62 check_package_data_first,
62 check_package_data_first,
63 find_entry_points,
63 find_entry_points,
64 build_scripts_entrypt,
64 build_scripts_entrypt,
65 find_data_files,
65 find_data_files,
66 check_for_dependencies,
66 check_for_dependencies,
67 git_prebuild,
67 git_prebuild,
68 check_submodule_status,
68 check_submodule_status,
69 update_submodules,
69 update_submodules,
70 require_submodules,
70 require_submodules,
71 UpdateSubmodules,
71 UpdateSubmodules,
72 get_bdist_wheel,
72 get_bdist_wheel,
73 CompileCSS,
73 CompileCSS,
74 JavascriptVersion,
74 JavascriptVersion,
75 css_js_prerelease,
75 css_js_prerelease,
76 install_symlinked,
76 install_symlinked,
77 install_lib_symlink,
77 install_lib_symlink,
78 install_scripts_for_symlink,
78 install_scripts_for_symlink,
79 unsymlink,
79 unsymlink,
80 )
80 )
81 from setupext import setupext
81 from setupext import setupext
82
82
83 isfile = os.path.isfile
83 isfile = os.path.isfile
84 pjoin = os.path.join
84 pjoin = os.path.join
85
85
86 #-------------------------------------------------------------------------------
86 #-------------------------------------------------------------------------------
87 # Handle OS specific things
87 # Handle OS specific things
88 #-------------------------------------------------------------------------------
88 #-------------------------------------------------------------------------------
89
89
90 if os.name in ('nt','dos'):
90 if os.name in ('nt','dos'):
91 os_name = 'windows'
91 os_name = 'windows'
92 else:
92 else:
93 os_name = os.name
93 os_name = os.name
94
94
95 # Under Windows, 'sdist' has not been supported. Now that the docs build with
95 # Under Windows, 'sdist' has not been supported. Now that the docs build with
96 # Sphinx it might work, but let's not turn it on until someone confirms that it
96 # Sphinx it might work, but let's not turn it on until someone confirms that it
97 # actually works.
97 # actually works.
98 if os_name == 'windows' and 'sdist' in sys.argv:
98 if os_name == 'windows' and 'sdist' in sys.argv:
99 print('The sdist command is not available under Windows. Exiting.')
99 print('The sdist command is not available under Windows. Exiting.')
100 sys.exit(1)
100 sys.exit(1)
101
101
102 #-------------------------------------------------------------------------------
102 #-------------------------------------------------------------------------------
103 # Make sure we aren't trying to run without submodules
103 # Make sure we aren't trying to run without submodules
104 #-------------------------------------------------------------------------------
104 #-------------------------------------------------------------------------------
105 here = os.path.abspath(os.path.dirname(__file__))
105 here = os.path.abspath(os.path.dirname(__file__))
106
106
107 def require_clean_submodules():
107 def require_clean_submodules():
108 """Check on git submodules before distutils can do anything
108 """Check on git submodules before distutils can do anything
109
109
110 Since distutils cannot be trusted to update the tree
110 Since distutils cannot be trusted to update the tree
111 after everything has been set in motion,
111 after everything has been set in motion,
112 this is not a distutils command.
112 this is not a distutils command.
113 """
113 """
114 # PACKAGERS: Add a return here to skip checks for git submodules
114 # PACKAGERS: Add a return here to skip checks for git submodules
115
115
116 # don't do anything if nothing is actually supposed to happen
116 # don't do anything if nothing is actually supposed to happen
117 for do_nothing in ('-h', '--help', '--help-commands', 'clean', 'submodule'):
117 for do_nothing in ('-h', '--help', '--help-commands', 'clean', 'submodule'):
118 if do_nothing in sys.argv:
118 if do_nothing in sys.argv:
119 return
119 return
120
120
121 status = check_submodule_status(here)
121 status = check_submodule_status(here)
122
122
123 if status == "missing":
123 if status == "missing":
124 print("checking out submodules for the first time")
124 print("checking out submodules for the first time")
125 update_submodules(here)
125 update_submodules(here)
126 elif status == "unclean":
126 elif status == "unclean":
127 print('\n'.join([
127 print('\n'.join([
128 "Cannot build / install IPython with unclean submodules",
128 "Cannot build / install IPython with unclean submodules",
129 "Please update submodules with",
129 "Please update submodules with",
130 " python setup.py submodule",
130 " python setup.py submodule",
131 "or",
131 "or",
132 " git submodule update",
132 " git submodule update",
133 "or commit any submodule changes you have made."
133 "or commit any submodule changes you have made."
134 ]))
134 ]))
135 sys.exit(1)
135 sys.exit(1)
136
136
137 require_clean_submodules()
137 require_clean_submodules()
138
138
139 #-------------------------------------------------------------------------------
139 #-------------------------------------------------------------------------------
140 # Things related to the IPython documentation
140 # Things related to the IPython documentation
141 #-------------------------------------------------------------------------------
141 #-------------------------------------------------------------------------------
142
142
143 # update the manuals when building a source dist
143 # update the manuals when building a source dist
144 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
144 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
145
145
146 # List of things to be updated. Each entry is a triplet of args for
146 # List of things to be updated. Each entry is a triplet of args for
147 # target_update()
147 # target_update()
148 to_update = [
148 to_update = [
149 # FIXME - Disabled for now: we need to redo an automatic way
149 # FIXME - Disabled for now: we need to redo an automatic way
150 # of generating the magic info inside the rst.
150 # of generating the magic info inside the rst.
151 #('docs/magic.tex',
151 #('docs/magic.tex',
152 #['IPython/Magic.py'],
152 #['IPython/Magic.py'],
153 #"cd doc && ./update_magic.sh" ),
153 #"cd doc && ./update_magic.sh" ),
154
154
155 ('docs/man/ipcluster.1.gz',
155 ('docs/man/ipcluster.1.gz',
156 ['docs/man/ipcluster.1'],
156 ['docs/man/ipcluster.1'],
157 'cd docs/man && gzip -9c ipcluster.1 > ipcluster.1.gz'),
157 'cd docs/man && gzip -9c ipcluster.1 > ipcluster.1.gz'),
158
158
159 ('docs/man/ipcontroller.1.gz',
159 ('docs/man/ipcontroller.1.gz',
160 ['docs/man/ipcontroller.1'],
160 ['docs/man/ipcontroller.1'],
161 'cd docs/man && gzip -9c ipcontroller.1 > ipcontroller.1.gz'),
161 'cd docs/man && gzip -9c ipcontroller.1 > ipcontroller.1.gz'),
162
162
163 ('docs/man/ipengine.1.gz',
163 ('docs/man/ipengine.1.gz',
164 ['docs/man/ipengine.1'],
164 ['docs/man/ipengine.1'],
165 'cd docs/man && gzip -9c ipengine.1 > ipengine.1.gz'),
165 'cd docs/man && gzip -9c ipengine.1 > ipengine.1.gz'),
166
166
167 ('docs/man/ipython.1.gz',
167 ('docs/man/ipython.1.gz',
168 ['docs/man/ipython.1'],
168 ['docs/man/ipython.1'],
169 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
169 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
170
170
171 ]
171 ]
172
172
173
173
174 [ target_update(*t) for t in to_update ]
174 [ target_update(*t) for t in to_update ]
175
175
176 #---------------------------------------------------------------------------
176 #---------------------------------------------------------------------------
177 # Find all the packages, package data, and data_files
177 # Find all the packages, package data, and data_files
178 #---------------------------------------------------------------------------
178 #---------------------------------------------------------------------------
179
179
180 packages = find_packages()
180 packages = find_packages()
181 package_data = find_package_data()
181 package_data = find_package_data()
182
182
183 data_files = find_data_files()
183 data_files = find_data_files()
184
184
185 setup_args['packages'] = packages
185 setup_args['packages'] = packages
186 setup_args['package_data'] = package_data
186 setup_args['package_data'] = package_data
187 setup_args['data_files'] = data_files
187 setup_args['data_files'] = data_files
188
188
189 #---------------------------------------------------------------------------
189 #---------------------------------------------------------------------------
190 # custom distutils commands
190 # custom distutils commands
191 #---------------------------------------------------------------------------
191 #---------------------------------------------------------------------------
192 # imports here, so they are after setuptools import if there was one
192 # imports here, so they are after setuptools import if there was one
193 from distutils.command.sdist import sdist
193 from distutils.command.sdist import sdist
194 from distutils.command.upload import upload
194 from distutils.command.upload import upload
195
195
196 class UploadWindowsInstallers(upload):
196 class UploadWindowsInstallers(upload):
197
197
198 description = "Upload Windows installers to PyPI (only used from tools/release_windows.py)"
198 description = "Upload Windows installers to PyPI (only used from tools/release_windows.py)"
199 user_options = upload.user_options + [
199 user_options = upload.user_options + [
200 ('files=', 'f', 'exe file (or glob) to upload')
200 ('files=', 'f', 'exe file (or glob) to upload')
201 ]
201 ]
202 def initialize_options(self):
202 def initialize_options(self):
203 upload.initialize_options(self)
203 upload.initialize_options(self)
204 meta = self.distribution.metadata
204 meta = self.distribution.metadata
205 base = '{name}-{version}'.format(
205 base = '{name}-{version}'.format(
206 name=meta.get_name(),
206 name=meta.get_name(),
207 version=meta.get_version()
207 version=meta.get_version()
208 )
208 )
209 self.files = os.path.join('dist', '%s.*.exe' % base)
209 self.files = os.path.join('dist', '%s.*.exe' % base)
210
210
211 def run(self):
211 def run(self):
212 for dist_file in glob(self.files):
212 for dist_file in glob(self.files):
213 self.upload_file('bdist_wininst', 'any', dist_file)
213 self.upload_file('bdist_wininst', 'any', dist_file)
214
214
215 setup_args['cmdclass'] = {
215 setup_args['cmdclass'] = {
216 'build_py': css_js_prerelease(
216 'build_py': css_js_prerelease(
217 check_package_data_first(git_prebuild('IPython'))),
217 check_package_data_first(git_prebuild('IPython'))),
218 'sdist' : css_js_prerelease(git_prebuild('IPython', sdist)),
218 'sdist' : css_js_prerelease(git_prebuild('IPython', sdist)),
219 'upload_wininst' : UploadWindowsInstallers,
219 'upload_wininst' : UploadWindowsInstallers,
220 'submodule' : UpdateSubmodules,
220 'submodule' : UpdateSubmodules,
221 'css' : CompileCSS,
221 'css' : CompileCSS,
222 'symlink': install_symlinked,
222 'symlink': install_symlinked,
223 'install_lib_symlink': install_lib_symlink,
223 'install_lib_symlink': install_lib_symlink,
224 'install_scripts_sym': install_scripts_for_symlink,
224 'install_scripts_sym': install_scripts_for_symlink,
225 'unsymlink': unsymlink,
225 'unsymlink': unsymlink,
226 'jsversion' : JavascriptVersion,
226 'jsversion' : JavascriptVersion,
227 }
227 }
228
228
229 #---------------------------------------------------------------------------
229 #---------------------------------------------------------------------------
230 # Handle scripts, dependencies, and setuptools specific things
230 # Handle scripts, dependencies, and setuptools specific things
231 #---------------------------------------------------------------------------
231 #---------------------------------------------------------------------------
232
232
233 # For some commands, use setuptools. Note that we do NOT list install here!
233 # For some commands, use setuptools. Note that we do NOT list install here!
234 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
234 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
235 needs_setuptools = set(('develop', 'release', 'bdist_egg', 'bdist_rpm',
235 needs_setuptools = set(('develop', 'release', 'bdist_egg', 'bdist_rpm',
236 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
236 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
237 'egg_info', 'easy_install', 'upload', 'install_egg_info',
237 'egg_info', 'easy_install', 'upload', 'install_egg_info',
238 ))
238 ))
239
239
240 if len(needs_setuptools.intersection(sys.argv)) > 0:
240 if len(needs_setuptools.intersection(sys.argv)) > 0:
241 import setuptools
241 import setuptools
242
242
243 # This dict is used for passing extra arguments that are setuptools
243 # This dict is used for passing extra arguments that are setuptools
244 # specific to setup
244 # specific to setup
245 setuptools_extra_args = {}
245 setuptools_extra_args = {}
246
246
247 # setuptools requirements
247 # setuptools requirements
248
248
249 pyzmq = 'pyzmq>=13'
250
249 extras_require = dict(
251 extras_require = dict(
250 parallel = ['pyzmq>=2.1.11'],
252 parallel = [pyzmq],
251 qtconsole = ['pyzmq>=2.1.11', 'pygments'],
253 qtconsole = [pyzmq, 'pygments'],
252 zmq = ['pyzmq>=2.1.11'],
253 doc = ['Sphinx>=1.1', 'numpydoc'],
254 doc = ['Sphinx>=1.1', 'numpydoc'],
254 test = ['nose>=0.10.1', 'requests'],
255 test = ['nose>=0.10.1', 'requests'],
255 terminal = [],
256 terminal = [],
256 nbformat = ['jsonschema>=2.0'],
257 nbformat = ['jsonschema>=2.0'],
257 notebook = ['tornado>=4.0', 'pyzmq>=2.1.11', 'jinja2', 'pygments', 'mistune>=0.5'],
258 notebook = ['tornado>=4.0', pyzmq, 'jinja2', 'pygments', 'mistune>=0.5'],
258 nbconvert = ['pygments', 'jinja2', 'mistune>=0.3.1']
259 nbconvert = ['pygments', 'jinja2', 'mistune>=0.3.1']
259 )
260 )
260
261
261 if not sys.platform.startswith('win'):
262 if not sys.platform.startswith('win'):
262 extras_require['notebook'].append('terminado>=0.3.3')
263 extras_require['notebook'].append('terminado>=0.3.3')
263
264
264 if sys.version_info < (3, 3):
265 if sys.version_info < (3, 3):
265 extras_require['test'].append('mock')
266 extras_require['test'].append('mock')
266
267
267 extras_require['notebook'].extend(extras_require['nbformat'])
268 extras_require['notebook'].extend(extras_require['nbformat'])
268 extras_require['nbconvert'].extend(extras_require['nbformat'])
269 extras_require['nbconvert'].extend(extras_require['nbformat'])
269
270
270 install_requires = []
271 install_requires = []
271
272
272 # add readline
273 # add readline
273 if sys.platform == 'darwin':
274 if sys.platform == 'darwin':
274 if 'bdist_wheel' in sys.argv[1:] or not setupext.check_for_readline():
275 if 'bdist_wheel' in sys.argv[1:] or not setupext.check_for_readline():
275 install_requires.append('gnureadline')
276 install_requires.append('gnureadline')
276 elif sys.platform.startswith('win'):
277 elif sys.platform.startswith('win'):
277 extras_require['terminal'].append('pyreadline>=2.0')
278 extras_require['terminal'].append('pyreadline>=2.0')
278
279
279 everything = set()
280 everything = set()
280 for deps in extras_require.values():
281 for deps in extras_require.values():
281 everything.update(deps)
282 everything.update(deps)
282 extras_require['all'] = everything
283 extras_require['all'] = everything
283
284
284 if 'setuptools' in sys.modules:
285 if 'setuptools' in sys.modules:
285 # setup.py develop should check for submodules
286 # setup.py develop should check for submodules
286 from setuptools.command.develop import develop
287 from setuptools.command.develop import develop
287 setup_args['cmdclass']['develop'] = require_submodules(develop)
288 setup_args['cmdclass']['develop'] = require_submodules(develop)
288 setup_args['cmdclass']['bdist_wheel'] = css_js_prerelease(get_bdist_wheel())
289 setup_args['cmdclass']['bdist_wheel'] = css_js_prerelease(get_bdist_wheel())
289
290
290 setuptools_extra_args['zip_safe'] = False
291 setuptools_extra_args['zip_safe'] = False
291 setuptools_extra_args['entry_points'] = {
292 setuptools_extra_args['entry_points'] = {
292 'console_scripts': find_entry_points(),
293 'console_scripts': find_entry_points(),
293 'pygments.lexers': [
294 'pygments.lexers': [
294 'ipythonconsole = IPython.nbconvert.utils.lexers:IPythonConsoleLexer',
295 'ipythonconsole = IPython.nbconvert.utils.lexers:IPythonConsoleLexer',
295 'ipython = IPython.nbconvert.utils.lexers:IPythonLexer',
296 'ipython = IPython.nbconvert.utils.lexers:IPythonLexer',
296 'ipython3 = IPython.nbconvert.utils.lexers:IPython3Lexer',
297 'ipython3 = IPython.nbconvert.utils.lexers:IPython3Lexer',
297 ],
298 ],
298 }
299 }
299 setup_args['extras_require'] = extras_require
300 setup_args['extras_require'] = extras_require
300 requires = setup_args['install_requires'] = install_requires
301 requires = setup_args['install_requires'] = install_requires
301
302
302 # Script to be run by the windows binary installer after the default setup
303 # Script to be run by the windows binary installer after the default setup
303 # routine, to add shortcuts and similar windows-only things. Windows
304 # routine, to add shortcuts and similar windows-only things. Windows
304 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
305 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
305 # doesn't find them.
306 # doesn't find them.
306 if 'bdist_wininst' in sys.argv:
307 if 'bdist_wininst' in sys.argv:
307 if len(sys.argv) > 2 and \
308 if len(sys.argv) > 2 and \
308 ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
309 ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
309 print("ERROR: bdist_wininst must be run alone. Exiting.", file=sys.stderr)
310 print("ERROR: bdist_wininst must be run alone. Exiting.", file=sys.stderr)
310 sys.exit(1)
311 sys.exit(1)
311 setup_args['data_files'].append(
312 setup_args['data_files'].append(
312 ['Scripts', ('scripts/ipython.ico', 'scripts/ipython_nb.ico')])
313 ['Scripts', ('scripts/ipython.ico', 'scripts/ipython_nb.ico')])
313 setup_args['scripts'] = [pjoin('scripts','ipython_win_post_install.py')]
314 setup_args['scripts'] = [pjoin('scripts','ipython_win_post_install.py')]
314 setup_args['options'] = {"bdist_wininst":
315 setup_args['options'] = {"bdist_wininst":
315 {"install_script":
316 {"install_script":
316 "ipython_win_post_install.py"}}
317 "ipython_win_post_install.py"}}
317
318
318 else:
319 else:
319 # If we are installing without setuptools, call this function which will
320 # If we are installing without setuptools, call this function which will
320 # check for dependencies an inform the user what is needed. This is
321 # check for dependencies an inform the user what is needed. This is
321 # just to make life easy for users.
322 # just to make life easy for users.
322 for install_cmd in ('install', 'symlink'):
323 for install_cmd in ('install', 'symlink'):
323 if install_cmd in sys.argv:
324 if install_cmd in sys.argv:
324 check_for_dependencies()
325 check_for_dependencies()
325 break
326 break
326 # scripts has to be a non-empty list, or install_scripts isn't called
327 # scripts has to be a non-empty list, or install_scripts isn't called
327 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
328 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
328
329
329 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
330 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
330
331
331 #---------------------------------------------------------------------------
332 #---------------------------------------------------------------------------
332 # Do the actual setup now
333 # Do the actual setup now
333 #---------------------------------------------------------------------------
334 #---------------------------------------------------------------------------
334
335
335 setup_args.update(setuptools_extra_args)
336 setup_args.update(setuptools_extra_args)
336
337
337 def main():
338 def main():
338 setup(**setup_args)
339 setup(**setup_args)
339
340
340 if __name__ == '__main__':
341 if __name__ == '__main__':
341 main()
342 main()
General Comments 0
You need to be logged in to leave comments. Login now