##// END OF EJS Templates
Merge pull request #6936 from minrk/profile-dir...
Matthias Bussonnier -
r20893:1d5c51bb merge
parent child Browse files
Show More
@@ -1,344 +1,330 b''
1 1 """ A minimal application base mixin for all ZMQ based IPython frontends.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations. This is a
5 5 refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
6 6 """
7 7 # Copyright (c) IPython Development Team.
8 8 # Distributed under the terms of the Modified BSD License.
9 9
10 10 import atexit
11 11 import os
12 12 import signal
13 13 import sys
14 14 import uuid
15 15
16 16
17 17 from IPython.config.application import boolean_flag
18 18 from IPython.core.profiledir import ProfileDir
19 19 from IPython.kernel.blocking import BlockingKernelClient
20 20 from IPython.kernel import KernelManager
21 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
21 from IPython.kernel import tunnel_to_kernel, find_connection_file
22 22 from IPython.kernel.kernelspec import NoSuchKernel
23 23 from IPython.utils.path import filefind
24 24 from IPython.utils.traitlets import (
25 25 Dict, List, Unicode, CUnicode, CBool, Any
26 26 )
27 from IPython.kernel.zmq.kernelapp import (
28 kernel_flags,
29 kernel_aliases,
30 IPKernelApp
31 )
32 from IPython.kernel.zmq.pylab.config import InlineBackend
33 27 from IPython.kernel.zmq.session import Session
34 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
35 28 from IPython.kernel.connect import ConnectionFileMixin
36 29
37 30 from IPython.utils.localinterfaces import localhost
38 31
39 32 #-----------------------------------------------------------------------------
40 33 # Aliases and Flags
41 34 #-----------------------------------------------------------------------------
42 35
43 flags = dict(kernel_flags)
36 flags = {}
44 37
45 38 # the flags that are specific to the frontend
46 39 # these must be scrubbed before being passed to the kernel,
47 40 # or it will raise an error on unrecognized flags
48 41 app_flags = {
49 42 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
50 43 "Connect to an existing kernel. If no argument specified, guess most recent"),
51 44 }
52 45 app_flags.update(boolean_flag(
53 46 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
54 47 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
55 48 to force a direct exit without any confirmation.
56 49 """,
57 50 """Don't prompt the user when exiting. This will terminate the kernel
58 51 if it is owned by the frontend, and leave it alive if it is external.
59 52 """
60 53 ))
61 54 flags.update(app_flags)
62 55
63 aliases = dict(kernel_aliases)
56 aliases = {}
64 57
65 58 # also scrub aliases from the frontend
66 59 app_aliases = dict(
67 60 ip = 'IPythonConsoleApp.ip',
68 61 transport = 'IPythonConsoleApp.transport',
69 62 hb = 'IPythonConsoleApp.hb_port',
70 63 shell = 'IPythonConsoleApp.shell_port',
71 64 iopub = 'IPythonConsoleApp.iopub_port',
72 65 stdin = 'IPythonConsoleApp.stdin_port',
73 66 existing = 'IPythonConsoleApp.existing',
74 67 f = 'IPythonConsoleApp.connection_file',
75 68
76 69 kernel = 'IPythonConsoleApp.kernel_name',
77 70
78 71 ssh = 'IPythonConsoleApp.sshserver',
79 72 )
80 73 aliases.update(app_aliases)
81 74
82 75 #-----------------------------------------------------------------------------
83 76 # Classes
84 77 #-----------------------------------------------------------------------------
85 78
86 79 classes = [KernelManager, ProfileDir, Session]
87 80
88 81 class IPythonConsoleApp(ConnectionFileMixin):
89 82 name = 'ipython-console-mixin'
90 83
91 84 description = """
92 85 The IPython Mixin Console.
93 86
94 87 This class contains the common portions of console client (QtConsole,
95 88 ZMQ-based terminal console, etc). It is not a full console, in that
96 89 launched terminal subprocesses will not be able to accept input.
97 90
98 91 The Console using this mixing supports various extra features beyond
99 92 the single-process Terminal IPython shell, such as connecting to
100 93 existing kernel, via:
101 94
102 95 ipython <appname> --existing
103 96
104 97 as well as tunnel via SSH
105 98
106 99 """
107 100
108 101 classes = classes
109 102 flags = Dict(flags)
110 103 aliases = Dict(aliases)
111 104 kernel_manager_class = KernelManager
112 105 kernel_client_class = BlockingKernelClient
113 106
114 107 kernel_argv = List(Unicode)
115 108 # frontend flags&aliases to be stripped when building kernel_argv
116 109 frontend_flags = Any(app_flags)
117 110 frontend_aliases = Any(app_aliases)
118 111
119 112 # create requested profiles by default, if they don't exist:
120 113 auto_create = CBool(True)
121 114 # connection info:
122 115
123 116 sshserver = Unicode('', config=True,
124 117 help="""The SSH server to use to connect to the kernel.""")
125 118 sshkey = Unicode('', config=True,
126 119 help="""Path to the ssh key to use for logging in to the ssh server.""")
127 120
128 121 def _connection_file_default(self):
129 122 return 'kernel-%i.json' % os.getpid()
130 123
131 124 existing = CUnicode('', config=True,
132 125 help="""Connect to an already running kernel""")
133 126
134 127 kernel_name = Unicode('python', config=True,
135 128 help="""The name of the default kernel to start.""")
136 129
137 130 confirm_exit = CBool(True, config=True,
138 131 help="""
139 132 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
140 133 to force a direct exit without any confirmation.""",
141 134 )
142 135
143 @property
144 def help_classes(self):
145 """ConsoleApps can configure kernels on the command-line
136 def build_kernel_argv(self, argv=None):
137 """build argv to be passed to kernel subprocess
146 138
147 But this shouldn't be written to a file
139 Override in subclasses if any args should be passed to the kernel
148 140 """
149 return self.classes + [IPKernelApp] + IPKernelApp.classes
150
151 def build_kernel_argv(self, argv=None):
152 """build argv to be passed to kernel subprocess"""
153 if argv is None:
154 argv = sys.argv[1:]
155 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
141 self.kernel_argv = self.extra_args
156 142
157 143 def init_connection_file(self):
158 144 """find the connection file, and load the info if found.
159 145
160 146 The current working directory and the current profile's security
161 147 directory will be searched for the file if it is not given by
162 148 absolute path.
163 149
164 150 When attempting to connect to an existing kernel and the `--existing`
165 151 argument does not match an existing file, it will be interpreted as a
166 152 fileglob, and the matching file in the current profile's security dir
167 153 with the latest access time will be used.
168 154
169 155 After this method is called, self.connection_file contains the *full path*
170 156 to the connection file, never just its name.
171 157 """
172 158 if self.existing:
173 159 try:
174 160 cf = find_connection_file(self.existing)
175 161 except Exception:
176 162 self.log.critical("Could not find existing kernel connection file %s", self.existing)
177 163 self.exit(1)
178 164 self.log.debug("Connecting to existing kernel: %s" % cf)
179 165 self.connection_file = cf
180 166 else:
181 167 # not existing, check if we are going to write the file
182 168 # and ensure that self.connection_file is a full path, not just the shortname
183 169 try:
184 170 cf = find_connection_file(self.connection_file)
185 171 except Exception:
186 172 # file might not exist
187 173 if self.connection_file == os.path.basename(self.connection_file):
188 174 # just shortname, put it in security dir
189 175 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
190 176 else:
191 177 cf = self.connection_file
192 178 self.connection_file = cf
193 179 try:
194 180 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
195 181 except IOError:
196 182 self.log.debug("Connection File not found: %s", self.connection_file)
197 183 return
198 184
199 185 # should load_connection_file only be used for existing?
200 186 # as it is now, this allows reusing ports if an existing
201 187 # file is requested
202 188 try:
203 189 self.load_connection_file()
204 190 except Exception:
205 191 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
206 192 self.exit(1)
207 193
208 194 def init_ssh(self):
209 195 """set up ssh tunnels, if needed."""
210 196 if not self.existing or (not self.sshserver and not self.sshkey):
211 197 return
212 198 self.load_connection_file()
213 199
214 200 transport = self.transport
215 201 ip = self.ip
216 202
217 203 if transport != 'tcp':
218 204 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
219 205 sys.exit(-1)
220 206
221 207 if self.sshkey and not self.sshserver:
222 208 # specifying just the key implies that we are connecting directly
223 209 self.sshserver = ip
224 210 ip = localhost()
225 211
226 212 # build connection dict for tunnels:
227 213 info = dict(ip=ip,
228 214 shell_port=self.shell_port,
229 215 iopub_port=self.iopub_port,
230 216 stdin_port=self.stdin_port,
231 217 hb_port=self.hb_port
232 218 )
233 219
234 220 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
235 221
236 222 # tunnels return a new set of ports, which will be on localhost:
237 223 self.ip = localhost()
238 224 try:
239 225 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
240 226 except:
241 227 # even catch KeyboardInterrupt
242 228 self.log.error("Could not setup tunnels", exc_info=True)
243 229 self.exit(1)
244 230
245 231 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
246 232
247 233 cf = self.connection_file
248 234 base,ext = os.path.splitext(cf)
249 235 base = os.path.basename(base)
250 236 self.connection_file = os.path.basename(base)+'-ssh'+ext
251 237 self.log.info("To connect another client via this tunnel, use:")
252 238 self.log.info("--existing %s" % self.connection_file)
253 239
254 240 def _new_connection_file(self):
255 241 cf = ''
256 242 while not cf:
257 243 # we don't need a 128b id to distinguish kernels, use more readable
258 244 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
259 245 # kernels can subclass.
260 246 ident = str(uuid.uuid4()).split('-')[-1]
261 247 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
262 248 # only keep if it's actually new. Protect against unlikely collision
263 249 # in 48b random search space
264 250 cf = cf if not os.path.exists(cf) else ''
265 251 return cf
266 252
267 253 def init_kernel_manager(self):
268 254 # Don't let Qt or ZMQ swallow KeyboardInterupts.
269 255 if self.existing:
270 256 self.kernel_manager = None
271 257 return
272 258 signal.signal(signal.SIGINT, signal.SIG_DFL)
273 259
274 260 # Create a KernelManager and start a kernel.
275 261 try:
276 262 self.kernel_manager = self.kernel_manager_class(
277 263 ip=self.ip,
278 264 session=self.session,
279 265 transport=self.transport,
280 266 shell_port=self.shell_port,
281 267 iopub_port=self.iopub_port,
282 268 stdin_port=self.stdin_port,
283 269 hb_port=self.hb_port,
284 270 connection_file=self.connection_file,
285 271 kernel_name=self.kernel_name,
286 272 parent=self,
287 273 ipython_dir=self.ipython_dir,
288 274 )
289 275 except NoSuchKernel:
290 276 self.log.critical("Could not find kernel %s", self.kernel_name)
291 277 self.exit(1)
292 278
293 279 self.kernel_manager.client_factory = self.kernel_client_class
294 280 # FIXME: remove special treatment of IPython kernels
295 281 kwargs = {}
296 282 if self.kernel_manager.ipython_kernel:
297 283 kwargs['extra_arguments'] = self.kernel_argv
298 284 self.kernel_manager.start_kernel(**kwargs)
299 285 atexit.register(self.kernel_manager.cleanup_ipc_files)
300 286
301 287 if self.sshserver:
302 288 # ssh, write new connection file
303 289 self.kernel_manager.write_connection_file()
304 290
305 291 # in case KM defaults / ssh writing changes things:
306 292 km = self.kernel_manager
307 293 self.shell_port=km.shell_port
308 294 self.iopub_port=km.iopub_port
309 295 self.stdin_port=km.stdin_port
310 296 self.hb_port=km.hb_port
311 297 self.connection_file = km.connection_file
312 298
313 299 atexit.register(self.kernel_manager.cleanup_connection_file)
314 300
315 301 def init_kernel_client(self):
316 302 if self.kernel_manager is not None:
317 303 self.kernel_client = self.kernel_manager.client()
318 304 else:
319 305 self.kernel_client = self.kernel_client_class(
320 306 session=self.session,
321 307 ip=self.ip,
322 308 transport=self.transport,
323 309 shell_port=self.shell_port,
324 310 iopub_port=self.iopub_port,
325 311 stdin_port=self.stdin_port,
326 312 hb_port=self.hb_port,
327 313 connection_file=self.connection_file,
328 314 parent=self,
329 315 )
330 316
331 317 self.kernel_client.start_channels()
332 318
333 319
334 320
335 321 def initialize(self, argv=None):
336 322 """
337 323 Classes which mix this class in should call:
338 324 IPythonConsoleApp.initialize(self,argv)
339 325 """
340 326 self.init_connection_file()
341 327 self.init_ssh()
342 328 self.init_kernel_manager()
343 329 self.init_kernel_client()
344 330
@@ -1,1137 +1,1127 b''
1 1 # coding: utf-8
2 2 """A tornado based IPython notebook server."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from __future__ import print_function
8 8
9 9 import base64
10 10 import datetime
11 11 import errno
12 12 import importlib
13 13 import io
14 14 import json
15 15 import logging
16 16 import os
17 17 import random
18 18 import re
19 19 import select
20 20 import signal
21 21 import socket
22 22 import sys
23 23 import threading
24 24 import webbrowser
25 25
26 26
27 27 # check for pyzmq
28 28 from IPython.utils.zmqrelated import check_for_zmq
29 29 check_for_zmq('13', 'IPython.html')
30 30
31 31 from jinja2 import Environment, FileSystemLoader
32 32
33 33 # Install the pyzmq ioloop. This has to be done before anything else from
34 34 # tornado is imported.
35 35 from zmq.eventloop import ioloop
36 36 ioloop.install()
37 37
38 38 # check for tornado 3.1.0
39 39 msg = "The IPython Notebook requires tornado >= 4.0"
40 40 try:
41 41 import tornado
42 42 except ImportError:
43 43 raise ImportError(msg)
44 44 try:
45 45 version_info = tornado.version_info
46 46 except AttributeError:
47 47 raise ImportError(msg + ", but you have < 1.1.0")
48 48 if version_info < (4,0):
49 49 raise ImportError(msg + ", but you have %s" % tornado.version)
50 50
51 51 from tornado import httpserver
52 52 from tornado import web
53 53 from tornado.log import LogFormatter, app_log, access_log, gen_log
54 54
55 55 from IPython.html import (
56 56 DEFAULT_STATIC_FILES_PATH,
57 57 DEFAULT_TEMPLATE_PATH_LIST,
58 58 )
59 59 from .base.handlers import Template404
60 60 from .log import log_request
61 61 from .services.kernels.kernelmanager import MappingKernelManager
62 62 from .services.config import ConfigManager
63 63 from .services.contents.manager import ContentsManager
64 64 from .services.contents.filemanager import FileContentsManager
65 65 from .services.clusters.clustermanager import ClusterManager
66 66 from .services.sessions.sessionmanager import SessionManager
67 67
68 68 from .auth.login import LoginHandler
69 69 from .auth.logout import LogoutHandler
70 70 from .base.handlers import IPythonHandler, FileFindHandler
71 71
72 72 from IPython.config import Config
73 73 from IPython.config.application import catch_config_error, boolean_flag
74 74 from IPython.core.application import (
75 75 BaseIPythonApplication, base_flags, base_aliases,
76 76 )
77 77 from IPython.core.profiledir import ProfileDir
78 78 from IPython.kernel import KernelManager
79 79 from IPython.kernel.kernelspec import KernelSpecManager
80 80 from IPython.kernel.zmq.session import Session
81 81 from IPython.nbformat.sign import NotebookNotary
82 82 from IPython.utils.importstring import import_item
83 83 from IPython.utils import submodule
84 84 from IPython.utils.process import check_pid
85 85 from IPython.utils.traitlets import (
86 86 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
87 87 TraitError, Type,
88 88 )
89 89 from IPython.utils import py3compat
90 90 from IPython.utils.path import filefind, get_ipython_dir
91 91 from IPython.utils.sysinfo import get_sys_info
92 92
93 93 from .nbextensions import SYSTEM_NBEXTENSIONS_DIRS
94 94 from .utils import url_path_join
95 95
96 96 #-----------------------------------------------------------------------------
97 97 # Module globals
98 98 #-----------------------------------------------------------------------------
99 99
100 100 _examples = """
101 101 ipython notebook # start the notebook
102 102 ipython notebook --profile=sympy # use the sympy profile
103 103 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
104 104 """
105 105
106 106 #-----------------------------------------------------------------------------
107 107 # Helper functions
108 108 #-----------------------------------------------------------------------------
109 109
110 110 def random_ports(port, n):
111 111 """Generate a list of n random ports near the given port.
112 112
113 113 The first 5 ports will be sequential, and the remaining n-5 will be
114 114 randomly selected in the range [port-2*n, port+2*n].
115 115 """
116 116 for i in range(min(5, n)):
117 117 yield port + i
118 118 for i in range(n-5):
119 119 yield max(1, port + random.randint(-2*n, 2*n))
120 120
121 121 def load_handlers(name):
122 122 """Load the (URL pattern, handler) tuples for each component."""
123 123 name = 'IPython.html.' + name
124 124 mod = __import__(name, fromlist=['default_handlers'])
125 125 return mod.default_handlers
126 126
127 127 #-----------------------------------------------------------------------------
128 128 # The Tornado web application
129 129 #-----------------------------------------------------------------------------
130 130
131 131 class NotebookWebApplication(web.Application):
132 132
133 133 def __init__(self, ipython_app, kernel_manager, contents_manager,
134 134 cluster_manager, session_manager, kernel_spec_manager,
135 135 config_manager, log,
136 136 base_url, default_url, settings_overrides, jinja_env_options):
137 137
138 138 settings = self.init_settings(
139 139 ipython_app, kernel_manager, contents_manager, cluster_manager,
140 140 session_manager, kernel_spec_manager, config_manager, log, base_url,
141 141 default_url, settings_overrides, jinja_env_options)
142 142 handlers = self.init_handlers(settings)
143 143
144 144 super(NotebookWebApplication, self).__init__(handlers, **settings)
145 145
146 146 def init_settings(self, ipython_app, kernel_manager, contents_manager,
147 147 cluster_manager, session_manager, kernel_spec_manager,
148 148 config_manager,
149 149 log, base_url, default_url, settings_overrides,
150 150 jinja_env_options=None):
151 151
152 152 _template_path = settings_overrides.get(
153 153 "template_path",
154 154 ipython_app.template_file_path,
155 155 )
156 156 if isinstance(_template_path, str):
157 157 _template_path = (_template_path,)
158 158 template_path = [os.path.expanduser(path) for path in _template_path]
159 159
160 160 jenv_opt = jinja_env_options if jinja_env_options else {}
161 161 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
162 162
163 163 sys_info = get_sys_info()
164 164 if sys_info['commit_source'] == 'repository':
165 165 # don't cache (rely on 304) when working from master
166 166 version_hash = ''
167 167 else:
168 168 # reset the cache on server restart
169 169 version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
170 170
171 171 settings = dict(
172 172 # basics
173 173 log_function=log_request,
174 174 base_url=base_url,
175 175 default_url=default_url,
176 176 template_path=template_path,
177 177 static_path=ipython_app.static_file_path,
178 178 static_handler_class = FileFindHandler,
179 179 static_url_prefix = url_path_join(base_url,'/static/'),
180 180 static_handler_args = {
181 181 # don't cache custom.js
182 182 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
183 183 },
184 184 version_hash=version_hash,
185 185
186 186 # authentication
187 187 cookie_secret=ipython_app.cookie_secret,
188 188 login_url=url_path_join(base_url,'/login'),
189 189 login_handler_class=ipython_app.login_handler_class,
190 190 logout_handler_class=ipython_app.logout_handler_class,
191 191 password=ipython_app.password,
192 192
193 193 # managers
194 194 kernel_manager=kernel_manager,
195 195 contents_manager=contents_manager,
196 196 cluster_manager=cluster_manager,
197 197 session_manager=session_manager,
198 198 kernel_spec_manager=kernel_spec_manager,
199 199 config_manager=config_manager,
200 200
201 201 # IPython stuff
202 202 nbextensions_path=ipython_app.nbextensions_path,
203 203 websocket_url=ipython_app.websocket_url,
204 204 mathjax_url=ipython_app.mathjax_url,
205 205 config=ipython_app.config,
206 206 jinja2_env=env,
207 207 terminals_available=False, # Set later if terminals are available
208 208 )
209 209
210 210 # allow custom overrides for the tornado web app.
211 211 settings.update(settings_overrides)
212 212 return settings
213 213
214 214 def init_handlers(self, settings):
215 215 """Load the (URL pattern, handler) tuples for each component."""
216 216
217 217 # Order matters. The first handler to match the URL will handle the request.
218 218 handlers = []
219 219 handlers.extend(load_handlers('tree.handlers'))
220 220 handlers.extend([(r"/login", settings['login_handler_class'])])
221 221 handlers.extend([(r"/logout", settings['logout_handler_class'])])
222 222 handlers.extend(load_handlers('files.handlers'))
223 223 handlers.extend(load_handlers('notebook.handlers'))
224 224 handlers.extend(load_handlers('nbconvert.handlers'))
225 225 handlers.extend(load_handlers('kernelspecs.handlers'))
226 226 handlers.extend(load_handlers('edit.handlers'))
227 227 handlers.extend(load_handlers('services.config.handlers'))
228 228 handlers.extend(load_handlers('services.kernels.handlers'))
229 229 handlers.extend(load_handlers('services.contents.handlers'))
230 230 handlers.extend(load_handlers('services.clusters.handlers'))
231 231 handlers.extend(load_handlers('services.sessions.handlers'))
232 232 handlers.extend(load_handlers('services.nbconvert.handlers'))
233 233 handlers.extend(load_handlers('services.kernelspecs.handlers'))
234 234 handlers.extend(load_handlers('services.security.handlers'))
235 235 handlers.append(
236 236 (r"/nbextensions/(.*)", FileFindHandler, {
237 237 'path': settings['nbextensions_path'],
238 238 'no_cache_paths': ['/'], # don't cache anything in nbextensions
239 239 }),
240 240 )
241 241 # register base handlers last
242 242 handlers.extend(load_handlers('base.handlers'))
243 243 # set the URL that will be redirected from `/`
244 244 handlers.append(
245 245 (r'/?', web.RedirectHandler, {
246 246 'url' : settings['default_url'],
247 247 'permanent': False, # want 302, not 301
248 248 })
249 249 )
250 250 # prepend base_url onto the patterns that we match
251 251 new_handlers = []
252 252 for handler in handlers:
253 253 pattern = url_path_join(settings['base_url'], handler[0])
254 254 new_handler = tuple([pattern] + list(handler[1:]))
255 255 new_handlers.append(new_handler)
256 256 # add 404 on the end, which will catch everything that falls through
257 257 new_handlers.append((r'(.*)', Template404))
258 258 return new_handlers
259 259
260 260
261 261 class NbserverListApp(BaseIPythonApplication):
262 262
263 263 description="List currently running notebook servers in this profile."
264 264
265 265 flags = dict(
266 266 json=({'NbserverListApp': {'json': True}},
267 267 "Produce machine-readable JSON output."),
268 268 )
269 269
270 270 json = Bool(False, config=True,
271 271 help="If True, each line of output will be a JSON object with the "
272 272 "details from the server info file.")
273 273
274 274 def start(self):
275 275 if not self.json:
276 276 print("Currently running servers:")
277 277 for serverinfo in list_running_servers(self.profile):
278 278 if self.json:
279 279 print(json.dumps(serverinfo))
280 280 else:
281 281 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
282 282
283 283 #-----------------------------------------------------------------------------
284 284 # Aliases and Flags
285 285 #-----------------------------------------------------------------------------
286 286
287 287 flags = dict(base_flags)
288 288 flags['no-browser']=(
289 289 {'NotebookApp' : {'open_browser' : False}},
290 290 "Don't open the notebook in a browser after startup."
291 291 )
292 292 flags['pylab']=(
293 293 {'NotebookApp' : {'pylab' : 'warn'}},
294 294 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
295 295 )
296 296 flags['no-mathjax']=(
297 297 {'NotebookApp' : {'enable_mathjax' : False}},
298 298 """Disable MathJax
299 299
300 300 MathJax is the javascript library IPython uses to render math/LaTeX. It is
301 301 very large, so you may want to disable it if you have a slow internet
302 302 connection, or for offline use of the notebook.
303 303
304 304 When disabled, equations etc. will appear as their untransformed TeX source.
305 305 """
306 306 )
307 307
308 308 # Add notebook manager flags
309 309 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
310 310 'DEPRECATED, IGNORED',
311 311 'DEPRECATED, IGNORED'))
312 312
313 313 aliases = dict(base_aliases)
314 314
315 315 aliases.update({
316 316 'ip': 'NotebookApp.ip',
317 317 'port': 'NotebookApp.port',
318 318 'port-retries': 'NotebookApp.port_retries',
319 319 'transport': 'KernelManager.transport',
320 320 'keyfile': 'NotebookApp.keyfile',
321 321 'certfile': 'NotebookApp.certfile',
322 322 'notebook-dir': 'NotebookApp.notebook_dir',
323 323 'browser': 'NotebookApp.browser',
324 324 'pylab': 'NotebookApp.pylab',
325 325 })
326 326
327 327 #-----------------------------------------------------------------------------
328 328 # NotebookApp
329 329 #-----------------------------------------------------------------------------
330 330
331 331 class NotebookApp(BaseIPythonApplication):
332 332
333 333 name = 'ipython-notebook'
334 334
335 335 description = """
336 336 The IPython HTML Notebook.
337 337
338 338 This launches a Tornado based HTML Notebook Server that serves up an
339 339 HTML5/Javascript Notebook client.
340 340 """
341 341 examples = _examples
342 342 aliases = aliases
343 343 flags = flags
344 344
345 345 classes = [
346 346 KernelManager, ProfileDir, Session, MappingKernelManager,
347 347 ContentsManager, FileContentsManager, NotebookNotary,
348 348 KernelSpecManager,
349 349 ]
350 350 flags = Dict(flags)
351 351 aliases = Dict(aliases)
352 352
353 353 subcommands = dict(
354 354 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
355 355 )
356 356
357 ipython_kernel_argv = List(Unicode)
358
359 357 _log_formatter_cls = LogFormatter
360 358
361 359 def _log_level_default(self):
362 360 return logging.INFO
363 361
364 362 def _log_datefmt_default(self):
365 363 """Exclude date from default date format"""
366 364 return "%H:%M:%S"
367 365
368 366 def _log_format_default(self):
369 367 """override default log format to include time"""
370 368 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
371 369
372 370 # create requested profiles by default, if they don't exist:
373 371 auto_create = Bool(True)
374 372
375 373 # file to be opened in the notebook server
376 374 file_to_run = Unicode('', config=True)
377 375
378 376 # Network related information
379 377
380 378 allow_origin = Unicode('', config=True,
381 379 help="""Set the Access-Control-Allow-Origin header
382 380
383 381 Use '*' to allow any origin to access your server.
384 382
385 383 Takes precedence over allow_origin_pat.
386 384 """
387 385 )
388 386
389 387 allow_origin_pat = Unicode('', config=True,
390 388 help="""Use a regular expression for the Access-Control-Allow-Origin header
391 389
392 390 Requests from an origin matching the expression will get replies with:
393 391
394 392 Access-Control-Allow-Origin: origin
395 393
396 394 where `origin` is the origin of the request.
397 395
398 396 Ignored if allow_origin is set.
399 397 """
400 398 )
401 399
402 400 allow_credentials = Bool(False, config=True,
403 401 help="Set the Access-Control-Allow-Credentials: true header"
404 402 )
405 403
406 404 default_url = Unicode('/tree', config=True,
407 405 help="The default URL to redirect to from `/`"
408 406 )
409 407
410 408 ip = Unicode('localhost', config=True,
411 409 help="The IP address the notebook server will listen on."
412 410 )
413 411 def _ip_default(self):
414 412 """Return localhost if available, 127.0.0.1 otherwise.
415 413
416 414 On some (horribly broken) systems, localhost cannot be bound.
417 415 """
418 416 s = socket.socket()
419 417 try:
420 418 s.bind(('localhost', 0))
421 419 except socket.error as e:
422 420 self.log.warn("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s", e)
423 421 return '127.0.0.1'
424 422 else:
425 423 s.close()
426 424 return 'localhost'
427 425
428 426 def _ip_changed(self, name, old, new):
429 427 if new == u'*': self.ip = u''
430 428
431 429 port = Integer(8888, config=True,
432 430 help="The port the notebook server will listen on."
433 431 )
434 432 port_retries = Integer(50, config=True,
435 433 help="The number of additional ports to try if the specified port is not available."
436 434 )
437 435
438 436 certfile = Unicode(u'', config=True,
439 437 help="""The full path to an SSL/TLS certificate file."""
440 438 )
441 439
442 440 keyfile = Unicode(u'', config=True,
443 441 help="""The full path to a private key file for usage with SSL/TLS."""
444 442 )
445 443
446 444 cookie_secret_file = Unicode(config=True,
447 445 help="""The file where the cookie secret is stored."""
448 446 )
449 447 def _cookie_secret_file_default(self):
450 448 if self.profile_dir is None:
451 449 return ''
452 450 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
453 451
454 452 cookie_secret = Bytes(b'', config=True,
455 453 help="""The random bytes used to secure cookies.
456 454 By default this is a new random number every time you start the Notebook.
457 455 Set it to a value in a config file to enable logins to persist across server sessions.
458 456
459 457 Note: Cookie secrets should be kept private, do not share config files with
460 458 cookie_secret stored in plaintext (you can read the value from a file).
461 459 """
462 460 )
463 461 def _cookie_secret_default(self):
464 462 if os.path.exists(self.cookie_secret_file):
465 463 with io.open(self.cookie_secret_file, 'rb') as f:
466 464 return f.read()
467 465 else:
468 466 secret = base64.encodestring(os.urandom(1024))
469 467 self._write_cookie_secret_file(secret)
470 468 return secret
471 469
472 470 def _write_cookie_secret_file(self, secret):
473 471 """write my secret to my secret_file"""
474 472 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
475 473 with io.open(self.cookie_secret_file, 'wb') as f:
476 474 f.write(secret)
477 475 try:
478 476 os.chmod(self.cookie_secret_file, 0o600)
479 477 except OSError:
480 478 self.log.warn(
481 479 "Could not set permissions on %s",
482 480 self.cookie_secret_file
483 481 )
484 482
485 483 password = Unicode(u'', config=True,
486 484 help="""Hashed password to use for web authentication.
487 485
488 486 To generate, type in a python/IPython shell:
489 487
490 488 from IPython.lib import passwd; passwd()
491 489
492 490 The string should be of the form type:salt:hashed-password.
493 491 """
494 492 )
495 493
496 494 open_browser = Bool(True, config=True,
497 495 help="""Whether to open in a browser after starting.
498 496 The specific browser used is platform dependent and
499 497 determined by the python standard library `webbrowser`
500 498 module, unless it is overridden using the --browser
501 499 (NotebookApp.browser) configuration option.
502 500 """)
503 501
504 502 browser = Unicode(u'', config=True,
505 503 help="""Specify what command to use to invoke a web
506 504 browser when opening the notebook. If not specified, the
507 505 default browser will be determined by the `webbrowser`
508 506 standard library module, which allows setting of the
509 507 BROWSER environment variable to override it.
510 508 """)
511 509
512 510 webapp_settings = Dict(config=True,
513 511 help="DEPRECATED, use tornado_settings"
514 512 )
515 513 def _webapp_settings_changed(self, name, old, new):
516 514 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
517 515 self.tornado_settings = new
518 516
519 517 tornado_settings = Dict(config=True,
520 518 help="Supply overrides for the tornado.web.Application that the "
521 519 "IPython notebook uses.")
522 520
523 521 ssl_options = Dict(config=True,
524 522 help="""Supply SSL options for the tornado HTTPServer.
525 523 See the tornado docs for details.""")
526 524
527 525 jinja_environment_options = Dict(config=True,
528 526 help="Supply extra arguments that will be passed to Jinja environment.")
529 527
530 528 enable_mathjax = Bool(True, config=True,
531 529 help="""Whether to enable MathJax for typesetting math/TeX
532 530
533 531 MathJax is the javascript library IPython uses to render math/LaTeX. It is
534 532 very large, so you may want to disable it if you have a slow internet
535 533 connection, or for offline use of the notebook.
536 534
537 535 When disabled, equations etc. will appear as their untransformed TeX source.
538 536 """
539 537 )
540 538 def _enable_mathjax_changed(self, name, old, new):
541 539 """set mathjax url to empty if mathjax is disabled"""
542 540 if not new:
543 541 self.mathjax_url = u''
544 542
545 543 base_url = Unicode('/', config=True,
546 544 help='''The base URL for the notebook server.
547 545
548 546 Leading and trailing slashes can be omitted,
549 547 and will automatically be added.
550 548 ''')
551 549 def _base_url_changed(self, name, old, new):
552 550 if not new.startswith('/'):
553 551 self.base_url = '/'+new
554 552 elif not new.endswith('/'):
555 553 self.base_url = new+'/'
556 554
557 555 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
558 556 def _base_project_url_changed(self, name, old, new):
559 557 self.log.warn("base_project_url is deprecated, use base_url")
560 558 self.base_url = new
561 559
562 560 extra_static_paths = List(Unicode, config=True,
563 561 help="""Extra paths to search for serving static files.
564 562
565 563 This allows adding javascript/css to be available from the notebook server machine,
566 564 or overriding individual files in the IPython"""
567 565 )
568 566 def _extra_static_paths_default(self):
569 567 return [os.path.join(self.profile_dir.location, 'static')]
570 568
571 569 @property
572 570 def static_file_path(self):
573 571 """return extra paths + the default location"""
574 572 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
575 573
576 574 extra_template_paths = List(Unicode, config=True,
577 575 help="""Extra paths to search for serving jinja templates.
578 576
579 577 Can be used to override templates from IPython.html.templates."""
580 578 )
581 579 def _extra_template_paths_default(self):
582 580 return []
583 581
584 582 @property
585 583 def template_file_path(self):
586 584 """return extra paths + the default locations"""
587 585 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
588 586
589 587 extra_nbextensions_path = List(Unicode, config=True,
590 588 help="""extra paths to look for Javascript notebook extensions"""
591 589 )
592 590
593 591 @property
594 592 def nbextensions_path(self):
595 593 """The path to look for Javascript notebook extensions"""
596 594 return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS
597 595
598 596 websocket_url = Unicode("", config=True,
599 597 help="""The base URL for websockets,
600 598 if it differs from the HTTP server (hint: it almost certainly doesn't).
601 599
602 600 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
603 601 """
604 602 )
605 603 mathjax_url = Unicode("", config=True,
606 604 help="""The url for MathJax.js."""
607 605 )
608 606 def _mathjax_url_default(self):
609 607 if not self.enable_mathjax:
610 608 return u''
611 609 static_url_prefix = self.tornado_settings.get("static_url_prefix",
612 610 url_path_join(self.base_url, "static")
613 611 )
614 612
615 613 # try local mathjax, either in nbextensions/mathjax or static/mathjax
616 614 for (url_prefix, search_path) in [
617 615 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
618 616 (static_url_prefix, self.static_file_path),
619 617 ]:
620 618 self.log.debug("searching for local mathjax in %s", search_path)
621 619 try:
622 620 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
623 621 except IOError:
624 622 continue
625 623 else:
626 624 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
627 625 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
628 626 return url
629 627
630 628 # no local mathjax, serve from CDN
631 629 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
632 630 self.log.info("Using MathJax from CDN: %s", url)
633 631 return url
634 632
635 633 def _mathjax_url_changed(self, name, old, new):
636 634 if new and not self.enable_mathjax:
637 635 # enable_mathjax=False overrides mathjax_url
638 636 self.mathjax_url = u''
639 637 else:
640 638 self.log.info("Using MathJax: %s", new)
641 639
642 640 contents_manager_class = Type(
643 641 default_value=FileContentsManager,
644 642 klass=ContentsManager,
645 643 config=True,
646 644 help='The notebook manager class to use.'
647 645 )
648 646 kernel_manager_class = Type(
649 647 default_value=MappingKernelManager,
650 648 config=True,
651 649 help='The kernel manager class to use.'
652 650 )
653 651 session_manager_class = Type(
654 652 default_value=SessionManager,
655 653 config=True,
656 654 help='The session manager class to use.'
657 655 )
658 656 cluster_manager_class = Type(
659 657 default_value=ClusterManager,
660 658 config=True,
661 659 help='The cluster manager class to use.'
662 660 )
663 661
664 662 config_manager_class = Type(
665 663 default_value=ConfigManager,
666 664 config = True,
667 665 help='The config manager class to use'
668 666 )
669 667
670 668 kernel_spec_manager = Instance(KernelSpecManager)
671 669
672 670 kernel_spec_manager_class = Type(
673 671 default_value=KernelSpecManager,
674 672 config=True,
675 673 help="""
676 674 The kernel spec manager class to use. Should be a subclass
677 675 of `IPython.kernel.kernelspec.KernelSpecManager`.
678 676
679 677 The Api of KernelSpecManager is provisional and might change
680 678 without warning between this version of IPython and the next stable one.
681 679 """
682 680 )
683 681
684 682 login_handler_class = Type(
685 683 default_value=LoginHandler,
686 684 klass=web.RequestHandler,
687 685 config=True,
688 686 help='The login handler class to use.',
689 687 )
690 688
691 689 logout_handler_class = Type(
692 690 default_value=LogoutHandler,
693 691 klass=web.RequestHandler,
694 692 config=True,
695 693 help='The logout handler class to use.',
696 694 )
697 695
698 696 trust_xheaders = Bool(False, config=True,
699 697 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
700 698 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
701 699 )
702 700
703 701 info_file = Unicode()
704 702
705 703 def _info_file_default(self):
706 704 info_file = "nbserver-%s.json"%os.getpid()
707 705 return os.path.join(self.profile_dir.security_dir, info_file)
708 706
709 707 pylab = Unicode('disabled', config=True,
710 708 help="""
711 709 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
712 710 """
713 711 )
714 712 def _pylab_changed(self, name, old, new):
715 713 """when --pylab is specified, display a warning and exit"""
716 714 if new != 'warn':
717 715 backend = ' %s' % new
718 716 else:
719 717 backend = ''
720 718 self.log.error("Support for specifying --pylab on the command line has been removed.")
721 719 self.log.error(
722 720 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
723 721 )
724 722 self.exit(1)
725 723
726 724 notebook_dir = Unicode(config=True,
727 725 help="The directory to use for notebooks and kernels."
728 726 )
729 727
730 728 def _notebook_dir_default(self):
731 729 if self.file_to_run:
732 730 return os.path.dirname(os.path.abspath(self.file_to_run))
733 731 else:
734 732 return py3compat.getcwd()
735 733
736 734 def _notebook_dir_changed(self, name, old, new):
737 735 """Do a bit of validation of the notebook dir."""
738 736 if not os.path.isabs(new):
739 737 # If we receive a non-absolute path, make it absolute.
740 738 self.notebook_dir = os.path.abspath(new)
741 739 return
742 740 if not os.path.isdir(new):
743 741 raise TraitError("No such notebook dir: %r" % new)
744 742
745 743 # setting App.notebook_dir implies setting notebook and kernel dirs as well
746 744 self.config.FileContentsManager.root_dir = new
747 745 self.config.MappingKernelManager.root_dir = new
748 746
749 747 server_extensions = List(Unicode(), config=True,
750 748 help=("Python modules to load as notebook server extensions. "
751 749 "This is an experimental API, and may change in future releases.")
752 750 )
753 751
754 752 reraise_server_extension_failures = Bool(
755 753 False,
756 754 config=True,
757 755 help="Reraise exceptions encountered loading server extensions?",
758 756 )
759 757
760 758 def parse_command_line(self, argv=None):
761 759 super(NotebookApp, self).parse_command_line(argv)
762 760
763 761 if self.extra_args:
764 762 arg0 = self.extra_args[0]
765 763 f = os.path.abspath(arg0)
766 764 self.argv.remove(arg0)
767 765 if not os.path.exists(f):
768 766 self.log.critical("No such file or directory: %s", f)
769 767 self.exit(1)
770 768
771 769 # Use config here, to ensure that it takes higher priority than
772 770 # anything that comes from the profile.
773 771 c = Config()
774 772 if os.path.isdir(f):
775 773 c.NotebookApp.notebook_dir = f
776 774 elif os.path.isfile(f):
777 775 c.NotebookApp.file_to_run = f
778 776 self.update_config(c)
779 777
780 def init_kernel_argv(self):
781 """add the profile-dir to arguments to be passed to IPython kernels"""
782 # FIXME: remove special treatment of IPython kernels
783 # Kernel should get *absolute* path to profile directory
784 self.ipython_kernel_argv = ["--profile-dir", self.profile_dir.location]
785
786 778 def init_configurables(self):
787 779 self.kernel_spec_manager = self.kernel_spec_manager_class(
788 780 parent=self,
789 781 ipython_dir=self.ipython_dir,
790 782 )
791 783 self.kernel_manager = self.kernel_manager_class(
792 784 parent=self,
793 785 log=self.log,
794 ipython_kernel_argv=self.ipython_kernel_argv,
795 786 connection_dir=self.profile_dir.security_dir,
796 787 )
797 788 self.contents_manager = self.contents_manager_class(
798 789 parent=self,
799 790 log=self.log,
800 791 )
801 792 self.session_manager = self.session_manager_class(
802 793 parent=self,
803 794 log=self.log,
804 795 kernel_manager=self.kernel_manager,
805 796 contents_manager=self.contents_manager,
806 797 )
807 798 self.cluster_manager = self.cluster_manager_class(
808 799 parent=self,
809 800 log=self.log,
810 801 )
811 802
812 803 self.config_manager = self.config_manager_class(
813 804 parent=self,
814 805 log=self.log,
815 806 profile_dir=self.profile_dir.location,
816 807 )
817 808
818 809 def init_logging(self):
819 810 # This prevents double log messages because tornado use a root logger that
820 811 # self.log is a child of. The logging module dipatches log messages to a log
821 812 # and all of its ancenstors until propagate is set to False.
822 813 self.log.propagate = False
823 814
824 815 for log in app_log, access_log, gen_log:
825 816 # consistent log output name (NotebookApp instead of tornado.access, etc.)
826 817 log.name = self.log.name
827 818 # hook up tornado 3's loggers to our app handlers
828 819 logger = logging.getLogger('tornado')
829 820 logger.propagate = True
830 821 logger.parent = self.log
831 822 logger.setLevel(self.log.level)
832 823
833 824 def init_webapp(self):
834 825 """initialize tornado webapp and httpserver"""
835 826 self.tornado_settings['allow_origin'] = self.allow_origin
836 827 if self.allow_origin_pat:
837 828 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
838 829 self.tornado_settings['allow_credentials'] = self.allow_credentials
839 830 # ensure default_url starts with base_url
840 831 if not self.default_url.startswith(self.base_url):
841 832 self.default_url = url_path_join(self.base_url, self.default_url)
842 833
843 834 self.web_app = NotebookWebApplication(
844 835 self, self.kernel_manager, self.contents_manager,
845 836 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
846 837 self.config_manager,
847 838 self.log, self.base_url, self.default_url, self.tornado_settings,
848 839 self.jinja_environment_options
849 840 )
850 841 ssl_options = self.ssl_options
851 842 if self.certfile:
852 843 ssl_options['certfile'] = self.certfile
853 844 if self.keyfile:
854 845 ssl_options['keyfile'] = self.keyfile
855 846 if not ssl_options:
856 847 # None indicates no SSL config
857 848 ssl_options = None
858 849 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
859 850 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
860 851 xheaders=self.trust_xheaders)
861 852
862 853 success = None
863 854 for port in random_ports(self.port, self.port_retries+1):
864 855 try:
865 856 self.http_server.listen(port, self.ip)
866 857 except socket.error as e:
867 858 if e.errno == errno.EADDRINUSE:
868 859 self.log.info('The port %i is already in use, trying another random port.' % port)
869 860 continue
870 861 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
871 862 self.log.warn("Permission to listen on port %i denied" % port)
872 863 continue
873 864 else:
874 865 raise
875 866 else:
876 867 self.port = port
877 868 success = True
878 869 break
879 870 if not success:
880 871 self.log.critical('ERROR: the notebook server could not be started because '
881 872 'no available port could be found.')
882 873 self.exit(1)
883 874
884 875 @property
885 876 def display_url(self):
886 877 ip = self.ip if self.ip else '[all ip addresses on your system]'
887 878 return self._url(ip)
888 879
889 880 @property
890 881 def connection_url(self):
891 882 ip = self.ip if self.ip else 'localhost'
892 883 return self._url(ip)
893 884
894 885 def _url(self, ip):
895 886 proto = 'https' if self.certfile else 'http'
896 887 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
897 888
898 889 def init_terminals(self):
899 890 try:
900 891 from .terminal import initialize
901 892 initialize(self.web_app)
902 893 self.web_app.settings['terminals_available'] = True
903 894 except ImportError as e:
904 895 log = self.log.debug if sys.platform == 'win32' else self.log.warn
905 896 log("Terminals not available (error was %s)", e)
906 897
907 898 def init_signal(self):
908 899 if not sys.platform.startswith('win'):
909 900 signal.signal(signal.SIGINT, self._handle_sigint)
910 901 signal.signal(signal.SIGTERM, self._signal_stop)
911 902 if hasattr(signal, 'SIGUSR1'):
912 903 # Windows doesn't support SIGUSR1
913 904 signal.signal(signal.SIGUSR1, self._signal_info)
914 905 if hasattr(signal, 'SIGINFO'):
915 906 # only on BSD-based systems
916 907 signal.signal(signal.SIGINFO, self._signal_info)
917 908
918 909 def _handle_sigint(self, sig, frame):
919 910 """SIGINT handler spawns confirmation dialog"""
920 911 # register more forceful signal handler for ^C^C case
921 912 signal.signal(signal.SIGINT, self._signal_stop)
922 913 # request confirmation dialog in bg thread, to avoid
923 914 # blocking the App
924 915 thread = threading.Thread(target=self._confirm_exit)
925 916 thread.daemon = True
926 917 thread.start()
927 918
928 919 def _restore_sigint_handler(self):
929 920 """callback for restoring original SIGINT handler"""
930 921 signal.signal(signal.SIGINT, self._handle_sigint)
931 922
932 923 def _confirm_exit(self):
933 924 """confirm shutdown on ^C
934 925
935 926 A second ^C, or answering 'y' within 5s will cause shutdown,
936 927 otherwise original SIGINT handler will be restored.
937 928
938 929 This doesn't work on Windows.
939 930 """
940 931 info = self.log.info
941 932 info('interrupted')
942 933 print(self.notebook_info())
943 934 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
944 935 sys.stdout.flush()
945 936 r,w,x = select.select([sys.stdin], [], [], 5)
946 937 if r:
947 938 line = sys.stdin.readline()
948 939 if line.lower().startswith('y') and 'n' not in line.lower():
949 940 self.log.critical("Shutdown confirmed")
950 941 ioloop.IOLoop.current().stop()
951 942 return
952 943 else:
953 944 print("No answer for 5s:", end=' ')
954 945 print("resuming operation...")
955 946 # no answer, or answer is no:
956 947 # set it back to original SIGINT handler
957 948 # use IOLoop.add_callback because signal.signal must be called
958 949 # from main thread
959 950 ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
960 951
961 952 def _signal_stop(self, sig, frame):
962 953 self.log.critical("received signal %s, stopping", sig)
963 954 ioloop.IOLoop.current().stop()
964 955
965 956 def _signal_info(self, sig, frame):
966 957 print(self.notebook_info())
967 958
968 959 def init_components(self):
969 960 """Check the components submodule, and warn if it's unclean"""
970 961 status = submodule.check_submodule_status()
971 962 if status == 'missing':
972 963 self.log.warn("components submodule missing, running `git submodule update`")
973 964 submodule.update_submodules(submodule.ipython_parent())
974 965 elif status == 'unclean':
975 966 self.log.warn("components submodule unclean, you may see 404s on static/components")
976 967 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
977 968
978 969 def init_server_extensions(self):
979 970 """Load any extensions specified by config.
980 971
981 972 Import the module, then call the load_jupyter_server_extension function,
982 973 if one exists.
983 974
984 975 The extension API is experimental, and may change in future releases.
985 976 """
986 977 for modulename in self.server_extensions:
987 978 try:
988 979 mod = importlib.import_module(modulename)
989 980 func = getattr(mod, 'load_jupyter_server_extension', None)
990 981 if func is not None:
991 982 func(self)
992 983 except Exception:
993 984 if self.reraise_server_extension_failures:
994 985 raise
995 986 self.log.warn("Error loading server extension %s", modulename,
996 987 exc_info=True)
997 988
998 989 @catch_config_error
999 990 def initialize(self, argv=None):
1000 991 super(NotebookApp, self).initialize(argv)
1001 992 self.init_logging()
1002 self.init_kernel_argv()
1003 993 self.init_configurables()
1004 994 self.init_components()
1005 995 self.init_webapp()
1006 996 self.init_terminals()
1007 997 self.init_signal()
1008 998 self.init_server_extensions()
1009 999
1010 1000 def cleanup_kernels(self):
1011 1001 """Shutdown all kernels.
1012 1002
1013 1003 The kernels will shutdown themselves when this process no longer exists,
1014 1004 but explicit shutdown allows the KernelManagers to cleanup the connection files.
1015 1005 """
1016 1006 self.log.info('Shutting down kernels')
1017 1007 self.kernel_manager.shutdown_all()
1018 1008
1019 1009 def notebook_info(self):
1020 1010 "Return the current working directory and the server url information"
1021 1011 info = self.contents_manager.info_string() + "\n"
1022 1012 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
1023 1013 return info + "The IPython Notebook is running at: %s" % self.display_url
1024 1014
1025 1015 def server_info(self):
1026 1016 """Return a JSONable dict of information about this server."""
1027 1017 return {'url': self.connection_url,
1028 1018 'hostname': self.ip if self.ip else 'localhost',
1029 1019 'port': self.port,
1030 1020 'secure': bool(self.certfile),
1031 1021 'base_url': self.base_url,
1032 1022 'notebook_dir': os.path.abspath(self.notebook_dir),
1033 1023 'pid': os.getpid()
1034 1024 }
1035 1025
1036 1026 def write_server_info_file(self):
1037 1027 """Write the result of server_info() to the JSON file info_file."""
1038 1028 with open(self.info_file, 'w') as f:
1039 1029 json.dump(self.server_info(), f, indent=2)
1040 1030
1041 1031 def remove_server_info_file(self):
1042 1032 """Remove the nbserver-<pid>.json file created for this server.
1043 1033
1044 1034 Ignores the error raised when the file has already been removed.
1045 1035 """
1046 1036 try:
1047 1037 os.unlink(self.info_file)
1048 1038 except OSError as e:
1049 1039 if e.errno != errno.ENOENT:
1050 1040 raise
1051 1041
1052 1042 def start(self):
1053 1043 """ Start the IPython Notebook server app, after initialization
1054 1044
1055 1045 This method takes no arguments so all configuration and initialization
1056 1046 must be done prior to calling this method."""
1057 1047 if self.subapp is not None:
1058 1048 return self.subapp.start()
1059 1049
1060 1050 info = self.log.info
1061 1051 for line in self.notebook_info().split("\n"):
1062 1052 info(line)
1063 1053 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
1064 1054
1065 1055 self.write_server_info_file()
1066 1056
1067 1057 if self.open_browser or self.file_to_run:
1068 1058 try:
1069 1059 browser = webbrowser.get(self.browser or None)
1070 1060 except webbrowser.Error as e:
1071 1061 self.log.warn('No web browser found: %s.' % e)
1072 1062 browser = None
1073 1063
1074 1064 if self.file_to_run:
1075 1065 if not os.path.exists(self.file_to_run):
1076 1066 self.log.critical("%s does not exist" % self.file_to_run)
1077 1067 self.exit(1)
1078 1068
1079 1069 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
1080 1070 uri = url_path_join('notebooks', *relpath.split(os.sep))
1081 1071 else:
1082 1072 uri = 'tree'
1083 1073 if browser:
1084 1074 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1085 1075 new=2)
1086 1076 threading.Thread(target=b).start()
1087 1077
1088 1078 self.io_loop = ioloop.IOLoop.current()
1089 1079 if sys.platform.startswith('win'):
1090 1080 # add no-op to wake every 5s
1091 1081 # to handle signals that may be ignored by the inner loop
1092 1082 pc = ioloop.PeriodicCallback(lambda : None, 5000)
1093 1083 pc.start()
1094 1084 try:
1095 1085 self.io_loop.start()
1096 1086 except KeyboardInterrupt:
1097 1087 info("Interrupted...")
1098 1088 finally:
1099 1089 self.cleanup_kernels()
1100 1090 self.remove_server_info_file()
1101 1091
1102 1092 def stop(self):
1103 1093 def _stop():
1104 1094 self.http_server.stop()
1105 1095 self.io_loop.stop()
1106 1096 self.io_loop.add_callback(_stop)
1107 1097
1108 1098
1109 1099 def list_running_servers(profile='default'):
1110 1100 """Iterate over the server info files of running notebook servers.
1111 1101
1112 1102 Given a profile name, find nbserver-* files in the security directory of
1113 1103 that profile, and yield dicts of their information, each one pertaining to
1114 1104 a currently running notebook server instance.
1115 1105 """
1116 1106 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1117 1107 for file in os.listdir(pd.security_dir):
1118 1108 if file.startswith('nbserver-'):
1119 1109 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1120 1110 info = json.load(f)
1121 1111
1122 1112 # Simple check whether that process is really still running
1123 1113 # Also remove leftover files from IPython 2.x without a pid field
1124 1114 if ('pid' in info) and check_pid(info['pid']):
1125 1115 yield info
1126 1116 else:
1127 1117 # If the process has died, try to delete its info file
1128 1118 try:
1129 1119 os.unlink(file)
1130 1120 except OSError:
1131 1121 pass # TODO: This should warn or log or something
1132 1122 #-----------------------------------------------------------------------------
1133 1123 # Main entry point
1134 1124 #-----------------------------------------------------------------------------
1135 1125
1136 1126 launch_new_instance = NotebookApp.launch_instance
1137 1127
@@ -1,395 +1,380 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The :class:`~IPython.core.application.Application` object for the command
5 5 line :command:`ipython` program.
6
7 Authors
8 -------
9
10 * Brian Granger
11 * Fernando Perez
12 * Min Ragan-Kelley
13 6 """
14 7
15 #-----------------------------------------------------------------------------
16 # Copyright (C) 2008-2011 The IPython Development Team
17 #
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
20 #-----------------------------------------------------------------------------
21
22 #-----------------------------------------------------------------------------
23 # Imports
24 #-----------------------------------------------------------------------------
8 # Copyright (c) IPython Development Team.
9 # Distributed under the terms of the Modified BSD License.
25 10
26 11 from __future__ import absolute_import
27 12 from __future__ import print_function
28 13
29 14 import logging
30 15 import os
31 16 import sys
32 17
33 18 from IPython.config.loader import Config
34 19 from IPython.config.application import boolean_flag, catch_config_error, Application
35 20 from IPython.core import release
36 21 from IPython.core import usage
37 22 from IPython.core.completer import IPCompleter
38 23 from IPython.core.crashhandler import CrashHandler
39 24 from IPython.core.formatters import PlainTextFormatter
40 25 from IPython.core.history import HistoryManager
41 26 from IPython.core.prompts import PromptManager
42 27 from IPython.core.application import (
43 28 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
44 29 )
45 30 from IPython.core.magics import ScriptMagics
46 31 from IPython.core.shellapp import (
47 32 InteractiveShellApp, shell_flags, shell_aliases
48 33 )
49 34 from IPython.extensions.storemagic import StoreMagics
50 35 from IPython.terminal.interactiveshell import TerminalInteractiveShell
51 36 from IPython.utils import warn
52 37 from IPython.utils.path import get_ipython_dir, check_for_old_config
53 38 from IPython.utils.traitlets import (
54 39 Bool, List, Dict,
55 40 )
56 41
57 42 #-----------------------------------------------------------------------------
58 43 # Globals, utilities and helpers
59 44 #-----------------------------------------------------------------------------
60 45
61 46 _examples = """
62 47 ipython --matplotlib # enable matplotlib integration
63 48 ipython --matplotlib=qt # enable matplotlib integration with qt4 backend
64 49
65 50 ipython --log-level=DEBUG # set logging to DEBUG
66 51 ipython --profile=foo # start with profile foo
67 52
68 53 ipython qtconsole # start the qtconsole GUI application
69 54 ipython help qtconsole # show the help for the qtconsole subcmd
70 55
71 56 ipython console # start the terminal-based console application
72 57 ipython help console # show the help for the console subcmd
73 58
74 59 ipython notebook # start the IPython notebook
75 60 ipython help notebook # show the help for the notebook subcmd
76 61
77 62 ipython profile create foo # create profile foo w/ default config files
78 63 ipython help profile # show the help for the profile subcmd
79 64
80 65 ipython locate # print the path to the IPython directory
81 66 ipython locate profile foo # print the path to the directory for profile `foo`
82 67
83 68 ipython nbconvert # convert notebooks to/from other formats
84 69 """
85 70
86 71 #-----------------------------------------------------------------------------
87 72 # Crash handler for this application
88 73 #-----------------------------------------------------------------------------
89 74
90 75 class IPAppCrashHandler(CrashHandler):
91 76 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
92 77
93 78 def __init__(self, app):
94 79 contact_name = release.author
95 80 contact_email = release.author_email
96 81 bug_tracker = 'https://github.com/ipython/ipython/issues'
97 82 super(IPAppCrashHandler,self).__init__(
98 83 app, contact_name, contact_email, bug_tracker
99 84 )
100 85
101 86 def make_report(self,traceback):
102 87 """Return a string containing a crash report."""
103 88
104 89 sec_sep = self.section_sep
105 90 # Start with parent report
106 91 report = [super(IPAppCrashHandler, self).make_report(traceback)]
107 92 # Add interactive-specific info we may have
108 93 rpt_add = report.append
109 94 try:
110 95 rpt_add(sec_sep+"History of session input:")
111 96 for line in self.app.shell.user_ns['_ih']:
112 97 rpt_add(line)
113 98 rpt_add('\n*** Last line of input (may not be in above history):\n')
114 99 rpt_add(self.app.shell._last_input_line+'\n')
115 100 except:
116 101 pass
117 102
118 103 return ''.join(report)
119 104
120 105 #-----------------------------------------------------------------------------
121 106 # Aliases and Flags
122 107 #-----------------------------------------------------------------------------
123 108 flags = dict(base_flags)
124 109 flags.update(shell_flags)
125 110 frontend_flags = {}
126 111 addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
127 112 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
128 113 'Turn on auto editing of files with syntax errors.',
129 114 'Turn off auto editing of files with syntax errors.'
130 115 )
131 116 addflag('banner', 'TerminalIPythonApp.display_banner',
132 117 "Display a banner upon starting IPython.",
133 118 "Don't display a banner upon starting IPython."
134 119 )
135 120 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
136 121 """Set to confirm when you try to exit IPython with an EOF (Control-D
137 122 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
138 123 you can force a direct exit without any confirmation.""",
139 124 "Don't prompt the user when exiting."
140 125 )
141 126 addflag('term-title', 'TerminalInteractiveShell.term_title',
142 127 "Enable auto setting the terminal title.",
143 128 "Disable auto setting the terminal title."
144 129 )
145 130 classic_config = Config()
146 131 classic_config.InteractiveShell.cache_size = 0
147 132 classic_config.PlainTextFormatter.pprint = False
148 133 classic_config.PromptManager.in_template = '>>> '
149 134 classic_config.PromptManager.in2_template = '... '
150 135 classic_config.PromptManager.out_template = ''
151 136 classic_config.InteractiveShell.separate_in = ''
152 137 classic_config.InteractiveShell.separate_out = ''
153 138 classic_config.InteractiveShell.separate_out2 = ''
154 139 classic_config.InteractiveShell.colors = 'NoColor'
155 140 classic_config.InteractiveShell.xmode = 'Plain'
156 141
157 142 frontend_flags['classic']=(
158 143 classic_config,
159 144 "Gives IPython a similar feel to the classic Python prompt."
160 145 )
161 146 # # log doesn't make so much sense this way anymore
162 147 # paa('--log','-l',
163 148 # action='store_true', dest='InteractiveShell.logstart',
164 149 # help="Start logging to the default log file (./ipython_log.py).")
165 150 #
166 151 # # quick is harder to implement
167 152 frontend_flags['quick']=(
168 153 {'TerminalIPythonApp' : {'quick' : True}},
169 154 "Enable quick startup with no config files."
170 155 )
171 156
172 157 frontend_flags['i'] = (
173 158 {'TerminalIPythonApp' : {'force_interact' : True}},
174 159 """If running code from the command line, become interactive afterwards."""
175 160 )
176 161 flags.update(frontend_flags)
177 162
178 163 aliases = dict(base_aliases)
179 164 aliases.update(shell_aliases)
180 165
181 166 #-----------------------------------------------------------------------------
182 167 # Main classes and functions
183 168 #-----------------------------------------------------------------------------
184 169
185 170
186 171 class LocateIPythonApp(BaseIPythonApplication):
187 172 description = """print the path to the IPython dir"""
188 173 subcommands = Dict(dict(
189 174 profile=('IPython.core.profileapp.ProfileLocate',
190 175 "print the path to an IPython profile directory",
191 176 ),
192 177 ))
193 178 def start(self):
194 179 if self.subapp is not None:
195 180 return self.subapp.start()
196 181 else:
197 182 print(self.ipython_dir)
198 183
199 184
200 185 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
201 186 name = u'ipython'
202 187 description = usage.cl_usage
203 188 crash_handler_class = IPAppCrashHandler
204 189 examples = _examples
205 190
206 191 flags = Dict(flags)
207 192 aliases = Dict(aliases)
208 193 classes = List()
209 194 def _classes_default(self):
210 195 """This has to be in a method, for TerminalIPythonApp to be available."""
211 196 return [
212 197 InteractiveShellApp, # ShellApp comes before TerminalApp, because
213 198 self.__class__, # it will also affect subclasses (e.g. QtConsole)
214 199 TerminalInteractiveShell,
215 200 PromptManager,
216 201 HistoryManager,
217 202 ProfileDir,
218 203 PlainTextFormatter,
219 204 IPCompleter,
220 205 ScriptMagics,
221 206 StoreMagics,
222 207 ]
223 208
224 209 subcommands = dict(
225 210 qtconsole=('IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
226 211 """Launch the IPython Qt Console."""
227 212 ),
228 213 notebook=('IPython.html.notebookapp.NotebookApp',
229 214 """Launch the IPython HTML Notebook Server."""
230 215 ),
231 216 profile = ("IPython.core.profileapp.ProfileApp",
232 217 "Create and manage IPython profiles."
233 218 ),
234 219 kernel = ("IPython.kernel.zmq.kernelapp.IPKernelApp",
235 220 "Start a kernel without an attached frontend."
236 221 ),
237 222 console=('IPython.terminal.console.app.ZMQTerminalIPythonApp',
238 223 """Launch the IPython terminal-based Console."""
239 224 ),
240 225 locate=('IPython.terminal.ipapp.LocateIPythonApp',
241 226 LocateIPythonApp.description
242 227 ),
243 228 history=('IPython.core.historyapp.HistoryApp',
244 229 "Manage the IPython history database."
245 230 ),
246 231 nbconvert=('IPython.nbconvert.nbconvertapp.NbConvertApp',
247 232 "Convert notebooks to/from other formats."
248 233 ),
249 234 trust=('IPython.nbformat.sign.TrustNotebookApp',
250 235 "Sign notebooks to trust their potentially unsafe contents at load."
251 236 ),
252 237 kernelspec=('IPython.kernel.kernelspecapp.KernelSpecApp',
253 238 "Manage IPython kernel specifications."
254 239 ),
255 240 )
256 241 subcommands['install-nbextension'] = (
257 242 "IPython.html.nbextensions.NBExtensionApp",
258 243 "Install IPython notebook extension files"
259 244 )
260 245
261 246 # *do* autocreate requested profile, but don't create the config file.
262 247 auto_create=Bool(True)
263 248 # configurables
264 249 ignore_old_config=Bool(False, config=True,
265 250 help="Suppress warning messages about legacy config files"
266 251 )
267 252 quick = Bool(False, config=True,
268 253 help="""Start IPython quickly by skipping the loading of config files."""
269 254 )
270 255 def _quick_changed(self, name, old, new):
271 256 if new:
272 257 self.load_config_file = lambda *a, **kw: None
273 258 self.ignore_old_config=True
274 259
275 260 display_banner = Bool(True, config=True,
276 261 help="Whether to display a banner upon starting IPython."
277 262 )
278 263
279 264 # if there is code of files to run from the cmd line, don't interact
280 265 # unless the --i flag (App.force_interact) is true.
281 266 force_interact = Bool(False, config=True,
282 267 help="""If a command or file is given via the command-line,
283 268 e.g. 'ipython foo.py', start an interactive shell after executing the
284 269 file or command."""
285 270 )
286 271 def _force_interact_changed(self, name, old, new):
287 272 if new:
288 273 self.interact = True
289 274
290 275 def _file_to_run_changed(self, name, old, new):
291 276 if new:
292 277 self.something_to_run = True
293 278 if new and not self.force_interact:
294 279 self.interact = False
295 280 _code_to_run_changed = _file_to_run_changed
296 281 _module_to_run_changed = _file_to_run_changed
297 282
298 283 # internal, not-configurable
299 284 interact=Bool(True)
300 285 something_to_run=Bool(False)
301 286
302 287 def parse_command_line(self, argv=None):
303 288 """override to allow old '-pylab' flag with deprecation warning"""
304 289
305 290 argv = sys.argv[1:] if argv is None else argv
306 291
307 292 if '-pylab' in argv:
308 293 # deprecated `-pylab` given,
309 294 # warn and transform into current syntax
310 295 argv = argv[:] # copy, don't clobber
311 296 idx = argv.index('-pylab')
312 297 warn.warn("`-pylab` flag has been deprecated.\n"
313 298 " Use `--matplotlib <backend>` and import pylab manually.")
314 299 argv[idx] = '--pylab'
315 300
316 301 return super(TerminalIPythonApp, self).parse_command_line(argv)
317 302
318 303 @catch_config_error
319 304 def initialize(self, argv=None):
320 305 """Do actions after construct, but before starting the app."""
321 306 super(TerminalIPythonApp, self).initialize(argv)
322 307 if self.subapp is not None:
323 308 # don't bother initializing further, starting subapp
324 309 return
325 310 if not self.ignore_old_config:
326 311 check_for_old_config(self.ipython_dir)
327 312 # print self.extra_args
328 313 if self.extra_args and not self.something_to_run:
329 314 self.file_to_run = self.extra_args[0]
330 315 self.init_path()
331 316 # create the shell
332 317 self.init_shell()
333 318 # and draw the banner
334 319 self.init_banner()
335 320 # Now a variety of things that happen after the banner is printed.
336 321 self.init_gui_pylab()
337 322 self.init_extensions()
338 323 self.init_code()
339 324
340 325 def init_shell(self):
341 326 """initialize the InteractiveShell instance"""
342 327 # Create an InteractiveShell instance.
343 328 # shell.display_banner should always be False for the terminal
344 329 # based app, because we call shell.show_banner() by hand below
345 330 # so the banner shows *before* all extension loading stuff.
346 331 self.shell = TerminalInteractiveShell.instance(parent=self,
347 332 display_banner=False, profile_dir=self.profile_dir,
348 333 ipython_dir=self.ipython_dir, user_ns=self.user_ns)
349 334 self.shell.configurables.append(self)
350 335
351 336 def init_banner(self):
352 337 """optionally display the banner"""
353 338 if self.display_banner and self.interact:
354 339 self.shell.show_banner()
355 340 # Make sure there is a space below the banner.
356 341 if self.log_level <= logging.INFO: print()
357 342
358 343 def _pylab_changed(self, name, old, new):
359 344 """Replace --pylab='inline' with --pylab='auto'"""
360 345 if new == 'inline':
361 346 warn.warn("'inline' not available as pylab backend, "
362 347 "using 'auto' instead.")
363 348 self.pylab = 'auto'
364 349
365 350 def start(self):
366 351 if self.subapp is not None:
367 352 return self.subapp.start()
368 353 # perform any prexec steps:
369 354 if self.interact:
370 355 self.log.debug("Starting IPython's mainloop...")
371 356 self.shell.mainloop()
372 357 else:
373 358 self.log.debug("IPython not interactive...")
374 359
375 360 def load_default_config(ipython_dir=None):
376 361 """Load the default config file from the default ipython_dir.
377 362
378 363 This is useful for embedded shells.
379 364 """
380 365 if ipython_dir is None:
381 366 ipython_dir = get_ipython_dir()
382 367
383 368 profile_dir = os.path.join(ipython_dir, 'profile_default')
384 369
385 370 config = Config()
386 371 for cf in Application._load_config_files("ipython_config", path=profile_dir):
387 372 config.update(cf)
388 373
389 374 return config
390 375
391 376 launch_new_instance = TerminalIPythonApp.launch_instance
392 377
393 378
394 379 if __name__ == '__main__':
395 380 launch_new_instance()
@@ -1,149 +1,146 b''
1 1 """ A minimal application using the ZMQ-based terminal IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
5
6 Authors:
7
8 * Min RK
9 * Paul Ivanov
10
11 5 """
12 6
13 #-----------------------------------------------------------------------------
14 # Imports
15 #-----------------------------------------------------------------------------
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
9
16 10 import signal
17 11
18 12 from IPython.terminal.ipapp import TerminalIPythonApp, frontend_flags as term_flags
19 13
20 14 from IPython.utils.traitlets import (
21 15 Dict, Any
22 16 )
23 17 from IPython.utils.warn import error
24 18
25 19 from IPython.consoleapp import (
26 20 IPythonConsoleApp, app_aliases, app_flags, aliases, flags
27 21 )
28 22
29 23 from IPython.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
30 24
31 25 #-----------------------------------------------------------------------------
32 26 # Globals
33 27 #-----------------------------------------------------------------------------
34 28
35 29 _examples = """
36 30 ipython console # start the ZMQ-based console
37 31 ipython console --existing # connect to an existing ipython session
38 32 """
39 33
40 34 #-----------------------------------------------------------------------------
41 35 # Flags and Aliases
42 36 #-----------------------------------------------------------------------------
43 37
44 38 # copy flags from mixin:
45 39 flags = dict(flags)
46 40 # start with mixin frontend flags:
47 41 frontend_flags = dict(app_flags)
48 42 # add TerminalIPApp flags:
49 43 frontend_flags.update(term_flags)
50 44 # disable quick startup, as it won't propagate to the kernel anyway
51 45 frontend_flags.pop('quick')
52 46 # update full dict with frontend flags:
53 47 flags.update(frontend_flags)
54 48
55 49 # copy flags from mixin
56 50 aliases = dict(aliases)
57 51 # start with mixin frontend flags
58 52 frontend_aliases = dict(app_aliases)
59 53 # load updated frontend flags into full dict
60 54 aliases.update(frontend_aliases)
55 aliases['colors'] = 'InteractiveShell.colors'
61 56
62 57 # get flags&aliases into sets, and remove a couple that
63 58 # shouldn't be scrubbed from backend flags:
64 59 frontend_aliases = set(frontend_aliases.keys())
65 60 frontend_flags = set(frontend_flags.keys())
66 61
67 62
68 63 #-----------------------------------------------------------------------------
69 64 # Classes
70 65 #-----------------------------------------------------------------------------
71 66
72 67
73 68 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):
74 69 name = "ipython-console"
75 70 """Start a terminal frontend to the IPython zmq kernel."""
76 71
77 72 description = """
78 73 The IPython terminal-based Console.
79 74
80 75 This launches a Console application inside a terminal.
81 76
82 77 The Console supports various extra features beyond the traditional
83 78 single-process Terminal IPython shell, such as connecting to an
84 79 existing ipython session, via:
85 80
86 81 ipython console --existing
87 82
88 83 where the previous session could have been created by another ipython
89 84 console, an ipython qtconsole, or by opening an ipython notebook.
90 85
91 86 """
92 87 examples = _examples
93 88
94 89 classes = [ZMQTerminalInteractiveShell] + IPythonConsoleApp.classes
95 90 flags = Dict(flags)
96 91 aliases = Dict(aliases)
97 92 frontend_aliases = Any(frontend_aliases)
98 93 frontend_flags = Any(frontend_flags)
99 94
100 95 subcommands = Dict()
101 96
97 force_interact = True
98
102 99 def parse_command_line(self, argv=None):
103 100 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
104 self.build_kernel_argv(argv)
101 self.build_kernel_argv(self.extra_args)
105 102
106 103 def init_shell(self):
107 104 IPythonConsoleApp.initialize(self)
108 105 # relay sigint to kernel
109 106 signal.signal(signal.SIGINT, self.handle_sigint)
110 107 self.shell = ZMQTerminalInteractiveShell.instance(parent=self,
111 108 display_banner=False, profile_dir=self.profile_dir,
112 109 ipython_dir=self.ipython_dir,
113 110 manager=self.kernel_manager,
114 111 client=self.kernel_client,
115 112 )
116 113
117 114 def init_gui_pylab(self):
118 115 # no-op, because we don't want to import matplotlib in the frontend.
119 116 pass
120 117
121 118 def handle_sigint(self, *args):
122 119 if self.shell._executing:
123 120 if self.kernel_manager:
124 121 # interrupt already gets passed to subprocess by signal handler.
125 122 # Only if we prevent that should we need to explicitly call
126 123 # interrupt_kernel, until which time, this would result in a
127 124 # double-interrupt:
128 125 # self.kernel_manager.interrupt_kernel()
129 126 pass
130 127 else:
131 128 self.shell.write_err('\n')
132 129 error("Cannot interrupt kernels we didn't start.\n")
133 130 else:
134 131 # raise the KeyboardInterrupt if we aren't waiting for execution,
135 132 # so that the interact loop advances, and prompt is redrawn, etc.
136 133 raise KeyboardInterrupt
137 134
138 135
139 136 def init_code(self):
140 137 # no-op in the frontend, code gets run in the backend
141 138 pass
142 139
143 140
144 141 launch_new_instance = ZMQTerminalIPythonApp.launch_instance
145 142
146 143
147 144 if __name__ == '__main__':
148 145 launch_new_instance()
149 146
@@ -1,386 +1,379 b''
1 1 """ A minimal application using the Qt console-style IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
5 5 """
6 6
7 7 # Copyright (c) IPython Development Team.
8 8 # Distributed under the terms of the Modified BSD License.
9 9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 # stdlib imports
15 10 import os
16 11 import signal
17 12 import sys
18 13
19 14 # If run on Windows, install an exception hook which pops up a
20 15 # message box. Pythonw.exe hides the console, so without this
21 16 # the application silently fails to load.
22 17 #
23 18 # We always install this handler, because the expectation is for
24 19 # qtconsole to bring up a GUI even if called from the console.
25 20 # The old handler is called, so the exception is printed as well.
26 21 # If desired, check for pythonw with an additional condition
27 22 # (sys.executable.lower().find('pythonw.exe') >= 0).
28 23 if os.name == 'nt':
29 24 old_excepthook = sys.excepthook
30 25
31 26 # Exclude this from our autogenerated API docs.
32 27 undoc = lambda func: func
33 28
34 29 @undoc
35 30 def gui_excepthook(exctype, value, tb):
36 31 try:
37 32 import ctypes, traceback
38 33 MB_ICONERROR = 0x00000010
39 34 title = u'Error starting IPython QtConsole'
40 35 msg = u''.join(traceback.format_exception(exctype, value, tb))
41 36 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
42 37 finally:
43 38 # Also call the old exception hook to let it do
44 39 # its thing too.
45 40 old_excepthook(exctype, value, tb)
46 41
47 42 sys.excepthook = gui_excepthook
48 43
49 # System library imports
50 44 from IPython.external.qt import QtCore, QtGui
51 45
52 # Local imports
53 46 from IPython.config.application import boolean_flag
54 47 from IPython.config.application import catch_config_error
55 48 from IPython.core.application import BaseIPythonApplication
56 49 from IPython.qt.console.ipython_widget import IPythonWidget
57 50 from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
58 51 from IPython.qt.console import styles
59 52 from IPython.qt.console.mainwindow import MainWindow
60 53 from IPython.qt.client import QtKernelClient
61 54 from IPython.qt.manager import QtKernelManager
62 55 from IPython.utils.traitlets import (
63 56 Dict, Unicode, CBool, Any
64 57 )
65 58
66 59 from IPython.consoleapp import (
67 60 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
68 61 )
69 62
70 63 #-----------------------------------------------------------------------------
71 64 # Network Constants
72 65 #-----------------------------------------------------------------------------
73 66
74 67 from IPython.utils.localinterfaces import is_local_ip
75 68
76 69 #-----------------------------------------------------------------------------
77 70 # Globals
78 71 #-----------------------------------------------------------------------------
79 72
80 73 _examples = """
81 74 ipython qtconsole # start the qtconsole
82 75 ipython qtconsole --matplotlib=inline # start with matplotlib inline plotting mode
83 76 """
84 77
85 78 #-----------------------------------------------------------------------------
86 79 # Aliases and Flags
87 80 #-----------------------------------------------------------------------------
88 81
89 82 # start with copy of flags
90 83 flags = dict(flags)
91 84 qt_flags = {
92 85 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
93 86 "Disable rich text support."),
94 87 }
95 88 qt_flags.update(boolean_flag(
96 89 'banner', 'IPythonQtConsoleApp.display_banner',
97 90 "Display a banner upon starting the QtConsole.",
98 91 "Don't display a banner upon starting the QtConsole."
99 92 ))
100 93
101 94 # and app_flags from the Console Mixin
102 95 qt_flags.update(app_flags)
103 96 # add frontend flags to the full set
104 97 flags.update(qt_flags)
105 98
106 99 # start with copy of front&backend aliases list
107 100 aliases = dict(aliases)
108 101 qt_aliases = dict(
109 102 style = 'IPythonWidget.syntax_style',
110 103 stylesheet = 'IPythonQtConsoleApp.stylesheet',
111 104 colors = 'ZMQInteractiveShell.colors',
112 105
113 106 editor = 'IPythonWidget.editor',
114 107 paging = 'ConsoleWidget.paging',
115 108 )
116 109 # and app_aliases from the Console Mixin
117 110 qt_aliases.update(app_aliases)
118 111 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
119 112 # add frontend aliases to the full set
120 113 aliases.update(qt_aliases)
121 114
122 115 # get flags&aliases into sets, and remove a couple that
123 116 # shouldn't be scrubbed from backend flags:
124 117 qt_aliases = set(qt_aliases.keys())
125 118 qt_aliases.remove('colors')
126 119 qt_flags = set(qt_flags.keys())
127 120
128 121 #-----------------------------------------------------------------------------
129 122 # Classes
130 123 #-----------------------------------------------------------------------------
131 124
132 125 #-----------------------------------------------------------------------------
133 126 # IPythonQtConsole
134 127 #-----------------------------------------------------------------------------
135 128
136 129
137 130 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
138 131 name = 'ipython-qtconsole'
139 132
140 133 description = """
141 134 The IPython QtConsole.
142 135
143 136 This launches a Console-style application using Qt. It is not a full
144 137 console, in that launched terminal subprocesses will not be able to accept
145 138 input.
146 139
147 140 The QtConsole supports various extra features beyond the Terminal IPython
148 141 shell, such as inline plotting with matplotlib, via:
149 142
150 143 ipython qtconsole --matplotlib=inline
151 144
152 145 as well as saving your session as HTML, and printing the output.
153 146
154 147 """
155 148 examples = _examples
156 149
157 150 classes = [IPythonWidget] + IPythonConsoleApp.classes
158 151 flags = Dict(flags)
159 152 aliases = Dict(aliases)
160 153 frontend_flags = Any(qt_flags)
161 154 frontend_aliases = Any(qt_aliases)
162 155 kernel_client_class = QtKernelClient
163 156 kernel_manager_class = QtKernelManager
164 157
165 158 stylesheet = Unicode('', config=True,
166 159 help="path to a custom CSS stylesheet")
167 160
168 161 hide_menubar = CBool(False, config=True,
169 162 help="Start the console window with the menu bar hidden.")
170 163
171 164 maximize = CBool(False, config=True,
172 165 help="Start the console window maximized.")
173 166
174 167 plain = CBool(False, config=True,
175 168 help="Use a plaintext widget instead of rich text (plain can't print/save).")
176 169
177 170 display_banner = CBool(True, config=True,
178 171 help="Whether to display a banner upon starting the QtConsole."
179 172 )
180 173
181 174 def _plain_changed(self, name, old, new):
182 175 kind = 'plain' if new else 'rich'
183 176 self.config.ConsoleWidget.kind = kind
184 177 if new:
185 178 self.widget_factory = IPythonWidget
186 179 else:
187 180 self.widget_factory = RichIPythonWidget
188 181
189 182 # the factory for creating a widget
190 183 widget_factory = Any(RichIPythonWidget)
191 184
192 185 def parse_command_line(self, argv=None):
193 186 super(IPythonQtConsoleApp, self).parse_command_line(argv)
194 self.build_kernel_argv(argv)
187 self.build_kernel_argv(self.extra_args)
195 188
196 189
197 190 def new_frontend_master(self):
198 191 """ Create and return new frontend attached to new kernel, launched on localhost.
199 192 """
200 193 kernel_manager = self.kernel_manager_class(
201 194 connection_file=self._new_connection_file(),
202 195 parent=self,
203 196 autorestart=True,
204 197 )
205 198 # start the kernel
206 199 kwargs = {}
207 200 # FIXME: remove special treatment of IPython kernels
208 201 if self.kernel_manager.ipython_kernel:
209 202 kwargs['extra_arguments'] = self.kernel_argv
210 203 kernel_manager.start_kernel(**kwargs)
211 204 kernel_manager.client_factory = self.kernel_client_class
212 205 kernel_client = kernel_manager.client()
213 206 kernel_client.start_channels(shell=True, iopub=True)
214 207 widget = self.widget_factory(config=self.config,
215 208 local_kernel=True)
216 209 self.init_colors(widget)
217 210 widget.kernel_manager = kernel_manager
218 211 widget.kernel_client = kernel_client
219 212 widget._existing = False
220 213 widget._may_close = True
221 214 widget._confirm_exit = self.confirm_exit
222 215 widget._display_banner = self.display_banner
223 216 return widget
224 217
225 218 def new_frontend_slave(self, current_widget):
226 219 """Create and return a new frontend attached to an existing kernel.
227 220
228 221 Parameters
229 222 ----------
230 223 current_widget : IPythonWidget
231 224 The IPythonWidget whose kernel this frontend is to share
232 225 """
233 226 kernel_client = self.kernel_client_class(
234 227 connection_file=current_widget.kernel_client.connection_file,
235 228 config = self.config,
236 229 )
237 230 kernel_client.load_connection_file()
238 231 kernel_client.start_channels()
239 232 widget = self.widget_factory(config=self.config,
240 233 local_kernel=False)
241 234 self.init_colors(widget)
242 235 widget._existing = True
243 236 widget._may_close = False
244 237 widget._confirm_exit = False
245 238 widget._display_banner = self.display_banner
246 239 widget.kernel_client = kernel_client
247 240 widget.kernel_manager = current_widget.kernel_manager
248 241 return widget
249 242
250 243 def init_qt_app(self):
251 244 # separate from qt_elements, because it must run first
252 245 self.app = QtGui.QApplication([])
253 246
254 247 def init_qt_elements(self):
255 248 # Create the widget.
256 249
257 250 base_path = os.path.abspath(os.path.dirname(__file__))
258 251 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
259 252 self.app.icon = QtGui.QIcon(icon_path)
260 253 QtGui.QApplication.setWindowIcon(self.app.icon)
261 254
262 255 ip = self.ip
263 256 local_kernel = (not self.existing) or is_local_ip(ip)
264 257 self.widget = self.widget_factory(config=self.config,
265 258 local_kernel=local_kernel)
266 259 self.init_colors(self.widget)
267 260 self.widget._existing = self.existing
268 261 self.widget._may_close = not self.existing
269 262 self.widget._confirm_exit = self.confirm_exit
270 263 self.widget._display_banner = self.display_banner
271 264
272 265 self.widget.kernel_manager = self.kernel_manager
273 266 self.widget.kernel_client = self.kernel_client
274 267 self.window = MainWindow(self.app,
275 268 confirm_exit=self.confirm_exit,
276 269 new_frontend_factory=self.new_frontend_master,
277 270 slave_frontend_factory=self.new_frontend_slave,
278 271 )
279 272 self.window.log = self.log
280 273 self.window.add_tab_with_frontend(self.widget)
281 274 self.window.init_magic_helper()
282 275 self.window.init_menu_bar()
283 276
284 277 # Ignore on OSX, where there is always a menu bar
285 278 if sys.platform != 'darwin' and self.hide_menubar:
286 279 self.window.menuBar().setVisible(False)
287 280
288 281 self.window.setWindowTitle('IPython')
289 282
290 283 def init_colors(self, widget):
291 284 """Configure the coloring of the widget"""
292 285 # Note: This will be dramatically simplified when colors
293 286 # are removed from the backend.
294 287
295 288 # parse the colors arg down to current known labels
296 289 cfg = self.config
297 290 colors = cfg.ZMQInteractiveShell.colors if 'ZMQInteractiveShell.colors' in cfg else None
298 291 style = cfg.IPythonWidget.syntax_style if 'IPythonWidget.syntax_style' in cfg else None
299 292 sheet = cfg.IPythonWidget.style_sheet if 'IPythonWidget.style_sheet' in cfg else None
300 293
301 294 # find the value for colors:
302 295 if colors:
303 296 colors=colors.lower()
304 297 if colors in ('lightbg', 'light'):
305 298 colors='lightbg'
306 299 elif colors in ('dark', 'linux'):
307 300 colors='linux'
308 301 else:
309 302 colors='nocolor'
310 303 elif style:
311 304 if style=='bw':
312 305 colors='nocolor'
313 306 elif styles.dark_style(style):
314 307 colors='linux'
315 308 else:
316 309 colors='lightbg'
317 310 else:
318 311 colors=None
319 312
320 313 # Configure the style
321 314 if style:
322 315 widget.style_sheet = styles.sheet_from_template(style, colors)
323 316 widget.syntax_style = style
324 317 widget._syntax_style_changed()
325 318 widget._style_sheet_changed()
326 319 elif colors:
327 320 # use a default dark/light/bw style
328 321 widget.set_default_style(colors=colors)
329 322
330 323 if self.stylesheet:
331 324 # we got an explicit stylesheet
332 325 if os.path.isfile(self.stylesheet):
333 326 with open(self.stylesheet) as f:
334 327 sheet = f.read()
335 328 else:
336 329 raise IOError("Stylesheet %r not found." % self.stylesheet)
337 330 if sheet:
338 331 widget.style_sheet = sheet
339 332 widget._style_sheet_changed()
340 333
341 334
342 335 def init_signal(self):
343 336 """allow clean shutdown on sigint"""
344 337 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
345 338 # need a timer, so that QApplication doesn't block until a real
346 339 # Qt event fires (can require mouse movement)
347 340 # timer trick from http://stackoverflow.com/q/4938723/938949
348 341 timer = QtCore.QTimer()
349 342 # Let the interpreter run each 200 ms:
350 343 timer.timeout.connect(lambda: None)
351 344 timer.start(200)
352 345 # hold onto ref, so the timer doesn't get cleaned up
353 346 self._sigint_timer = timer
354 347
355 348 @catch_config_error
356 349 def initialize(self, argv=None):
357 350 self.init_qt_app()
358 351 super(IPythonQtConsoleApp, self).initialize(argv)
359 352 IPythonConsoleApp.initialize(self,argv)
360 353 self.init_qt_elements()
361 354 self.init_signal()
362 355
363 356 def start(self):
364 357
365 358 # draw the window
366 359 if self.maximize:
367 360 self.window.showMaximized()
368 361 else:
369 362 self.window.show()
370 363 self.window.raise_()
371 364
372 365 # Start the application main loop.
373 366 self.app.exec_()
374 367
375 368 #-----------------------------------------------------------------------------
376 369 # Main entry point
377 370 #-----------------------------------------------------------------------------
378 371
379 372 def main():
380 373 app = IPythonQtConsoleApp()
381 374 app.initialize()
382 375 app.start()
383 376
384 377
385 378 if __name__ == '__main__':
386 379 main()
General Comments 0
You need to be logged in to leave comments. Login now