##// END OF EJS Templates
add InlineBackend to ConsoleApp class list...
MinRK -
Show More
@@ -1,348 +1,356 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/frontend/qt/console/qtconsoleapp.py
6 6
7 7 Authors:
8 8
9 9 * Evan Patterson
10 10 * Min RK
11 11 * Erik Tollerud
12 12 * Fernando Perez
13 13 * Bussonnier Matthias
14 14 * Thomas Kluyver
15 15 * Paul Ivanov
16 16
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 # stdlib imports
24 24 import atexit
25 25 import json
26 26 import os
27 27 import signal
28 28 import sys
29 29 import uuid
30 30
31 31
32 32 # Local imports
33 33 from IPython.config.application import boolean_flag
34 34 from IPython.config.configurable import Configurable
35 35 from IPython.core.profiledir import ProfileDir
36 36 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
37 37 from IPython.zmq.blockingkernelmanager import BlockingKernelManager
38 38 from IPython.utils.path import filefind
39 39 from IPython.utils.py3compat import str_to_bytes
40 40 from IPython.utils.traitlets import (
41 41 Dict, List, Unicode, CUnicode, Int, CBool, Any
42 42 )
43 43 from IPython.zmq.ipkernel import (
44 44 flags as ipkernel_flags,
45 45 aliases as ipkernel_aliases,
46 46 IPKernelApp
47 47 )
48 48 from IPython.zmq.session import Session, default_secure
49 49 from IPython.zmq.zmqshell import ZMQInteractiveShell
50 50
51 51 #-----------------------------------------------------------------------------
52 52 # Network Constants
53 53 #-----------------------------------------------------------------------------
54 54
55 55 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
56 56
57 57 #-----------------------------------------------------------------------------
58 58 # Globals
59 59 #-----------------------------------------------------------------------------
60 60
61 61
62 62 #-----------------------------------------------------------------------------
63 63 # Aliases and Flags
64 64 #-----------------------------------------------------------------------------
65 65
66 66 flags = dict(ipkernel_flags)
67 67
68 68 # the flags that are specific to the frontend
69 69 # these must be scrubbed before being passed to the kernel,
70 70 # or it will raise an error on unrecognized flags
71 71 app_flags = {
72 72 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
73 73 "Connect to an existing kernel. If no argument specified, guess most recent"),
74 74 }
75 75 app_flags.update(boolean_flag(
76 76 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
77 77 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
78 78 to force a direct exit without any confirmation.
79 79 """,
80 80 """Don't prompt the user when exiting. This will terminate the kernel
81 81 if it is owned by the frontend, and leave it alive if it is external.
82 82 """
83 83 ))
84 84 flags.update(app_flags)
85 85
86 86 aliases = dict(ipkernel_aliases)
87 87
88 88 # also scrub aliases from the frontend
89 89 app_aliases = dict(
90 90 hb = 'IPythonConsoleApp.hb_port',
91 91 shell = 'IPythonConsoleApp.shell_port',
92 92 iopub = 'IPythonConsoleApp.iopub_port',
93 93 stdin = 'IPythonConsoleApp.stdin_port',
94 94 ip = 'IPythonConsoleApp.ip',
95 95 existing = 'IPythonConsoleApp.existing',
96 96 f = 'IPythonConsoleApp.connection_file',
97 97
98 98
99 99 ssh = 'IPythonConsoleApp.sshserver',
100 100 )
101 101 aliases.update(app_aliases)
102 102
103 103 #-----------------------------------------------------------------------------
104 104 # Classes
105 105 #-----------------------------------------------------------------------------
106 106
107 107 #-----------------------------------------------------------------------------
108 108 # IPythonConsole
109 109 #-----------------------------------------------------------------------------
110 110
111 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session]
112
113 try:
114 from IPython.zmq.pylab.backend_inline import InlineBackend
115 except ImportError:
116 pass
117 else:
118 classes.append(InlineBackend)
111 119
112 120 class IPythonConsoleApp(Configurable):
113 121 name = 'ipython-console-mixin'
114 122 default_config_file_name='ipython_config.py'
115 123
116 124 description = """
117 125 The IPython Mixin Console.
118 126
119 127 This class contains the common portions of console client (QtConsole,
120 128 ZMQ-based terminal console, etc). It is not a full console, in that
121 129 launched terminal subprocesses will not be able to accept input.
122 130
123 131 The Console using this mixing supports various extra features beyond
124 132 the single-process Terminal IPython shell, such as connecting to
125 133 existing kernel, via:
126 134
127 135 ipython <appname> --existing
128 136
129 137 as well as tunnel via SSH
130 138
131 139 """
132 140
133 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session]
141 classes = classes
134 142 flags = Dict(flags)
135 143 aliases = Dict(aliases)
136 144 kernel_manager_class = BlockingKernelManager
137 145
138 146 kernel_argv = List(Unicode)
139 147 # frontend flags&aliases to be stripped when building kernel_argv
140 148 frontend_flags = Any(app_flags)
141 149 frontend_aliases = Any(app_aliases)
142 150
143 151 # create requested profiles by default, if they don't exist:
144 152 auto_create = CBool(True)
145 153 # connection info:
146 154 ip = Unicode(LOCALHOST, config=True,
147 155 help="""Set the kernel\'s IP address [default localhost].
148 156 If the IP address is something other than localhost, then
149 157 Consoles on other machines will be able to connect
150 158 to the Kernel, so be careful!"""
151 159 )
152 160
153 161 sshserver = Unicode('', config=True,
154 162 help="""The SSH server to use to connect to the kernel.""")
155 163 sshkey = Unicode('', config=True,
156 164 help="""Path to the ssh key to use for logging in to the ssh server.""")
157 165
158 166 hb_port = Int(0, config=True,
159 167 help="set the heartbeat port [default: random]")
160 168 shell_port = Int(0, config=True,
161 169 help="set the shell (XREP) port [default: random]")
162 170 iopub_port = Int(0, config=True,
163 171 help="set the iopub (PUB) port [default: random]")
164 172 stdin_port = Int(0, config=True,
165 173 help="set the stdin (XREQ) port [default: random]")
166 174 connection_file = Unicode('', config=True,
167 175 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
168 176
169 177 This file will contain the IP, ports, and authentication key needed to connect
170 178 clients to this kernel. By default, this file will be created in the security-dir
171 179 of the current profile, but can be specified by absolute path.
172 180 """)
173 181 def _connection_file_default(self):
174 182 return 'kernel-%i.json' % os.getpid()
175 183
176 184 existing = CUnicode('', config=True,
177 185 help="""Connect to an already running kernel""")
178 186
179 187 confirm_exit = CBool(True, config=True,
180 188 help="""
181 189 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
182 190 to force a direct exit without any confirmation.""",
183 191 )
184 192
185 193
186 194 def build_kernel_argv(self, argv=None):
187 195 """build argv to be passed to kernel subprocess"""
188 196 if argv is None:
189 197 argv = sys.argv[1:]
190 198 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
191 199 # kernel should inherit default config file from frontend
192 200 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
193 201
194 202 def init_connection_file(self):
195 203 """find the connection file, and load the info if found.
196 204
197 205 The current working directory and the current profile's security
198 206 directory will be searched for the file if it is not given by
199 207 absolute path.
200 208
201 209 When attempting to connect to an existing kernel and the `--existing`
202 210 argument does not match an existing file, it will be interpreted as a
203 211 fileglob, and the matching file in the current profile's security dir
204 212 with the latest access time will be used.
205 213
206 214 After this method is called, self.connection_file contains the *full path*
207 215 to the connection file, never just its name.
208 216 """
209 217 if self.existing:
210 218 try:
211 219 cf = find_connection_file(self.existing)
212 220 except Exception:
213 221 self.log.critical("Could not find existing kernel connection file %s", self.existing)
214 222 self.exit(1)
215 223 self.log.info("Connecting to existing kernel: %s" % cf)
216 224 self.connection_file = cf
217 225 else:
218 226 # not existing, check if we are going to write the file
219 227 # and ensure that self.connection_file is a full path, not just the shortname
220 228 try:
221 229 cf = find_connection_file(self.connection_file)
222 230 except Exception:
223 231 # file might not exist
224 232 if self.connection_file == os.path.basename(self.connection_file):
225 233 # just shortname, put it in security dir
226 234 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
227 235 else:
228 236 cf = self.connection_file
229 237 self.connection_file = cf
230 238
231 239 # should load_connection_file only be used for existing?
232 240 # as it is now, this allows reusing ports if an existing
233 241 # file is requested
234 242 try:
235 243 self.load_connection_file()
236 244 except Exception:
237 245 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
238 246 self.exit(1)
239 247
240 248 def load_connection_file(self):
241 249 """load ip/port/hmac config from JSON connection file"""
242 250 # this is identical to KernelApp.load_connection_file
243 251 # perhaps it can be centralized somewhere?
244 252 try:
245 253 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
246 254 except IOError:
247 255 self.log.debug("Connection File not found: %s", self.connection_file)
248 256 return
249 257 self.log.debug(u"Loading connection file %s", fname)
250 258 with open(fname) as f:
251 259 s = f.read()
252 260 cfg = json.loads(s)
253 261 if self.ip == LOCALHOST and 'ip' in cfg:
254 262 # not overridden by config or cl_args
255 263 self.ip = cfg['ip']
256 264 for channel in ('hb', 'shell', 'iopub', 'stdin'):
257 265 name = channel + '_port'
258 266 if getattr(self, name) == 0 and name in cfg:
259 267 # not overridden by config or cl_args
260 268 setattr(self, name, cfg[name])
261 269 if 'key' in cfg:
262 270 self.config.Session.key = str_to_bytes(cfg['key'])
263 271
264 272 def init_ssh(self):
265 273 """set up ssh tunnels, if needed."""
266 274 if not self.sshserver and not self.sshkey:
267 275 return
268 276
269 277 if self.sshkey and not self.sshserver:
270 278 # specifying just the key implies that we are connecting directly
271 279 self.sshserver = self.ip
272 280 self.ip = LOCALHOST
273 281
274 282 # build connection dict for tunnels:
275 283 info = dict(ip=self.ip,
276 284 shell_port=self.shell_port,
277 285 iopub_port=self.iopub_port,
278 286 stdin_port=self.stdin_port,
279 287 hb_port=self.hb_port
280 288 )
281 289
282 290 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
283 291
284 292 # tunnels return a new set of ports, which will be on localhost:
285 293 self.ip = LOCALHOST
286 294 try:
287 295 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
288 296 except:
289 297 # even catch KeyboardInterrupt
290 298 self.log.error("Could not setup tunnels", exc_info=True)
291 299 self.exit(1)
292 300
293 301 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
294 302
295 303 cf = self.connection_file
296 304 base,ext = os.path.splitext(cf)
297 305 base = os.path.basename(base)
298 306 self.connection_file = os.path.basename(base)+'-ssh'+ext
299 307 self.log.critical("To connect another client via this tunnel, use:")
300 308 self.log.critical("--existing %s" % self.connection_file)
301 309
302 310 def _new_connection_file(self):
303 311 cf = ''
304 312 while not cf:
305 313 # we don't need a 128b id to distinguish kernels, use more readable
306 314 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
307 315 # kernels can subclass.
308 316 ident = str(uuid.uuid4()).split('-')[-1]
309 317 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
310 318 # only keep if it's actually new. Protect against unlikely collision
311 319 # in 48b random search space
312 320 cf = cf if not os.path.exists(cf) else ''
313 321 return cf
314 322
315 323 def init_kernel_manager(self):
316 324 # Don't let Qt or ZMQ swallow KeyboardInterupts.
317 325 signal.signal(signal.SIGINT, signal.SIG_DFL)
318 326
319 327 # Create a KernelManager and start a kernel.
320 328 self.kernel_manager = self.kernel_manager_class(
321 329 ip=self.ip,
322 330 shell_port=self.shell_port,
323 331 iopub_port=self.iopub_port,
324 332 stdin_port=self.stdin_port,
325 333 hb_port=self.hb_port,
326 334 connection_file=self.connection_file,
327 335 config=self.config,
328 336 )
329 337 # start the kernel
330 338 if not self.existing:
331 339 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
332 340 elif self.sshserver:
333 341 # ssh, write new connection file
334 342 self.kernel_manager.write_connection_file()
335 343 atexit.register(self.kernel_manager.cleanup_connection_file)
336 344 self.kernel_manager.start_channels()
337 345
338 346
339 347 def initialize(self, argv=None):
340 348 """
341 349 Classes which mix this class in should call:
342 350 IPythonConsoleApp.initialize(self,argv)
343 351 """
344 352 self.init_connection_file()
345 353 default_secure(self.config)
346 354 self.init_ssh()
347 355 self.init_kernel_manager()
348 356
@@ -1,584 +1,584 b''
1 1 # coding: utf-8
2 2 """A tornado based IPython notebook server.
3 3
4 4 Authors:
5 5
6 6 * Brian Granger
7 7 """
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # stdlib
20 20 import errno
21 21 import logging
22 22 import os
23 23 import random
24 24 import re
25 25 import select
26 26 import signal
27 27 import socket
28 28 import sys
29 29 import threading
30 30 import time
31 31 import webbrowser
32 32
33 33 # Third party
34 34 import zmq
35 35
36 36 # Install the pyzmq ioloop. This has to be done before anything else from
37 37 # tornado is imported.
38 38 from zmq.eventloop import ioloop
39 39 ioloop.install()
40 40
41 41 from tornado import httpserver
42 42 from tornado import web
43 43
44 44 # Our own libraries
45 45 from .kernelmanager import MappingKernelManager
46 46 from .handlers import (LoginHandler, LogoutHandler,
47 47 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
48 48 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
49 49 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
50 50 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
51 51 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler
52 52 )
53 53 from .notebookmanager import NotebookManager
54 54 from .clustermanager import ClusterManager
55 55
56 56 from IPython.config.application import catch_config_error, boolean_flag
57 57 from IPython.core.application import BaseIPythonApplication
58 58 from IPython.core.profiledir import ProfileDir
59 from IPython.frontend.consoleapp import IPythonConsoleApp
59 60 from IPython.lib.kernel import swallow_argv
60 61 from IPython.zmq.session import Session, default_secure
61 62 from IPython.zmq.zmqshell import ZMQInteractiveShell
62 63 from IPython.zmq.ipkernel import (
63 64 flags as ipkernel_flags,
64 65 aliases as ipkernel_aliases,
65 66 IPKernelApp
66 67 )
67 68 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
68 69 from IPython.utils import py3compat
69 70
70 71 #-----------------------------------------------------------------------------
71 72 # Module globals
72 73 #-----------------------------------------------------------------------------
73 74
74 75 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
75 76 _kernel_action_regex = r"(?P<action>restart|interrupt)"
76 77 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
77 78 _profile_regex = r"(?P<profile>[a-zA-Z0-9]+)"
78 79 _cluster_action_regex = r"(?P<action>start|stop)"
79 80
80 81
81 82 LOCALHOST = '127.0.0.1'
82 83
83 84 _examples = """
84 85 ipython notebook # start the notebook
85 86 ipython notebook --profile=sympy # use the sympy profile
86 87 ipython notebook --pylab=inline # pylab in inline plotting mode
87 88 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
88 89 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
89 90 """
90 91
91 92 #-----------------------------------------------------------------------------
92 93 # Helper functions
93 94 #-----------------------------------------------------------------------------
94 95
95 96 def url_path_join(a,b):
96 97 if a.endswith('/') and b.startswith('/'):
97 98 return a[:-1]+b
98 99 else:
99 100 return a+b
100 101
101 102 def random_ports(port, n):
102 103 """Generate a list of n random ports near the given port.
103 104
104 105 The first 5 ports will be sequential, and the remaining n-5 will be
105 106 randomly selected in the range [port-2*n, port+2*n].
106 107 """
107 108 for i in range(min(5, n)):
108 109 yield port + i
109 110 for i in range(n-5):
110 111 yield port + random.randint(-2*n, 2*n)
111 112
112 113 #-----------------------------------------------------------------------------
113 114 # The Tornado web application
114 115 #-----------------------------------------------------------------------------
115 116
116 117 class NotebookWebApplication(web.Application):
117 118
118 119 def __init__(self, ipython_app, kernel_manager, notebook_manager,
119 120 cluster_manager, log,
120 121 base_project_url, settings_overrides):
121 122 handlers = [
122 123 (r"/", ProjectDashboardHandler),
123 124 (r"/login", LoginHandler),
124 125 (r"/logout", LogoutHandler),
125 126 (r"/new", NewHandler),
126 127 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
127 128 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
128 129 (r"/%s/print" % _notebook_id_regex, PrintNotebookHandler),
129 130 (r"/kernels", MainKernelHandler),
130 131 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
131 132 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
132 133 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
133 134 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
134 135 (r"/notebooks", NotebookRootHandler),
135 136 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
136 137 (r"/rstservice/render", RSTHandler),
137 138 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
138 139 (r"/clusters", MainClusterHandler),
139 140 (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler),
140 141 (r"/clusters/%s" % _profile_regex, ClusterProfileHandler),
141 142 ]
142 143 settings = dict(
143 144 template_path=os.path.join(os.path.dirname(__file__), "templates"),
144 145 static_path=os.path.join(os.path.dirname(__file__), "static"),
145 146 cookie_secret=os.urandom(1024),
146 147 login_url="/login",
147 148 )
148 149
149 150 # allow custom overrides for the tornado web app.
150 151 settings.update(settings_overrides)
151 152
152 153 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
153 154 # base_project_url will always be unicode, which will in turn
154 155 # make the patterns unicode, and ultimately result in unicode
155 156 # keys in kwargs to handler._execute(**kwargs) in tornado.
156 157 # This enforces that base_project_url be ascii in that situation.
157 158 #
158 159 # Note that the URLs these patterns check against are escaped,
159 160 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
160 161 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
161 162
162 163 # prepend base_project_url onto the patterns that we match
163 164 new_handlers = []
164 165 for handler in handlers:
165 166 pattern = url_path_join(base_project_url, handler[0])
166 167 new_handler = tuple([pattern]+list(handler[1:]))
167 168 new_handlers.append( new_handler )
168 169
169 170 super(NotebookWebApplication, self).__init__(new_handlers, **settings)
170 171
171 172 self.kernel_manager = kernel_manager
172 173 self.notebook_manager = notebook_manager
173 174 self.cluster_manager = cluster_manager
174 175 self.ipython_app = ipython_app
175 176 self.read_only = self.ipython_app.read_only
176 177 self.log = log
177 178
178 179
179 180 #-----------------------------------------------------------------------------
180 181 # Aliases and Flags
181 182 #-----------------------------------------------------------------------------
182 183
183 184 flags = dict(ipkernel_flags)
184 185 flags['no-browser']=(
185 186 {'NotebookApp' : {'open_browser' : False}},
186 187 "Don't open the notebook in a browser after startup."
187 188 )
188 189 flags['no-mathjax']=(
189 190 {'NotebookApp' : {'enable_mathjax' : False}},
190 191 """Disable MathJax
191 192
192 193 MathJax is the javascript library IPython uses to render math/LaTeX. It is
193 194 very large, so you may want to disable it if you have a slow internet
194 195 connection, or for offline use of the notebook.
195 196
196 197 When disabled, equations etc. will appear as their untransformed TeX source.
197 198 """
198 199 )
199 200 flags['read-only'] = (
200 201 {'NotebookApp' : {'read_only' : True}},
201 202 """Allow read-only access to notebooks.
202 203
203 204 When using a password to protect the notebook server, this flag
204 205 allows unauthenticated clients to view the notebook list, and
205 206 individual notebooks, but not edit them, start kernels, or run
206 207 code.
207 208
208 209 If no password is set, the server will be entirely read-only.
209 210 """
210 211 )
211 212
212 213 # Add notebook manager flags
213 214 flags.update(boolean_flag('script', 'NotebookManager.save_script',
214 215 'Auto-save a .py script everytime the .ipynb notebook is saved',
215 216 'Do not auto-save .py scripts for every notebook'))
216 217
217 218 # the flags that are specific to the frontend
218 219 # these must be scrubbed before being passed to the kernel,
219 220 # or it will raise an error on unrecognized flags
220 221 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
221 222
222 223 aliases = dict(ipkernel_aliases)
223 224
224 225 aliases.update({
225 226 'ip': 'NotebookApp.ip',
226 227 'port': 'NotebookApp.port',
227 228 'port-retries': 'NotebookApp.port_retries',
228 229 'keyfile': 'NotebookApp.keyfile',
229 230 'certfile': 'NotebookApp.certfile',
230 231 'notebook-dir': 'NotebookManager.notebook_dir',
231 232 'browser': 'NotebookApp.browser',
232 233 })
233 234
234 235 # remove ipkernel flags that are singletons, and don't make sense in
235 236 # multi-kernel evironment:
236 237 aliases.pop('f', None)
237 238
238 239 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
239 240 u'notebook-dir']
240 241
241 242 #-----------------------------------------------------------------------------
242 243 # NotebookApp
243 244 #-----------------------------------------------------------------------------
244 245
245 246 class NotebookApp(BaseIPythonApplication):
246 247
247 248 name = 'ipython-notebook'
248 249 default_config_file_name='ipython_notebook_config.py'
249 250
250 251 description = """
251 252 The IPython HTML Notebook.
252 253
253 254 This launches a Tornado based HTML Notebook Server that serves up an
254 255 HTML5/Javascript Notebook client.
255 256 """
256 257 examples = _examples
257 258
258 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
259 MappingKernelManager, NotebookManager]
259 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager]
260 260 flags = Dict(flags)
261 261 aliases = Dict(aliases)
262 262
263 263 kernel_argv = List(Unicode)
264 264
265 265 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
266 266 default_value=logging.INFO,
267 267 config=True,
268 268 help="Set the log level by value or name.")
269 269
270 270 # create requested profiles by default, if they don't exist:
271 271 auto_create = Bool(True)
272 272
273 273 # file to be opened in the notebook server
274 274 file_to_run = Unicode('')
275 275
276 276 # Network related information.
277 277
278 278 ip = Unicode(LOCALHOST, config=True,
279 279 help="The IP address the notebook server will listen on."
280 280 )
281 281
282 282 def _ip_changed(self, name, old, new):
283 283 if new == u'*': self.ip = u''
284 284
285 285 port = Integer(8888, config=True,
286 286 help="The port the notebook server will listen on."
287 287 )
288 288 port_retries = Integer(50, config=True,
289 289 help="The number of additional ports to try if the specified port is not available."
290 290 )
291 291
292 292 certfile = Unicode(u'', config=True,
293 293 help="""The full path to an SSL/TLS certificate file."""
294 294 )
295 295
296 296 keyfile = Unicode(u'', config=True,
297 297 help="""The full path to a private key file for usage with SSL/TLS."""
298 298 )
299 299
300 300 password = Unicode(u'', config=True,
301 301 help="""Hashed password to use for web authentication.
302 302
303 303 To generate, type in a python/IPython shell:
304 304
305 305 from IPython.lib import passwd; passwd()
306 306
307 307 The string should be of the form type:salt:hashed-password.
308 308 """
309 309 )
310 310
311 311 open_browser = Bool(True, config=True,
312 312 help="""Whether to open in a browser after starting.
313 313 The specific browser used is platform dependent and
314 314 determined by the python standard library `webbrowser`
315 315 module, unless it is overridden using the --browser
316 316 (NotebookApp.browser) configuration option.
317 317 """)
318 318
319 319 browser = Unicode(u'', config=True,
320 320 help="""Specify what command to use to invoke a web
321 321 browser when opening the notebook. If not specified, the
322 322 default browser will be determined by the `webbrowser`
323 323 standard library module, which allows setting of the
324 324 BROWSER environment variable to override it.
325 325 """)
326 326
327 327 read_only = Bool(False, config=True,
328 328 help="Whether to prevent editing/execution of notebooks."
329 329 )
330 330
331 331 webapp_settings = Dict(config=True,
332 332 help="Supply overrides for the tornado.web.Application that the "
333 333 "IPython notebook uses.")
334 334
335 335 enable_mathjax = Bool(True, config=True,
336 336 help="""Whether to enable MathJax for typesetting math/TeX
337 337
338 338 MathJax is the javascript library IPython uses to render math/LaTeX. It is
339 339 very large, so you may want to disable it if you have a slow internet
340 340 connection, or for offline use of the notebook.
341 341
342 342 When disabled, equations etc. will appear as their untransformed TeX source.
343 343 """
344 344 )
345 345 def _enable_mathjax_changed(self, name, old, new):
346 346 """set mathjax url to empty if mathjax is disabled"""
347 347 if not new:
348 348 self.mathjax_url = u''
349 349
350 350 base_project_url = Unicode('/', config=True,
351 351 help='''The base URL for the notebook server''')
352 352 base_kernel_url = Unicode('/', config=True,
353 353 help='''The base URL for the kernel server''')
354 354 websocket_host = Unicode("", config=True,
355 355 help="""The hostname for the websocket server."""
356 356 )
357 357
358 358 mathjax_url = Unicode("", config=True,
359 359 help="""The url for MathJax.js."""
360 360 )
361 361 def _mathjax_url_default(self):
362 362 if not self.enable_mathjax:
363 363 return u''
364 364 static_path = self.webapp_settings.get("static_path", os.path.join(os.path.dirname(__file__), "static"))
365 365 static_url_prefix = self.webapp_settings.get("static_url_prefix",
366 366 "/static/")
367 367 if os.path.exists(os.path.join(static_path, 'mathjax', "MathJax.js")):
368 368 self.log.info("Using local MathJax")
369 369 return static_url_prefix+u"mathjax/MathJax.js"
370 370 else:
371 371 if self.certfile:
372 372 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
373 373 base = u"https://c328740.ssl.cf1.rackcdn.com"
374 374 else:
375 375 base = u"http://cdn.mathjax.org"
376 376
377 377 url = base + u"/mathjax/latest/MathJax.js"
378 378 self.log.info("Using MathJax from CDN: %s", url)
379 379 return url
380 380
381 381 def _mathjax_url_changed(self, name, old, new):
382 382 if new and not self.enable_mathjax:
383 383 # enable_mathjax=False overrides mathjax_url
384 384 self.mathjax_url = u''
385 385 else:
386 386 self.log.info("Using MathJax: %s", new)
387 387
388 388 def parse_command_line(self, argv=None):
389 389 super(NotebookApp, self).parse_command_line(argv)
390 390 if argv is None:
391 391 argv = sys.argv[1:]
392 392
393 393 # Scrub frontend-specific flags
394 394 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
395 395 # Kernel should inherit default config file from frontend
396 396 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
397 397
398 398 if self.extra_args:
399 399 self.file_to_run = os.path.abspath(self.extra_args[0])
400 400 self.config.NotebookManager.notebook_dir = os.path.dirname(self.file_to_run)
401 401
402 402 def init_configurables(self):
403 403 # force Session default to be secure
404 404 default_secure(self.config)
405 405 # Create a KernelManager and start a kernel.
406 406 self.kernel_manager = MappingKernelManager(
407 407 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
408 408 connection_dir = self.profile_dir.security_dir,
409 409 )
410 410 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
411 411 self.notebook_manager.list_notebooks()
412 412 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
413 413 self.cluster_manager.update_profiles()
414 414
415 415 def init_logging(self):
416 416 # This prevents double log messages because tornado use a root logger that
417 417 # self.log is a child of. The logging module dipatches log messages to a log
418 418 # and all of its ancenstors until propagate is set to False.
419 419 self.log.propagate = False
420 420
421 421 def init_webapp(self):
422 422 """initialize tornado webapp and httpserver"""
423 423 self.web_app = NotebookWebApplication(
424 424 self, self.kernel_manager, self.notebook_manager,
425 425 self.cluster_manager, self.log,
426 426 self.base_project_url, self.webapp_settings
427 427 )
428 428 if self.certfile:
429 429 ssl_options = dict(certfile=self.certfile)
430 430 if self.keyfile:
431 431 ssl_options['keyfile'] = self.keyfile
432 432 else:
433 433 ssl_options = None
434 434 self.web_app.password = self.password
435 435 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
436 436 if ssl_options is None and not self.ip and not (self.read_only and not self.password):
437 437 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
438 438 'but not using any encryption or authentication. This is highly '
439 439 'insecure and not recommended.')
440 440
441 441 success = None
442 442 for port in random_ports(self.port, self.port_retries+1):
443 443 try:
444 444 self.http_server.listen(port, self.ip)
445 445 except socket.error, e:
446 446 if e.errno != errno.EADDRINUSE:
447 447 raise
448 448 self.log.info('The port %i is already in use, trying another random port.' % port)
449 449 else:
450 450 self.port = port
451 451 success = True
452 452 break
453 453 if not success:
454 454 self.log.critical('ERROR: the notebook server could not be started because '
455 455 'no available port could be found.')
456 456 self.exit(1)
457 457
458 458 def init_signal(self):
459 459 # FIXME: remove this check when pyzmq dependency is >= 2.1.11
460 460 # safely extract zmq version info:
461 461 try:
462 462 zmq_v = zmq.pyzmq_version_info()
463 463 except AttributeError:
464 464 zmq_v = [ int(n) for n in re.findall(r'\d+', zmq.__version__) ]
465 465 if 'dev' in zmq.__version__:
466 466 zmq_v.append(999)
467 467 zmq_v = tuple(zmq_v)
468 468 if zmq_v >= (2,1,9):
469 469 # This won't work with 2.1.7 and
470 470 # 2.1.9-10 will log ugly 'Interrupted system call' messages,
471 471 # but it will work
472 472 signal.signal(signal.SIGINT, self._handle_sigint)
473 473 signal.signal(signal.SIGTERM, self._signal_stop)
474 474
475 475 def _handle_sigint(self, sig, frame):
476 476 """SIGINT handler spawns confirmation dialog"""
477 477 # register more forceful signal handler for ^C^C case
478 478 signal.signal(signal.SIGINT, self._signal_stop)
479 479 # request confirmation dialog in bg thread, to avoid
480 480 # blocking the App
481 481 thread = threading.Thread(target=self._confirm_exit)
482 482 thread.daemon = True
483 483 thread.start()
484 484
485 485 def _restore_sigint_handler(self):
486 486 """callback for restoring original SIGINT handler"""
487 487 signal.signal(signal.SIGINT, self._handle_sigint)
488 488
489 489 def _confirm_exit(self):
490 490 """confirm shutdown on ^C
491 491
492 492 A second ^C, or answering 'y' within 5s will cause shutdown,
493 493 otherwise original SIGINT handler will be restored.
494 494 """
495 495 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
496 496 time.sleep(0.1)
497 497 sys.stdout.write("Shutdown Notebook Server (y/[n])? ")
498 498 sys.stdout.flush()
499 499 r,w,x = select.select([sys.stdin], [], [], 5)
500 500 if r:
501 501 line = sys.stdin.readline()
502 502 if line.lower().startswith('y'):
503 503 self.log.critical("Shutdown confirmed")
504 504 ioloop.IOLoop.instance().stop()
505 505 return
506 506 else:
507 507 print "No answer for 5s:",
508 508 print "resuming operation..."
509 509 # no answer, or answer is no:
510 510 # set it back to original SIGINT handler
511 511 # use IOLoop.add_callback because signal.signal must be called
512 512 # from main thread
513 513 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
514 514
515 515 def _signal_stop(self, sig, frame):
516 516 self.log.critical("received signal %s, stopping", sig)
517 517 ioloop.IOLoop.instance().stop()
518 518
519 519 @catch_config_error
520 520 def initialize(self, argv=None):
521 521 self.init_logging()
522 522 super(NotebookApp, self).initialize(argv)
523 523 self.init_configurables()
524 524 self.init_webapp()
525 525 self.init_signal()
526 526
527 527 def cleanup_kernels(self):
528 528 """shutdown all kernels
529 529
530 530 The kernels will shutdown themselves when this process no longer exists,
531 531 but explicit shutdown allows the KernelManagers to cleanup the connection files.
532 532 """
533 533 self.log.info('Shutting down kernels')
534 534 km = self.kernel_manager
535 535 # copy list, since kill_kernel deletes keys
536 536 for kid in list(km.kernel_ids):
537 537 km.kill_kernel(kid)
538 538
539 539 def start(self):
540 540 ip = self.ip if self.ip else '[all ip addresses on your system]'
541 541 proto = 'https' if self.certfile else 'http'
542 542 info = self.log.info
543 543 info("The IPython Notebook is running at: %s://%s:%i%s" %
544 544 (proto, ip, self.port,self.base_project_url) )
545 545 info("Use Control-C to stop this server and shut down all kernels.")
546 546
547 547 if self.open_browser:
548 548 ip = self.ip or '127.0.0.1'
549 549 if self.browser:
550 550 browser = webbrowser.get(self.browser)
551 551 else:
552 552 browser = webbrowser.get()
553 553
554 554 if self.file_to_run:
555 555 filename, _ = os.path.splitext(os.path.basename(self.file_to_run))
556 556 for nb in self.notebook_manager.list_notebooks():
557 557 if filename == nb['name']:
558 558 url = nb['notebook_id']
559 559 break
560 560 else:
561 561 url = ''
562 562 else:
563 563 url = ''
564 564 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
565 565 self.port, self.base_project_url, url),
566 566 new=2)
567 567 threading.Thread(target=b).start()
568 568 try:
569 569 ioloop.IOLoop.instance().start()
570 570 except KeyboardInterrupt:
571 571 info("Interrupted...")
572 572 finally:
573 573 self.cleanup_kernels()
574 574
575 575
576 576 #-----------------------------------------------------------------------------
577 577 # Main entry point
578 578 #-----------------------------------------------------------------------------
579 579
580 580 def launch_new_instance():
581 581 app = NotebookApp.instance()
582 582 app.initialize()
583 583 app.start()
584 584
@@ -1,372 +1,372 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 Authors:
7 7
8 8 * Evan Patterson
9 9 * Min RK
10 10 * Erik Tollerud
11 11 * Fernando Perez
12 12 * Bussonnier Matthias
13 13 * Thomas Kluyver
14 14 * Paul Ivanov
15 15
16 16 """
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # stdlib imports
23 23 import json
24 24 import os
25 25 import signal
26 26 import sys
27 27 import uuid
28 28
29 29 # If run on Windows, install an exception hook which pops up a
30 30 # message box. Pythonw.exe hides the console, so without this
31 31 # the application silently fails to load.
32 32 #
33 33 # We always install this handler, because the expectation is for
34 34 # qtconsole to bring up a GUI even if called from the console.
35 35 # The old handler is called, so the exception is printed as well.
36 36 # If desired, check for pythonw with an additional condition
37 37 # (sys.executable.lower().find('pythonw.exe') >= 0).
38 38 if os.name == 'nt':
39 39 old_excepthook = sys.excepthook
40 40
41 41 def gui_excepthook(exctype, value, tb):
42 42 try:
43 43 import ctypes, traceback
44 44 MB_ICONERROR = 0x00000010L
45 45 title = u'Error starting IPython QtConsole'
46 46 msg = u''.join(traceback.format_exception(exctype, value, tb))
47 47 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
48 48 finally:
49 49 # Also call the old exception hook to let it do
50 50 # its thing too.
51 51 old_excepthook(exctype, value, tb)
52 52
53 53 sys.excepthook = gui_excepthook
54 54
55 55 # System library imports
56 56 from IPython.external.qt import QtCore, QtGui
57 57
58 58 # Local imports
59 59 from IPython.config.application import boolean_flag, catch_config_error
60 60 from IPython.core.application import BaseIPythonApplication
61 61 from IPython.core.profiledir import ProfileDir
62 62 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
63 63 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
64 64 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
65 65 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
66 66 from IPython.frontend.qt.console import styles
67 67 from IPython.frontend.qt.console.mainwindow import MainWindow
68 68 from IPython.frontend.qt.kernelmanager import QtKernelManager
69 69 from IPython.utils.path import filefind
70 70 from IPython.utils.py3compat import str_to_bytes
71 71 from IPython.utils.traitlets import (
72 72 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
73 73 )
74 74 from IPython.zmq.ipkernel import IPKernelApp
75 75 from IPython.zmq.session import Session, default_secure
76 76 from IPython.zmq.zmqshell import ZMQInteractiveShell
77 77
78 78 from IPython.frontend.consoleapp import (
79 79 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
80 80 )
81 81
82 82 #-----------------------------------------------------------------------------
83 83 # Network Constants
84 84 #-----------------------------------------------------------------------------
85 85
86 86 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
87 87
88 88 #-----------------------------------------------------------------------------
89 89 # Globals
90 90 #-----------------------------------------------------------------------------
91 91
92 92 _examples = """
93 93 ipython qtconsole # start the qtconsole
94 94 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
95 95 """
96 96
97 97 #-----------------------------------------------------------------------------
98 98 # Aliases and Flags
99 99 #-----------------------------------------------------------------------------
100 100
101 101 # start with copy of flags
102 102 flags = dict(flags)
103 103 qt_flags = {
104 104 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
105 105 "Disable rich text support."),
106 106 }
107 107 qt_flags.update(boolean_flag(
108 108 'gui-completion', 'ConsoleWidget.gui_completion',
109 109 "use a GUI widget for tab completion",
110 110 "use plaintext output for completion"
111 111 ))
112 112 # and app_flags from the Console Mixin
113 113 qt_flags.update(app_flags)
114 114 # add frontend flags to the full set
115 115 flags.update(qt_flags)
116 116
117 117 # start with copy of front&backend aliases list
118 118 aliases = dict(aliases)
119 119 qt_aliases = dict(
120 120
121 121 style = 'IPythonWidget.syntax_style',
122 122 stylesheet = 'IPythonQtConsoleApp.stylesheet',
123 123 colors = 'ZMQInteractiveShell.colors',
124 124
125 125 editor = 'IPythonWidget.editor',
126 126 paging = 'ConsoleWidget.paging',
127 127 )
128 128 # and app_aliases from the Console Mixin
129 129 qt_aliases.update(app_aliases)
130 130 # add frontend aliases to the full set
131 131 aliases.update(qt_aliases)
132 132
133 133 # get flags&aliases into sets, and remove a couple that
134 134 # shouldn't be scrubbed from backend flags:
135 135 qt_aliases = set(qt_aliases.keys())
136 136 qt_aliases.remove('colors')
137 137 qt_flags = set(qt_flags.keys())
138 138
139 139 #-----------------------------------------------------------------------------
140 140 # Classes
141 141 #-----------------------------------------------------------------------------
142 142
143 143 #-----------------------------------------------------------------------------
144 144 # IPythonQtConsole
145 145 #-----------------------------------------------------------------------------
146 146
147 147
148 148 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
149 149 name = 'ipython-qtconsole'
150 150
151 151 description = """
152 152 The IPython QtConsole.
153 153
154 154 This launches a Console-style application using Qt. It is not a full
155 155 console, in that launched terminal subprocesses will not be able to accept
156 156 input.
157 157
158 158 The QtConsole supports various extra features beyond the Terminal IPython
159 159 shell, such as inline plotting with matplotlib, via:
160 160
161 161 ipython qtconsole --pylab=inline
162 162
163 163 as well as saving your session as HTML, and printing the output.
164 164
165 165 """
166 166 examples = _examples
167 167
168 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
168 classes = [IPythonWidget] + IPythonConsoleApp.classes
169 169 flags = Dict(flags)
170 170 aliases = Dict(aliases)
171 171 frontend_flags = Any(qt_flags)
172 172 frontend_aliases = Any(qt_aliases)
173 173 kernel_manager_class = QtKernelManager
174 174
175 175 stylesheet = Unicode('', config=True,
176 176 help="path to a custom CSS stylesheet")
177 177
178 178 plain = CBool(False, config=True,
179 179 help="Use a plaintext widget instead of rich text (plain can't print/save).")
180 180
181 181 def _plain_changed(self, name, old, new):
182 182 kind = 'plain' if new else 'rich'
183 183 self.config.ConsoleWidget.kind = kind
184 184 if new:
185 185 self.widget_factory = IPythonWidget
186 186 else:
187 187 self.widget_factory = RichIPythonWidget
188 188
189 189 # the factory for creating a widget
190 190 widget_factory = Any(RichIPythonWidget)
191 191
192 192 def parse_command_line(self, argv=None):
193 193 super(IPythonQtConsoleApp, self).parse_command_line(argv)
194 194 self.build_kernel_argv(argv)
195 195
196 196
197 197 def new_frontend_master(self):
198 198 """ Create and return new frontend attached to new kernel, launched on localhost.
199 199 """
200 200 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
201 201 kernel_manager = self.kernel_manager_class(
202 202 ip=ip,
203 203 connection_file=self._new_connection_file(),
204 204 config=self.config,
205 205 )
206 206 # start the kernel
207 207 kwargs = dict()
208 208 kwargs['extra_arguments'] = self.kernel_argv
209 209 kernel_manager.start_kernel(**kwargs)
210 210 kernel_manager.start_channels()
211 211 widget = self.widget_factory(config=self.config,
212 212 local_kernel=True)
213 213 self.init_colors(widget)
214 214 widget.kernel_manager = kernel_manager
215 215 widget._existing = False
216 216 widget._may_close = True
217 217 widget._confirm_exit = self.confirm_exit
218 218 return widget
219 219
220 220 def new_frontend_slave(self, current_widget):
221 221 """Create and return a new frontend attached to an existing kernel.
222 222
223 223 Parameters
224 224 ----------
225 225 current_widget : IPythonWidget
226 226 The IPythonWidget whose kernel this frontend is to share
227 227 """
228 228 kernel_manager = self.kernel_manager_class(
229 229 connection_file=current_widget.kernel_manager.connection_file,
230 230 config = self.config,
231 231 )
232 232 kernel_manager.load_connection_file()
233 233 kernel_manager.start_channels()
234 234 widget = self.widget_factory(config=self.config,
235 235 local_kernel=False)
236 236 self.init_colors(widget)
237 237 widget._existing = True
238 238 widget._may_close = False
239 239 widget._confirm_exit = False
240 240 widget.kernel_manager = kernel_manager
241 241 return widget
242 242
243 243 def init_qt_elements(self):
244 244 # Create the widget.
245 245 self.app = QtGui.QApplication([])
246 246
247 247 base_path = os.path.abspath(os.path.dirname(__file__))
248 248 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
249 249 self.app.icon = QtGui.QIcon(icon_path)
250 250 QtGui.QApplication.setWindowIcon(self.app.icon)
251 251
252 252 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
253 253 self.widget = self.widget_factory(config=self.config,
254 254 local_kernel=local_kernel)
255 255 self.init_colors(self.widget)
256 256 self.widget._existing = self.existing
257 257 self.widget._may_close = not self.existing
258 258 self.widget._confirm_exit = self.confirm_exit
259 259
260 260 self.widget.kernel_manager = self.kernel_manager
261 261 self.window = MainWindow(self.app,
262 262 confirm_exit=self.confirm_exit,
263 263 new_frontend_factory=self.new_frontend_master,
264 264 slave_frontend_factory=self.new_frontend_slave,
265 265 )
266 266 self.window.log = self.log
267 267 self.window.add_tab_with_frontend(self.widget)
268 268 self.window.init_menu_bar()
269 269
270 270 self.window.setWindowTitle('IPython')
271 271
272 272 def init_colors(self, widget):
273 273 """Configure the coloring of the widget"""
274 274 # Note: This will be dramatically simplified when colors
275 275 # are removed from the backend.
276 276
277 277 # parse the colors arg down to current known labels
278 278 try:
279 279 colors = self.config.ZMQInteractiveShell.colors
280 280 except AttributeError:
281 281 colors = None
282 282 try:
283 283 style = self.config.IPythonWidget.syntax_style
284 284 except AttributeError:
285 285 style = None
286 286 try:
287 287 sheet = self.config.IPythonWidget.style_sheet
288 288 except AttributeError:
289 289 sheet = None
290 290
291 291 # find the value for colors:
292 292 if colors:
293 293 colors=colors.lower()
294 294 if colors in ('lightbg', 'light'):
295 295 colors='lightbg'
296 296 elif colors in ('dark', 'linux'):
297 297 colors='linux'
298 298 else:
299 299 colors='nocolor'
300 300 elif style:
301 301 if style=='bw':
302 302 colors='nocolor'
303 303 elif styles.dark_style(style):
304 304 colors='linux'
305 305 else:
306 306 colors='lightbg'
307 307 else:
308 308 colors=None
309 309
310 310 # Configure the style
311 311 if style:
312 312 widget.style_sheet = styles.sheet_from_template(style, colors)
313 313 widget.syntax_style = style
314 314 widget._syntax_style_changed()
315 315 widget._style_sheet_changed()
316 316 elif colors:
317 317 # use a default dark/light/bw style
318 318 widget.set_default_style(colors=colors)
319 319
320 320 if self.stylesheet:
321 321 # we got an explicit stylesheet
322 322 if os.path.isfile(self.stylesheet):
323 323 with open(self.stylesheet) as f:
324 324 sheet = f.read()
325 325 else:
326 326 raise IOError("Stylesheet %r not found." % self.stylesheet)
327 327 if sheet:
328 328 widget.style_sheet = sheet
329 329 widget._style_sheet_changed()
330 330
331 331
332 332 def init_signal(self):
333 333 """allow clean shutdown on sigint"""
334 334 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
335 335 # need a timer, so that QApplication doesn't block until a real
336 336 # Qt event fires (can require mouse movement)
337 337 # timer trick from http://stackoverflow.com/q/4938723/938949
338 338 timer = QtCore.QTimer()
339 339 # Let the interpreter run each 200 ms:
340 340 timer.timeout.connect(lambda: None)
341 341 timer.start(200)
342 342 # hold onto ref, so the timer doesn't get cleaned up
343 343 self._sigint_timer = timer
344 344
345 345 @catch_config_error
346 346 def initialize(self, argv=None):
347 347 super(IPythonQtConsoleApp, self).initialize(argv)
348 348 IPythonConsoleApp.initialize(self,argv)
349 349 self.init_qt_elements()
350 350 self.init_signal()
351 351
352 352 def start(self):
353 353
354 354 # draw the window
355 355 self.window.show()
356 356 self.window.raise_()
357 357
358 358 # Start the application main loop.
359 359 self.app.exec_()
360 360
361 361 #-----------------------------------------------------------------------------
362 362 # Main entry point
363 363 #-----------------------------------------------------------------------------
364 364
365 365 def main():
366 366 app = IPythonQtConsoleApp()
367 367 app.initialize()
368 368 app.start()
369 369
370 370
371 371 if __name__ == '__main__':
372 372 main()
@@ -1,154 +1,154 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 5
6 6 Authors:
7 7
8 8 * Min RK
9 9 * Paul Ivanov
10 10
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 import signal
17 17 import sys
18 18 import time
19 19
20 20 from IPython.frontend.terminal.ipapp import TerminalIPythonApp, frontend_flags as term_flags
21 21
22 22 from IPython.utils.traitlets import (
23 23 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
24 24 )
25 25 from IPython.utils.warn import warn,error
26 26
27 27 from IPython.zmq.ipkernel import IPKernelApp
28 28 from IPython.zmq.session import Session, default_secure
29 29 from IPython.zmq.zmqshell import ZMQInteractiveShell
30 30 from IPython.frontend.consoleapp import (
31 31 IPythonConsoleApp, app_aliases, app_flags, aliases, app_aliases, flags
32 32 )
33 33
34 34 from IPython.frontend.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Globals
38 38 #-----------------------------------------------------------------------------
39 39
40 40 _examples = """
41 41 ipython console # start the ZMQ-based console
42 42 ipython console --existing # connect to an existing ipython session
43 43 """
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # Flags and Aliases
47 47 #-----------------------------------------------------------------------------
48 48
49 49 # copy flags from mixin:
50 50 flags = dict(flags)
51 51 # start with mixin frontend flags:
52 52 frontend_flags = dict(app_flags)
53 53 # add TerminalIPApp flags:
54 54 frontend_flags.update(term_flags)
55 55 # disable quick startup, as it won't propagate to the kernel anyway
56 56 frontend_flags.pop('quick')
57 57 # update full dict with frontend flags:
58 58 flags.update(frontend_flags)
59 59
60 60 # copy flags from mixin
61 61 aliases = dict(aliases)
62 62 # start with mixin frontend flags
63 63 frontend_aliases = dict(app_aliases)
64 64 # load updated frontend flags into full dict
65 65 aliases.update(frontend_aliases)
66 66
67 67 # get flags&aliases into sets, and remove a couple that
68 68 # shouldn't be scrubbed from backend flags:
69 69 frontend_aliases = set(frontend_aliases.keys())
70 70 frontend_flags = set(frontend_flags.keys())
71 71
72 72
73 73 #-----------------------------------------------------------------------------
74 74 # Classes
75 75 #-----------------------------------------------------------------------------
76 76
77 77
78 78 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):
79 79 name = "ipython-console"
80 80 """Start a terminal frontend to the IPython zmq kernel."""
81 81
82 82 description = """
83 83 The IPython terminal-based Console.
84 84
85 85 This launches a Console application inside a terminal.
86 86
87 87 The Console supports various extra features beyond the traditional
88 88 single-process Terminal IPython shell, such as connecting to an
89 89 existing ipython session, via:
90 90
91 91 ipython console --existing
92 92
93 93 where the previous session could have been created by another ipython
94 94 console, an ipython qtconsole, or by opening an ipython notebook.
95 95
96 96 """
97 97 examples = _examples
98 98
99 classes = List([IPKernelApp, ZMQTerminalInteractiveShell, Session])
99 classes = [ZMQTerminalInteractiveShell] + IPythonConsoleApp.classes
100 100 flags = Dict(flags)
101 101 aliases = Dict(aliases)
102 102 frontend_aliases = Any(frontend_aliases)
103 103 frontend_flags = Any(frontend_flags)
104 104
105 105 subcommands = Dict()
106 106
107 107 def parse_command_line(self, argv=None):
108 108 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
109 109 self.build_kernel_argv(argv)
110 110
111 111 def init_shell(self):
112 112 IPythonConsoleApp.initialize(self)
113 113 # relay sigint to kernel
114 114 signal.signal(signal.SIGINT, self.handle_sigint)
115 115 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
116 116 display_banner=False, profile_dir=self.profile_dir,
117 117 ipython_dir=self.ipython_dir, kernel_manager=self.kernel_manager)
118 118
119 119 def init_gui_pylab(self):
120 120 # no-op, because we don't want to import matplotlib in the frontend.
121 121 pass
122 122
123 123 def handle_sigint(self, *args):
124 124 if self.shell._executing:
125 125 if self.kernel_manager.has_kernel:
126 126 # interrupt already gets passed to subprocess by signal handler.
127 127 # Only if we prevent that should we need to explicitly call
128 128 # interrupt_kernel, until which time, this would result in a
129 129 # double-interrupt:
130 130 # self.kernel_manager.interrupt_kernel()
131 131 pass
132 132 else:
133 133 self.shell.write_err('\n')
134 134 error("Cannot interrupt kernels we didn't start.\n")
135 135 else:
136 136 # raise the KeyboardInterrupt if we aren't waiting for execution,
137 137 # so that the interact loop advances, and prompt is redrawn, etc.
138 138 raise KeyboardInterrupt
139 139
140 140
141 141 def init_code(self):
142 142 # no-op in the frontend, code gets run in the backend
143 143 pass
144 144
145 145 def launch_new_instance():
146 146 """Create and run a full blown IPython instance"""
147 147 app = ZMQTerminalIPythonApp.instance()
148 148 app.initialize()
149 149 app.start()
150 150
151 151
152 152 if __name__ == '__main__':
153 153 launch_new_instance()
154 154
General Comments 0
You need to be logged in to leave comments. Login now