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