##// END OF EJS Templates
zmqterminal now uses the zmq mixin app
Paul Ivanov -
Show More
@@ -1,366 +1,364 b''
1 1 """ A minimal application base mixin for all ZMQ based IPython frontends.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations. This is a
5 5 refactoring of what used to be the IPython/frontend/qt/console/qtconsoleapp.py
6 6
7 7 Authors:
8 8
9 9 * Evan Patterson
10 10 * Min RK
11 11 * Erik Tollerud
12 12 * Fernando Perez
13 13 * Bussonnier Matthias
14 14 * Thomas Kluyver
15 15 * Paul Ivanov
16 16
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 # stdlib imports
24 24 import json
25 25 import os
26 26 import signal
27 27 import sys
28 28 import uuid
29 29
30 30
31 31 # Local imports
32 32 from IPython.config.application import boolean_flag
33 33 from IPython.config.configurable import Configurable
34 34 from IPython.core.profiledir import ProfileDir
35 35 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
36 36 from IPython.zmq.blockingkernelmanager import BlockingKernelManager
37 37 from IPython.utils.path import filefind
38 38 from IPython.utils.py3compat import str_to_bytes
39 39 from IPython.utils.traitlets import (
40 40 Dict, List, Unicode, CUnicode, Int, CBool, Any
41 41 )
42 42 from IPython.zmq.ipkernel import (
43 43 flags as ipkernel_flags,
44 44 aliases as ipkernel_aliases,
45 45 IPKernelApp
46 46 )
47 47 from IPython.zmq.session import Session, default_secure
48 48 from IPython.zmq.zmqshell import ZMQInteractiveShell
49 49
50 50 #-----------------------------------------------------------------------------
51 51 # Network Constants
52 52 #-----------------------------------------------------------------------------
53 53
54 54 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
55 55
56 56 #-----------------------------------------------------------------------------
57 57 # Globals
58 58 #-----------------------------------------------------------------------------
59 59
60 60
61 61 #-----------------------------------------------------------------------------
62 62 # Aliases and Flags
63 63 #-----------------------------------------------------------------------------
64 64
65 65 flags = dict(ipkernel_flags)
66
67 # the flags that are specific to the frontend
68 # these must be scrubbed before being passed to the kernel,
69 # or it will raise an error on unrecognized flags
66 70 app_flags = {
67 71 'existing' : ({'IPythonMixinConsoleApp' : {'existing' : 'kernel*.json'}},
68 72 "Connect to an existing kernel. If no argument specified, guess most recent"),
69 'pure' : ({'IPythonMixinConsoleApp' : {'pure' : True}},
70 "Use a pure Python kernel instead of an IPython kernel."),
71 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
72 "Disable rich text support."),
73 73 }
74 74 app_flags.update(boolean_flag(
75 75 'confirm-exit', 'IPythonMixinConsoleApp.confirm_exit',
76 76 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
77 77 to force a direct exit without any confirmation.
78 78 """,
79 79 """Don't prompt the user when exiting. This will terminate the kernel
80 80 if it is owned by the frontend, and leave it alive if it is external.
81 81 """
82 82 ))
83 83 flags.update(app_flags)
84 84
85 85 aliases = dict(ipkernel_aliases)
86 86
87 # also scrub aliases from the frontend
87 88 app_aliases = dict(
88 89 hb = 'IPythonMixinConsoleApp.hb_port',
89 90 shell = 'IPythonMixinConsoleApp.shell_port',
90 91 iopub = 'IPythonMixinConsoleApp.iopub_port',
91 92 stdin = 'IPythonMixinConsoleApp.stdin_port',
92 93 ip = 'IPythonMixinConsoleApp.ip',
93 94 existing = 'IPythonMixinConsoleApp.existing',
94 95 f = 'IPythonMixinConsoleApp.connection_file',
95 96
96 97
97 98 ssh = 'IPythonMixinConsoleApp.sshserver',
98 99 )
99 100 aliases.update(app_aliases)
100 101
101 102 #-----------------------------------------------------------------------------
102 103 # Classes
103 104 #-----------------------------------------------------------------------------
104 105
105 106 #-----------------------------------------------------------------------------
106 107 # IPythonMixinConsole
107 108 #-----------------------------------------------------------------------------
108 109
109 110
110 111 class IPythonMixinConsoleApp(Configurable):
111 112 name = 'ipython-console-mixin'
112 113 default_config_file_name='ipython_config.py'
113 114
114 115 description = """
115 116 The IPython Mixin Console.
116 117
117 118 This class contains the common portions of console client (QtConsole,
118 119 ZMQ-based terminal console, etc). It is not a full console, in that
119 120 launched terminal subprocesses will not be able to accept input.
120 121
121 122 The Console using this mixing supports various extra features beyond
122 123 the single-process Terminal IPython shell, such as connecting to
123 124 existing kernel, via:
124 125
125 126 ipython <appname> --existing
126 127
127 128 as well as tunnel via SSH
128 129
129 130 """
130 131
131 132 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session]
132 133 flags = Dict(flags)
133 134 aliases = Dict(aliases)
134 135 kernel_manager_class = BlockingKernelManager
135 136
136 137 kernel_argv = List(Unicode)
137 138
139 pure = CBool(False, config=True,
140 help="Use a pure Python kernel instead of an IPython kernel.")
138 141 # create requested profiles by default, if they don't exist:
139 142 auto_create = CBool(True)
140 143 # connection info:
141 144 ip = Unicode(LOCALHOST, config=True,
142 145 help="""Set the kernel\'s IP address [default localhost].
143 146 If the IP address is something other than localhost, then
144 147 Consoles on other machines will be able to connect
145 148 to the Kernel, so be careful!"""
146 149 )
147 150
148 151 sshserver = Unicode('', config=True,
149 152 help="""The SSH server to use to connect to the kernel.""")
150 153 sshkey = Unicode('', config=True,
151 154 help="""Path to the ssh key to use for logging in to the ssh server.""")
152 155
153 156 hb_port = Int(0, config=True,
154 157 help="set the heartbeat port [default: random]")
155 158 shell_port = Int(0, config=True,
156 159 help="set the shell (XREP) port [default: random]")
157 160 iopub_port = Int(0, config=True,
158 161 help="set the iopub (PUB) port [default: random]")
159 162 stdin_port = Int(0, config=True,
160 163 help="set the stdin (XREQ) port [default: random]")
161 164 connection_file = Unicode('', config=True,
162 165 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
163 166
164 167 This file will contain the IP, ports, and authentication key needed to connect
165 168 clients to this kernel. By default, this file will be created in the security-dir
166 169 of the current profile, but can be specified by absolute path.
167 170 """)
168 171 def _connection_file_default(self):
169 172 return 'kernel-%i.json' % os.getpid()
170 173
171 174 existing = CUnicode('', config=True,
172 175 help="""Connect to an already running kernel""")
173 176
174 177 confirm_exit = CBool(True, config=True,
175 178 help="""
176 179 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
177 180 to force a direct exit without any confirmation.""",
178 181 )
179
182
180 183
181 184 def parse_command_line(self, argv=None):
182 super(PythonBaseConsoleApp, self).parse_command_line(argv)
185 #super(PythonBaseConsoleApp, self).parse_command_line(argv)
183 186 # make this stuff after this a function, in case the super stuff goes
184 187 # away. Also, Min notes that this functionality should be moved to a
185 188 # generic library of kernel stuff
189 self.swallow_args(app_aliases,app_flags,argv=argv)
190
191 def swallow_args(self, aliases,flags, argv=None):
186 192 if argv is None:
187 193 argv = sys.argv[1:]
188 194 self.kernel_argv = list(argv) # copy
189 195 # kernel should inherit default config file from frontend
190 196 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
191 197 # Scrub frontend-specific flags
192 198 swallow_next = False
193 199 was_flag = False
194 200 # copy again, in case some aliases have the same name as a flag
195 201 # argv = list(self.kernel_argv)
196 202 for a in argv:
197 203 if swallow_next:
198 204 swallow_next = False
199 205 # last arg was an alias, remove the next one
200 206 # *unless* the last alias has a no-arg flag version, in which
201 207 # case, don't swallow the next arg if it's also a flag:
202 208 if not (was_flag and a.startswith('-')):
203 209 self.kernel_argv.remove(a)
204 210 continue
205 211 if a.startswith('-'):
206 212 split = a.lstrip('-').split('=')
207 213 alias = split[0]
208 if alias in qt_aliases:
214 if alias in aliases:
209 215 self.kernel_argv.remove(a)
210 216 if len(split) == 1:
211 217 # alias passed with arg via space
212 218 swallow_next = True
213 219 # could have been a flag that matches an alias, e.g. `existing`
214 220 # in which case, we might not swallow the next arg
215 was_flag = alias in qt_flags
216 elif alias in qt_flags:
221 was_flag = alias in flags
222 elif alias in flags:
217 223 # strip flag, but don't swallow next, as flags don't take args
218 224 self.kernel_argv.remove(a)
219 225
220 226 def init_connection_file(self):
221 227 """find the connection file, and load the info if found.
222 228
223 229 The current working directory and the current profile's security
224 230 directory will be searched for the file if it is not given by
225 231 absolute path.
226 232
227 233 When attempting to connect to an existing kernel and the `--existing`
228 234 argument does not match an existing file, it will be interpreted as a
229 235 fileglob, and the matching file in the current profile's security dir
230 236 with the latest access time will be used.
231 237 """
232 238 if self.existing:
233 239 try:
234 240 cf = find_connection_file(self.existing)
235 241 except Exception:
236 242 self.log.critical("Could not find existing kernel connection file %s", self.existing)
237 243 self.exit(1)
238 244 self.log.info("Connecting to existing kernel: %s" % cf)
239 245 self.connection_file = cf
240 246 # should load_connection_file only be used for existing?
241 247 # as it is now, this allows reusing ports if an existing
242 248 # file is requested
243 249 try:
244 250 self.load_connection_file()
245 251 except Exception:
246 252 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
247 253 self.exit(1)
248 254
249 255 def load_connection_file(self):
250 256 """load ip/port/hmac config from JSON connection file"""
251 257 # this is identical to KernelApp.load_connection_file
252 258 # perhaps it can be centralized somewhere?
253 259 try:
254 260 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
255 261 except IOError:
256 262 self.log.debug("Connection File not found: %s", self.connection_file)
257 263 return
258 264 self.log.debug(u"Loading connection file %s", fname)
259 265 with open(fname) as f:
260 266 s = f.read()
261 267 cfg = json.loads(s)
262 268 if self.ip == LOCALHOST and 'ip' in cfg:
263 269 # not overridden by config or cl_args
264 270 self.ip = cfg['ip']
265 271 for channel in ('hb', 'shell', 'iopub', 'stdin'):
266 272 name = channel + '_port'
267 273 if getattr(self, name) == 0 and name in cfg:
268 274 # not overridden by config or cl_args
269 275 setattr(self, name, cfg[name])
270 276 if 'key' in cfg:
271 277 self.config.Session.key = str_to_bytes(cfg['key'])
272 278
273 279 def init_ssh(self):
274 280 """set up ssh tunnels, if needed."""
275 281 if not self.sshserver and not self.sshkey:
276 282 return
277 283
278 284 if self.sshkey and not self.sshserver:
279 285 # specifying just the key implies that we are connecting directly
280 286 self.sshserver = self.ip
281 287 self.ip = LOCALHOST
282 288
283 289 # build connection dict for tunnels:
284 290 info = dict(ip=self.ip,
285 291 shell_port=self.shell_port,
286 292 iopub_port=self.iopub_port,
287 293 stdin_port=self.stdin_port,
288 294 hb_port=self.hb_port
289 295 )
290 296
291 297 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
292 298
293 299 # tunnels return a new set of ports, which will be on localhost:
294 300 self.ip = LOCALHOST
295 301 try:
296 302 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
297 303 except:
298 304 # even catch KeyboardInterrupt
299 305 self.log.error("Could not setup tunnels", exc_info=True)
300 306 self.exit(1)
301 307
302 308 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
303 309
304 310 cf = self.connection_file
305 311 base,ext = os.path.splitext(cf)
306 312 base = os.path.basename(base)
307 313 self.connection_file = os.path.basename(base)+'-ssh'+ext
308 314 self.log.critical("To connect another client via this tunnel, use:")
309 315 self.log.critical("--existing %s" % self.connection_file)
310 316
311 317 def _new_connection_file(self):
312 318 return os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % uuid.uuid4())
313 319
314 320 def init_kernel_manager(self):
315 321 # Don't let Qt or ZMQ swallow KeyboardInterupts.
316 322 signal.signal(signal.SIGINT, signal.SIG_DFL)
317 323 sec = self.profile_dir.security_dir
318 324 try:
319 325 cf = filefind(self.connection_file, ['.', sec])
320 326 except IOError:
321 327 # file might not exist
322 328 if self.connection_file == os.path.basename(self.connection_file):
323 329 # just shortname, put it in security dir
324 330 cf = os.path.join(sec, self.connection_file)
325 331 else:
326 332 cf = self.connection_file
327 333
328 334 # Create a KernelManager and start a kernel.
329 335 self.kernel_manager = self.kernel_manager_class(
330 336 ip=self.ip,
331 337 shell_port=self.shell_port,
332 338 iopub_port=self.iopub_port,
333 339 stdin_port=self.stdin_port,
334 340 hb_port=self.hb_port,
335 341 connection_file=cf,
336 342 config=self.config,
337 343 )
338 344 # start the kernel
339 345 if not self.existing:
340 346 kwargs = dict(ipython=not self.pure)
341 347 kwargs['extra_arguments'] = self.kernel_argv
342 348 self.kernel_manager.start_kernel(**kwargs)
343 349 elif self.sshserver:
344 350 # ssh, write new connection file
345 351 self.kernel_manager.write_connection_file()
346 352 self.kernel_manager.start_channels()
347 353
348 354
349 355 def initialize(self, argv=None):
350 super(IPythonMixinConsoleApp, self).initialize(argv)
356 """
357 Classes which mix this class in should call:
358 IPythonMixinConsoleApp.initialize(self,argv)
359 """
351 360 self.init_connection_file()
352 361 default_secure(self.config)
353 362 self.init_ssh()
354 363 self.init_kernel_manager()
355 self.init_colors()
356
357 #-----------------------------------------------------------------------------
358 # Main entry point
359 #-----------------------------------------------------------------------------
360
361 def main():
362 raise NotImplementedError
363
364 364
365 if __name__ == '__main__':
366 main()
@@ -1,371 +1,327 b''
1 1 """ A minimal application using the Qt console-style IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
5 5
6 6 Authors:
7 7
8 8 * Evan Patterson
9 9 * Min RK
10 10 * Erik Tollerud
11 11 * Fernando Perez
12 12 * Bussonnier Matthias
13 13 * Thomas Kluyver
14 14 * Paul Ivanov
15 15
16 16 """
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # stdlib imports
23 23 import json
24 24 import os
25 25 import signal
26 26 import sys
27 27 import uuid
28 28
29 29 # System library imports
30 30 from IPython.external.qt import QtGui
31 31
32 32 # Local imports
33 33 from IPython.config.application import boolean_flag, catch_config_error
34 34 from IPython.core.application import BaseIPythonApplication
35 35 from IPython.core.profiledir import ProfileDir
36 36 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
37 37 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
38 38 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
39 39 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
40 40 from IPython.frontend.qt.console import styles
41 41 from IPython.frontend.qt.console.mainwindow import MainWindow
42 42 from IPython.frontend.qt.kernelmanager import QtKernelManager
43 43 from IPython.utils.path import filefind
44 44 from IPython.utils.py3compat import str_to_bytes
45 45 from IPython.utils.traitlets import (
46 46 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
47 47 )
48 48 from IPython.zmq.ipkernel import IPKernelApp
49 49 from IPython.zmq.session import Session, default_secure
50 50 from IPython.zmq.zmqshell import ZMQInteractiveShell
51
51 52 from IPython.frontend.kernelmixinapp import (
52 53 IPythonMixinConsoleApp, app_aliases, app_flags
53 54 )
54 55
55 56 #-----------------------------------------------------------------------------
56 57 # Network Constants
57 58 #-----------------------------------------------------------------------------
58 59
59 60 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
60 61
61 62 #-----------------------------------------------------------------------------
62 63 # Globals
63 64 #-----------------------------------------------------------------------------
64 65
65 66 _examples = """
66 67 ipython qtconsole # start the qtconsole
67 68 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
68 69 """
69 70
70 71 #-----------------------------------------------------------------------------
71 72 # Aliases and Flags
72 73 #-----------------------------------------------------------------------------
73 74
75 # XXX: the app_flags should really be flags from the mixin
74 76 flags = dict(app_flags)
75 77 qt_flags = {
76 #'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
77 # "Connect to an existing kernel. If no argument specified, guess most recent"),
78 78 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
79 79 "Use a pure Python kernel instead of an IPython kernel."),
80 80 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
81 81 "Disable rich text support."),
82 82 }
83 83 qt_flags.update(boolean_flag(
84 84 'gui-completion', 'ConsoleWidget.gui_completion',
85 85 "use a GUI widget for tab completion",
86 86 "use plaintext output for completion"
87 87 ))
88 88 flags.update(qt_flags)
89 89
90 90 aliases = dict(app_aliases)
91 91
92 92 qt_aliases = dict(
93 existing = 'IPythonQtConsoleApp.existing',
94 f = 'IPythonQtConsoleApp.connection_file',
95 93
96 94 style = 'IPythonWidget.syntax_style',
97 95 stylesheet = 'IPythonQtConsoleApp.stylesheet',
98 96 colors = 'ZMQInteractiveShell.colors',
99 97
100 98 editor = 'IPythonWidget.editor',
101 99 paging = 'ConsoleWidget.paging',
102 100 )
103 101 aliases.update(qt_aliases)
104 102
105 103 #-----------------------------------------------------------------------------
106 104 # Classes
107 105 #-----------------------------------------------------------------------------
108 106
109 107 #-----------------------------------------------------------------------------
110 108 # IPythonQtConsole
111 109 #-----------------------------------------------------------------------------
112 110
113 111
114 112 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonMixinConsoleApp):
115 113 name = 'ipython-qtconsole'
116 default_config_file_name='ipython_config.py'
117 114
118 115 description = """
119 116 The IPython QtConsole.
120 117
121 118 This launches a Console-style application using Qt. It is not a full
122 119 console, in that launched terminal subprocesses will not be able to accept
123 120 input.
124 121
125 122 The QtConsole supports various extra features beyond the Terminal IPython
126 123 shell, such as inline plotting with matplotlib, via:
127 124
128 125 ipython qtconsole --pylab=inline
129 126
130 127 as well as saving your session as HTML, and printing the output.
131 128
132 129 """
133 130 examples = _examples
134 131
135 132 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
136 133 flags = Dict(flags)
137 134 aliases = Dict(aliases)
138 135 kernel_manager_class = QtKernelManager
139 136
140 137 stylesheet = Unicode('', config=True,
141 138 help="path to a custom CSS stylesheet")
142 139
143 pure = CBool(False, config=True,
144 help="Use a pure Python kernel instead of an IPython kernel.")
145 140 plain = CBool(False, config=True,
146 141 help="Use a plaintext widget instead of rich text (plain can't print/save).")
147 142
148 143 def _pure_changed(self, name, old, new):
149 144 kind = 'plain' if self.plain else 'rich'
150 145 self.config.ConsoleWidget.kind = kind
151 146 if self.pure:
152 147 self.widget_factory = FrontendWidget
153 148 elif self.plain:
154 149 self.widget_factory = IPythonWidget
155 150 else:
156 151 self.widget_factory = RichIPythonWidget
157 152
158 153 _plain_changed = _pure_changed
159 154
160 confirm_exit = CBool(True, config=True,
161 help="""
162 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
163 to force a direct exit without any confirmation.""",
164 )
165
166 155 # the factory for creating a widget
167 156 widget_factory = Any(RichIPythonWidget)
168 157
169 158 def parse_command_line(self, argv=None):
170 159 super(IPythonQtConsoleApp, self).parse_command_line(argv)
171 if argv is None:
172 argv = sys.argv[1:]
173 self.kernel_argv = list(argv) # copy
174 # kernel should inherit default config file from frontend
175 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
176 # Scrub frontend-specific flags
177 swallow_next = False
178 was_flag = False
179 # copy again, in case some aliases have the same name as a flag
180 # argv = list(self.kernel_argv)
181 for a in argv:
182 if swallow_next:
183 swallow_next = False
184 # last arg was an alias, remove the next one
185 # *unless* the last alias has a no-arg flag version, in which
186 # case, don't swallow the next arg if it's also a flag:
187 if not (was_flag and a.startswith('-')):
188 self.kernel_argv.remove(a)
189 continue
190 if a.startswith('-'):
191 split = a.lstrip('-').split('=')
192 alias = split[0]
193 if alias in qt_aliases:
194 self.kernel_argv.remove(a)
195 if len(split) == 1:
196 # alias passed with arg via space
197 swallow_next = True
198 # could have been a flag that matches an alias, e.g. `existing`
199 # in which case, we might not swallow the next arg
200 was_flag = alias in qt_flags
201 elif alias in qt_flags:
202 # strip flag, but don't swallow next, as flags don't take args
203 self.kernel_argv.remove(a)
160 IPythonMixinConsoleApp.parse_command_line(self,argv)
161 self.swallow_args(qt_aliases,qt_flags,argv=argv)
162
204 163
205 164
206 165
207 166 def new_frontend_master(self):
208 167 """ Create and return new frontend attached to new kernel, launched on localhost.
209 168 """
210 169 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
211 170 kernel_manager = QtKernelManager(
212 171 ip=ip,
213 172 connection_file=self._new_connection_file(),
214 173 config=self.config,
215 174 )
216 175 # start the kernel
217 176 kwargs = dict(ipython=not self.pure)
218 177 kwargs['extra_arguments'] = self.kernel_argv
219 178 kernel_manager.start_kernel(**kwargs)
220 179 kernel_manager.start_channels()
221 180 widget = self.widget_factory(config=self.config,
222 181 local_kernel=True)
223 182 widget.kernel_manager = kernel_manager
224 183 widget._existing = False
225 184 widget._may_close = True
226 185 widget._confirm_exit = self.confirm_exit
227 186 return widget
228 187
229 188 def new_frontend_slave(self, current_widget):
230 189 """Create and return a new frontend attached to an existing kernel.
231 190
232 191 Parameters
233 192 ----------
234 193 current_widget : IPythonWidget
235 194 The IPythonWidget whose kernel this frontend is to share
236 195 """
237 196 kernel_manager = QtKernelManager(
238 197 connection_file=current_widget.kernel_manager.connection_file,
239 198 config = self.config,
240 199 )
241 200 kernel_manager.load_connection_file()
242 201 kernel_manager.start_channels()
243 202 widget = self.widget_factory(config=self.config,
244 203 local_kernel=False)
245 204 widget._existing = True
246 205 widget._may_close = False
247 206 widget._confirm_exit = False
248 207 widget.kernel_manager = kernel_manager
249 208 return widget
250 209
251 210 def init_qt_elements(self):
252 211 # Create the widget.
253 212 self.app = QtGui.QApplication([])
254 213
255 214 base_path = os.path.abspath(os.path.dirname(__file__))
256 215 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
257 216 self.app.icon = QtGui.QIcon(icon_path)
258 217 QtGui.QApplication.setWindowIcon(self.app.icon)
259 218
260 219 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
261 220 self.widget = self.widget_factory(config=self.config,
262 221 local_kernel=local_kernel)
263 222 self.widget._existing = self.existing
264 223 self.widget._may_close = not self.existing
265 224 self.widget._confirm_exit = self.confirm_exit
266 225
267 226 self.widget.kernel_manager = self.kernel_manager
268 227 self.window = MainWindow(self.app,
269 228 confirm_exit=self.confirm_exit,
270 229 new_frontend_factory=self.new_frontend_master,
271 230 slave_frontend_factory=self.new_frontend_slave,
272 231 )
273 232 self.window.log = self.log
274 233 self.window.add_tab_with_frontend(self.widget)
275 234 self.window.init_menu_bar()
276 235
277 236 self.window.setWindowTitle('Python' if self.pure else 'IPython')
278 237
279 238 def init_colors(self):
280 239 """Configure the coloring of the widget"""
281 240 # Note: This will be dramatically simplified when colors
282 241 # are removed from the backend.
283 242
284 243 if self.pure:
285 244 # only IPythonWidget supports styling
286 245 return
287 246
288 247 # parse the colors arg down to current known labels
289 248 try:
290 249 colors = self.config.ZMQInteractiveShell.colors
291 250 except AttributeError:
292 251 colors = None
293 252 try:
294 253 style = self.config.IPythonWidget.syntax_style
295 254 except AttributeError:
296 255 style = None
297 256
298 257 # find the value for colors:
299 258 if colors:
300 259 colors=colors.lower()
301 260 if colors in ('lightbg', 'light'):
302 261 colors='lightbg'
303 262 elif colors in ('dark', 'linux'):
304 263 colors='linux'
305 264 else:
306 265 colors='nocolor'
307 266 elif style:
308 267 if style=='bw':
309 268 colors='nocolor'
310 269 elif styles.dark_style(style):
311 270 colors='linux'
312 271 else:
313 272 colors='lightbg'
314 273 else:
315 274 colors=None
316 275
317 276 # Configure the style.
318 277 widget = self.widget
319 278 if style:
320 279 widget.style_sheet = styles.sheet_from_template(style, colors)
321 280 widget.syntax_style = style
322 281 widget._syntax_style_changed()
323 282 widget._style_sheet_changed()
324 283 elif colors:
325 284 # use a default style
326 285 widget.set_default_style(colors=colors)
327 286 else:
328 287 # this is redundant for now, but allows the widget's
329 288 # defaults to change
330 289 widget.set_default_style()
331 290
332 291 if self.stylesheet:
333 292 # we got an expicit stylesheet
334 293 if os.path.isfile(self.stylesheet):
335 294 with open(self.stylesheet) as f:
336 295 sheet = f.read()
337 296 widget.style_sheet = sheet
338 297 widget._style_sheet_changed()
339 298 else:
340 299 raise IOError("Stylesheet %r not found."%self.stylesheet)
341 300
342 301 @catch_config_error
343 302 def initialize(self, argv=None):
344 303 super(IPythonQtConsoleApp, self).initialize(argv)
345 self.init_connection_file()
346 default_secure(self.config)
347 self.init_ssh()
348 self.init_kernel_manager()
304 IPythonMixinConsoleApp.initialize(self,argv)
349 305 self.init_qt_elements()
350 306 self.init_colors()
351 307
352 308 def start(self):
353 309
354 310 # draw the window
355 311 self.window.show()
356 312
357 313 # Start the application main loop.
358 314 self.app.exec_()
359 315
360 316 #-----------------------------------------------------------------------------
361 317 # Main entry point
362 318 #-----------------------------------------------------------------------------
363 319
364 320 def main():
365 321 app = IPythonQtConsoleApp()
366 322 app.initialize()
367 323 app.start()
368 324
369 325
370 326 if __name__ == '__main__':
371 327 main()
@@ -1,160 +1,100 b''
1 from __future__ import print_function
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
4 input, there is no real readline support, among other limitations.
5
6 Authors:
7
8 * Min RK
9 * Paul Ivanov
10
11 """
12
13 #-----------------------------------------------------------------------------
14 # Imports
15 #-----------------------------------------------------------------------------
3 16 import signal
4 17 import sys
5 18 import time
6 19
7 20 from IPython.frontend.terminal.ipapp import TerminalIPythonApp
8 21
9 22 from IPython.utils.traitlets import (
10 23 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
11 24 )
12 from IPython.zmq.ipkernel import (
13 flags as ipkernel_flags,
14 aliases as ipkernel_aliases,
15 IPKernelApp
16 )
17 from IPython.zmq.session import Session
25 from IPython.zmq.ipkernel import IPKernelApp
26 from IPython.zmq.session import Session, default_secure
18 27 from IPython.zmq.zmqshell import ZMQInteractiveShell
19 from IPython.zmq.blockingkernelmanager import BlockingKernelManager
20 from IPython.zmq.ipkernel import (
21 flags as ipkernel_flags,
22 aliases as ipkernel_aliases,
23 IPKernelApp
24 )
28 from IPython.frontend.kernelmixinapp import (
29 IPythonMixinConsoleApp, app_aliases, app_flags
30 )
31
25 32 from IPython.frontend.zmqterminal.interactiveshell import ZMQTerminalInteractiveShell
26 33
27 34 #-----------------------------------------------------------------------------
28 # Network Constants
35 # Globals
29 36 #-----------------------------------------------------------------------------
30 37
31 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
38 _examples = """
39 ipython console # start the ZMQ-based console
40 ipython console --pylab # start with pylab plotting mode
41 """
32 42
33 43 #-----------------------------------------------------------------------------
34 44 # Flags and Aliases
35 45 #-----------------------------------------------------------------------------
36 46
37
38 flags = dict(ipkernel_flags)
39 frontend_flags = {
40 'existing' : ({'ZMQTerminalIPythonApp' : {'existing' : True}},
41 "Connect to an existing kernel."),
42 }
47 # XXX: the app_flags should really be flags from the mixin
48 flags = dict(app_flags)
49 frontend_flags = { }
43 50 flags.update(frontend_flags)
44 # the flags that are specific to the frontend
45 # these must be scrubbed before being passed to the kernel,
46 # or it will raise an error on unrecognized flags
51
47 52 frontend_flags = frontend_flags.keys()
48 53
49 aliases = dict(ipkernel_aliases)
54 aliases = dict(app_aliases)
55
56 frontend_aliases = dict()
50 57
51 frontend_aliases = dict(
52 hb = 'ZMQTerminalIPythonApp.hb_port',
53 shell = 'ZMQTerminalIPythonApp.shell_port',
54 iopub = 'ZMQTerminalIPythonApp.iopub_port',
55 stdin = 'ZMQTerminalIPythonApp.stdin_port',
56 ip = 'ZMQTerminalIPythonApp.ip',
57 )
58 58 aliases.update(frontend_aliases)
59 # also scrub aliases from the frontend
60 frontend_flags.extend(frontend_aliases.keys())
61 59
62 60 #-----------------------------------------------------------------------------
63 61 # Classes
64 62 #-----------------------------------------------------------------------------
65 63
66 64
67 class ZMQTerminalIPythonApp(TerminalIPythonApp):
65 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonMixinConsoleApp):
68 66 """Start a terminal frontend to the IPython zmq kernel."""
69 67
70 kernel_argv = List(Unicode)
68 classes = List([IPKernelApp, ZMQTerminalInteractiveShell])
71 69 flags = Dict(flags)
72 70 aliases = Dict(aliases)
73 classes = List([IPKernelApp, ZMQTerminalInteractiveShell])
74
75 # connection info:
76 ip = Unicode(LOCALHOST, config=True,
77 help="""Set the kernel\'s IP address [default localhost].
78 If the IP address is something other than localhost, then
79 Consoles on other machines will be able to connect
80 to the Kernel, so be careful!"""
81 )
82 pure = False
83 hb_port = Int(0, config=True,
84 help="set the heartbeat port [default: random]")
85 shell_port = Int(0, config=True,
86 help="set the shell (XREP) port [default: random]")
87 iopub_port = Int(0, config=True,
88 help="set the iopub (PUB) port [default: random]")
89 stdin_port = Int(0, config=True,
90 help="set the stdin (XREQ) port [default: random]")
91
92 existing = CBool(False, config=True,
93 help="Whether to connect to an already running Kernel.")
94
95 # from qtconsoleapp:
96 71 def parse_command_line(self, argv=None):
97 72 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
98 if argv is None:
99 argv = sys.argv[1:]
100
101 self.kernel_argv = list(argv) # copy
102 # kernel should inherit default config file from frontend
103 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
104 # scrub frontend-specific flags
105 for a in argv:
106
107 if a.startswith('-'):
108 key = a.lstrip('-').split('=')[0]
109 if key in frontend_flags:
110 self.kernel_argv.remove(a)
111
112 def init_kernel_manager(self):
113 """init kernel manager (from qtconsole)"""
114 # Don't let Qt or ZMQ swallow KeyboardInterupts.
115 # signal.signal(signal.SIGINT, signal.SIG_DFL)
116
117 # Create a KernelManager and start a kernel.
118 self.kernel_manager = BlockingKernelManager(
119 shell_address=(self.ip, self.shell_port),
120 sub_address=(self.ip, self.iopub_port),
121 stdin_address=(self.ip, self.stdin_port),
122 hb_address=(self.ip, self.hb_port),
123 config=self.config
124 )
125 # start the kernel
126 if not self.existing:
127 kwargs = dict(ip=self.ip, ipython=not self.pure)
128 kwargs['extra_arguments'] = self.kernel_argv
129 self.kernel_manager.start_kernel(**kwargs)
130 # wait for kernel to start
131 time.sleep(0.5)
132 self.kernel_manager.start_channels()
133 # relay sigint to kernel
134 signal.signal(signal.SIGINT, self.handle_sigint)
73 IPythonMixinConsoleApp.parse_command_line(self,argv)
74 self.swallow_args(frontend_aliases,frontend_flags,argv=argv)
135 75
136 76 def init_shell(self):
137 self.init_kernel_manager()
77 IPythonMixinConsoleApp.initialize(self)
78 #self.init_kernel_manager()
138 79 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
139 80 display_banner=False, profile_dir=self.profile_dir,
140 81 ipython_dir=self.ipython_dir, kernel_manager=self.kernel_manager)
141
82
142 83 def handle_sigint(self, *args):
143 84 self.shell.write('KeyboardInterrupt\n')
144 85 self.kernel_manager.interrupt_kernel()
145
86
146 87 def init_code(self):
147 88 # no-op in the frontend, code gets run in the backend
148 89 pass
149
150 90
151 91 def launch_new_instance():
152 92 """Create and run a full blown IPython instance"""
153 93 app = ZMQTerminalIPythonApp.instance()
154 94 app.initialize()
155 95 app.start()
156 96
157 97
158 98 if __name__ == '__main__':
159 99 launch_new_instance()
160 100
General Comments 0
You need to be logged in to leave comments. Login now