##// END OF EJS Templates
Split swallow_argv into standalone function in lib.kernel...
MinRK -
Show More
@@ -0,0 +1,63 b''
1 """Tests for kernel utility functions
2
3 Authors
4 -------
5 * MinRK
6 """
7 #-----------------------------------------------------------------------------
8 # Copyright (c) 2011, the IPython Development Team.
9 #
10 # Distributed under the terms of the Modified BSD License.
11 #
12 # The full license is in the file COPYING.txt, distributed with this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
19 # Stdlib imports
20 from unittest import TestCase
21
22 # Third-party imports
23 import nose.tools as nt
24
25 # Our own imports
26 from IPython.testing import decorators as dec
27 from IPython.lib import kernel
28
29 #-----------------------------------------------------------------------------
30 # Classes and functions
31 #-----------------------------------------------------------------------------
32
33 @dec.parametric
34 def test_swallow_argv():
35 tests = [
36 # expected , argv , aliases, flags
37 (['-a', '5'], ['-a', '5'], None, None),
38 (['5'], ['-a', '5'], None, ['a']),
39 ([], ['-a', '5'], ['a'], None),
40 ([], ['-a', '5'], ['a'], ['a']),
41 ([], ['--foo'], None, ['foo']),
42 (['--foo'], ['--foo'], ['foobar'], []),
43 ([], ['--foo', '5'], ['foo'], []),
44 ([], ['--foo=5'], ['foo'], []),
45 (['--foo=5'], ['--foo=5'], [], ['foo']),
46 (['5'], ['--foo', '5'], [], ['foo']),
47 (['bar'], ['--foo', '5', 'bar'], ['foo'], ['foo']),
48 (['bar'], ['--foo=5', 'bar'], ['foo'], ['foo']),
49 (['5','bar'], ['--foo', '5', 'bar'], None, ['foo']),
50 (['bar'], ['--foo', '5', 'bar'], ['foo'], None),
51 (['bar'], ['--foo=5', 'bar'], ['foo'], None),
52 ]
53 for expected, argv, aliases, flags in tests:
54 stripped = kernel.swallow_argv(argv, aliases=aliases, flags=flags)
55 message = '\n'.join(['',
56 "argv: %r" % argv,
57 "aliases: %r" % aliases,
58 "flags : %r" % flags,
59 "expected : %r" % expected,
60 "returned : %r" % stripped,
61 ])
62 yield nt.assert_equal(expected, stripped, message)
63
@@ -1,381 +1,352 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 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
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 111
112 112 class IPythonConsoleApp(Configurable):
113 113 name = 'ipython-console-mixin'
114 114 default_config_file_name='ipython_config.py'
115 115
116 116 description = """
117 117 The IPython Mixin Console.
118 118
119 119 This class contains the common portions of console client (QtConsole,
120 120 ZMQ-based terminal console, etc). It is not a full console, in that
121 121 launched terminal subprocesses will not be able to accept input.
122 122
123 123 The Console using this mixing supports various extra features beyond
124 124 the single-process Terminal IPython shell, such as connecting to
125 125 existing kernel, via:
126 126
127 127 ipython <appname> --existing
128 128
129 129 as well as tunnel via SSH
130 130
131 131 """
132 132
133 133 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session]
134 134 flags = Dict(flags)
135 135 aliases = Dict(aliases)
136 136 kernel_manager_class = BlockingKernelManager
137 137
138 138 kernel_argv = List(Unicode)
139 # frontend flags&aliases to be stripped when building kernel_argv
140 frontend_flags = Any(app_flags)
141 frontend_aliases = Any(app_aliases)
139 142
140 143 pure = CBool(False, config=True,
141 144 help="Use a pure Python kernel instead of an IPython kernel.")
142 145 # create requested profiles by default, if they don't exist:
143 146 auto_create = CBool(True)
144 147 # connection info:
145 148 ip = Unicode(LOCALHOST, config=True,
146 149 help="""Set the kernel\'s IP address [default localhost].
147 150 If the IP address is something other than localhost, then
148 151 Consoles on other machines will be able to connect
149 152 to the Kernel, so be careful!"""
150 153 )
151 154
152 155 sshserver = Unicode('', config=True,
153 156 help="""The SSH server to use to connect to the kernel.""")
154 157 sshkey = Unicode('', config=True,
155 158 help="""Path to the ssh key to use for logging in to the ssh server.""")
156 159
157 160 hb_port = Int(0, config=True,
158 161 help="set the heartbeat port [default: random]")
159 162 shell_port = Int(0, config=True,
160 163 help="set the shell (XREP) port [default: random]")
161 164 iopub_port = Int(0, config=True,
162 165 help="set the iopub (PUB) port [default: random]")
163 166 stdin_port = Int(0, config=True,
164 167 help="set the stdin (XREQ) port [default: random]")
165 168 connection_file = Unicode('', config=True,
166 169 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
167 170
168 171 This file will contain the IP, ports, and authentication key needed to connect
169 172 clients to this kernel. By default, this file will be created in the security-dir
170 173 of the current profile, but can be specified by absolute path.
171 174 """)
172 175 def _connection_file_default(self):
173 176 return 'kernel-%i.json' % os.getpid()
174 177
175 178 existing = CUnicode('', config=True,
176 179 help="""Connect to an already running kernel""")
177 180
178 181 confirm_exit = CBool(True, config=True,
179 182 help="""
180 183 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
181 184 to force a direct exit without any confirmation.""",
182 185 )
183 186
184 187
185 def parse_command_line(self, argv=None):
186 #super(PythonBaseConsoleApp, self).parse_command_line(argv)
187 # make this stuff after this a function, in case the super stuff goes
188 # away. Also, Min notes that this functionality should be moved to a
189 # generic library of kernel stuff
190 self.swallow_args(app_aliases,app_flags,argv=argv)
191
192 def swallow_args(self, aliases,flags, argv=None):
188 def build_kernel_argv(self, argv=None):
189 """build argv to be passed to kernel subprocess"""
193 190 if argv is None:
194 191 argv = sys.argv[1:]
195 self.kernel_argv = list(argv) # copy
192 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
196 193 # kernel should inherit default config file from frontend
197 194 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
198 # Scrub frontend-specific flags
199 swallow_next = False
200 was_flag = False
201 for a in argv:
202 if swallow_next:
203 swallow_next = False
204 # last arg was an alias, remove the next one
205 # *unless* the last alias has a no-arg flag version, in which
206 # case, don't swallow the next arg if it's also a flag:
207 if not (was_flag and a.startswith('-')):
208 self.kernel_argv.remove(a)
209 continue
210 if a.startswith('-'):
211 split = a.lstrip('-').split('=')
212 alias = split[0]
213 if alias in aliases:
214 self.kernel_argv.remove(a)
215 if len(split) == 1:
216 # alias passed with arg via space
217 swallow_next = True
218 # could have been a flag that matches an alias, e.g. `existing`
219 # in which case, we might not swallow the next arg
220 was_flag = alias in flags
221 elif alias in flags:
222 # strip flag, but don't swallow next, as flags don't take args
223 self.kernel_argv.remove(a)
224 195
225 196 def init_connection_file(self):
226 197 """find the connection file, and load the info if found.
227 198
228 199 The current working directory and the current profile's security
229 200 directory will be searched for the file if it is not given by
230 201 absolute path.
231 202
232 203 When attempting to connect to an existing kernel and the `--existing`
233 204 argument does not match an existing file, it will be interpreted as a
234 205 fileglob, and the matching file in the current profile's security dir
235 206 with the latest access time will be used.
236 207
237 208 After this method is called, self.connection_file contains the *full path*
238 209 to the connection file, never just its name.
239 210 """
240 211 if self.existing:
241 212 try:
242 213 cf = find_connection_file(self.existing)
243 214 except Exception:
244 215 self.log.critical("Could not find existing kernel connection file %s", self.existing)
245 216 self.exit(1)
246 217 self.log.info("Connecting to existing kernel: %s" % cf)
247 218 self.connection_file = cf
248 219 else:
249 220 # not existing, check if we are going to write the file
250 221 # and ensure that self.connection_file is a full path, not just the shortname
251 222 try:
252 223 cf = find_connection_file(self.connection_file)
253 224 except Exception:
254 225 # file might not exist
255 226 if self.connection_file == os.path.basename(self.connection_file):
256 227 # just shortname, put it in security dir
257 228 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
258 229 else:
259 230 cf = self.connection_file
260 231 self.connection_file = cf
261 232
262 233 # should load_connection_file only be used for existing?
263 234 # as it is now, this allows reusing ports if an existing
264 235 # file is requested
265 236 try:
266 237 self.load_connection_file()
267 238 except Exception:
268 239 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
269 240 self.exit(1)
270 241
271 242 def load_connection_file(self):
272 243 """load ip/port/hmac config from JSON connection file"""
273 244 # this is identical to KernelApp.load_connection_file
274 245 # perhaps it can be centralized somewhere?
275 246 try:
276 247 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
277 248 except IOError:
278 249 self.log.debug("Connection File not found: %s", self.connection_file)
279 250 return
280 251 self.log.debug(u"Loading connection file %s", fname)
281 252 with open(fname) as f:
282 253 s = f.read()
283 254 cfg = json.loads(s)
284 255 if self.ip == LOCALHOST and 'ip' in cfg:
285 256 # not overridden by config or cl_args
286 257 self.ip = cfg['ip']
287 258 for channel in ('hb', 'shell', 'iopub', 'stdin'):
288 259 name = channel + '_port'
289 260 if getattr(self, name) == 0 and name in cfg:
290 261 # not overridden by config or cl_args
291 262 setattr(self, name, cfg[name])
292 263 if 'key' in cfg:
293 264 self.config.Session.key = str_to_bytes(cfg['key'])
294 265
295 266 def init_ssh(self):
296 267 """set up ssh tunnels, if needed."""
297 268 if not self.sshserver and not self.sshkey:
298 269 return
299 270
300 271 if self.sshkey and not self.sshserver:
301 272 # specifying just the key implies that we are connecting directly
302 273 self.sshserver = self.ip
303 274 self.ip = LOCALHOST
304 275
305 276 # build connection dict for tunnels:
306 277 info = dict(ip=self.ip,
307 278 shell_port=self.shell_port,
308 279 iopub_port=self.iopub_port,
309 280 stdin_port=self.stdin_port,
310 281 hb_port=self.hb_port
311 282 )
312 283
313 284 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
314 285
315 286 # tunnels return a new set of ports, which will be on localhost:
316 287 self.ip = LOCALHOST
317 288 try:
318 289 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
319 290 except:
320 291 # even catch KeyboardInterrupt
321 292 self.log.error("Could not setup tunnels", exc_info=True)
322 293 self.exit(1)
323 294
324 295 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
325 296
326 297 cf = self.connection_file
327 298 base,ext = os.path.splitext(cf)
328 299 base = os.path.basename(base)
329 300 self.connection_file = os.path.basename(base)+'-ssh'+ext
330 301 self.log.critical("To connect another client via this tunnel, use:")
331 302 self.log.critical("--existing %s" % self.connection_file)
332 303
333 304 def _new_connection_file(self):
334 305 cf = ''
335 306 while not cf:
336 307 # we don't need a 128b id to distinguish kernels, use more readable
337 308 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
338 309 # kernels can subclass.
339 310 ident = str(uuid.uuid4()).split('-')[-1]
340 311 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
341 312 # only keep if it's actually new. Protect against unlikely collision
342 313 # in 48b random search space
343 314 cf = cf if not os.path.exists(cf) else ''
344 315 return cf
345 316
346 317 def init_kernel_manager(self):
347 318 # Don't let Qt or ZMQ swallow KeyboardInterupts.
348 319 signal.signal(signal.SIGINT, signal.SIG_DFL)
349 320
350 321 # Create a KernelManager and start a kernel.
351 322 self.kernel_manager = self.kernel_manager_class(
352 323 ip=self.ip,
353 324 shell_port=self.shell_port,
354 325 iopub_port=self.iopub_port,
355 326 stdin_port=self.stdin_port,
356 327 hb_port=self.hb_port,
357 328 connection_file=self.connection_file,
358 329 config=self.config,
359 330 )
360 331 # start the kernel
361 332 if not self.existing:
362 333 kwargs = dict(ipython=not self.pure)
363 334 kwargs['extra_arguments'] = self.kernel_argv
364 335 self.kernel_manager.start_kernel(**kwargs)
365 336 elif self.sshserver:
366 337 # ssh, write new connection file
367 338 self.kernel_manager.write_connection_file()
368 339 atexit.register(self.kernel_manager.cleanup_connection_file)
369 340 self.kernel_manager.start_channels()
370 341
371 342
372 343 def initialize(self, argv=None):
373 344 """
374 345 Classes which mix this class in should call:
375 346 IPythonConsoleApp.initialize(self,argv)
376 347 """
377 348 self.init_connection_file()
378 349 default_secure(self.config)
379 350 self.init_ssh()
380 351 self.init_kernel_manager()
381 352
@@ -1,385 +1,369 b''
1 1 """A tornado based IPython notebook server.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
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 signal
24 24 import socket
25 25 import sys
26 26 import threading
27 27 import webbrowser
28 28
29 29 # Third party
30 30 import zmq
31 31
32 32 # Install the pyzmq ioloop. This has to be done before anything else from
33 33 # tornado is imported.
34 34 from zmq.eventloop import ioloop
35 35 # FIXME: ioloop.install is new in pyzmq-2.1.7, so remove this conditional
36 36 # when pyzmq dependency is updated beyond that.
37 37 if hasattr(ioloop, 'install'):
38 38 ioloop.install()
39 39 else:
40 40 import tornado.ioloop
41 41 tornado.ioloop.IOLoop = ioloop.IOLoop
42 42
43 43 from tornado import httpserver
44 44 from tornado import web
45 45
46 46 # Our own libraries
47 47 from .kernelmanager import MappingKernelManager
48 48 from .handlers import (LoginHandler, LogoutHandler,
49 49 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
50 50 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
51 51 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
52 52 )
53 53 from .notebookmanager import NotebookManager
54 54
55 55 from IPython.config.application import catch_config_error
56 56 from IPython.core.application import BaseIPythonApplication
57 57 from IPython.core.profiledir import ProfileDir
58 from IPython.lib.kernel import swallow_argv
58 59 from IPython.zmq.session import Session, default_secure
59 60 from IPython.zmq.zmqshell import ZMQInteractiveShell
60 61 from IPython.zmq.ipkernel import (
61 62 flags as ipkernel_flags,
62 63 aliases as ipkernel_aliases,
63 64 IPKernelApp
64 65 )
65 66 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
66 67
67 68 #-----------------------------------------------------------------------------
68 69 # Module globals
69 70 #-----------------------------------------------------------------------------
70 71
71 72 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
72 73 _kernel_action_regex = r"(?P<action>restart|interrupt)"
73 74 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
74 75
75 76 LOCALHOST = '127.0.0.1'
76 77
77 78 _examples = """
78 79 ipython notebook # start the notebook
79 80 ipython notebook --profile=sympy # use the sympy profile
80 81 ipython notebook --pylab=inline # pylab in inline plotting mode
81 82 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
82 83 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
83 84 """
84 85
85 86 #-----------------------------------------------------------------------------
86 87 # The Tornado web application
87 88 #-----------------------------------------------------------------------------
88 89
89 90 class NotebookWebApplication(web.Application):
90 91
91 92 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
92 93 handlers = [
93 94 (r"/", ProjectDashboardHandler),
94 95 (r"/login", LoginHandler),
95 96 (r"/logout", LogoutHandler),
96 97 (r"/new", NewHandler),
97 98 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
98 99 (r"/kernels", MainKernelHandler),
99 100 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
100 101 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
101 102 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
102 103 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
103 104 (r"/notebooks", NotebookRootHandler),
104 105 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
105 106 (r"/rstservice/render", RSTHandler)
106 107 ]
107 108 settings = dict(
108 109 template_path=os.path.join(os.path.dirname(__file__), "templates"),
109 110 static_path=os.path.join(os.path.dirname(__file__), "static"),
110 111 cookie_secret=os.urandom(1024),
111 112 login_url="/login",
112 113 )
113 114 web.Application.__init__(self, handlers, **settings)
114 115
115 116 self.kernel_manager = kernel_manager
116 117 self.log = log
117 118 self.notebook_manager = notebook_manager
118 119 self.ipython_app = ipython_app
119 120 self.read_only = self.ipython_app.read_only
120 121
121 122
122 123 #-----------------------------------------------------------------------------
123 124 # Aliases and Flags
124 125 #-----------------------------------------------------------------------------
125 126
126 127 flags = dict(ipkernel_flags)
127 128 flags['no-browser']=(
128 129 {'NotebookApp' : {'open_browser' : False}},
129 130 "Don't open the notebook in a browser after startup."
130 131 )
131 132 flags['no-mathjax']=(
132 133 {'NotebookApp' : {'enable_mathjax' : False}},
133 134 """Disable MathJax
134 135
135 136 MathJax is the javascript library IPython uses to render math/LaTeX. It is
136 137 very large, so you may want to disable it if you have a slow internet
137 138 connection, or for offline use of the notebook.
138 139
139 140 When disabled, equations etc. will appear as their untransformed TeX source.
140 141 """
141 142 )
142 143 flags['read-only'] = (
143 144 {'NotebookApp' : {'read_only' : True}},
144 145 """Allow read-only access to notebooks.
145 146
146 147 When using a password to protect the notebook server, this flag
147 148 allows unauthenticated clients to view the notebook list, and
148 149 individual notebooks, but not edit them, start kernels, or run
149 150 code.
150 151
151 152 If no password is set, the server will be entirely read-only.
152 153 """
153 154 )
154 155
155 156 # the flags that are specific to the frontend
156 157 # these must be scrubbed before being passed to the kernel,
157 158 # or it will raise an error on unrecognized flags
158 159 notebook_flags = ['no-browser', 'no-mathjax', 'read-only']
159 160
160 161 aliases = dict(ipkernel_aliases)
161 162
162 163 aliases.update({
163 164 'ip': 'NotebookApp.ip',
164 165 'port': 'NotebookApp.port',
165 166 'keyfile': 'NotebookApp.keyfile',
166 167 'certfile': 'NotebookApp.certfile',
167 168 'notebook-dir': 'NotebookManager.notebook_dir',
168 169 })
169 170
170 171 # remove ipkernel flags that are singletons, and don't make sense in
171 172 # multi-kernel evironment:
172 173 aliases.pop('f', None)
173 174
174 175 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
175 176 u'notebook-dir']
176 177
177 178 #-----------------------------------------------------------------------------
178 179 # NotebookApp
179 180 #-----------------------------------------------------------------------------
180 181
181 182 class NotebookApp(BaseIPythonApplication):
182 183
183 184 name = 'ipython-notebook'
184 185 default_config_file_name='ipython_notebook_config.py'
185 186
186 187 description = """
187 188 The IPython HTML Notebook.
188 189
189 190 This launches a Tornado based HTML Notebook Server that serves up an
190 191 HTML5/Javascript Notebook client.
191 192 """
192 193 examples = _examples
193 194
194 195 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
195 196 MappingKernelManager, NotebookManager]
196 197 flags = Dict(flags)
197 198 aliases = Dict(aliases)
198 199
199 200 kernel_argv = List(Unicode)
200 201
201 202 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
202 203 default_value=logging.INFO,
203 204 config=True,
204 205 help="Set the log level by value or name.")
205 206
206 207 # Network related information.
207 208
208 209 ip = Unicode(LOCALHOST, config=True,
209 210 help="The IP address the notebook server will listen on."
210 211 )
211 212
212 213 def _ip_changed(self, name, old, new):
213 214 if new == u'*': self.ip = u''
214 215
215 216 port = Integer(8888, config=True,
216 217 help="The port the notebook server will listen on."
217 218 )
218 219
219 220 certfile = Unicode(u'', config=True,
220 221 help="""The full path to an SSL/TLS certificate file."""
221 222 )
222 223
223 224 keyfile = Unicode(u'', config=True,
224 225 help="""The full path to a private key file for usage with SSL/TLS."""
225 226 )
226 227
227 228 password = Unicode(u'', config=True,
228 229 help="""Hashed password to use for web authentication.
229 230
230 231 To generate, type in a python/IPython shell:
231 232
232 233 from IPython.lib import passwd; passwd()
233 234
234 235 The string should be of the form type:salt:hashed-password.
235 236 """
236 237 )
237 238
238 239 open_browser = Bool(True, config=True,
239 240 help="Whether to open in a browser after starting.")
240 241
241 242 read_only = Bool(False, config=True,
242 243 help="Whether to prevent editing/execution of notebooks."
243 244 )
244 245
245 246 enable_mathjax = Bool(True, config=True,
246 247 help="""Whether to enable MathJax for typesetting math/TeX
247 248
248 249 MathJax is the javascript library IPython uses to render math/LaTeX. It is
249 250 very large, so you may want to disable it if you have a slow internet
250 251 connection, or for offline use of the notebook.
251 252
252 253 When disabled, equations etc. will appear as their untransformed TeX source.
253 254 """
254 255 )
255 256 def _enable_mathjax_changed(self, name, old, new):
256 257 """set mathjax url to empty if mathjax is disabled"""
257 258 if not new:
258 259 self.mathjax_url = u''
259 260
260 261 mathjax_url = Unicode("", config=True,
261 262 help="""The url for MathJax.js."""
262 263 )
263 264 def _mathjax_url_default(self):
264 265 if not self.enable_mathjax:
265 266 return u''
266 267 static_path = os.path.join(os.path.dirname(__file__), "static")
267 268 if os.path.exists(os.path.join(static_path, 'mathjax', "MathJax.js")):
268 269 self.log.info("Using local MathJax")
269 270 return u"static/mathjax/MathJax.js"
270 271 else:
271 272 self.log.info("Using MathJax from CDN")
272 273 return u"http://cdn.mathjax.org/mathjax/latest/MathJax.js"
273 274
274 275 def _mathjax_url_changed(self, name, old, new):
275 276 if new and not self.enable_mathjax:
276 277 # enable_mathjax=False overrides mathjax_url
277 278 self.mathjax_url = u''
278 279 else:
279 280 self.log.info("Using MathJax: %s", new)
280 281
281 282 def parse_command_line(self, argv=None):
282 283 super(NotebookApp, self).parse_command_line(argv)
283 284 if argv is None:
284 285 argv = sys.argv[1:]
285 286
286 self.kernel_argv = list(argv) # copy
287 # Scrub frontend-specific flags
288 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
287 289 # Kernel should inherit default config file from frontend
288 290 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
289 # Scrub frontend-specific flags
290 for a in argv:
291 if a.startswith('-') and a.lstrip('-') in notebook_flags:
292 self.kernel_argv.remove(a)
293 swallow_next = False
294 for a in argv:
295 if swallow_next:
296 self.kernel_argv.remove(a)
297 swallow_next = False
298 continue
299 if a.startswith('-'):
300 split = a.lstrip('-').split('=')
301 alias = split[0]
302 if alias in notebook_aliases:
303 self.kernel_argv.remove(a)
304 if len(split) == 1:
305 # alias passed with arg via space
306 swallow_next = True
307 291
308 292 def init_configurables(self):
309 293 # Don't let Qt or ZMQ swallow KeyboardInterupts.
310 294 signal.signal(signal.SIGINT, signal.SIG_DFL)
311 295
312 296 # force Session default to be secure
313 297 default_secure(self.config)
314 298 # Create a KernelManager and start a kernel.
315 299 self.kernel_manager = MappingKernelManager(
316 300 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
317 301 connection_dir = self.profile_dir.security_dir,
318 302 )
319 303 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
320 304 self.notebook_manager.list_notebooks()
321 305
322 306 def init_logging(self):
323 307 super(NotebookApp, self).init_logging()
324 308 # This prevents double log messages because tornado use a root logger that
325 309 # self.log is a child of. The logging module dipatches log messages to a log
326 310 # and all of its ancenstors until propagate is set to False.
327 311 self.log.propagate = False
328 312
329 313 @catch_config_error
330 314 def initialize(self, argv=None):
331 315 super(NotebookApp, self).initialize(argv)
332 316 self.init_configurables()
333 317 self.web_app = NotebookWebApplication(
334 318 self, self.kernel_manager, self.notebook_manager, self.log
335 319 )
336 320 if self.certfile:
337 321 ssl_options = dict(certfile=self.certfile)
338 322 if self.keyfile:
339 323 ssl_options['keyfile'] = self.keyfile
340 324 else:
341 325 ssl_options = None
342 326 self.web_app.password = self.password
343 327 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
344 328 if ssl_options is None and not self.ip:
345 329 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
346 330 'but not using any encryption or authentication. This is highly '
347 331 'insecure and not recommended.')
348 332
349 333 # Try random ports centered around the default.
350 334 from random import randint
351 335 n = 50 # Max number of attempts, keep reasonably large.
352 336 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
353 337 try:
354 338 self.http_server.listen(port, self.ip)
355 339 except socket.error, e:
356 340 if e.errno != errno.EADDRINUSE:
357 341 raise
358 342 self.log.info('The port %i is already in use, trying another random port.' % port)
359 343 else:
360 344 self.port = port
361 345 break
362 346
363 347 def start(self):
364 348 ip = self.ip if self.ip else '[all ip addresses on your system]'
365 349 proto = 'https' if self.certfile else 'http'
366 350 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
367 351 ip,
368 352 self.port))
369 353 if self.open_browser:
370 354 ip = self.ip or '127.0.0.1'
371 355 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
372 356 new=2)
373 357 threading.Thread(target=b).start()
374 358
375 359 ioloop.IOLoop.instance().start()
376 360
377 361 #-----------------------------------------------------------------------------
378 362 # Main entry point
379 363 #-----------------------------------------------------------------------------
380 364
381 365 def launch_new_instance():
382 366 app = NotebookApp()
383 367 app.initialize()
384 368 app.start()
385 369
@@ -1,350 +1,352 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 # System library imports
30 30 from IPython.external.qt import QtCore, QtGui
31 31
32 32 # Local imports
33 33 from IPython.config.application import boolean_flag, catch_config_error
34 34 from IPython.core.application import BaseIPythonApplication
35 35 from IPython.core.profiledir import ProfileDir
36 36 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
37 37 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
38 38 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
39 39 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
40 40 from IPython.frontend.qt.console import styles
41 41 from IPython.frontend.qt.console.mainwindow import MainWindow
42 42 from IPython.frontend.qt.kernelmanager import QtKernelManager
43 43 from IPython.utils.path import filefind
44 44 from IPython.utils.py3compat import str_to_bytes
45 45 from IPython.utils.traitlets import (
46 46 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
47 47 )
48 48 from IPython.zmq.ipkernel import IPKernelApp
49 49 from IPython.zmq.session import Session, default_secure
50 50 from IPython.zmq.zmqshell import ZMQInteractiveShell
51 51
52 52 from IPython.frontend.consoleapp import (
53 53 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
54 54 )
55 55
56 56 #-----------------------------------------------------------------------------
57 57 # Network Constants
58 58 #-----------------------------------------------------------------------------
59 59
60 60 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
61 61
62 62 #-----------------------------------------------------------------------------
63 63 # Globals
64 64 #-----------------------------------------------------------------------------
65 65
66 66 _examples = """
67 67 ipython qtconsole # start the qtconsole
68 68 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
69 69 """
70 70
71 71 #-----------------------------------------------------------------------------
72 72 # Aliases and Flags
73 73 #-----------------------------------------------------------------------------
74 74
75 75 # start with copy of flags
76 76 flags = dict(flags)
77 77 qt_flags = {
78 78 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
79 79 "Use a pure Python kernel instead of an IPython kernel."),
80 80 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
81 81 "Disable rich text support."),
82 82 }
83 83 qt_flags.update(boolean_flag(
84 84 'gui-completion', 'ConsoleWidget.gui_completion',
85 85 "use a GUI widget for tab completion",
86 86 "use plaintext output for completion"
87 87 ))
88 88 # and app_flags from the Console Mixin
89 89 qt_flags.update(app_flags)
90 90 # add frontend flags to the full set
91 91 flags.update(qt_flags)
92 92
93 93 # start with copy of front&backend aliases list
94 94 aliases = dict(aliases)
95 95 qt_aliases = dict(
96 96
97 97 style = 'IPythonWidget.syntax_style',
98 98 stylesheet = 'IPythonQtConsoleApp.stylesheet',
99 99 colors = 'ZMQInteractiveShell.colors',
100 100
101 101 editor = 'IPythonWidget.editor',
102 102 paging = 'ConsoleWidget.paging',
103 103 )
104 104 # and app_aliases from the Console Mixin
105 105 qt_aliases.update(app_aliases)
106 106 # add frontend aliases to the full set
107 107 aliases.update(qt_aliases)
108 108
109 109 # get flags&aliases into sets, and remove a couple that
110 110 # shouldn't be scrubbed from backend flags:
111 111 qt_aliases = set(qt_aliases.keys())
112 112 qt_aliases.remove('colors')
113 113 qt_flags = set(qt_flags.keys())
114 114
115 115 #-----------------------------------------------------------------------------
116 116 # Classes
117 117 #-----------------------------------------------------------------------------
118 118
119 119 #-----------------------------------------------------------------------------
120 120 # IPythonQtConsole
121 121 #-----------------------------------------------------------------------------
122 122
123 123
124 124 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
125 125 name = 'ipython-qtconsole'
126 126
127 127 description = """
128 128 The IPython QtConsole.
129 129
130 130 This launches a Console-style application using Qt. It is not a full
131 131 console, in that launched terminal subprocesses will not be able to accept
132 132 input.
133 133
134 134 The QtConsole supports various extra features beyond the Terminal IPython
135 135 shell, such as inline plotting with matplotlib, via:
136 136
137 137 ipython qtconsole --pylab=inline
138 138
139 139 as well as saving your session as HTML, and printing the output.
140 140
141 141 """
142 142 examples = _examples
143 143
144 144 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
145 145 flags = Dict(flags)
146 146 aliases = Dict(aliases)
147 frontend_flags = Any(qt_flags)
148 frontend_aliases = Any(qt_aliases)
147 149 kernel_manager_class = QtKernelManager
148 150
149 151 stylesheet = Unicode('', config=True,
150 152 help="path to a custom CSS stylesheet")
151 153
152 154 plain = CBool(False, config=True,
153 155 help="Use a plaintext widget instead of rich text (plain can't print/save).")
154 156
155 157 def _pure_changed(self, name, old, new):
156 158 kind = 'plain' if self.plain else 'rich'
157 159 self.config.ConsoleWidget.kind = kind
158 160 if self.pure:
159 161 self.widget_factory = FrontendWidget
160 162 elif self.plain:
161 163 self.widget_factory = IPythonWidget
162 164 else:
163 165 self.widget_factory = RichIPythonWidget
164 166
165 167 _plain_changed = _pure_changed
166 168
167 169 # the factory for creating a widget
168 170 widget_factory = Any(RichIPythonWidget)
169 171
170 172 def parse_command_line(self, argv=None):
171 173 super(IPythonQtConsoleApp, self).parse_command_line(argv)
172 self.swallow_args(qt_aliases,qt_flags,argv=argv)
174 self.build_kernel_argv(argv)
173 175
174 176
175 177 def new_frontend_master(self):
176 178 """ Create and return new frontend attached to new kernel, launched on localhost.
177 179 """
178 180 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
179 181 kernel_manager = QtKernelManager(
180 182 ip=ip,
181 183 connection_file=self._new_connection_file(),
182 184 config=self.config,
183 185 )
184 186 # start the kernel
185 187 kwargs = dict(ipython=not self.pure)
186 188 kwargs['extra_arguments'] = self.kernel_argv
187 189 kernel_manager.start_kernel(**kwargs)
188 190 kernel_manager.start_channels()
189 191 widget = self.widget_factory(config=self.config,
190 192 local_kernel=True)
191 193 widget.kernel_manager = kernel_manager
192 194 widget._existing = False
193 195 widget._may_close = True
194 196 widget._confirm_exit = self.confirm_exit
195 197 return widget
196 198
197 199 def new_frontend_slave(self, current_widget):
198 200 """Create and return a new frontend attached to an existing kernel.
199 201
200 202 Parameters
201 203 ----------
202 204 current_widget : IPythonWidget
203 205 The IPythonWidget whose kernel this frontend is to share
204 206 """
205 207 kernel_manager = QtKernelManager(
206 208 connection_file=current_widget.kernel_manager.connection_file,
207 209 config = self.config,
208 210 )
209 211 kernel_manager.load_connection_file()
210 212 kernel_manager.start_channels()
211 213 widget = self.widget_factory(config=self.config,
212 214 local_kernel=False)
213 215 widget._existing = True
214 216 widget._may_close = False
215 217 widget._confirm_exit = False
216 218 widget.kernel_manager = kernel_manager
217 219 return widget
218 220
219 221 def init_qt_elements(self):
220 222 # Create the widget.
221 223 self.app = QtGui.QApplication([])
222 224
223 225 base_path = os.path.abspath(os.path.dirname(__file__))
224 226 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
225 227 self.app.icon = QtGui.QIcon(icon_path)
226 228 QtGui.QApplication.setWindowIcon(self.app.icon)
227 229
228 230 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
229 231 self.widget = self.widget_factory(config=self.config,
230 232 local_kernel=local_kernel)
231 233 self.widget._existing = self.existing
232 234 self.widget._may_close = not self.existing
233 235 self.widget._confirm_exit = self.confirm_exit
234 236
235 237 self.widget.kernel_manager = self.kernel_manager
236 238 self.window = MainWindow(self.app,
237 239 confirm_exit=self.confirm_exit,
238 240 new_frontend_factory=self.new_frontend_master,
239 241 slave_frontend_factory=self.new_frontend_slave,
240 242 )
241 243 self.window.log = self.log
242 244 self.window.add_tab_with_frontend(self.widget)
243 245 self.window.init_menu_bar()
244 246
245 247 self.window.setWindowTitle('Python' if self.pure else 'IPython')
246 248
247 249 def init_colors(self):
248 250 """Configure the coloring of the widget"""
249 251 # Note: This will be dramatically simplified when colors
250 252 # are removed from the backend.
251 253
252 254 if self.pure:
253 255 # only IPythonWidget supports styling
254 256 return
255 257
256 258 # parse the colors arg down to current known labels
257 259 try:
258 260 colors = self.config.ZMQInteractiveShell.colors
259 261 except AttributeError:
260 262 colors = None
261 263 try:
262 264 style = self.config.IPythonWidget.syntax_style
263 265 except AttributeError:
264 266 style = None
265 267
266 268 # find the value for colors:
267 269 if colors:
268 270 colors=colors.lower()
269 271 if colors in ('lightbg', 'light'):
270 272 colors='lightbg'
271 273 elif colors in ('dark', 'linux'):
272 274 colors='linux'
273 275 else:
274 276 colors='nocolor'
275 277 elif style:
276 278 if style=='bw':
277 279 colors='nocolor'
278 280 elif styles.dark_style(style):
279 281 colors='linux'
280 282 else:
281 283 colors='lightbg'
282 284 else:
283 285 colors=None
284 286
285 287 # Configure the style.
286 288 widget = self.widget
287 289 if style:
288 290 widget.style_sheet = styles.sheet_from_template(style, colors)
289 291 widget.syntax_style = style
290 292 widget._syntax_style_changed()
291 293 widget._style_sheet_changed()
292 294 elif colors:
293 295 # use a default style
294 296 widget.set_default_style(colors=colors)
295 297 else:
296 298 # this is redundant for now, but allows the widget's
297 299 # defaults to change
298 300 widget.set_default_style()
299 301
300 302 if self.stylesheet:
301 303 # we got an expicit stylesheet
302 304 if os.path.isfile(self.stylesheet):
303 305 with open(self.stylesheet) as f:
304 306 sheet = f.read()
305 307 widget.style_sheet = sheet
306 308 widget._style_sheet_changed()
307 309 else:
308 310 raise IOError("Stylesheet %r not found."%self.stylesheet)
309 311
310 312 def init_signal(self):
311 313 """allow clean shutdown on sigint"""
312 314 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
313 315 # need a timer, so that QApplication doesn't block until a real
314 316 # Qt event fires (can require mouse movement)
315 317 # timer trick from http://stackoverflow.com/q/4938723/938949
316 318 timer = QtCore.QTimer()
317 319 # Let the interpreter run each 200 ms:
318 320 timer.timeout.connect(lambda: None)
319 321 timer.start(200)
320 322 # hold onto ref, so the timer doesn't get cleaned up
321 323 self._sigint_timer = timer
322 324
323 325 @catch_config_error
324 326 def initialize(self, argv=None):
325 327 super(IPythonQtConsoleApp, self).initialize(argv)
326 328 IPythonConsoleApp.initialize(self,argv)
327 329 self.init_qt_elements()
328 330 self.init_colors()
329 331 self.init_signal()
330 332
331 333 def start(self):
332 334
333 335 # draw the window
334 336 self.window.show()
335 337
336 338 # Start the application main loop.
337 339 self.app.exec_()
338 340
339 341 #-----------------------------------------------------------------------------
340 342 # Main entry point
341 343 #-----------------------------------------------------------------------------
342 344
343 345 def main():
344 346 app = IPythonQtConsoleApp()
345 347 app.initialize()
346 348 app.start()
347 349
348 350
349 351 if __name__ == '__main__':
350 352 main()
@@ -1,149 +1,152 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 # pylab is not frontend-specific in two-process IPython
56 56 frontend_flags.pop('pylab')
57 57 # disable quick startup, as it won't propagate to the kernel anyway
58 58 frontend_flags.pop('quick')
59 59 # update full dict with frontend flags:
60 60 flags.update(frontend_flags)
61 61
62 62 # copy flags from mixin
63 63 aliases = dict(aliases)
64 64 # start with mixin frontend flags
65 65 frontend_aliases = dict(app_aliases)
66 66 # load updated frontend flags into full dict
67 67 aliases.update(frontend_aliases)
68 68
69 69 # get flags&aliases into sets, and remove a couple that
70 70 # shouldn't be scrubbed from backend flags:
71 71 frontend_aliases = set(frontend_aliases.keys())
72 72 frontend_flags = set(frontend_flags.keys())
73 73
74 74
75 75 #-----------------------------------------------------------------------------
76 76 # Classes
77 77 #-----------------------------------------------------------------------------
78 78
79 79
80 80 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):
81 81 name = "ipython-console"
82 82 """Start a terminal frontend to the IPython zmq kernel."""
83 83
84 84 description = """
85 85 The IPython terminal-based Console.
86 86
87 87 This launches a Console application inside a terminal.
88 88
89 89 The Console supports various extra features beyond the traditional
90 90 single-process Terminal IPython shell, such as connecting to an
91 91 existing ipython session, via:
92 92
93 93 ipython console --existing
94 94
95 95 where the previous session could have been created by another ipython
96 96 console, an ipython qtconsole, or by opening an ipython notebook.
97 97
98 98 """
99 99 examples = _examples
100 100
101 101 classes = List([IPKernelApp, ZMQTerminalInteractiveShell, Session])
102 102 flags = Dict(flags)
103 103 aliases = Dict(aliases)
104 frontend_aliases = Any(frontend_aliases)
105 frontend_flags = Any(frontend_flags)
106
104 107 subcommands = Dict()
105 108
106 109 def parse_command_line(self, argv=None):
107 110 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
108 self.swallow_args(frontend_aliases,frontend_flags,argv=argv)
111 self.build_kernel_argv(argv)
109 112
110 113 def init_shell(self):
111 114 IPythonConsoleApp.initialize(self)
112 115 # relay sigint to kernel
113 116 signal.signal(signal.SIGINT, self.handle_sigint)
114 117 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
115 118 display_banner=False, profile_dir=self.profile_dir,
116 119 ipython_dir=self.ipython_dir, kernel_manager=self.kernel_manager)
117 120
118 121 def handle_sigint(self, *args):
119 122 if self.shell._executing:
120 123 if self.kernel_manager.has_kernel:
121 124 # interrupt already gets passed to subprocess by signal handler.
122 125 # Only if we prevent that should we need to explicitly call
123 126 # interrupt_kernel, until which time, this would result in a
124 127 # double-interrupt:
125 128 # self.kernel_manager.interrupt_kernel()
126 129 pass
127 130 else:
128 131 self.shell.write_err('\n')
129 132 error("Cannot interrupt kernels we didn't start.\n")
130 133 else:
131 134 # raise the KeyboardInterrupt if we aren't waiting for execution,
132 135 # so that the interact loop advances, and prompt is redrawn, etc.
133 136 raise KeyboardInterrupt
134 137
135 138
136 139 def init_code(self):
137 140 # no-op in the frontend, code gets run in the backend
138 141 pass
139 142
140 143 def launch_new_instance():
141 144 """Create and run a full blown IPython instance"""
142 145 app = ZMQTerminalIPythonApp.instance()
143 146 app.initialize()
144 147 app.start()
145 148
146 149
147 150 if __name__ == '__main__':
148 151 launch_new_instance()
149 152
@@ -1,255 +1,315 b''
1 1 """Utilities for connecting to kernels
2 2
3 3 Authors:
4 4
5 5 * Min Ragan-Kelley
6 6
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2011 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 20 import glob
21 21 import json
22 22 import os
23 23 import sys
24 24 from getpass import getpass
25 25 from subprocess import Popen, PIPE
26 26
27 27 # external imports
28 28 from IPython.external.ssh import tunnel
29 29
30 30 # IPython imports
31 31 from IPython.core.profiledir import ProfileDir
32 32 from IPython.utils.path import filefind, get_ipython_dir
33 33 from IPython.utils.py3compat import str_to_bytes
34 34
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Functions
38 38 #-----------------------------------------------------------------------------
39 39
40 40 def get_connection_file(app=None):
41 41 """Return the path to the connection file of an app
42 42
43 43 Parameters
44 44 ----------
45 45 app : KernelApp instance [optional]
46 46 If unspecified, the currently running app will be used
47 47 """
48 48 if app is None:
49 49 from IPython.zmq.kernelapp import KernelApp
50 50 if not KernelApp.initialized():
51 51 raise RuntimeError("app not specified, and not in a running Kernel")
52 52
53 53 app = KernelApp.instance()
54 54 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
55 55
56 56 def find_connection_file(filename, profile=None):
57 57 """find a connection file, and return its absolute path.
58 58
59 59 The current working directory and the profile's security
60 60 directory will be searched for the file if it is not given by
61 61 absolute path.
62 62
63 63 If profile is unspecified, then the current running application's
64 64 profile will be used, or 'default', if not run from IPython.
65 65
66 66 If the argument does not match an existing file, it will be interpreted as a
67 67 fileglob, and the matching file in the profile's security dir with
68 68 the latest access time will be used.
69 69
70 70 Parameters
71 71 ----------
72 72 filename : str
73 73 The connection file or fileglob to search for.
74 74 profile : str [optional]
75 75 The name of the profile to use when searching for the connection file,
76 76 if different from the current IPython session or 'default'.
77 77
78 78 Returns
79 79 -------
80 80 str : The absolute path of the connection file.
81 81 """
82 82 from IPython.core.application import BaseIPythonApplication as IPApp
83 83 try:
84 84 # quick check for absolute path, before going through logic
85 85 return filefind(filename)
86 86 except IOError:
87 87 pass
88 88
89 89 if profile is None:
90 90 # profile unspecified, check if running from an IPython app
91 91 if IPApp.initialized():
92 92 app = IPApp.instance()
93 93 profile_dir = app.profile_dir
94 94 else:
95 95 # not running in IPython, use default profile
96 96 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
97 97 else:
98 98 # find profiledir by profile name:
99 99 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
100 100 security_dir = profile_dir.security_dir
101 101
102 102 try:
103 103 # first, try explicit name
104 104 return filefind(filename, ['.', security_dir])
105 105 except IOError:
106 106 pass
107 107
108 108 # not found by full name
109 109
110 110 if '*' in filename:
111 111 # given as a glob already
112 112 pat = filename
113 113 else:
114 114 # accept any substring match
115 115 pat = '*%s*' % filename
116 116 matches = glob.glob( os.path.join(security_dir, pat) )
117 117 if not matches:
118 118 raise IOError("Could not find %r in %r" % (filename, security_dir))
119 119 elif len(matches) == 1:
120 120 return matches[0]
121 121 else:
122 122 # get most recent match, by access time:
123 123 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
124 124
125 125 def get_connection_info(connection_file=None, unpack=False, profile=None):
126 126 """Return the connection information for the current Kernel.
127 127
128 128 Parameters
129 129 ----------
130 130 connection_file : str [optional]
131 131 The connection file to be used. Can be given by absolute path, or
132 132 IPython will search in the security directory of a given profile.
133 133 If run from IPython,
134 134
135 135 If unspecified, the connection file for the currently running
136 136 IPython Kernel will be used, which is only allowed from inside a kernel.
137 137 unpack : bool [default: False]
138 138 if True, return the unpacked dict, otherwise just the string contents
139 139 of the file.
140 140 profile : str [optional]
141 141 The name of the profile to use when searching for the connection file,
142 142 if different from the current IPython session or 'default'.
143 143
144 144
145 145 Returns
146 146 -------
147 147 The connection dictionary of the current kernel, as string or dict,
148 148 depending on `unpack`.
149 149 """
150 150 if connection_file is None:
151 151 # get connection file from current kernel
152 152 cf = get_connection_file()
153 153 else:
154 154 # connection file specified, allow shortnames:
155 155 cf = find_connection_file(connection_file, profile=profile)
156 156
157 157 with open(cf) as f:
158 158 info = f.read()
159 159
160 160 if unpack:
161 161 info = json.loads(info)
162 162 # ensure key is bytes:
163 163 info['key'] = str_to_bytes(info.get('key', ''))
164 164 return info
165 165
166 166 def connect_qtconsole(connection_file=None, argv=None, profile=None):
167 167 """Connect a qtconsole to the current kernel.
168 168
169 169 This is useful for connecting a second qtconsole to a kernel, or to a
170 170 local notebook.
171 171
172 172 Parameters
173 173 ----------
174 174 connection_file : str [optional]
175 175 The connection file to be used. Can be given by absolute path, or
176 176 IPython will search in the security directory of a given profile.
177 177 If run from IPython,
178 178
179 179 If unspecified, the connection file for the currently running
180 180 IPython Kernel will be used, which is only allowed from inside a kernel.
181 181 argv : list [optional]
182 182 Any extra args to be passed to the console.
183 183 profile : str [optional]
184 184 The name of the profile to use when searching for the connection file,
185 185 if different from the current IPython session or 'default'.
186 186
187 187
188 188 Returns
189 189 -------
190 190 subprocess.Popen instance running the qtconsole frontend
191 191 """
192 192 argv = [] if argv is None else argv
193 193
194 194 if connection_file is None:
195 195 # get connection file from current kernel
196 196 cf = get_connection_file()
197 197 else:
198 198 cf = find_connection_file(connection_file, profile=profile)
199 199
200 200 cmd = ';'.join([
201 201 "from IPython.frontend.qt.console import qtconsoleapp",
202 202 "qtconsoleapp.main()"
203 203 ])
204 204
205 205 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
206 206
207 207 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
208 208 """tunnel connections to a kernel via ssh
209 209
210 210 This will open four SSH tunnels from localhost on this machine to the
211 211 ports associated with the kernel. They can be either direct
212 212 localhost-localhost tunnels, or if an intermediate server is necessary,
213 213 the kernel must be listening on a public IP.
214 214
215 215 Parameters
216 216 ----------
217 217 connection_info : dict or str (path)
218 218 Either a connection dict, or the path to a JSON connection file
219 219 sshserver : str
220 220 The ssh sever to use to tunnel to the kernel. Can be a full
221 221 `user@server:port` string. ssh config aliases are respected.
222 222 sshkey : str [optional]
223 223 Path to file containing ssh key to use for authentication.
224 224 Only necessary if your ssh config does not already associate
225 225 a keyfile with the host.
226 226
227 227 Returns
228 228 -------
229 229
230 230 (shell, iopub, stdin, hb) : ints
231 231 The four ports on localhost that have been forwarded to the kernel.
232 232 """
233 233 if isinstance(connection_info, basestring):
234 234 # it's a path, unpack it
235 235 with open(connection_info) as f:
236 236 connection_info = json.loads(f.read())
237 237
238 238 cf = connection_info
239 239
240 240 lports = tunnel.select_random_ports(4)
241 241 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
242 242
243 243 remote_ip = cf['ip']
244 244
245 245 if tunnel.try_passwordless_ssh(sshserver, sshkey):
246 246 password=False
247 247 else:
248 248 password = getpass("SSH Password for %s: "%sshserver)
249 249
250 250 for lp,rp in zip(lports, rports):
251 251 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
252 252
253 253 return tuple(lports)
254 254
255 255
256 def swallow_argv(argv, aliases=None, flags=None):
257 """strip frontend-specific aliases and flags from an argument list
258
259 For use primarily in frontend apps that want to pass a subset of command-line
260 arguments through to a subprocess, where frontend-specific flags and aliases
261 should be removed from the list.
262
263 Parameters
264 ----------
265
266 argv : list(str)
267 The starting argv, to be filtered
268 aliases : container of aliases (dict, list, set, etc.)
269 The frontend-specific aliases to be removed
270 flags : container of flags (dict, list, set, etc.)
271 The frontend-specific flags to be removed
272
273 Returns
274 -------
275
276 argv : list(str)
277 The argv list, excluding flags and aliases that have been stripped
278 """
279
280 if aliases is None:
281 aliases = set()
282 if flags is None:
283 flags = set()
284
285 stripped = list(argv) # copy
286
287 swallow_next = False
288 was_flag = False
289 for a in argv:
290 if swallow_next:
291 swallow_next = False
292 # last arg was an alias, remove the next one
293 # *unless* the last alias has a no-arg flag version, in which
294 # case, don't swallow the next arg if it's also a flag:
295 if not (was_flag and a.startswith('-')):
296 stripped.remove(a)
297 continue
298 if a.startswith('-'):
299 split = a.lstrip('-').split('=')
300 alias = split[0]
301 if alias in aliases:
302 stripped.remove(a)
303 if len(split) == 1:
304 # alias passed with arg via space
305 swallow_next = True
306 # could have been a flag that matches an alias, e.g. `existing`
307 # in which case, we might not swallow the next arg
308 was_flag = alias in flags
309 elif alias in flags and len(split) == 1:
310 # strip flag, but don't swallow next, as flags don't take args
311 stripped.remove(a)
312
313 # return shortened list
314 return stripped
315
General Comments 0
You need to be logged in to leave comments. Login now