##// END OF EJS Templates
move IPKernelApp from zmq.ipkernel to zmq.kernelapp...
MinRK -
Show More
@@ -0,0 +1,57 b''
1 """Simple function for embedding an IPython kernel
2 """
3 #-----------------------------------------------------------------------------
4 # Imports
5 #-----------------------------------------------------------------------------
6
7 import sys
8
9 from IPython.utils.frame import extract_module_locals
10
11 from kernelapp import IPKernelApp
12
13 #-----------------------------------------------------------------------------
14 # Code
15 #-----------------------------------------------------------------------------
16
17 def embed_kernel(module=None, local_ns=None, **kwargs):
18 """Embed and start an IPython kernel in a given scope.
19
20 Parameters
21 ----------
22 module : ModuleType, optional
23 The module to load into IPython globals (default: caller)
24 local_ns : dict, optional
25 The namespace to load into IPython user namespace (default: caller)
26
27 kwargs : various, optional
28 Further keyword args are relayed to the KernelApp constructor,
29 allowing configuration of the Kernel. Will only have an effect
30 on the first embed_kernel call for a given process.
31
32 """
33 # get the app if it exists, or set it up if it doesn't
34 if IPKernelApp.initialized():
35 app = IPKernelApp.instance()
36 else:
37 app = IPKernelApp.instance(**kwargs)
38 app.initialize([])
39 # Undo unnecessary sys module mangling from init_sys_modules.
40 # This would not be necessary if we could prevent it
41 # in the first place by using a different InteractiveShell
42 # subclass, as in the regular embed case.
43 main = app.kernel.shell._orig_sys_modules_main_mod
44 if main is not None:
45 sys.modules[app.kernel.shell._orig_sys_modules_main_name] = main
46
47 # load the calling scope if not given
48 (caller_module, caller_locals) = extract_module_locals(1)
49 if module is None:
50 module = caller_module
51 if local_ns is None:
52 local_ns = caller_locals
53
54 app.kernel.user_module = module
55 app.kernel.user_ns = local_ns
56 app.shell.set_completer_frame()
57 app.start()
@@ -1,85 +1,85 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 IPython: tools for interactive and parallel computing in Python.
3 IPython: tools for interactive and parallel computing in Python.
4
4
5 http://ipython.org
5 http://ipython.org
6 """
6 """
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (c) 2008-2011, IPython Development Team.
8 # Copyright (c) 2008-2011, IPython Development Team.
9 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
9 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
10 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
10 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
11 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
11 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
12 #
12 #
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14 #
14 #
15 # The full license is in the file COPYING.txt, distributed with this software.
15 # The full license is in the file COPYING.txt, distributed with this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 from __future__ import absolute_import
21 from __future__ import absolute_import
22
22
23 import os
23 import os
24 import sys
24 import sys
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Setup everything
27 # Setup everything
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 # Don't forget to also update setup.py when this changes!
30 # Don't forget to also update setup.py when this changes!
31 if sys.version[0:3] < '2.6':
31 if sys.version[0:3] < '2.6':
32 raise ImportError('Python Version 2.6 or above is required for IPython.')
32 raise ImportError('Python Version 2.6 or above is required for IPython.')
33
33
34 # Make it easy to import extensions - they are always directly on pythonpath.
34 # Make it easy to import extensions - they are always directly on pythonpath.
35 # Therefore, non-IPython modules can be added to extensions directory.
35 # Therefore, non-IPython modules can be added to extensions directory.
36 # This should probably be in ipapp.py.
36 # This should probably be in ipapp.py.
37 sys.path.append(os.path.join(os.path.dirname(__file__), "extensions"))
37 sys.path.append(os.path.join(os.path.dirname(__file__), "extensions"))
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # Setup the top level names
40 # Setup the top level names
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43 from .config.loader import Config
43 from .config.loader import Config
44 from .core import release
44 from .core import release
45 from .core.application import Application
45 from .core.application import Application
46 from .frontend.terminal.embed import embed
46 from .frontend.terminal.embed import embed
47
47
48 from .core.error import TryNext
48 from .core.error import TryNext
49 from .core.interactiveshell import InteractiveShell
49 from .core.interactiveshell import InteractiveShell
50 from .testing import test
50 from .testing import test
51 from .utils.sysinfo import sys_info
51 from .utils.sysinfo import sys_info
52 from .utils.frame import extract_module_locals
52 from .utils.frame import extract_module_locals
53
53
54 # Release data
54 # Release data
55 __author__ = '%s <%s>' % (release.author, release.author_email)
55 __author__ = '%s <%s>' % (release.author, release.author_email)
56 __license__ = release.license
56 __license__ = release.license
57 __version__ = release.version
57 __version__ = release.version
58 version_info = release.version_info
58 version_info = release.version_info
59
59
60 def embed_kernel(module=None, local_ns=None, **kwargs):
60 def embed_kernel(module=None, local_ns=None, **kwargs):
61 """Embed and start an IPython kernel in a given scope.
61 """Embed and start an IPython kernel in a given scope.
62
62
63 Parameters
63 Parameters
64 ----------
64 ----------
65 module : ModuleType, optional
65 module : ModuleType, optional
66 The module to load into IPython globals (default: caller)
66 The module to load into IPython globals (default: caller)
67 local_ns : dict, optional
67 local_ns : dict, optional
68 The namespace to load into IPython user namespace (default: caller)
68 The namespace to load into IPython user namespace (default: caller)
69
69
70 kwargs : various, optional
70 kwargs : various, optional
71 Further keyword args are relayed to the KernelApp constructor,
71 Further keyword args are relayed to the KernelApp constructor,
72 allowing configuration of the Kernel. Will only have an effect
72 allowing configuration of the Kernel. Will only have an effect
73 on the first embed_kernel call for a given process.
73 on the first embed_kernel call for a given process.
74
74
75 """
75 """
76
76
77 (caller_module, caller_locals) = extract_module_locals(1)
77 (caller_module, caller_locals) = extract_module_locals(1)
78 if module is None:
78 if module is None:
79 module = caller_module
79 module = caller_module
80 if local_ns is None:
80 if local_ns is None:
81 local_ns = caller_locals
81 local_ns = caller_locals
82
82
83 # Only import .zmq when we really need it
83 # Only import .zmq when we really need it
84 from .zmq.ipkernel import embed_kernel as real_embed_kernel
84 from .zmq.embed import embed_kernel as real_embed_kernel
85 real_embed_kernel(module=module, local_ns=local_ns, **kwargs)
85 real_embed_kernel(module=module, local_ns=local_ns, **kwargs)
@@ -1,362 +1,362 b''
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 shutil
27 import shutil
28 import signal
28 import signal
29 import sys
29 import sys
30 import uuid
30 import uuid
31
31
32
32
33 # Local imports
33 # Local imports
34 from IPython.config.application import boolean_flag
34 from IPython.config.application import boolean_flag
35 from IPython.config.configurable import Configurable
35 from IPython.config.configurable import Configurable
36 from IPython.core.profiledir import ProfileDir
36 from IPython.core.profiledir import ProfileDir
37 from IPython.zmq.blockingkernelmanager import BlockingKernelManager
37 from IPython.zmq.blockingkernelmanager import BlockingKernelManager
38 from IPython.zmq.kernelmanager import KernelManager
38 from IPython.zmq.kernelmanager import KernelManager
39 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
39 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
40 from IPython.utils.path import filefind
40 from IPython.utils.path import filefind
41 from IPython.utils.py3compat import str_to_bytes
41 from IPython.utils.py3compat import str_to_bytes
42 from IPython.utils.traitlets import (
42 from IPython.utils.traitlets import (
43 Dict, List, Unicode, CUnicode, Int, CBool, Any, CaselessStrEnum
43 Dict, List, Unicode, CUnicode, Int, CBool, Any, CaselessStrEnum
44 )
44 )
45 from IPython.zmq.ipkernel import (
45 from IPython.zmq.kernelapp import (
46 flags as ipkernel_flags,
46 kernel_flags,
47 aliases as ipkernel_aliases,
47 kernel_aliases,
48 IPKernelApp
48 IPKernelApp
49 )
49 )
50 from IPython.zmq.session import Session, default_secure
50 from IPython.zmq.session import Session, default_secure
51 from IPython.zmq.zmqshell import ZMQInteractiveShell
51 from IPython.zmq.zmqshell import ZMQInteractiveShell
52
52
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54 # Network Constants
54 # Network Constants
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56
56
57 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
57 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
58
58
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60 # Globals
60 # Globals
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62
62
63
63
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65 # Aliases and Flags
65 # Aliases and Flags
66 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
67
67
68 flags = dict(ipkernel_flags)
68 flags = dict(kernel_flags)
69
69
70 # the flags that are specific to the frontend
70 # the flags that are specific to the frontend
71 # these must be scrubbed before being passed to the kernel,
71 # these must be scrubbed before being passed to the kernel,
72 # or it will raise an error on unrecognized flags
72 # or it will raise an error on unrecognized flags
73 app_flags = {
73 app_flags = {
74 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
74 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
75 "Connect to an existing kernel. If no argument specified, guess most recent"),
75 "Connect to an existing kernel. If no argument specified, guess most recent"),
76 }
76 }
77 app_flags.update(boolean_flag(
77 app_flags.update(boolean_flag(
78 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
78 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
79 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
79 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
80 to force a direct exit without any confirmation.
80 to force a direct exit without any confirmation.
81 """,
81 """,
82 """Don't prompt the user when exiting. This will terminate the kernel
82 """Don't prompt the user when exiting. This will terminate the kernel
83 if it is owned by the frontend, and leave it alive if it is external.
83 if it is owned by the frontend, and leave it alive if it is external.
84 """
84 """
85 ))
85 ))
86 flags.update(app_flags)
86 flags.update(app_flags)
87
87
88 aliases = dict(ipkernel_aliases)
88 aliases = dict(kernel_aliases)
89
89
90 # also scrub aliases from the frontend
90 # also scrub aliases from the frontend
91 app_aliases = dict(
91 app_aliases = dict(
92 ip = 'KernelManager.ip',
92 ip = 'KernelManager.ip',
93 transport = 'KernelManager.transport',
93 transport = 'KernelManager.transport',
94 hb = 'IPythonConsoleApp.hb_port',
94 hb = 'IPythonConsoleApp.hb_port',
95 shell = 'IPythonConsoleApp.shell_port',
95 shell = 'IPythonConsoleApp.shell_port',
96 iopub = 'IPythonConsoleApp.iopub_port',
96 iopub = 'IPythonConsoleApp.iopub_port',
97 stdin = 'IPythonConsoleApp.stdin_port',
97 stdin = 'IPythonConsoleApp.stdin_port',
98 existing = 'IPythonConsoleApp.existing',
98 existing = 'IPythonConsoleApp.existing',
99 f = 'IPythonConsoleApp.connection_file',
99 f = 'IPythonConsoleApp.connection_file',
100
100
101
101
102 ssh = 'IPythonConsoleApp.sshserver',
102 ssh = 'IPythonConsoleApp.sshserver',
103 )
103 )
104 aliases.update(app_aliases)
104 aliases.update(app_aliases)
105
105
106 #-----------------------------------------------------------------------------
106 #-----------------------------------------------------------------------------
107 # Classes
107 # Classes
108 #-----------------------------------------------------------------------------
108 #-----------------------------------------------------------------------------
109
109
110 #-----------------------------------------------------------------------------
110 #-----------------------------------------------------------------------------
111 # IPythonConsole
111 # IPythonConsole
112 #-----------------------------------------------------------------------------
112 #-----------------------------------------------------------------------------
113
113
114 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session]
114 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session]
115
115
116 try:
116 try:
117 from IPython.zmq.pylab.backend_inline import InlineBackend
117 from IPython.zmq.pylab.backend_inline import InlineBackend
118 except ImportError:
118 except ImportError:
119 pass
119 pass
120 else:
120 else:
121 classes.append(InlineBackend)
121 classes.append(InlineBackend)
122
122
123 class IPythonConsoleApp(Configurable):
123 class IPythonConsoleApp(Configurable):
124 name = 'ipython-console-mixin'
124 name = 'ipython-console-mixin'
125 default_config_file_name='ipython_config.py'
125 default_config_file_name='ipython_config.py'
126
126
127 description = """
127 description = """
128 The IPython Mixin Console.
128 The IPython Mixin Console.
129
129
130 This class contains the common portions of console client (QtConsole,
130 This class contains the common portions of console client (QtConsole,
131 ZMQ-based terminal console, etc). It is not a full console, in that
131 ZMQ-based terminal console, etc). It is not a full console, in that
132 launched terminal subprocesses will not be able to accept input.
132 launched terminal subprocesses will not be able to accept input.
133
133
134 The Console using this mixing supports various extra features beyond
134 The Console using this mixing supports various extra features beyond
135 the single-process Terminal IPython shell, such as connecting to
135 the single-process Terminal IPython shell, such as connecting to
136 existing kernel, via:
136 existing kernel, via:
137
137
138 ipython <appname> --existing
138 ipython <appname> --existing
139
139
140 as well as tunnel via SSH
140 as well as tunnel via SSH
141
141
142 """
142 """
143
143
144 classes = classes
144 classes = classes
145 flags = Dict(flags)
145 flags = Dict(flags)
146 aliases = Dict(aliases)
146 aliases = Dict(aliases)
147 kernel_manager_class = BlockingKernelManager
147 kernel_manager_class = BlockingKernelManager
148
148
149 kernel_argv = List(Unicode)
149 kernel_argv = List(Unicode)
150 # frontend flags&aliases to be stripped when building kernel_argv
150 # frontend flags&aliases to be stripped when building kernel_argv
151 frontend_flags = Any(app_flags)
151 frontend_flags = Any(app_flags)
152 frontend_aliases = Any(app_aliases)
152 frontend_aliases = Any(app_aliases)
153
153
154 # create requested profiles by default, if they don't exist:
154 # create requested profiles by default, if they don't exist:
155 auto_create = CBool(True)
155 auto_create = CBool(True)
156 # connection info:
156 # connection info:
157
157
158 sshserver = Unicode('', config=True,
158 sshserver = Unicode('', config=True,
159 help="""The SSH server to use to connect to the kernel.""")
159 help="""The SSH server to use to connect to the kernel.""")
160 sshkey = Unicode('', config=True,
160 sshkey = Unicode('', config=True,
161 help="""Path to the ssh key to use for logging in to the ssh server.""")
161 help="""Path to the ssh key to use for logging in to the ssh server.""")
162
162
163 hb_port = Int(0, config=True,
163 hb_port = Int(0, config=True,
164 help="set the heartbeat port [default: random]")
164 help="set the heartbeat port [default: random]")
165 shell_port = Int(0, config=True,
165 shell_port = Int(0, config=True,
166 help="set the shell (ROUTER) port [default: random]")
166 help="set the shell (ROUTER) port [default: random]")
167 iopub_port = Int(0, config=True,
167 iopub_port = Int(0, config=True,
168 help="set the iopub (PUB) port [default: random]")
168 help="set the iopub (PUB) port [default: random]")
169 stdin_port = Int(0, config=True,
169 stdin_port = Int(0, config=True,
170 help="set the stdin (DEALER) port [default: random]")
170 help="set the stdin (DEALER) port [default: random]")
171 connection_file = Unicode('', config=True,
171 connection_file = Unicode('', config=True,
172 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
172 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
173
173
174 This file will contain the IP, ports, and authentication key needed to connect
174 This file will contain the IP, ports, and authentication key needed to connect
175 clients to this kernel. By default, this file will be created in the security-dir
175 clients to this kernel. By default, this file will be created in the security-dir
176 of the current profile, but can be specified by absolute path.
176 of the current profile, but can be specified by absolute path.
177 """)
177 """)
178 def _connection_file_default(self):
178 def _connection_file_default(self):
179 return 'kernel-%i.json' % os.getpid()
179 return 'kernel-%i.json' % os.getpid()
180
180
181 existing = CUnicode('', config=True,
181 existing = CUnicode('', config=True,
182 help="""Connect to an already running kernel""")
182 help="""Connect to an already running kernel""")
183
183
184 confirm_exit = CBool(True, config=True,
184 confirm_exit = CBool(True, config=True,
185 help="""
185 help="""
186 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
186 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
187 to force a direct exit without any confirmation.""",
187 to force a direct exit without any confirmation.""",
188 )
188 )
189
189
190
190
191 def build_kernel_argv(self, argv=None):
191 def build_kernel_argv(self, argv=None):
192 """build argv to be passed to kernel subprocess"""
192 """build argv to be passed to kernel subprocess"""
193 if argv is None:
193 if argv is None:
194 argv = sys.argv[1:]
194 argv = sys.argv[1:]
195 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
195 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
196 # kernel should inherit default config file from frontend
196 # kernel should inherit default config file from frontend
197 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
197 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
198
198
199 def init_connection_file(self):
199 def init_connection_file(self):
200 """find the connection file, and load the info if found.
200 """find the connection file, and load the info if found.
201
201
202 The current working directory and the current profile's security
202 The current working directory and the current profile's security
203 directory will be searched for the file if it is not given by
203 directory will be searched for the file if it is not given by
204 absolute path.
204 absolute path.
205
205
206 When attempting to connect to an existing kernel and the `--existing`
206 When attempting to connect to an existing kernel and the `--existing`
207 argument does not match an existing file, it will be interpreted as a
207 argument does not match an existing file, it will be interpreted as a
208 fileglob, and the matching file in the current profile's security dir
208 fileglob, and the matching file in the current profile's security dir
209 with the latest access time will be used.
209 with the latest access time will be used.
210
210
211 After this method is called, self.connection_file contains the *full path*
211 After this method is called, self.connection_file contains the *full path*
212 to the connection file, never just its name.
212 to the connection file, never just its name.
213 """
213 """
214 if self.existing:
214 if self.existing:
215 try:
215 try:
216 cf = find_connection_file(self.existing)
216 cf = find_connection_file(self.existing)
217 except Exception:
217 except Exception:
218 self.log.critical("Could not find existing kernel connection file %s", self.existing)
218 self.log.critical("Could not find existing kernel connection file %s", self.existing)
219 self.exit(1)
219 self.exit(1)
220 self.log.info("Connecting to existing kernel: %s" % cf)
220 self.log.info("Connecting to existing kernel: %s" % cf)
221 self.connection_file = cf
221 self.connection_file = cf
222 else:
222 else:
223 # not existing, check if we are going to write the file
223 # not existing, check if we are going to write the file
224 # and ensure that self.connection_file is a full path, not just the shortname
224 # and ensure that self.connection_file is a full path, not just the shortname
225 try:
225 try:
226 cf = find_connection_file(self.connection_file)
226 cf = find_connection_file(self.connection_file)
227 except Exception:
227 except Exception:
228 # file might not exist
228 # file might not exist
229 if self.connection_file == os.path.basename(self.connection_file):
229 if self.connection_file == os.path.basename(self.connection_file):
230 # just shortname, put it in security dir
230 # just shortname, put it in security dir
231 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
231 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
232 else:
232 else:
233 cf = self.connection_file
233 cf = self.connection_file
234 self.connection_file = cf
234 self.connection_file = cf
235
235
236 # should load_connection_file only be used for existing?
236 # should load_connection_file only be used for existing?
237 # as it is now, this allows reusing ports if an existing
237 # as it is now, this allows reusing ports if an existing
238 # file is requested
238 # file is requested
239 try:
239 try:
240 self.load_connection_file()
240 self.load_connection_file()
241 except Exception:
241 except Exception:
242 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
242 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
243 self.exit(1)
243 self.exit(1)
244
244
245 def load_connection_file(self):
245 def load_connection_file(self):
246 """load ip/port/hmac config from JSON connection file"""
246 """load ip/port/hmac config from JSON connection file"""
247 # this is identical to KernelApp.load_connection_file
247 # this is identical to KernelApp.load_connection_file
248 # perhaps it can be centralized somewhere?
248 # perhaps it can be centralized somewhere?
249 try:
249 try:
250 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
250 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
251 except IOError:
251 except IOError:
252 self.log.debug("Connection File not found: %s", self.connection_file)
252 self.log.debug("Connection File not found: %s", self.connection_file)
253 return
253 return
254 self.log.debug(u"Loading connection file %s", fname)
254 self.log.debug(u"Loading connection file %s", fname)
255 with open(fname) as f:
255 with open(fname) as f:
256 cfg = json.load(f)
256 cfg = json.load(f)
257
257
258 self.config.KernelManager.transport = cfg.get('transport', 'tcp')
258 self.config.KernelManager.transport = cfg.get('transport', 'tcp')
259 self.config.KernelManager.ip = cfg.get('ip', LOCALHOST)
259 self.config.KernelManager.ip = cfg.get('ip', LOCALHOST)
260
260
261 for channel in ('hb', 'shell', 'iopub', 'stdin'):
261 for channel in ('hb', 'shell', 'iopub', 'stdin'):
262 name = channel + '_port'
262 name = channel + '_port'
263 if getattr(self, name) == 0 and name in cfg:
263 if getattr(self, name) == 0 and name in cfg:
264 # not overridden by config or cl_args
264 # not overridden by config or cl_args
265 setattr(self, name, cfg[name])
265 setattr(self, name, cfg[name])
266 if 'key' in cfg:
266 if 'key' in cfg:
267 self.config.Session.key = str_to_bytes(cfg['key'])
267 self.config.Session.key = str_to_bytes(cfg['key'])
268
268
269 def init_ssh(self):
269 def init_ssh(self):
270 """set up ssh tunnels, if needed."""
270 """set up ssh tunnels, if needed."""
271 if not self.existing or (not self.sshserver and not self.sshkey):
271 if not self.existing or (not self.sshserver and not self.sshkey):
272 return
272 return
273
273
274 self.load_connection_file()
274 self.load_connection_file()
275
275
276 transport = self.config.KernelManager.transport
276 transport = self.config.KernelManager.transport
277 ip = self.config.KernelManager.ip
277 ip = self.config.KernelManager.ip
278
278
279 if transport != 'tcp':
279 if transport != 'tcp':
280 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
280 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
281 sys.exit(-1)
281 sys.exit(-1)
282
282
283 if self.sshkey and not self.sshserver:
283 if self.sshkey and not self.sshserver:
284 # specifying just the key implies that we are connecting directly
284 # specifying just the key implies that we are connecting directly
285 self.sshserver = ip
285 self.sshserver = ip
286 ip = LOCALHOST
286 ip = LOCALHOST
287
287
288 # build connection dict for tunnels:
288 # build connection dict for tunnels:
289 info = dict(ip=ip,
289 info = dict(ip=ip,
290 shell_port=self.shell_port,
290 shell_port=self.shell_port,
291 iopub_port=self.iopub_port,
291 iopub_port=self.iopub_port,
292 stdin_port=self.stdin_port,
292 stdin_port=self.stdin_port,
293 hb_port=self.hb_port
293 hb_port=self.hb_port
294 )
294 )
295
295
296 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
296 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
297
297
298 # tunnels return a new set of ports, which will be on localhost:
298 # tunnels return a new set of ports, which will be on localhost:
299 self.config.KernelManager.ip = LOCALHOST
299 self.config.KernelManager.ip = LOCALHOST
300 try:
300 try:
301 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
301 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
302 except:
302 except:
303 # even catch KeyboardInterrupt
303 # even catch KeyboardInterrupt
304 self.log.error("Could not setup tunnels", exc_info=True)
304 self.log.error("Could not setup tunnels", exc_info=True)
305 self.exit(1)
305 self.exit(1)
306
306
307 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
307 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
308
308
309 cf = self.connection_file
309 cf = self.connection_file
310 base,ext = os.path.splitext(cf)
310 base,ext = os.path.splitext(cf)
311 base = os.path.basename(base)
311 base = os.path.basename(base)
312 self.connection_file = os.path.basename(base)+'-ssh'+ext
312 self.connection_file = os.path.basename(base)+'-ssh'+ext
313 self.log.critical("To connect another client via this tunnel, use:")
313 self.log.critical("To connect another client via this tunnel, use:")
314 self.log.critical("--existing %s" % self.connection_file)
314 self.log.critical("--existing %s" % self.connection_file)
315
315
316 def _new_connection_file(self):
316 def _new_connection_file(self):
317 cf = ''
317 cf = ''
318 while not cf:
318 while not cf:
319 # we don't need a 128b id to distinguish kernels, use more readable
319 # we don't need a 128b id to distinguish kernels, use more readable
320 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
320 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
321 # kernels can subclass.
321 # kernels can subclass.
322 ident = str(uuid.uuid4()).split('-')[-1]
322 ident = str(uuid.uuid4()).split('-')[-1]
323 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
323 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
324 # only keep if it's actually new. Protect against unlikely collision
324 # only keep if it's actually new. Protect against unlikely collision
325 # in 48b random search space
325 # in 48b random search space
326 cf = cf if not os.path.exists(cf) else ''
326 cf = cf if not os.path.exists(cf) else ''
327 return cf
327 return cf
328
328
329 def init_kernel_manager(self):
329 def init_kernel_manager(self):
330 # Don't let Qt or ZMQ swallow KeyboardInterupts.
330 # Don't let Qt or ZMQ swallow KeyboardInterupts.
331 signal.signal(signal.SIGINT, signal.SIG_DFL)
331 signal.signal(signal.SIGINT, signal.SIG_DFL)
332
332
333 # Create a KernelManager and start a kernel.
333 # Create a KernelManager and start a kernel.
334 self.kernel_manager = self.kernel_manager_class(
334 self.kernel_manager = self.kernel_manager_class(
335 shell_port=self.shell_port,
335 shell_port=self.shell_port,
336 iopub_port=self.iopub_port,
336 iopub_port=self.iopub_port,
337 stdin_port=self.stdin_port,
337 stdin_port=self.stdin_port,
338 hb_port=self.hb_port,
338 hb_port=self.hb_port,
339 connection_file=self.connection_file,
339 connection_file=self.connection_file,
340 config=self.config,
340 config=self.config,
341 )
341 )
342 # start the kernel
342 # start the kernel
343 if not self.existing:
343 if not self.existing:
344 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
344 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
345 atexit.register(self.kernel_manager.cleanup_ipc_files)
345 atexit.register(self.kernel_manager.cleanup_ipc_files)
346 elif self.sshserver:
346 elif self.sshserver:
347 # ssh, write new connection file
347 # ssh, write new connection file
348 self.kernel_manager.write_connection_file()
348 self.kernel_manager.write_connection_file()
349 atexit.register(self.kernel_manager.cleanup_connection_file)
349 atexit.register(self.kernel_manager.cleanup_connection_file)
350 self.kernel_manager.start_channels()
350 self.kernel_manager.start_channels()
351
351
352
352
353 def initialize(self, argv=None):
353 def initialize(self, argv=None):
354 """
354 """
355 Classes which mix this class in should call:
355 Classes which mix this class in should call:
356 IPythonConsoleApp.initialize(self,argv)
356 IPythonConsoleApp.initialize(self,argv)
357 """
357 """
358 self.init_connection_file()
358 self.init_connection_file()
359 default_secure(self.config)
359 default_secure(self.config)
360 self.init_ssh()
360 self.init_ssh()
361 self.init_kernel_manager()
361 self.init_kernel_manager()
362
362
@@ -1,643 +1,643 b''
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server.
2 """A tornado based IPython notebook server.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
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 random
23 import random
24 import re
24 import re
25 import select
25 import select
26 import signal
26 import signal
27 import socket
27 import socket
28 import sys
28 import sys
29 import threading
29 import threading
30 import time
30 import time
31 import uuid
31 import uuid
32 import webbrowser
32 import webbrowser
33
33
34 # Third party
34 # Third party
35 import zmq
35 import zmq
36 from jinja2 import Environment, FileSystemLoader
36 from jinja2 import Environment, FileSystemLoader
37
37
38 # Install the pyzmq ioloop. This has to be done before anything else from
38 # Install the pyzmq ioloop. This has to be done before anything else from
39 # tornado is imported.
39 # tornado is imported.
40 from zmq.eventloop import ioloop
40 from zmq.eventloop import ioloop
41 ioloop.install()
41 ioloop.install()
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, NotebookCopyHandler,
51 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
52 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
52 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
53 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
53 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
54 FileFindHandler,
54 FileFindHandler,
55 )
55 )
56 from .nbmanager import NotebookManager
56 from .nbmanager import NotebookManager
57 from .filenbmanager import FileNotebookManager
57 from .filenbmanager import FileNotebookManager
58 from .clustermanager import ClusterManager
58 from .clustermanager import ClusterManager
59
59
60 from IPython.config.application import catch_config_error, boolean_flag
60 from IPython.config.application import catch_config_error, boolean_flag
61 from IPython.core.application import BaseIPythonApplication
61 from IPython.core.application import BaseIPythonApplication
62 from IPython.core.profiledir import ProfileDir
62 from IPython.core.profiledir import ProfileDir
63 from IPython.frontend.consoleapp import IPythonConsoleApp
63 from IPython.frontend.consoleapp import IPythonConsoleApp
64 from IPython.zmq.session import Session, default_secure
64 from IPython.zmq.session import Session, default_secure
65 from IPython.zmq.zmqshell import ZMQInteractiveShell
65 from IPython.zmq.zmqshell import ZMQInteractiveShell
66 from IPython.zmq.ipkernel import (
66 from IPython.zmq.kernelapp import (
67 flags as ipkernel_flags,
67 kernel_flags,
68 aliases as ipkernel_aliases,
68 kernel_aliases,
69 IPKernelApp
69 IPKernelApp
70 )
70 )
71 from IPython.utils.importstring import import_item
71 from IPython.utils.importstring import import_item
72 from IPython.utils.localinterfaces import LOCALHOST
72 from IPython.utils.localinterfaces import LOCALHOST
73 from IPython.kernel import swallow_argv
73 from IPython.kernel import swallow_argv
74 from IPython.utils.traitlets import (
74 from IPython.utils.traitlets import (
75 Dict, Unicode, Integer, List, Enum, Bool,
75 Dict, Unicode, Integer, List, Enum, Bool,
76 DottedObjectName
76 DottedObjectName
77 )
77 )
78 from IPython.utils import py3compat
78 from IPython.utils import py3compat
79 from IPython.utils.path import filefind
79 from IPython.utils.path import filefind
80
80
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82 # Module globals
82 # Module globals
83 #-----------------------------------------------------------------------------
83 #-----------------------------------------------------------------------------
84
84
85 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
85 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
86 _kernel_action_regex = r"(?P<action>restart|interrupt)"
86 _kernel_action_regex = r"(?P<action>restart|interrupt)"
87 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
87 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
88 _profile_regex = r"(?P<profile>[^\/]+)" # there is almost no text that is invalid
88 _profile_regex = r"(?P<profile>[^\/]+)" # there is almost no text that is invalid
89 _cluster_action_regex = r"(?P<action>start|stop)"
89 _cluster_action_regex = r"(?P<action>start|stop)"
90
90
91 _examples = """
91 _examples = """
92 ipython notebook # start the notebook
92 ipython notebook # start the notebook
93 ipython notebook --profile=sympy # use the sympy profile
93 ipython notebook --profile=sympy # use the sympy profile
94 ipython notebook --pylab=inline # pylab in inline plotting mode
94 ipython notebook --pylab=inline # pylab in inline plotting mode
95 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
95 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
96 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
96 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
97 """
97 """
98
98
99 #-----------------------------------------------------------------------------
99 #-----------------------------------------------------------------------------
100 # Helper functions
100 # Helper functions
101 #-----------------------------------------------------------------------------
101 #-----------------------------------------------------------------------------
102
102
103 def url_path_join(a,b):
103 def url_path_join(a,b):
104 if a.endswith('/') and b.startswith('/'):
104 if a.endswith('/') and b.startswith('/'):
105 return a[:-1]+b
105 return a[:-1]+b
106 else:
106 else:
107 return a+b
107 return a+b
108
108
109 def random_ports(port, n):
109 def random_ports(port, n):
110 """Generate a list of n random ports near the given port.
110 """Generate a list of n random ports near the given port.
111
111
112 The first 5 ports will be sequential, and the remaining n-5 will be
112 The first 5 ports will be sequential, and the remaining n-5 will be
113 randomly selected in the range [port-2*n, port+2*n].
113 randomly selected in the range [port-2*n, port+2*n].
114 """
114 """
115 for i in range(min(5, n)):
115 for i in range(min(5, n)):
116 yield port + i
116 yield port + i
117 for i in range(n-5):
117 for i in range(n-5):
118 yield port + random.randint(-2*n, 2*n)
118 yield port + random.randint(-2*n, 2*n)
119
119
120 #-----------------------------------------------------------------------------
120 #-----------------------------------------------------------------------------
121 # The Tornado web application
121 # The Tornado web application
122 #-----------------------------------------------------------------------------
122 #-----------------------------------------------------------------------------
123
123
124 class NotebookWebApplication(web.Application):
124 class NotebookWebApplication(web.Application):
125
125
126 def __init__(self, ipython_app, kernel_manager, notebook_manager,
126 def __init__(self, ipython_app, kernel_manager, notebook_manager,
127 cluster_manager, log,
127 cluster_manager, log,
128 base_project_url, settings_overrides):
128 base_project_url, settings_overrides):
129 handlers = [
129 handlers = [
130 (r"/", ProjectDashboardHandler),
130 (r"/", ProjectDashboardHandler),
131 (r"/login", LoginHandler),
131 (r"/login", LoginHandler),
132 (r"/logout", LogoutHandler),
132 (r"/logout", LogoutHandler),
133 (r"/new", NewHandler),
133 (r"/new", NewHandler),
134 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
134 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
135 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
135 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
136 (r"/%s/print" % _notebook_id_regex, PrintNotebookHandler),
136 (r"/%s/print" % _notebook_id_regex, PrintNotebookHandler),
137 (r"/kernels", MainKernelHandler),
137 (r"/kernels", MainKernelHandler),
138 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
138 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
139 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
139 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
140 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
140 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
141 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
141 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
142 (r"/notebooks", NotebookRootHandler),
142 (r"/notebooks", NotebookRootHandler),
143 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
143 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
144 (r"/rstservice/render", RSTHandler),
144 (r"/rstservice/render", RSTHandler),
145 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
145 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
146 (r"/clusters", MainClusterHandler),
146 (r"/clusters", MainClusterHandler),
147 (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler),
147 (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler),
148 (r"/clusters/%s" % _profile_regex, ClusterProfileHandler),
148 (r"/clusters/%s" % _profile_regex, ClusterProfileHandler),
149 ]
149 ]
150
150
151 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
151 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
152 # base_project_url will always be unicode, which will in turn
152 # base_project_url will always be unicode, which will in turn
153 # make the patterns unicode, and ultimately result in unicode
153 # make the patterns unicode, and ultimately result in unicode
154 # keys in kwargs to handler._execute(**kwargs) in tornado.
154 # keys in kwargs to handler._execute(**kwargs) in tornado.
155 # This enforces that base_project_url be ascii in that situation.
155 # This enforces that base_project_url be ascii in that situation.
156 #
156 #
157 # Note that the URLs these patterns check against are escaped,
157 # Note that the URLs these patterns check against are escaped,
158 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
158 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
159 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
159 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
160
160
161 settings = dict(
161 settings = dict(
162 template_path=os.path.join(os.path.dirname(__file__), "templates"),
162 template_path=os.path.join(os.path.dirname(__file__), "templates"),
163 static_path=ipython_app.static_file_path,
163 static_path=ipython_app.static_file_path,
164 static_handler_class = FileFindHandler,
164 static_handler_class = FileFindHandler,
165 static_url_prefix = url_path_join(base_project_url,'/static/'),
165 static_url_prefix = url_path_join(base_project_url,'/static/'),
166 cookie_secret=os.urandom(1024),
166 cookie_secret=os.urandom(1024),
167 login_url=url_path_join(base_project_url,'/login'),
167 login_url=url_path_join(base_project_url,'/login'),
168 cookie_name='username-%s' % uuid.uuid4(),
168 cookie_name='username-%s' % uuid.uuid4(),
169 )
169 )
170
170
171 # allow custom overrides for the tornado web app.
171 # allow custom overrides for the tornado web app.
172 settings.update(settings_overrides)
172 settings.update(settings_overrides)
173
173
174 # prepend base_project_url onto the patterns that we match
174 # prepend base_project_url onto the patterns that we match
175 new_handlers = []
175 new_handlers = []
176 for handler in handlers:
176 for handler in handlers:
177 pattern = url_path_join(base_project_url, handler[0])
177 pattern = url_path_join(base_project_url, handler[0])
178 new_handler = tuple([pattern]+list(handler[1:]))
178 new_handler = tuple([pattern]+list(handler[1:]))
179 new_handlers.append( new_handler )
179 new_handlers.append( new_handler )
180
180
181 super(NotebookWebApplication, self).__init__(new_handlers, **settings)
181 super(NotebookWebApplication, self).__init__(new_handlers, **settings)
182
182
183 self.kernel_manager = kernel_manager
183 self.kernel_manager = kernel_manager
184 self.notebook_manager = notebook_manager
184 self.notebook_manager = notebook_manager
185 self.cluster_manager = cluster_manager
185 self.cluster_manager = cluster_manager
186 self.ipython_app = ipython_app
186 self.ipython_app = ipython_app
187 self.read_only = self.ipython_app.read_only
187 self.read_only = self.ipython_app.read_only
188 self.config = self.ipython_app.config
188 self.config = self.ipython_app.config
189 self.log = log
189 self.log = log
190 self.jinja2_env = Environment(loader=FileSystemLoader(os.path.join(os.path.dirname(__file__), "templates")))
190 self.jinja2_env = Environment(loader=FileSystemLoader(os.path.join(os.path.dirname(__file__), "templates")))
191
191
192
192
193
193
194 #-----------------------------------------------------------------------------
194 #-----------------------------------------------------------------------------
195 # Aliases and Flags
195 # Aliases and Flags
196 #-----------------------------------------------------------------------------
196 #-----------------------------------------------------------------------------
197
197
198 flags = dict(ipkernel_flags)
198 flags = dict(kernel_flags)
199 flags['no-browser']=(
199 flags['no-browser']=(
200 {'NotebookApp' : {'open_browser' : False}},
200 {'NotebookApp' : {'open_browser' : False}},
201 "Don't open the notebook in a browser after startup."
201 "Don't open the notebook in a browser after startup."
202 )
202 )
203 flags['no-mathjax']=(
203 flags['no-mathjax']=(
204 {'NotebookApp' : {'enable_mathjax' : False}},
204 {'NotebookApp' : {'enable_mathjax' : False}},
205 """Disable MathJax
205 """Disable MathJax
206
206
207 MathJax is the javascript library IPython uses to render math/LaTeX. It is
207 MathJax is the javascript library IPython uses to render math/LaTeX. It is
208 very large, so you may want to disable it if you have a slow internet
208 very large, so you may want to disable it if you have a slow internet
209 connection, or for offline use of the notebook.
209 connection, or for offline use of the notebook.
210
210
211 When disabled, equations etc. will appear as their untransformed TeX source.
211 When disabled, equations etc. will appear as their untransformed TeX source.
212 """
212 """
213 )
213 )
214 flags['read-only'] = (
214 flags['read-only'] = (
215 {'NotebookApp' : {'read_only' : True}},
215 {'NotebookApp' : {'read_only' : True}},
216 """Allow read-only access to notebooks.
216 """Allow read-only access to notebooks.
217
217
218 When using a password to protect the notebook server, this flag
218 When using a password to protect the notebook server, this flag
219 allows unauthenticated clients to view the notebook list, and
219 allows unauthenticated clients to view the notebook list, and
220 individual notebooks, but not edit them, start kernels, or run
220 individual notebooks, but not edit them, start kernels, or run
221 code.
221 code.
222
222
223 If no password is set, the server will be entirely read-only.
223 If no password is set, the server will be entirely read-only.
224 """
224 """
225 )
225 )
226
226
227 # Add notebook manager flags
227 # Add notebook manager flags
228 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
228 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
229 'Auto-save a .py script everytime the .ipynb notebook is saved',
229 'Auto-save a .py script everytime the .ipynb notebook is saved',
230 'Do not auto-save .py scripts for every notebook'))
230 'Do not auto-save .py scripts for every notebook'))
231
231
232 # the flags that are specific to the frontend
232 # the flags that are specific to the frontend
233 # these must be scrubbed before being passed to the kernel,
233 # these must be scrubbed before being passed to the kernel,
234 # or it will raise an error on unrecognized flags
234 # or it will raise an error on unrecognized flags
235 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
235 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
236
236
237 aliases = dict(ipkernel_aliases)
237 aliases = dict(kernel_aliases)
238
238
239 aliases.update({
239 aliases.update({
240 'ip': 'NotebookApp.ip',
240 'ip': 'NotebookApp.ip',
241 'port': 'NotebookApp.port',
241 'port': 'NotebookApp.port',
242 'port-retries': 'NotebookApp.port_retries',
242 'port-retries': 'NotebookApp.port_retries',
243 'transport': 'KernelManager.transport',
243 'transport': 'KernelManager.transport',
244 'keyfile': 'NotebookApp.keyfile',
244 'keyfile': 'NotebookApp.keyfile',
245 'certfile': 'NotebookApp.certfile',
245 'certfile': 'NotebookApp.certfile',
246 'notebook-dir': 'NotebookManager.notebook_dir',
246 'notebook-dir': 'NotebookManager.notebook_dir',
247 'browser': 'NotebookApp.browser',
247 'browser': 'NotebookApp.browser',
248 })
248 })
249
249
250 # remove ipkernel flags that are singletons, and don't make sense in
250 # remove ipkernel flags that are singletons, and don't make sense in
251 # multi-kernel evironment:
251 # multi-kernel evironment:
252 aliases.pop('f', None)
252 aliases.pop('f', None)
253
253
254 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
254 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
255 u'notebook-dir']
255 u'notebook-dir']
256
256
257 #-----------------------------------------------------------------------------
257 #-----------------------------------------------------------------------------
258 # NotebookApp
258 # NotebookApp
259 #-----------------------------------------------------------------------------
259 #-----------------------------------------------------------------------------
260
260
261 class NotebookApp(BaseIPythonApplication):
261 class NotebookApp(BaseIPythonApplication):
262
262
263 name = 'ipython-notebook'
263 name = 'ipython-notebook'
264 default_config_file_name='ipython_notebook_config.py'
264 default_config_file_name='ipython_notebook_config.py'
265
265
266 description = """
266 description = """
267 The IPython HTML Notebook.
267 The IPython HTML Notebook.
268
268
269 This launches a Tornado based HTML Notebook Server that serves up an
269 This launches a Tornado based HTML Notebook Server that serves up an
270 HTML5/Javascript Notebook client.
270 HTML5/Javascript Notebook client.
271 """
271 """
272 examples = _examples
272 examples = _examples
273
273
274 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
274 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
275 FileNotebookManager]
275 FileNotebookManager]
276 flags = Dict(flags)
276 flags = Dict(flags)
277 aliases = Dict(aliases)
277 aliases = Dict(aliases)
278
278
279 kernel_argv = List(Unicode)
279 kernel_argv = List(Unicode)
280
280
281 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
281 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
282 default_value=logging.INFO,
282 default_value=logging.INFO,
283 config=True,
283 config=True,
284 help="Set the log level by value or name.")
284 help="Set the log level by value or name.")
285
285
286 # create requested profiles by default, if they don't exist:
286 # create requested profiles by default, if they don't exist:
287 auto_create = Bool(True)
287 auto_create = Bool(True)
288
288
289 # file to be opened in the notebook server
289 # file to be opened in the notebook server
290 file_to_run = Unicode('')
290 file_to_run = Unicode('')
291
291
292 # Network related information.
292 # Network related information.
293
293
294 ip = Unicode(LOCALHOST, config=True,
294 ip = Unicode(LOCALHOST, config=True,
295 help="The IP address the notebook server will listen on."
295 help="The IP address the notebook server will listen on."
296 )
296 )
297
297
298 def _ip_changed(self, name, old, new):
298 def _ip_changed(self, name, old, new):
299 if new == u'*': self.ip = u''
299 if new == u'*': self.ip = u''
300
300
301 port = Integer(8888, config=True,
301 port = Integer(8888, config=True,
302 help="The port the notebook server will listen on."
302 help="The port the notebook server will listen on."
303 )
303 )
304 port_retries = Integer(50, config=True,
304 port_retries = Integer(50, config=True,
305 help="The number of additional ports to try if the specified port is not available."
305 help="The number of additional ports to try if the specified port is not available."
306 )
306 )
307
307
308 certfile = Unicode(u'', config=True,
308 certfile = Unicode(u'', config=True,
309 help="""The full path to an SSL/TLS certificate file."""
309 help="""The full path to an SSL/TLS certificate file."""
310 )
310 )
311
311
312 keyfile = Unicode(u'', config=True,
312 keyfile = Unicode(u'', config=True,
313 help="""The full path to a private key file for usage with SSL/TLS."""
313 help="""The full path to a private key file for usage with SSL/TLS."""
314 )
314 )
315
315
316 password = Unicode(u'', config=True,
316 password = Unicode(u'', config=True,
317 help="""Hashed password to use for web authentication.
317 help="""Hashed password to use for web authentication.
318
318
319 To generate, type in a python/IPython shell:
319 To generate, type in a python/IPython shell:
320
320
321 from IPython.lib import passwd; passwd()
321 from IPython.lib import passwd; passwd()
322
322
323 The string should be of the form type:salt:hashed-password.
323 The string should be of the form type:salt:hashed-password.
324 """
324 """
325 )
325 )
326
326
327 open_browser = Bool(True, config=True,
327 open_browser = Bool(True, config=True,
328 help="""Whether to open in a browser after starting.
328 help="""Whether to open in a browser after starting.
329 The specific browser used is platform dependent and
329 The specific browser used is platform dependent and
330 determined by the python standard library `webbrowser`
330 determined by the python standard library `webbrowser`
331 module, unless it is overridden using the --browser
331 module, unless it is overridden using the --browser
332 (NotebookApp.browser) configuration option.
332 (NotebookApp.browser) configuration option.
333 """)
333 """)
334
334
335 browser = Unicode(u'', config=True,
335 browser = Unicode(u'', config=True,
336 help="""Specify what command to use to invoke a web
336 help="""Specify what command to use to invoke a web
337 browser when opening the notebook. If not specified, the
337 browser when opening the notebook. If not specified, the
338 default browser will be determined by the `webbrowser`
338 default browser will be determined by the `webbrowser`
339 standard library module, which allows setting of the
339 standard library module, which allows setting of the
340 BROWSER environment variable to override it.
340 BROWSER environment variable to override it.
341 """)
341 """)
342
342
343 read_only = Bool(False, config=True,
343 read_only = Bool(False, config=True,
344 help="Whether to prevent editing/execution of notebooks."
344 help="Whether to prevent editing/execution of notebooks."
345 )
345 )
346
346
347 webapp_settings = Dict(config=True,
347 webapp_settings = Dict(config=True,
348 help="Supply overrides for the tornado.web.Application that the "
348 help="Supply overrides for the tornado.web.Application that the "
349 "IPython notebook uses.")
349 "IPython notebook uses.")
350
350
351 enable_mathjax = Bool(True, config=True,
351 enable_mathjax = Bool(True, config=True,
352 help="""Whether to enable MathJax for typesetting math/TeX
352 help="""Whether to enable MathJax for typesetting math/TeX
353
353
354 MathJax is the javascript library IPython uses to render math/LaTeX. It is
354 MathJax is the javascript library IPython uses to render math/LaTeX. It is
355 very large, so you may want to disable it if you have a slow internet
355 very large, so you may want to disable it if you have a slow internet
356 connection, or for offline use of the notebook.
356 connection, or for offline use of the notebook.
357
357
358 When disabled, equations etc. will appear as their untransformed TeX source.
358 When disabled, equations etc. will appear as their untransformed TeX source.
359 """
359 """
360 )
360 )
361 def _enable_mathjax_changed(self, name, old, new):
361 def _enable_mathjax_changed(self, name, old, new):
362 """set mathjax url to empty if mathjax is disabled"""
362 """set mathjax url to empty if mathjax is disabled"""
363 if not new:
363 if not new:
364 self.mathjax_url = u''
364 self.mathjax_url = u''
365
365
366 base_project_url = Unicode('/', config=True,
366 base_project_url = Unicode('/', config=True,
367 help='''The base URL for the notebook server.
367 help='''The base URL for the notebook server.
368
368
369 Leading and trailing slashes can be omitted,
369 Leading and trailing slashes can be omitted,
370 and will automatically be added.
370 and will automatically be added.
371 ''')
371 ''')
372 def _base_project_url_changed(self, name, old, new):
372 def _base_project_url_changed(self, name, old, new):
373 if not new.startswith('/'):
373 if not new.startswith('/'):
374 self.base_project_url = '/'+new
374 self.base_project_url = '/'+new
375 elif not new.endswith('/'):
375 elif not new.endswith('/'):
376 self.base_project_url = new+'/'
376 self.base_project_url = new+'/'
377
377
378 base_kernel_url = Unicode('/', config=True,
378 base_kernel_url = Unicode('/', config=True,
379 help='''The base URL for the kernel server
379 help='''The base URL for the kernel server
380
380
381 Leading and trailing slashes can be omitted,
381 Leading and trailing slashes can be omitted,
382 and will automatically be added.
382 and will automatically be added.
383 ''')
383 ''')
384 def _base_kernel_url_changed(self, name, old, new):
384 def _base_kernel_url_changed(self, name, old, new):
385 if not new.startswith('/'):
385 if not new.startswith('/'):
386 self.base_kernel_url = '/'+new
386 self.base_kernel_url = '/'+new
387 elif not new.endswith('/'):
387 elif not new.endswith('/'):
388 self.base_kernel_url = new+'/'
388 self.base_kernel_url = new+'/'
389
389
390 websocket_host = Unicode("", config=True,
390 websocket_host = Unicode("", config=True,
391 help="""The hostname for the websocket server."""
391 help="""The hostname for the websocket server."""
392 )
392 )
393
393
394 extra_static_paths = List(Unicode, config=True,
394 extra_static_paths = List(Unicode, config=True,
395 help="""Extra paths to search for serving static files.
395 help="""Extra paths to search for serving static files.
396
396
397 This allows adding javascript/css to be available from the notebook server machine,
397 This allows adding javascript/css to be available from the notebook server machine,
398 or overriding individual files in the IPython"""
398 or overriding individual files in the IPython"""
399 )
399 )
400 def _extra_static_paths_default(self):
400 def _extra_static_paths_default(self):
401 return [os.path.join(self.profile_dir.location, 'static')]
401 return [os.path.join(self.profile_dir.location, 'static')]
402
402
403 @property
403 @property
404 def static_file_path(self):
404 def static_file_path(self):
405 """return extra paths + the default location"""
405 """return extra paths + the default location"""
406 return self.extra_static_paths + [os.path.join(os.path.dirname(__file__), "static")]
406 return self.extra_static_paths + [os.path.join(os.path.dirname(__file__), "static")]
407
407
408 mathjax_url = Unicode("", config=True,
408 mathjax_url = Unicode("", config=True,
409 help="""The url for MathJax.js."""
409 help="""The url for MathJax.js."""
410 )
410 )
411 def _mathjax_url_default(self):
411 def _mathjax_url_default(self):
412 if not self.enable_mathjax:
412 if not self.enable_mathjax:
413 return u''
413 return u''
414 static_url_prefix = self.webapp_settings.get("static_url_prefix",
414 static_url_prefix = self.webapp_settings.get("static_url_prefix",
415 "/static/")
415 "/static/")
416 try:
416 try:
417 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
417 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
418 except IOError:
418 except IOError:
419 if self.certfile:
419 if self.certfile:
420 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
420 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
421 base = u"https://c328740.ssl.cf1.rackcdn.com"
421 base = u"https://c328740.ssl.cf1.rackcdn.com"
422 else:
422 else:
423 base = u"http://cdn.mathjax.org"
423 base = u"http://cdn.mathjax.org"
424
424
425 url = base + u"/mathjax/latest/MathJax.js"
425 url = base + u"/mathjax/latest/MathJax.js"
426 self.log.info("Using MathJax from CDN: %s", url)
426 self.log.info("Using MathJax from CDN: %s", url)
427 return url
427 return url
428 else:
428 else:
429 self.log.info("Using local MathJax from %s" % mathjax)
429 self.log.info("Using local MathJax from %s" % mathjax)
430 return static_url_prefix+u"mathjax/MathJax.js"
430 return static_url_prefix+u"mathjax/MathJax.js"
431
431
432 def _mathjax_url_changed(self, name, old, new):
432 def _mathjax_url_changed(self, name, old, new):
433 if new and not self.enable_mathjax:
433 if new and not self.enable_mathjax:
434 # enable_mathjax=False overrides mathjax_url
434 # enable_mathjax=False overrides mathjax_url
435 self.mathjax_url = u''
435 self.mathjax_url = u''
436 else:
436 else:
437 self.log.info("Using MathJax: %s", new)
437 self.log.info("Using MathJax: %s", new)
438
438
439 notebook_manager_class = DottedObjectName('IPython.frontend.html.notebook.filenbmanager.FileNotebookManager',
439 notebook_manager_class = DottedObjectName('IPython.frontend.html.notebook.filenbmanager.FileNotebookManager',
440 config=True,
440 config=True,
441 help='The notebook manager class to use.')
441 help='The notebook manager class to use.')
442
442
443 def parse_command_line(self, argv=None):
443 def parse_command_line(self, argv=None):
444 super(NotebookApp, self).parse_command_line(argv)
444 super(NotebookApp, self).parse_command_line(argv)
445 if argv is None:
445 if argv is None:
446 argv = sys.argv[1:]
446 argv = sys.argv[1:]
447
447
448 # Scrub frontend-specific flags
448 # Scrub frontend-specific flags
449 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
449 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
450 # Kernel should inherit default config file from frontend
450 # Kernel should inherit default config file from frontend
451 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
451 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
452
452
453 if self.extra_args:
453 if self.extra_args:
454 f = os.path.abspath(self.extra_args[0])
454 f = os.path.abspath(self.extra_args[0])
455 if os.path.isdir(f):
455 if os.path.isdir(f):
456 nbdir = f
456 nbdir = f
457 else:
457 else:
458 self.file_to_run = f
458 self.file_to_run = f
459 nbdir = os.path.dirname(f)
459 nbdir = os.path.dirname(f)
460 self.config.NotebookManager.notebook_dir = nbdir
460 self.config.NotebookManager.notebook_dir = nbdir
461
461
462 def init_configurables(self):
462 def init_configurables(self):
463 # force Session default to be secure
463 # force Session default to be secure
464 default_secure(self.config)
464 default_secure(self.config)
465 self.kernel_manager = MappingKernelManager(
465 self.kernel_manager = MappingKernelManager(
466 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
466 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
467 connection_dir = self.profile_dir.security_dir,
467 connection_dir = self.profile_dir.security_dir,
468 )
468 )
469 kls = import_item(self.notebook_manager_class)
469 kls = import_item(self.notebook_manager_class)
470 self.notebook_manager = kls(config=self.config, log=self.log)
470 self.notebook_manager = kls(config=self.config, log=self.log)
471 self.notebook_manager.log_info()
471 self.notebook_manager.log_info()
472 self.notebook_manager.load_notebook_names()
472 self.notebook_manager.load_notebook_names()
473 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
473 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
474 self.cluster_manager.update_profiles()
474 self.cluster_manager.update_profiles()
475
475
476 def init_logging(self):
476 def init_logging(self):
477 # This prevents double log messages because tornado use a root logger that
477 # This prevents double log messages because tornado use a root logger that
478 # self.log is a child of. The logging module dipatches log messages to a log
478 # self.log is a child of. The logging module dipatches log messages to a log
479 # and all of its ancenstors until propagate is set to False.
479 # and all of its ancenstors until propagate is set to False.
480 self.log.propagate = False
480 self.log.propagate = False
481
481
482 def init_webapp(self):
482 def init_webapp(self):
483 """initialize tornado webapp and httpserver"""
483 """initialize tornado webapp and httpserver"""
484 self.web_app = NotebookWebApplication(
484 self.web_app = NotebookWebApplication(
485 self, self.kernel_manager, self.notebook_manager,
485 self, self.kernel_manager, self.notebook_manager,
486 self.cluster_manager, self.log,
486 self.cluster_manager, self.log,
487 self.base_project_url, self.webapp_settings
487 self.base_project_url, self.webapp_settings
488 )
488 )
489 if self.certfile:
489 if self.certfile:
490 ssl_options = dict(certfile=self.certfile)
490 ssl_options = dict(certfile=self.certfile)
491 if self.keyfile:
491 if self.keyfile:
492 ssl_options['keyfile'] = self.keyfile
492 ssl_options['keyfile'] = self.keyfile
493 else:
493 else:
494 ssl_options = None
494 ssl_options = None
495 self.web_app.password = self.password
495 self.web_app.password = self.password
496 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
496 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
497 if not self.ip:
497 if not self.ip:
498 warning = "WARNING: The notebook server is listening on all IP addresses"
498 warning = "WARNING: The notebook server is listening on all IP addresses"
499 if ssl_options is None:
499 if ssl_options is None:
500 self.log.critical(warning + " and not using encryption. This"
500 self.log.critical(warning + " and not using encryption. This"
501 "is not recommended.")
501 "is not recommended.")
502 if not self.password and not self.read_only:
502 if not self.password and not self.read_only:
503 self.log.critical(warning + "and not using authentication."
503 self.log.critical(warning + "and not using authentication."
504 "This is highly insecure and not recommended.")
504 "This is highly insecure and not recommended.")
505 success = None
505 success = None
506 for port in random_ports(self.port, self.port_retries+1):
506 for port in random_ports(self.port, self.port_retries+1):
507 try:
507 try:
508 self.http_server.listen(port, self.ip)
508 self.http_server.listen(port, self.ip)
509 except socket.error as e:
509 except socket.error as e:
510 if e.errno != errno.EADDRINUSE:
510 if e.errno != errno.EADDRINUSE:
511 raise
511 raise
512 self.log.info('The port %i is already in use, trying another random port.' % port)
512 self.log.info('The port %i is already in use, trying another random port.' % port)
513 else:
513 else:
514 self.port = port
514 self.port = port
515 success = True
515 success = True
516 break
516 break
517 if not success:
517 if not success:
518 self.log.critical('ERROR: the notebook server could not be started because '
518 self.log.critical('ERROR: the notebook server could not be started because '
519 'no available port could be found.')
519 'no available port could be found.')
520 self.exit(1)
520 self.exit(1)
521
521
522 def init_signal(self):
522 def init_signal(self):
523 # FIXME: remove this check when pyzmq dependency is >= 2.1.11
523 # FIXME: remove this check when pyzmq dependency is >= 2.1.11
524 # safely extract zmq version info:
524 # safely extract zmq version info:
525 try:
525 try:
526 zmq_v = zmq.pyzmq_version_info()
526 zmq_v = zmq.pyzmq_version_info()
527 except AttributeError:
527 except AttributeError:
528 zmq_v = [ int(n) for n in re.findall(r'\d+', zmq.__version__) ]
528 zmq_v = [ int(n) for n in re.findall(r'\d+', zmq.__version__) ]
529 if 'dev' in zmq.__version__:
529 if 'dev' in zmq.__version__:
530 zmq_v.append(999)
530 zmq_v.append(999)
531 zmq_v = tuple(zmq_v)
531 zmq_v = tuple(zmq_v)
532 if zmq_v >= (2,1,9) and not sys.platform.startswith('win'):
532 if zmq_v >= (2,1,9) and not sys.platform.startswith('win'):
533 # This won't work with 2.1.7 and
533 # This won't work with 2.1.7 and
534 # 2.1.9-10 will log ugly 'Interrupted system call' messages,
534 # 2.1.9-10 will log ugly 'Interrupted system call' messages,
535 # but it will work
535 # but it will work
536 signal.signal(signal.SIGINT, self._handle_sigint)
536 signal.signal(signal.SIGINT, self._handle_sigint)
537 signal.signal(signal.SIGTERM, self._signal_stop)
537 signal.signal(signal.SIGTERM, self._signal_stop)
538
538
539 def _handle_sigint(self, sig, frame):
539 def _handle_sigint(self, sig, frame):
540 """SIGINT handler spawns confirmation dialog"""
540 """SIGINT handler spawns confirmation dialog"""
541 # register more forceful signal handler for ^C^C case
541 # register more forceful signal handler for ^C^C case
542 signal.signal(signal.SIGINT, self._signal_stop)
542 signal.signal(signal.SIGINT, self._signal_stop)
543 # request confirmation dialog in bg thread, to avoid
543 # request confirmation dialog in bg thread, to avoid
544 # blocking the App
544 # blocking the App
545 thread = threading.Thread(target=self._confirm_exit)
545 thread = threading.Thread(target=self._confirm_exit)
546 thread.daemon = True
546 thread.daemon = True
547 thread.start()
547 thread.start()
548
548
549 def _restore_sigint_handler(self):
549 def _restore_sigint_handler(self):
550 """callback for restoring original SIGINT handler"""
550 """callback for restoring original SIGINT handler"""
551 signal.signal(signal.SIGINT, self._handle_sigint)
551 signal.signal(signal.SIGINT, self._handle_sigint)
552
552
553 def _confirm_exit(self):
553 def _confirm_exit(self):
554 """confirm shutdown on ^C
554 """confirm shutdown on ^C
555
555
556 A second ^C, or answering 'y' within 5s will cause shutdown,
556 A second ^C, or answering 'y' within 5s will cause shutdown,
557 otherwise original SIGINT handler will be restored.
557 otherwise original SIGINT handler will be restored.
558
558
559 This doesn't work on Windows.
559 This doesn't work on Windows.
560 """
560 """
561 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
561 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
562 time.sleep(0.1)
562 time.sleep(0.1)
563 sys.stdout.write("Shutdown Notebook Server (y/[n])? ")
563 sys.stdout.write("Shutdown Notebook Server (y/[n])? ")
564 sys.stdout.flush()
564 sys.stdout.flush()
565 r,w,x = select.select([sys.stdin], [], [], 5)
565 r,w,x = select.select([sys.stdin], [], [], 5)
566 if r:
566 if r:
567 line = sys.stdin.readline()
567 line = sys.stdin.readline()
568 if line.lower().startswith('y'):
568 if line.lower().startswith('y'):
569 self.log.critical("Shutdown confirmed")
569 self.log.critical("Shutdown confirmed")
570 ioloop.IOLoop.instance().stop()
570 ioloop.IOLoop.instance().stop()
571 return
571 return
572 else:
572 else:
573 print "No answer for 5s:",
573 print "No answer for 5s:",
574 print "resuming operation..."
574 print "resuming operation..."
575 # no answer, or answer is no:
575 # no answer, or answer is no:
576 # set it back to original SIGINT handler
576 # set it back to original SIGINT handler
577 # use IOLoop.add_callback because signal.signal must be called
577 # use IOLoop.add_callback because signal.signal must be called
578 # from main thread
578 # from main thread
579 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
579 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
580
580
581 def _signal_stop(self, sig, frame):
581 def _signal_stop(self, sig, frame):
582 self.log.critical("received signal %s, stopping", sig)
582 self.log.critical("received signal %s, stopping", sig)
583 ioloop.IOLoop.instance().stop()
583 ioloop.IOLoop.instance().stop()
584
584
585 @catch_config_error
585 @catch_config_error
586 def initialize(self, argv=None):
586 def initialize(self, argv=None):
587 self.init_logging()
587 self.init_logging()
588 super(NotebookApp, self).initialize(argv)
588 super(NotebookApp, self).initialize(argv)
589 self.init_configurables()
589 self.init_configurables()
590 self.init_webapp()
590 self.init_webapp()
591 self.init_signal()
591 self.init_signal()
592
592
593 def cleanup_kernels(self):
593 def cleanup_kernels(self):
594 """Shutdown all kernels.
594 """Shutdown all kernels.
595
595
596 The kernels will shutdown themselves when this process no longer exists,
596 The kernels will shutdown themselves when this process no longer exists,
597 but explicit shutdown allows the KernelManagers to cleanup the connection files.
597 but explicit shutdown allows the KernelManagers to cleanup the connection files.
598 """
598 """
599 self.log.info('Shutting down kernels')
599 self.log.info('Shutting down kernels')
600 self.kernel_manager.shutdown_all()
600 self.kernel_manager.shutdown_all()
601
601
602 def start(self):
602 def start(self):
603 ip = self.ip if self.ip else '[all ip addresses on your system]'
603 ip = self.ip if self.ip else '[all ip addresses on your system]'
604 proto = 'https' if self.certfile else 'http'
604 proto = 'https' if self.certfile else 'http'
605 info = self.log.info
605 info = self.log.info
606 info("The IPython Notebook is running at: %s://%s:%i%s" %
606 info("The IPython Notebook is running at: %s://%s:%i%s" %
607 (proto, ip, self.port,self.base_project_url) )
607 (proto, ip, self.port,self.base_project_url) )
608 info("Use Control-C to stop this server and shut down all kernels.")
608 info("Use Control-C to stop this server and shut down all kernels.")
609
609
610 if self.open_browser or self.file_to_run:
610 if self.open_browser or self.file_to_run:
611 ip = self.ip or LOCALHOST
611 ip = self.ip or LOCALHOST
612 try:
612 try:
613 browser = webbrowser.get(self.browser or None)
613 browser = webbrowser.get(self.browser or None)
614 except webbrowser.Error as e:
614 except webbrowser.Error as e:
615 self.log.warn('No web browser found: %s.' % e)
615 self.log.warn('No web browser found: %s.' % e)
616 browser = None
616 browser = None
617
617
618 if self.file_to_run:
618 if self.file_to_run:
619 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
619 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
620 url = self.notebook_manager.rev_mapping.get(name, '')
620 url = self.notebook_manager.rev_mapping.get(name, '')
621 else:
621 else:
622 url = ''
622 url = ''
623 if browser:
623 if browser:
624 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
624 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
625 self.port, self.base_project_url, url), new=2)
625 self.port, self.base_project_url, url), new=2)
626 threading.Thread(target=b).start()
626 threading.Thread(target=b).start()
627 try:
627 try:
628 ioloop.IOLoop.instance().start()
628 ioloop.IOLoop.instance().start()
629 except KeyboardInterrupt:
629 except KeyboardInterrupt:
630 info("Interrupted...")
630 info("Interrupted...")
631 finally:
631 finally:
632 self.cleanup_kernels()
632 self.cleanup_kernels()
633
633
634
634
635 #-----------------------------------------------------------------------------
635 #-----------------------------------------------------------------------------
636 # Main entry point
636 # Main entry point
637 #-----------------------------------------------------------------------------
637 #-----------------------------------------------------------------------------
638
638
639 def launch_new_instance():
639 def launch_new_instance():
640 app = NotebookApp.instance()
640 app = NotebookApp.instance()
641 app.initialize()
641 app.initialize()
642 app.start()
642 app.start()
643
643
@@ -1,370 +1,370 b''
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 # If run on Windows, install an exception hook which pops up a
29 # If run on Windows, install an exception hook which pops up a
30 # message box. Pythonw.exe hides the console, so without this
30 # message box. Pythonw.exe hides the console, so without this
31 # the application silently fails to load.
31 # the application silently fails to load.
32 #
32 #
33 # We always install this handler, because the expectation is for
33 # We always install this handler, because the expectation is for
34 # qtconsole to bring up a GUI even if called from the console.
34 # qtconsole to bring up a GUI even if called from the console.
35 # The old handler is called, so the exception is printed as well.
35 # The old handler is called, so the exception is printed as well.
36 # If desired, check for pythonw with an additional condition
36 # If desired, check for pythonw with an additional condition
37 # (sys.executable.lower().find('pythonw.exe') >= 0).
37 # (sys.executable.lower().find('pythonw.exe') >= 0).
38 if os.name == 'nt':
38 if os.name == 'nt':
39 old_excepthook = sys.excepthook
39 old_excepthook = sys.excepthook
40
40
41 def gui_excepthook(exctype, value, tb):
41 def gui_excepthook(exctype, value, tb):
42 try:
42 try:
43 import ctypes, traceback
43 import ctypes, traceback
44 MB_ICONERROR = 0x00000010L
44 MB_ICONERROR = 0x00000010L
45 title = u'Error starting IPython QtConsole'
45 title = u'Error starting IPython QtConsole'
46 msg = u''.join(traceback.format_exception(exctype, value, tb))
46 msg = u''.join(traceback.format_exception(exctype, value, tb))
47 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
47 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
48 finally:
48 finally:
49 # Also call the old exception hook to let it do
49 # Also call the old exception hook to let it do
50 # its thing too.
50 # its thing too.
51 old_excepthook(exctype, value, tb)
51 old_excepthook(exctype, value, tb)
52
52
53 sys.excepthook = gui_excepthook
53 sys.excepthook = gui_excepthook
54
54
55 # System library imports
55 # System library imports
56 from IPython.external.qt import QtCore, QtGui
56 from IPython.external.qt import QtCore, QtGui
57
57
58 # Local imports
58 # Local imports
59 from IPython.config.application import boolean_flag, catch_config_error
59 from IPython.config.application import boolean_flag, catch_config_error
60 from IPython.core.application import BaseIPythonApplication
60 from IPython.core.application import BaseIPythonApplication
61 from IPython.core.profiledir import ProfileDir
61 from IPython.core.profiledir import ProfileDir
62 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
62 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
63 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
63 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
64 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
64 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
65 from IPython.frontend.qt.console import styles
65 from IPython.frontend.qt.console import styles
66 from IPython.frontend.qt.console.mainwindow import MainWindow
66 from IPython.frontend.qt.console.mainwindow import MainWindow
67 from IPython.frontend.qt.kernelmanager import QtKernelManager
67 from IPython.frontend.qt.kernelmanager import QtKernelManager
68 from IPython.kernel import tunnel_to_kernel, find_connection_file
68 from IPython.kernel import tunnel_to_kernel, find_connection_file
69 from IPython.utils.path import filefind
69 from IPython.utils.path import filefind
70 from IPython.utils.py3compat import str_to_bytes
70 from IPython.utils.py3compat import str_to_bytes
71 from IPython.utils.traitlets import (
71 from IPython.utils.traitlets import (
72 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
72 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
73 )
73 )
74 from IPython.zmq.ipkernel import IPKernelApp
74 from IPython.zmq.kernelapp import IPKernelApp
75 from IPython.zmq.session import Session, default_secure
75 from IPython.zmq.session import Session, default_secure
76 from IPython.zmq.zmqshell import ZMQInteractiveShell
76 from IPython.zmq.zmqshell import ZMQInteractiveShell
77
77
78 from IPython.frontend.consoleapp import (
78 from IPython.frontend.consoleapp import (
79 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
79 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
80 )
80 )
81
81
82 #-----------------------------------------------------------------------------
82 #-----------------------------------------------------------------------------
83 # Network Constants
83 # Network Constants
84 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
85
85
86 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
86 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
87
87
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89 # Globals
89 # Globals
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91
91
92 _examples = """
92 _examples = """
93 ipython qtconsole # start the qtconsole
93 ipython qtconsole # start the qtconsole
94 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
94 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
95 """
95 """
96
96
97 #-----------------------------------------------------------------------------
97 #-----------------------------------------------------------------------------
98 # Aliases and Flags
98 # Aliases and Flags
99 #-----------------------------------------------------------------------------
99 #-----------------------------------------------------------------------------
100
100
101 # start with copy of flags
101 # start with copy of flags
102 flags = dict(flags)
102 flags = dict(flags)
103 qt_flags = {
103 qt_flags = {
104 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
104 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
105 "Disable rich text support."),
105 "Disable rich text support."),
106 }
106 }
107
107
108 # and app_flags from the Console Mixin
108 # and app_flags from the Console Mixin
109 qt_flags.update(app_flags)
109 qt_flags.update(app_flags)
110 # add frontend flags to the full set
110 # add frontend flags to the full set
111 flags.update(qt_flags)
111 flags.update(qt_flags)
112
112
113 # start with copy of front&backend aliases list
113 # start with copy of front&backend aliases list
114 aliases = dict(aliases)
114 aliases = dict(aliases)
115 qt_aliases = dict(
115 qt_aliases = dict(
116 style = 'IPythonWidget.syntax_style',
116 style = 'IPythonWidget.syntax_style',
117 stylesheet = 'IPythonQtConsoleApp.stylesheet',
117 stylesheet = 'IPythonQtConsoleApp.stylesheet',
118 colors = 'ZMQInteractiveShell.colors',
118 colors = 'ZMQInteractiveShell.colors',
119
119
120 editor = 'IPythonWidget.editor',
120 editor = 'IPythonWidget.editor',
121 paging = 'ConsoleWidget.paging',
121 paging = 'ConsoleWidget.paging',
122 )
122 )
123 # and app_aliases from the Console Mixin
123 # and app_aliases from the Console Mixin
124 qt_aliases.update(app_aliases)
124 qt_aliases.update(app_aliases)
125 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
125 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
126 # add frontend aliases to the full set
126 # add frontend aliases to the full set
127 aliases.update(qt_aliases)
127 aliases.update(qt_aliases)
128
128
129 # get flags&aliases into sets, and remove a couple that
129 # get flags&aliases into sets, and remove a couple that
130 # shouldn't be scrubbed from backend flags:
130 # shouldn't be scrubbed from backend flags:
131 qt_aliases = set(qt_aliases.keys())
131 qt_aliases = set(qt_aliases.keys())
132 qt_aliases.remove('colors')
132 qt_aliases.remove('colors')
133 qt_flags = set(qt_flags.keys())
133 qt_flags = set(qt_flags.keys())
134
134
135 #-----------------------------------------------------------------------------
135 #-----------------------------------------------------------------------------
136 # Classes
136 # Classes
137 #-----------------------------------------------------------------------------
137 #-----------------------------------------------------------------------------
138
138
139 #-----------------------------------------------------------------------------
139 #-----------------------------------------------------------------------------
140 # IPythonQtConsole
140 # IPythonQtConsole
141 #-----------------------------------------------------------------------------
141 #-----------------------------------------------------------------------------
142
142
143
143
144 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
144 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
145 name = 'ipython-qtconsole'
145 name = 'ipython-qtconsole'
146
146
147 description = """
147 description = """
148 The IPython QtConsole.
148 The IPython QtConsole.
149
149
150 This launches a Console-style application using Qt. It is not a full
150 This launches a Console-style application using Qt. It is not a full
151 console, in that launched terminal subprocesses will not be able to accept
151 console, in that launched terminal subprocesses will not be able to accept
152 input.
152 input.
153
153
154 The QtConsole supports various extra features beyond the Terminal IPython
154 The QtConsole supports various extra features beyond the Terminal IPython
155 shell, such as inline plotting with matplotlib, via:
155 shell, such as inline plotting with matplotlib, via:
156
156
157 ipython qtconsole --pylab=inline
157 ipython qtconsole --pylab=inline
158
158
159 as well as saving your session as HTML, and printing the output.
159 as well as saving your session as HTML, and printing the output.
160
160
161 """
161 """
162 examples = _examples
162 examples = _examples
163
163
164 classes = [IPythonWidget] + IPythonConsoleApp.classes
164 classes = [IPythonWidget] + IPythonConsoleApp.classes
165 flags = Dict(flags)
165 flags = Dict(flags)
166 aliases = Dict(aliases)
166 aliases = Dict(aliases)
167 frontend_flags = Any(qt_flags)
167 frontend_flags = Any(qt_flags)
168 frontend_aliases = Any(qt_aliases)
168 frontend_aliases = Any(qt_aliases)
169 kernel_manager_class = QtKernelManager
169 kernel_manager_class = QtKernelManager
170
170
171 stylesheet = Unicode('', config=True,
171 stylesheet = Unicode('', config=True,
172 help="path to a custom CSS stylesheet")
172 help="path to a custom CSS stylesheet")
173
173
174 plain = CBool(False, config=True,
174 plain = CBool(False, config=True,
175 help="Use a plaintext widget instead of rich text (plain can't print/save).")
175 help="Use a plaintext widget instead of rich text (plain can't print/save).")
176
176
177 def _plain_changed(self, name, old, new):
177 def _plain_changed(self, name, old, new):
178 kind = 'plain' if new else 'rich'
178 kind = 'plain' if new else 'rich'
179 self.config.ConsoleWidget.kind = kind
179 self.config.ConsoleWidget.kind = kind
180 if new:
180 if new:
181 self.widget_factory = IPythonWidget
181 self.widget_factory = IPythonWidget
182 else:
182 else:
183 self.widget_factory = RichIPythonWidget
183 self.widget_factory = RichIPythonWidget
184
184
185 # the factory for creating a widget
185 # the factory for creating a widget
186 widget_factory = Any(RichIPythonWidget)
186 widget_factory = Any(RichIPythonWidget)
187
187
188 def parse_command_line(self, argv=None):
188 def parse_command_line(self, argv=None):
189 super(IPythonQtConsoleApp, self).parse_command_line(argv)
189 super(IPythonQtConsoleApp, self).parse_command_line(argv)
190 self.build_kernel_argv(argv)
190 self.build_kernel_argv(argv)
191
191
192
192
193 def new_frontend_master(self):
193 def new_frontend_master(self):
194 """ Create and return new frontend attached to new kernel, launched on localhost.
194 """ Create and return new frontend attached to new kernel, launched on localhost.
195 """
195 """
196 kernel_manager = self.kernel_manager_class(
196 kernel_manager = self.kernel_manager_class(
197 connection_file=self._new_connection_file(),
197 connection_file=self._new_connection_file(),
198 config=self.config,
198 config=self.config,
199 )
199 )
200 # start the kernel
200 # start the kernel
201 kwargs = dict()
201 kwargs = dict()
202 kwargs['extra_arguments'] = self.kernel_argv
202 kwargs['extra_arguments'] = self.kernel_argv
203 kernel_manager.start_kernel(**kwargs)
203 kernel_manager.start_kernel(**kwargs)
204 kernel_manager.start_channels()
204 kernel_manager.start_channels()
205 widget = self.widget_factory(config=self.config,
205 widget = self.widget_factory(config=self.config,
206 local_kernel=True)
206 local_kernel=True)
207 self.init_colors(widget)
207 self.init_colors(widget)
208 widget.kernel_manager = kernel_manager
208 widget.kernel_manager = kernel_manager
209 widget._existing = False
209 widget._existing = False
210 widget._may_close = True
210 widget._may_close = True
211 widget._confirm_exit = self.confirm_exit
211 widget._confirm_exit = self.confirm_exit
212 return widget
212 return widget
213
213
214 def new_frontend_slave(self, current_widget):
214 def new_frontend_slave(self, current_widget):
215 """Create and return a new frontend attached to an existing kernel.
215 """Create and return a new frontend attached to an existing kernel.
216
216
217 Parameters
217 Parameters
218 ----------
218 ----------
219 current_widget : IPythonWidget
219 current_widget : IPythonWidget
220 The IPythonWidget whose kernel this frontend is to share
220 The IPythonWidget whose kernel this frontend is to share
221 """
221 """
222 kernel_manager = self.kernel_manager_class(
222 kernel_manager = self.kernel_manager_class(
223 connection_file=current_widget.kernel_manager.connection_file,
223 connection_file=current_widget.kernel_manager.connection_file,
224 config = self.config,
224 config = self.config,
225 )
225 )
226 kernel_manager.load_connection_file()
226 kernel_manager.load_connection_file()
227 kernel_manager.start_channels()
227 kernel_manager.start_channels()
228 widget = self.widget_factory(config=self.config,
228 widget = self.widget_factory(config=self.config,
229 local_kernel=False)
229 local_kernel=False)
230 self.init_colors(widget)
230 self.init_colors(widget)
231 widget._existing = True
231 widget._existing = True
232 widget._may_close = False
232 widget._may_close = False
233 widget._confirm_exit = False
233 widget._confirm_exit = False
234 widget.kernel_manager = kernel_manager
234 widget.kernel_manager = kernel_manager
235 return widget
235 return widget
236
236
237 def init_qt_elements(self):
237 def init_qt_elements(self):
238 # Create the widget.
238 # Create the widget.
239 self.app = QtGui.QApplication([])
239 self.app = QtGui.QApplication([])
240
240
241 base_path = os.path.abspath(os.path.dirname(__file__))
241 base_path = os.path.abspath(os.path.dirname(__file__))
242 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
242 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
243 self.app.icon = QtGui.QIcon(icon_path)
243 self.app.icon = QtGui.QIcon(icon_path)
244 QtGui.QApplication.setWindowIcon(self.app.icon)
244 QtGui.QApplication.setWindowIcon(self.app.icon)
245
245
246 try:
246 try:
247 ip = self.config.KernelManager.ip
247 ip = self.config.KernelManager.ip
248 except AttributeError:
248 except AttributeError:
249 ip = LOCALHOST
249 ip = LOCALHOST
250 local_kernel = (not self.existing) or ip in LOCAL_IPS
250 local_kernel = (not self.existing) or ip in LOCAL_IPS
251 self.widget = self.widget_factory(config=self.config,
251 self.widget = self.widget_factory(config=self.config,
252 local_kernel=local_kernel)
252 local_kernel=local_kernel)
253 self.init_colors(self.widget)
253 self.init_colors(self.widget)
254 self.widget._existing = self.existing
254 self.widget._existing = self.existing
255 self.widget._may_close = not self.existing
255 self.widget._may_close = not self.existing
256 self.widget._confirm_exit = self.confirm_exit
256 self.widget._confirm_exit = self.confirm_exit
257
257
258 self.widget.kernel_manager = self.kernel_manager
258 self.widget.kernel_manager = self.kernel_manager
259 self.window = MainWindow(self.app,
259 self.window = MainWindow(self.app,
260 confirm_exit=self.confirm_exit,
260 confirm_exit=self.confirm_exit,
261 new_frontend_factory=self.new_frontend_master,
261 new_frontend_factory=self.new_frontend_master,
262 slave_frontend_factory=self.new_frontend_slave,
262 slave_frontend_factory=self.new_frontend_slave,
263 )
263 )
264 self.window.log = self.log
264 self.window.log = self.log
265 self.window.add_tab_with_frontend(self.widget)
265 self.window.add_tab_with_frontend(self.widget)
266 self.window.init_menu_bar()
266 self.window.init_menu_bar()
267
267
268 self.window.setWindowTitle('IPython')
268 self.window.setWindowTitle('IPython')
269
269
270 def init_colors(self, widget):
270 def init_colors(self, widget):
271 """Configure the coloring of the widget"""
271 """Configure the coloring of the widget"""
272 # Note: This will be dramatically simplified when colors
272 # Note: This will be dramatically simplified when colors
273 # are removed from the backend.
273 # are removed from the backend.
274
274
275 # parse the colors arg down to current known labels
275 # parse the colors arg down to current known labels
276 try:
276 try:
277 colors = self.config.ZMQInteractiveShell.colors
277 colors = self.config.ZMQInteractiveShell.colors
278 except AttributeError:
278 except AttributeError:
279 colors = None
279 colors = None
280 try:
280 try:
281 style = self.config.IPythonWidget.syntax_style
281 style = self.config.IPythonWidget.syntax_style
282 except AttributeError:
282 except AttributeError:
283 style = None
283 style = None
284 try:
284 try:
285 sheet = self.config.IPythonWidget.style_sheet
285 sheet = self.config.IPythonWidget.style_sheet
286 except AttributeError:
286 except AttributeError:
287 sheet = None
287 sheet = None
288
288
289 # find the value for colors:
289 # find the value for colors:
290 if colors:
290 if colors:
291 colors=colors.lower()
291 colors=colors.lower()
292 if colors in ('lightbg', 'light'):
292 if colors in ('lightbg', 'light'):
293 colors='lightbg'
293 colors='lightbg'
294 elif colors in ('dark', 'linux'):
294 elif colors in ('dark', 'linux'):
295 colors='linux'
295 colors='linux'
296 else:
296 else:
297 colors='nocolor'
297 colors='nocolor'
298 elif style:
298 elif style:
299 if style=='bw':
299 if style=='bw':
300 colors='nocolor'
300 colors='nocolor'
301 elif styles.dark_style(style):
301 elif styles.dark_style(style):
302 colors='linux'
302 colors='linux'
303 else:
303 else:
304 colors='lightbg'
304 colors='lightbg'
305 else:
305 else:
306 colors=None
306 colors=None
307
307
308 # Configure the style
308 # Configure the style
309 if style:
309 if style:
310 widget.style_sheet = styles.sheet_from_template(style, colors)
310 widget.style_sheet = styles.sheet_from_template(style, colors)
311 widget.syntax_style = style
311 widget.syntax_style = style
312 widget._syntax_style_changed()
312 widget._syntax_style_changed()
313 widget._style_sheet_changed()
313 widget._style_sheet_changed()
314 elif colors:
314 elif colors:
315 # use a default dark/light/bw style
315 # use a default dark/light/bw style
316 widget.set_default_style(colors=colors)
316 widget.set_default_style(colors=colors)
317
317
318 if self.stylesheet:
318 if self.stylesheet:
319 # we got an explicit stylesheet
319 # we got an explicit stylesheet
320 if os.path.isfile(self.stylesheet):
320 if os.path.isfile(self.stylesheet):
321 with open(self.stylesheet) as f:
321 with open(self.stylesheet) as f:
322 sheet = f.read()
322 sheet = f.read()
323 else:
323 else:
324 raise IOError("Stylesheet %r not found." % self.stylesheet)
324 raise IOError("Stylesheet %r not found." % self.stylesheet)
325 if sheet:
325 if sheet:
326 widget.style_sheet = sheet
326 widget.style_sheet = sheet
327 widget._style_sheet_changed()
327 widget._style_sheet_changed()
328
328
329
329
330 def init_signal(self):
330 def init_signal(self):
331 """allow clean shutdown on sigint"""
331 """allow clean shutdown on sigint"""
332 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
332 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
333 # need a timer, so that QApplication doesn't block until a real
333 # need a timer, so that QApplication doesn't block until a real
334 # Qt event fires (can require mouse movement)
334 # Qt event fires (can require mouse movement)
335 # timer trick from http://stackoverflow.com/q/4938723/938949
335 # timer trick from http://stackoverflow.com/q/4938723/938949
336 timer = QtCore.QTimer()
336 timer = QtCore.QTimer()
337 # Let the interpreter run each 200 ms:
337 # Let the interpreter run each 200 ms:
338 timer.timeout.connect(lambda: None)
338 timer.timeout.connect(lambda: None)
339 timer.start(200)
339 timer.start(200)
340 # hold onto ref, so the timer doesn't get cleaned up
340 # hold onto ref, so the timer doesn't get cleaned up
341 self._sigint_timer = timer
341 self._sigint_timer = timer
342
342
343 @catch_config_error
343 @catch_config_error
344 def initialize(self, argv=None):
344 def initialize(self, argv=None):
345 super(IPythonQtConsoleApp, self).initialize(argv)
345 super(IPythonQtConsoleApp, self).initialize(argv)
346 IPythonConsoleApp.initialize(self,argv)
346 IPythonConsoleApp.initialize(self,argv)
347 self.init_qt_elements()
347 self.init_qt_elements()
348 self.init_signal()
348 self.init_signal()
349
349
350 def start(self):
350 def start(self):
351
351
352 # draw the window
352 # draw the window
353 self.window.show()
353 self.window.show()
354 self.window.raise_()
354 self.window.raise_()
355
355
356 # Start the application main loop.
356 # Start the application main loop.
357 self.app.exec_()
357 self.app.exec_()
358
358
359 #-----------------------------------------------------------------------------
359 #-----------------------------------------------------------------------------
360 # Main entry point
360 # Main entry point
361 #-----------------------------------------------------------------------------
361 #-----------------------------------------------------------------------------
362
362
363 def main():
363 def main():
364 app = IPythonQtConsoleApp()
364 app = IPythonQtConsoleApp()
365 app.initialize()
365 app.initialize()
366 app.start()
366 app.start()
367
367
368
368
369 if __name__ == '__main__':
369 if __name__ == '__main__':
370 main()
370 main()
@@ -1,154 +1,154 b''
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.kernelapp 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 # disable quick startup, as it won't propagate to the kernel anyway
55 # disable quick startup, as it won't propagate to the kernel anyway
56 frontend_flags.pop('quick')
56 frontend_flags.pop('quick')
57 # update full dict with frontend flags:
57 # update full dict with frontend flags:
58 flags.update(frontend_flags)
58 flags.update(frontend_flags)
59
59
60 # copy flags from mixin
60 # copy flags from mixin
61 aliases = dict(aliases)
61 aliases = dict(aliases)
62 # start with mixin frontend flags
62 # start with mixin frontend flags
63 frontend_aliases = dict(app_aliases)
63 frontend_aliases = dict(app_aliases)
64 # load updated frontend flags into full dict
64 # load updated frontend flags into full dict
65 aliases.update(frontend_aliases)
65 aliases.update(frontend_aliases)
66
66
67 # get flags&aliases into sets, and remove a couple that
67 # get flags&aliases into sets, and remove a couple that
68 # shouldn't be scrubbed from backend flags:
68 # shouldn't be scrubbed from backend flags:
69 frontend_aliases = set(frontend_aliases.keys())
69 frontend_aliases = set(frontend_aliases.keys())
70 frontend_flags = set(frontend_flags.keys())
70 frontend_flags = set(frontend_flags.keys())
71
71
72
72
73 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
74 # Classes
74 # Classes
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76
76
77
77
78 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):
78 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):
79 name = "ipython-console"
79 name = "ipython-console"
80 """Start a terminal frontend to the IPython zmq kernel."""
80 """Start a terminal frontend to the IPython zmq kernel."""
81
81
82 description = """
82 description = """
83 The IPython terminal-based Console.
83 The IPython terminal-based Console.
84
84
85 This launches a Console application inside a terminal.
85 This launches a Console application inside a terminal.
86
86
87 The Console supports various extra features beyond the traditional
87 The Console supports various extra features beyond the traditional
88 single-process Terminal IPython shell, such as connecting to an
88 single-process Terminal IPython shell, such as connecting to an
89 existing ipython session, via:
89 existing ipython session, via:
90
90
91 ipython console --existing
91 ipython console --existing
92
92
93 where the previous session could have been created by another ipython
93 where the previous session could have been created by another ipython
94 console, an ipython qtconsole, or by opening an ipython notebook.
94 console, an ipython qtconsole, or by opening an ipython notebook.
95
95
96 """
96 """
97 examples = _examples
97 examples = _examples
98
98
99 classes = [ZMQTerminalInteractiveShell] + IPythonConsoleApp.classes
99 classes = [ZMQTerminalInteractiveShell] + IPythonConsoleApp.classes
100 flags = Dict(flags)
100 flags = Dict(flags)
101 aliases = Dict(aliases)
101 aliases = Dict(aliases)
102 frontend_aliases = Any(frontend_aliases)
102 frontend_aliases = Any(frontend_aliases)
103 frontend_flags = Any(frontend_flags)
103 frontend_flags = Any(frontend_flags)
104
104
105 subcommands = Dict()
105 subcommands = Dict()
106
106
107 def parse_command_line(self, argv=None):
107 def parse_command_line(self, argv=None):
108 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
108 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
109 self.build_kernel_argv(argv)
109 self.build_kernel_argv(argv)
110
110
111 def init_shell(self):
111 def init_shell(self):
112 IPythonConsoleApp.initialize(self)
112 IPythonConsoleApp.initialize(self)
113 # relay sigint to kernel
113 # relay sigint to kernel
114 signal.signal(signal.SIGINT, self.handle_sigint)
114 signal.signal(signal.SIGINT, self.handle_sigint)
115 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
115 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
116 display_banner=False, profile_dir=self.profile_dir,
116 display_banner=False, profile_dir=self.profile_dir,
117 ipython_dir=self.ipython_dir, kernel_manager=self.kernel_manager)
117 ipython_dir=self.ipython_dir, kernel_manager=self.kernel_manager)
118
118
119 def init_gui_pylab(self):
119 def init_gui_pylab(self):
120 # no-op, because we don't want to import matplotlib in the frontend.
120 # no-op, because we don't want to import matplotlib in the frontend.
121 pass
121 pass
122
122
123 def handle_sigint(self, *args):
123 def handle_sigint(self, *args):
124 if self.shell._executing:
124 if self.shell._executing:
125 if self.kernel_manager.has_kernel:
125 if self.kernel_manager.has_kernel:
126 # interrupt already gets passed to subprocess by signal handler.
126 # interrupt already gets passed to subprocess by signal handler.
127 # Only if we prevent that should we need to explicitly call
127 # Only if we prevent that should we need to explicitly call
128 # interrupt_kernel, until which time, this would result in a
128 # interrupt_kernel, until which time, this would result in a
129 # double-interrupt:
129 # double-interrupt:
130 # self.kernel_manager.interrupt_kernel()
130 # self.kernel_manager.interrupt_kernel()
131 pass
131 pass
132 else:
132 else:
133 self.shell.write_err('\n')
133 self.shell.write_err('\n')
134 error("Cannot interrupt kernels we didn't start.\n")
134 error("Cannot interrupt kernels we didn't start.\n")
135 else:
135 else:
136 # raise the KeyboardInterrupt if we aren't waiting for execution,
136 # raise the KeyboardInterrupt if we aren't waiting for execution,
137 # so that the interact loop advances, and prompt is redrawn, etc.
137 # so that the interact loop advances, and prompt is redrawn, etc.
138 raise KeyboardInterrupt
138 raise KeyboardInterrupt
139
139
140
140
141 def init_code(self):
141 def init_code(self):
142 # no-op in the frontend, code gets run in the backend
142 # no-op in the frontend, code gets run in the backend
143 pass
143 pass
144
144
145 def launch_new_instance():
145 def launch_new_instance():
146 """Create and run a full blown IPython instance"""
146 """Create and run a full blown IPython instance"""
147 app = ZMQTerminalIPythonApp.instance()
147 app = ZMQTerminalIPythonApp.instance()
148 app.initialize()
148 app.initialize()
149 app.start()
149 app.start()
150
150
151
151
152 if __name__ == '__main__':
152 if __name__ == '__main__':
153 launch_new_instance()
153 launch_new_instance()
154
154
@@ -1,393 +1,393 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The :class:`~IPython.core.application.Application` object for the command
4 The :class:`~IPython.core.application.Application` object for the command
5 line :command:`ipython` program.
5 line :command:`ipython` program.
6
6
7 Authors
7 Authors
8 -------
8 -------
9
9
10 * Brian Granger
10 * Brian Granger
11 * Fernando Perez
11 * Fernando Perez
12 * Min Ragan-Kelley
12 * Min Ragan-Kelley
13 """
13 """
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Copyright (C) 2008-2011 The IPython Development Team
16 # Copyright (C) 2008-2011 The IPython Development Team
17 #
17 #
18 # Distributed under the terms of the BSD License. The full license is in
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
19 # the file COPYING, distributed as part of this software.
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 from __future__ import absolute_import
26 from __future__ import absolute_import
27
27
28 import logging
28 import logging
29 import os
29 import os
30 import sys
30 import sys
31
31
32 from IPython.config.loader import (
32 from IPython.config.loader import (
33 Config, PyFileConfigLoader, ConfigFileNotFound
33 Config, PyFileConfigLoader, ConfigFileNotFound
34 )
34 )
35 from IPython.config.application import boolean_flag, catch_config_error
35 from IPython.config.application import boolean_flag, catch_config_error
36 from IPython.core import release
36 from IPython.core import release
37 from IPython.core import usage
37 from IPython.core import usage
38 from IPython.core.completer import IPCompleter
38 from IPython.core.completer import IPCompleter
39 from IPython.core.crashhandler import CrashHandler
39 from IPython.core.crashhandler import CrashHandler
40 from IPython.core.formatters import PlainTextFormatter
40 from IPython.core.formatters import PlainTextFormatter
41 from IPython.core.history import HistoryManager
41 from IPython.core.history import HistoryManager
42 from IPython.core.prompts import PromptManager
42 from IPython.core.prompts import PromptManager
43 from IPython.core.application import (
43 from IPython.core.application import (
44 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
44 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
45 )
45 )
46 from IPython.core.magics import ScriptMagics
46 from IPython.core.magics import ScriptMagics
47 from IPython.core.shellapp import (
47 from IPython.core.shellapp import (
48 InteractiveShellApp, shell_flags, shell_aliases
48 InteractiveShellApp, shell_flags, shell_aliases
49 )
49 )
50 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
50 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
51 from IPython.lib import inputhook
51 from IPython.lib import inputhook
52 from IPython.utils import warn
52 from IPython.utils import warn
53 from IPython.utils.path import get_ipython_dir, check_for_old_config
53 from IPython.utils.path import get_ipython_dir, check_for_old_config
54 from IPython.utils.traitlets import (
54 from IPython.utils.traitlets import (
55 Bool, List, Dict, CaselessStrEnum
55 Bool, List, Dict, CaselessStrEnum
56 )
56 )
57
57
58 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
59 # Globals, utilities and helpers
59 # Globals, utilities and helpers
60 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
61
61
62 #: The default config file name for this application.
62 #: The default config file name for this application.
63 default_config_file_name = u'ipython_config.py'
63 default_config_file_name = u'ipython_config.py'
64
64
65 _examples = """
65 _examples = """
66 ipython --pylab # start in pylab mode
66 ipython --pylab # start in pylab mode
67 ipython --pylab=qt # start in pylab mode with the qt4 backend
67 ipython --pylab=qt # start in pylab mode with the qt4 backend
68 ipython --log-level=DEBUG # set logging to DEBUG
68 ipython --log-level=DEBUG # set logging to DEBUG
69 ipython --profile=foo # start with profile foo
69 ipython --profile=foo # start with profile foo
70
70
71 ipython qtconsole # start the qtconsole GUI application
71 ipython qtconsole # start the qtconsole GUI application
72 ipython help qtconsole # show the help for the qtconsole subcmd
72 ipython help qtconsole # show the help for the qtconsole subcmd
73
73
74 ipython console # start the terminal-based console application
74 ipython console # start the terminal-based console application
75 ipython help console # show the help for the console subcmd
75 ipython help console # show the help for the console subcmd
76
76
77 ipython notebook # start the IPython notebook
77 ipython notebook # start the IPython notebook
78 ipython help notebook # show the help for the notebook subcmd
78 ipython help notebook # show the help for the notebook subcmd
79
79
80 ipython profile create foo # create profile foo w/ default config files
80 ipython profile create foo # create profile foo w/ default config files
81 ipython help profile # show the help for the profile subcmd
81 ipython help profile # show the help for the profile subcmd
82
82
83 ipython locate # print the path to the IPython directory
83 ipython locate # print the path to the IPython directory
84 ipython locate profile foo # print the path to the directory for profile `foo`
84 ipython locate profile foo # print the path to the directory for profile `foo`
85 """
85 """
86
86
87 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
88 # Crash handler for this application
88 # Crash handler for this application
89 #-----------------------------------------------------------------------------
89 #-----------------------------------------------------------------------------
90
90
91 class IPAppCrashHandler(CrashHandler):
91 class IPAppCrashHandler(CrashHandler):
92 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
92 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
93
93
94 def __init__(self, app):
94 def __init__(self, app):
95 contact_name = release.author
95 contact_name = release.author
96 contact_email = release.author_email
96 contact_email = release.author_email
97 bug_tracker = 'https://github.com/ipython/ipython/issues'
97 bug_tracker = 'https://github.com/ipython/ipython/issues'
98 super(IPAppCrashHandler,self).__init__(
98 super(IPAppCrashHandler,self).__init__(
99 app, contact_name, contact_email, bug_tracker
99 app, contact_name, contact_email, bug_tracker
100 )
100 )
101
101
102 def make_report(self,traceback):
102 def make_report(self,traceback):
103 """Return a string containing a crash report."""
103 """Return a string containing a crash report."""
104
104
105 sec_sep = self.section_sep
105 sec_sep = self.section_sep
106 # Start with parent report
106 # Start with parent report
107 report = [super(IPAppCrashHandler, self).make_report(traceback)]
107 report = [super(IPAppCrashHandler, self).make_report(traceback)]
108 # Add interactive-specific info we may have
108 # Add interactive-specific info we may have
109 rpt_add = report.append
109 rpt_add = report.append
110 try:
110 try:
111 rpt_add(sec_sep+"History of session input:")
111 rpt_add(sec_sep+"History of session input:")
112 for line in self.app.shell.user_ns['_ih']:
112 for line in self.app.shell.user_ns['_ih']:
113 rpt_add(line)
113 rpt_add(line)
114 rpt_add('\n*** Last line of input (may not be in above history):\n')
114 rpt_add('\n*** Last line of input (may not be in above history):\n')
115 rpt_add(self.app.shell._last_input_line+'\n')
115 rpt_add(self.app.shell._last_input_line+'\n')
116 except:
116 except:
117 pass
117 pass
118
118
119 return ''.join(report)
119 return ''.join(report)
120
120
121 #-----------------------------------------------------------------------------
121 #-----------------------------------------------------------------------------
122 # Aliases and Flags
122 # Aliases and Flags
123 #-----------------------------------------------------------------------------
123 #-----------------------------------------------------------------------------
124 flags = dict(base_flags)
124 flags = dict(base_flags)
125 flags.update(shell_flags)
125 flags.update(shell_flags)
126 frontend_flags = {}
126 frontend_flags = {}
127 addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
127 addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
128 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
128 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
129 'Turn on auto editing of files with syntax errors.',
129 'Turn on auto editing of files with syntax errors.',
130 'Turn off auto editing of files with syntax errors.'
130 'Turn off auto editing of files with syntax errors.'
131 )
131 )
132 addflag('banner', 'TerminalIPythonApp.display_banner',
132 addflag('banner', 'TerminalIPythonApp.display_banner',
133 "Display a banner upon starting IPython.",
133 "Display a banner upon starting IPython.",
134 "Don't display a banner upon starting IPython."
134 "Don't display a banner upon starting IPython."
135 )
135 )
136 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
136 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
137 """Set to confirm when you try to exit IPython with an EOF (Control-D
137 """Set to confirm when you try to exit IPython with an EOF (Control-D
138 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
138 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
139 you can force a direct exit without any confirmation.""",
139 you can force a direct exit without any confirmation.""",
140 "Don't prompt the user when exiting."
140 "Don't prompt the user when exiting."
141 )
141 )
142 addflag('term-title', 'TerminalInteractiveShell.term_title',
142 addflag('term-title', 'TerminalInteractiveShell.term_title',
143 "Enable auto setting the terminal title.",
143 "Enable auto setting the terminal title.",
144 "Disable auto setting the terminal title."
144 "Disable auto setting the terminal title."
145 )
145 )
146 classic_config = Config()
146 classic_config = Config()
147 classic_config.InteractiveShell.cache_size = 0
147 classic_config.InteractiveShell.cache_size = 0
148 classic_config.PlainTextFormatter.pprint = False
148 classic_config.PlainTextFormatter.pprint = False
149 classic_config.PromptManager.in_template = '>>> '
149 classic_config.PromptManager.in_template = '>>> '
150 classic_config.PromptManager.in2_template = '... '
150 classic_config.PromptManager.in2_template = '... '
151 classic_config.PromptManager.out_template = ''
151 classic_config.PromptManager.out_template = ''
152 classic_config.InteractiveShell.separate_in = ''
152 classic_config.InteractiveShell.separate_in = ''
153 classic_config.InteractiveShell.separate_out = ''
153 classic_config.InteractiveShell.separate_out = ''
154 classic_config.InteractiveShell.separate_out2 = ''
154 classic_config.InteractiveShell.separate_out2 = ''
155 classic_config.InteractiveShell.colors = 'NoColor'
155 classic_config.InteractiveShell.colors = 'NoColor'
156 classic_config.InteractiveShell.xmode = 'Plain'
156 classic_config.InteractiveShell.xmode = 'Plain'
157
157
158 frontend_flags['classic']=(
158 frontend_flags['classic']=(
159 classic_config,
159 classic_config,
160 "Gives IPython a similar feel to the classic Python prompt."
160 "Gives IPython a similar feel to the classic Python prompt."
161 )
161 )
162 # # log doesn't make so much sense this way anymore
162 # # log doesn't make so much sense this way anymore
163 # paa('--log','-l',
163 # paa('--log','-l',
164 # action='store_true', dest='InteractiveShell.logstart',
164 # action='store_true', dest='InteractiveShell.logstart',
165 # help="Start logging to the default log file (./ipython_log.py).")
165 # help="Start logging to the default log file (./ipython_log.py).")
166 #
166 #
167 # # quick is harder to implement
167 # # quick is harder to implement
168 frontend_flags['quick']=(
168 frontend_flags['quick']=(
169 {'TerminalIPythonApp' : {'quick' : True}},
169 {'TerminalIPythonApp' : {'quick' : True}},
170 "Enable quick startup with no config files."
170 "Enable quick startup with no config files."
171 )
171 )
172
172
173 frontend_flags['i'] = (
173 frontend_flags['i'] = (
174 {'TerminalIPythonApp' : {'force_interact' : True}},
174 {'TerminalIPythonApp' : {'force_interact' : True}},
175 """If running code from the command line, become interactive afterwards.
175 """If running code from the command line, become interactive afterwards.
176 Note: can also be given simply as '-i.'"""
176 Note: can also be given simply as '-i.'"""
177 )
177 )
178 flags.update(frontend_flags)
178 flags.update(frontend_flags)
179
179
180 aliases = dict(base_aliases)
180 aliases = dict(base_aliases)
181 aliases.update(shell_aliases)
181 aliases.update(shell_aliases)
182
182
183 #-----------------------------------------------------------------------------
183 #-----------------------------------------------------------------------------
184 # Main classes and functions
184 # Main classes and functions
185 #-----------------------------------------------------------------------------
185 #-----------------------------------------------------------------------------
186
186
187
187
188 class LocateIPythonApp(BaseIPythonApplication):
188 class LocateIPythonApp(BaseIPythonApplication):
189 description = """print the path to the IPython dir"""
189 description = """print the path to the IPython dir"""
190 subcommands = Dict(dict(
190 subcommands = Dict(dict(
191 profile=('IPython.core.profileapp.ProfileLocate',
191 profile=('IPython.core.profileapp.ProfileLocate',
192 "print the path to an IPython profile directory",
192 "print the path to an IPython profile directory",
193 ),
193 ),
194 ))
194 ))
195 def start(self):
195 def start(self):
196 if self.subapp is not None:
196 if self.subapp is not None:
197 return self.subapp.start()
197 return self.subapp.start()
198 else:
198 else:
199 print self.ipython_dir
199 print self.ipython_dir
200
200
201
201
202 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
202 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
203 name = u'ipython'
203 name = u'ipython'
204 description = usage.cl_usage
204 description = usage.cl_usage
205 default_config_file_name = default_config_file_name
205 default_config_file_name = default_config_file_name
206 crash_handler_class = IPAppCrashHandler
206 crash_handler_class = IPAppCrashHandler
207 examples = _examples
207 examples = _examples
208
208
209 flags = Dict(flags)
209 flags = Dict(flags)
210 aliases = Dict(aliases)
210 aliases = Dict(aliases)
211 classes = List()
211 classes = List()
212 def _classes_default(self):
212 def _classes_default(self):
213 """This has to be in a method, for TerminalIPythonApp to be available."""
213 """This has to be in a method, for TerminalIPythonApp to be available."""
214 return [
214 return [
215 InteractiveShellApp, # ShellApp comes before TerminalApp, because
215 InteractiveShellApp, # ShellApp comes before TerminalApp, because
216 self.__class__, # it will also affect subclasses (e.g. QtConsole)
216 self.__class__, # it will also affect subclasses (e.g. QtConsole)
217 TerminalInteractiveShell,
217 TerminalInteractiveShell,
218 PromptManager,
218 PromptManager,
219 HistoryManager,
219 HistoryManager,
220 ProfileDir,
220 ProfileDir,
221 PlainTextFormatter,
221 PlainTextFormatter,
222 IPCompleter,
222 IPCompleter,
223 ScriptMagics,
223 ScriptMagics,
224 ]
224 ]
225
225
226 subcommands = Dict(dict(
226 subcommands = Dict(dict(
227 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
227 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
228 """Launch the IPython Qt Console."""
228 """Launch the IPython Qt Console."""
229 ),
229 ),
230 notebook=('IPython.frontend.html.notebook.notebookapp.NotebookApp',
230 notebook=('IPython.frontend.html.notebook.notebookapp.NotebookApp',
231 """Launch the IPython HTML Notebook Server."""
231 """Launch the IPython HTML Notebook Server."""
232 ),
232 ),
233 profile = ("IPython.core.profileapp.ProfileApp",
233 profile = ("IPython.core.profileapp.ProfileApp",
234 "Create and manage IPython profiles."
234 "Create and manage IPython profiles."
235 ),
235 ),
236 kernel = ("IPython.zmq.ipkernel.IPKernelApp",
236 kernel = ("IPython.zmq.kernelapp.IPKernelApp",
237 "Start a kernel without an attached frontend."
237 "Start a kernel without an attached frontend."
238 ),
238 ),
239 console=('IPython.frontend.terminal.console.app.ZMQTerminalIPythonApp',
239 console=('IPython.frontend.terminal.console.app.ZMQTerminalIPythonApp',
240 """Launch the IPython terminal-based Console."""
240 """Launch the IPython terminal-based Console."""
241 ),
241 ),
242 locate=('IPython.frontend.terminal.ipapp.LocateIPythonApp',
242 locate=('IPython.frontend.terminal.ipapp.LocateIPythonApp',
243 LocateIPythonApp.description
243 LocateIPythonApp.description
244 ),
244 ),
245 ))
245 ))
246
246
247 # *do* autocreate requested profile, but don't create the config file.
247 # *do* autocreate requested profile, but don't create the config file.
248 auto_create=Bool(True)
248 auto_create=Bool(True)
249 # configurables
249 # configurables
250 ignore_old_config=Bool(False, config=True,
250 ignore_old_config=Bool(False, config=True,
251 help="Suppress warning messages about legacy config files"
251 help="Suppress warning messages about legacy config files"
252 )
252 )
253 quick = Bool(False, config=True,
253 quick = Bool(False, config=True,
254 help="""Start IPython quickly by skipping the loading of config files."""
254 help="""Start IPython quickly by skipping the loading of config files."""
255 )
255 )
256 def _quick_changed(self, name, old, new):
256 def _quick_changed(self, name, old, new):
257 if new:
257 if new:
258 self.load_config_file = lambda *a, **kw: None
258 self.load_config_file = lambda *a, **kw: None
259 self.ignore_old_config=True
259 self.ignore_old_config=True
260
260
261 display_banner = Bool(True, config=True,
261 display_banner = Bool(True, config=True,
262 help="Whether to display a banner upon starting IPython."
262 help="Whether to display a banner upon starting IPython."
263 )
263 )
264
264
265 # if there is code of files to run from the cmd line, don't interact
265 # if there is code of files to run from the cmd line, don't interact
266 # unless the --i flag (App.force_interact) is true.
266 # unless the --i flag (App.force_interact) is true.
267 force_interact = Bool(False, config=True,
267 force_interact = Bool(False, config=True,
268 help="""If a command or file is given via the command-line,
268 help="""If a command or file is given via the command-line,
269 e.g. 'ipython foo.py"""
269 e.g. 'ipython foo.py"""
270 )
270 )
271 def _force_interact_changed(self, name, old, new):
271 def _force_interact_changed(self, name, old, new):
272 if new:
272 if new:
273 self.interact = True
273 self.interact = True
274
274
275 def _file_to_run_changed(self, name, old, new):
275 def _file_to_run_changed(self, name, old, new):
276 if new:
276 if new:
277 self.something_to_run = True
277 self.something_to_run = True
278 if new and not self.force_interact:
278 if new and not self.force_interact:
279 self.interact = False
279 self.interact = False
280 _code_to_run_changed = _file_to_run_changed
280 _code_to_run_changed = _file_to_run_changed
281 _module_to_run_changed = _file_to_run_changed
281 _module_to_run_changed = _file_to_run_changed
282
282
283 # internal, not-configurable
283 # internal, not-configurable
284 interact=Bool(True)
284 interact=Bool(True)
285 something_to_run=Bool(False)
285 something_to_run=Bool(False)
286
286
287 def parse_command_line(self, argv=None):
287 def parse_command_line(self, argv=None):
288 """override to allow old '-pylab' flag with deprecation warning"""
288 """override to allow old '-pylab' flag with deprecation warning"""
289
289
290 argv = sys.argv[1:] if argv is None else argv
290 argv = sys.argv[1:] if argv is None else argv
291
291
292 if '-pylab' in argv:
292 if '-pylab' in argv:
293 # deprecated `-pylab` given,
293 # deprecated `-pylab` given,
294 # warn and transform into current syntax
294 # warn and transform into current syntax
295 argv = argv[:] # copy, don't clobber
295 argv = argv[:] # copy, don't clobber
296 idx = argv.index('-pylab')
296 idx = argv.index('-pylab')
297 warn.warn("`-pylab` flag has been deprecated.\n"
297 warn.warn("`-pylab` flag has been deprecated.\n"
298 " Use `--pylab` instead, or `--pylab=foo` to specify a backend.")
298 " Use `--pylab` instead, or `--pylab=foo` to specify a backend.")
299 sub = '--pylab'
299 sub = '--pylab'
300 if len(argv) > idx+1:
300 if len(argv) > idx+1:
301 # check for gui arg, as in '-pylab qt'
301 # check for gui arg, as in '-pylab qt'
302 gui = argv[idx+1]
302 gui = argv[idx+1]
303 if gui in ('wx', 'qt', 'qt4', 'gtk', 'auto'):
303 if gui in ('wx', 'qt', 'qt4', 'gtk', 'auto'):
304 sub = '--pylab='+gui
304 sub = '--pylab='+gui
305 argv.pop(idx+1)
305 argv.pop(idx+1)
306 argv[idx] = sub
306 argv[idx] = sub
307
307
308 return super(TerminalIPythonApp, self).parse_command_line(argv)
308 return super(TerminalIPythonApp, self).parse_command_line(argv)
309
309
310 @catch_config_error
310 @catch_config_error
311 def initialize(self, argv=None):
311 def initialize(self, argv=None):
312 """Do actions after construct, but before starting the app."""
312 """Do actions after construct, but before starting the app."""
313 super(TerminalIPythonApp, self).initialize(argv)
313 super(TerminalIPythonApp, self).initialize(argv)
314 if self.subapp is not None:
314 if self.subapp is not None:
315 # don't bother initializing further, starting subapp
315 # don't bother initializing further, starting subapp
316 return
316 return
317 if not self.ignore_old_config:
317 if not self.ignore_old_config:
318 check_for_old_config(self.ipython_dir)
318 check_for_old_config(self.ipython_dir)
319 # print self.extra_args
319 # print self.extra_args
320 if self.extra_args and not self.something_to_run:
320 if self.extra_args and not self.something_to_run:
321 self.file_to_run = self.extra_args[0]
321 self.file_to_run = self.extra_args[0]
322 self.init_path()
322 self.init_path()
323 # create the shell
323 # create the shell
324 self.init_shell()
324 self.init_shell()
325 # and draw the banner
325 # and draw the banner
326 self.init_banner()
326 self.init_banner()
327 # Now a variety of things that happen after the banner is printed.
327 # Now a variety of things that happen after the banner is printed.
328 self.init_gui_pylab()
328 self.init_gui_pylab()
329 self.init_extensions()
329 self.init_extensions()
330 self.init_code()
330 self.init_code()
331
331
332 def init_shell(self):
332 def init_shell(self):
333 """initialize the InteractiveShell instance"""
333 """initialize the InteractiveShell instance"""
334 # Create an InteractiveShell instance.
334 # Create an InteractiveShell instance.
335 # shell.display_banner should always be False for the terminal
335 # shell.display_banner should always be False for the terminal
336 # based app, because we call shell.show_banner() by hand below
336 # based app, because we call shell.show_banner() by hand below
337 # so the banner shows *before* all extension loading stuff.
337 # so the banner shows *before* all extension loading stuff.
338 self.shell = TerminalInteractiveShell.instance(config=self.config,
338 self.shell = TerminalInteractiveShell.instance(config=self.config,
339 display_banner=False, profile_dir=self.profile_dir,
339 display_banner=False, profile_dir=self.profile_dir,
340 ipython_dir=self.ipython_dir)
340 ipython_dir=self.ipython_dir)
341 self.shell.configurables.append(self)
341 self.shell.configurables.append(self)
342
342
343 def init_banner(self):
343 def init_banner(self):
344 """optionally display the banner"""
344 """optionally display the banner"""
345 if self.display_banner and self.interact:
345 if self.display_banner and self.interact:
346 self.shell.show_banner()
346 self.shell.show_banner()
347 # Make sure there is a space below the banner.
347 # Make sure there is a space below the banner.
348 if self.log_level <= logging.INFO: print
348 if self.log_level <= logging.INFO: print
349
349
350 def _pylab_changed(self, name, old, new):
350 def _pylab_changed(self, name, old, new):
351 """Replace --pylab='inline' with --pylab='auto'"""
351 """Replace --pylab='inline' with --pylab='auto'"""
352 if new == 'inline':
352 if new == 'inline':
353 warn.warn("'inline' not available as pylab backend, "
353 warn.warn("'inline' not available as pylab backend, "
354 "using 'auto' instead.")
354 "using 'auto' instead.")
355 self.pylab = 'auto'
355 self.pylab = 'auto'
356
356
357 def start(self):
357 def start(self):
358 if self.subapp is not None:
358 if self.subapp is not None:
359 return self.subapp.start()
359 return self.subapp.start()
360 # perform any prexec steps:
360 # perform any prexec steps:
361 if self.interact:
361 if self.interact:
362 self.log.debug("Starting IPython's mainloop...")
362 self.log.debug("Starting IPython's mainloop...")
363 self.shell.mainloop()
363 self.shell.mainloop()
364 else:
364 else:
365 self.log.debug("IPython not interactive...")
365 self.log.debug("IPython not interactive...")
366
366
367
367
368 def load_default_config(ipython_dir=None):
368 def load_default_config(ipython_dir=None):
369 """Load the default config file from the default ipython_dir.
369 """Load the default config file from the default ipython_dir.
370
370
371 This is useful for embedded shells.
371 This is useful for embedded shells.
372 """
372 """
373 if ipython_dir is None:
373 if ipython_dir is None:
374 ipython_dir = get_ipython_dir()
374 ipython_dir = get_ipython_dir()
375 profile_dir = os.path.join(ipython_dir, 'profile_default')
375 profile_dir = os.path.join(ipython_dir, 'profile_default')
376 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
376 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
377 try:
377 try:
378 config = cl.load_config()
378 config = cl.load_config()
379 except ConfigFileNotFound:
379 except ConfigFileNotFound:
380 # no config found
380 # no config found
381 config = Config()
381 config = Config()
382 return config
382 return config
383
383
384
384
385 def launch_new_instance():
385 def launch_new_instance():
386 """Create and run a full blown IPython instance"""
386 """Create and run a full blown IPython instance"""
387 app = TerminalIPythonApp.instance()
387 app = TerminalIPythonApp.instance()
388 app.initialize()
388 app.initialize()
389 app.start()
389 app.start()
390
390
391
391
392 if __name__ == '__main__':
392 if __name__ == '__main__':
393 launch_new_instance()
393 launch_new_instance()
@@ -1,339 +1,339 b''
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) 2013 The IPython Development Team
10 # Copyright (C) 2013 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 socket
23 import socket
24 import sys
24 import sys
25 from getpass import getpass
25 from getpass import getpass
26 from subprocess import Popen, PIPE
26 from subprocess import Popen, PIPE
27 import tempfile
27 import tempfile
28
28
29 # external imports
29 # external imports
30 from IPython.external.ssh import tunnel
30 from IPython.external.ssh import tunnel
31
31
32 # IPython imports
32 # IPython imports
33 from IPython.core.profiledir import ProfileDir
33 from IPython.core.profiledir import ProfileDir
34 from IPython.utils.localinterfaces import LOCALHOST
34 from IPython.utils.localinterfaces import LOCALHOST
35 from IPython.utils.path import filefind, get_ipython_dir
35 from IPython.utils.path import filefind, get_ipython_dir
36 from IPython.utils.py3compat import str_to_bytes, bytes_to_str
36 from IPython.utils.py3compat import str_to_bytes, bytes_to_str
37
37
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # Working with Connection Files
40 # Working with Connection Files
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
43 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
44 ip=LOCALHOST, key=b'', transport='tcp'):
44 ip=LOCALHOST, key=b'', transport='tcp'):
45 """Generates a JSON config file, including the selection of random ports.
45 """Generates a JSON config file, including the selection of random ports.
46
46
47 Parameters
47 Parameters
48 ----------
48 ----------
49
49
50 fname : unicode
50 fname : unicode
51 The path to the file to write
51 The path to the file to write
52
52
53 shell_port : int, optional
53 shell_port : int, optional
54 The port to use for ROUTER channel.
54 The port to use for ROUTER channel.
55
55
56 iopub_port : int, optional
56 iopub_port : int, optional
57 The port to use for the SUB channel.
57 The port to use for the SUB channel.
58
58
59 stdin_port : int, optional
59 stdin_port : int, optional
60 The port to use for the REQ (raw input) channel.
60 The port to use for the REQ (raw input) channel.
61
61
62 hb_port : int, optional
62 hb_port : int, optional
63 The port to use for the hearbeat REP channel.
63 The port to use for the hearbeat REP channel.
64
64
65 ip : str, optional
65 ip : str, optional
66 The ip address the kernel will bind to.
66 The ip address the kernel will bind to.
67
67
68 key : str, optional
68 key : str, optional
69 The Session key used for HMAC authentication.
69 The Session key used for HMAC authentication.
70
70
71 """
71 """
72 # default to temporary connector file
72 # default to temporary connector file
73 if not fname:
73 if not fname:
74 fname = tempfile.mktemp('.json')
74 fname = tempfile.mktemp('.json')
75
75
76 # Find open ports as necessary.
76 # Find open ports as necessary.
77
77
78 ports = []
78 ports = []
79 ports_needed = int(shell_port <= 0) + int(iopub_port <= 0) + \
79 ports_needed = int(shell_port <= 0) + int(iopub_port <= 0) + \
80 int(stdin_port <= 0) + int(hb_port <= 0)
80 int(stdin_port <= 0) + int(hb_port <= 0)
81 if transport == 'tcp':
81 if transport == 'tcp':
82 for i in range(ports_needed):
82 for i in range(ports_needed):
83 sock = socket.socket()
83 sock = socket.socket()
84 sock.bind(('', 0))
84 sock.bind(('', 0))
85 ports.append(sock)
85 ports.append(sock)
86 for i, sock in enumerate(ports):
86 for i, sock in enumerate(ports):
87 port = sock.getsockname()[1]
87 port = sock.getsockname()[1]
88 sock.close()
88 sock.close()
89 ports[i] = port
89 ports[i] = port
90 else:
90 else:
91 N = 1
91 N = 1
92 for i in range(ports_needed):
92 for i in range(ports_needed):
93 while os.path.exists("%s-%s" % (ip, str(N))):
93 while os.path.exists("%s-%s" % (ip, str(N))):
94 N += 1
94 N += 1
95 ports.append(N)
95 ports.append(N)
96 N += 1
96 N += 1
97 if shell_port <= 0:
97 if shell_port <= 0:
98 shell_port = ports.pop(0)
98 shell_port = ports.pop(0)
99 if iopub_port <= 0:
99 if iopub_port <= 0:
100 iopub_port = ports.pop(0)
100 iopub_port = ports.pop(0)
101 if stdin_port <= 0:
101 if stdin_port <= 0:
102 stdin_port = ports.pop(0)
102 stdin_port = ports.pop(0)
103 if hb_port <= 0:
103 if hb_port <= 0:
104 hb_port = ports.pop(0)
104 hb_port = ports.pop(0)
105
105
106 cfg = dict( shell_port=shell_port,
106 cfg = dict( shell_port=shell_port,
107 iopub_port=iopub_port,
107 iopub_port=iopub_port,
108 stdin_port=stdin_port,
108 stdin_port=stdin_port,
109 hb_port=hb_port,
109 hb_port=hb_port,
110 )
110 )
111 cfg['ip'] = ip
111 cfg['ip'] = ip
112 cfg['key'] = bytes_to_str(key)
112 cfg['key'] = bytes_to_str(key)
113 cfg['transport'] = transport
113 cfg['transport'] = transport
114
114
115 with open(fname, 'w') as f:
115 with open(fname, 'w') as f:
116 f.write(json.dumps(cfg, indent=2))
116 f.write(json.dumps(cfg, indent=2))
117
117
118 return fname, cfg
118 return fname, cfg
119
119
120
120
121 def get_connection_file(app=None):
121 def get_connection_file(app=None):
122 """Return the path to the connection file of an app
122 """Return the path to the connection file of an app
123
123
124 Parameters
124 Parameters
125 ----------
125 ----------
126 app : KernelApp instance [optional]
126 app : KernelApp instance [optional]
127 If unspecified, the currently running app will be used
127 If unspecified, the currently running app will be used
128 """
128 """
129 if app is None:
129 if app is None:
130 from IPython.zmq.ipkernel import IPKernelApp
130 from IPython.zmq.kernelapp import IPKernelApp
131 if not IPKernelApp.initialized():
131 if not IPKernelApp.initialized():
132 raise RuntimeError("app not specified, and not in a running Kernel")
132 raise RuntimeError("app not specified, and not in a running Kernel")
133
133
134 app = IPKernelApp.instance()
134 app = IPKernelApp.instance()
135 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
135 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
136
136
137
137
138 def find_connection_file(filename, profile=None):
138 def find_connection_file(filename, profile=None):
139 """find a connection file, and return its absolute path.
139 """find a connection file, and return its absolute path.
140
140
141 The current working directory and the profile's security
141 The current working directory and the profile's security
142 directory will be searched for the file if it is not given by
142 directory will be searched for the file if it is not given by
143 absolute path.
143 absolute path.
144
144
145 If profile is unspecified, then the current running application's
145 If profile is unspecified, then the current running application's
146 profile will be used, or 'default', if not run from IPython.
146 profile will be used, or 'default', if not run from IPython.
147
147
148 If the argument does not match an existing file, it will be interpreted as a
148 If the argument does not match an existing file, it will be interpreted as a
149 fileglob, and the matching file in the profile's security dir with
149 fileglob, and the matching file in the profile's security dir with
150 the latest access time will be used.
150 the latest access time will be used.
151
151
152 Parameters
152 Parameters
153 ----------
153 ----------
154 filename : str
154 filename : str
155 The connection file or fileglob to search for.
155 The connection file or fileglob to search for.
156 profile : str [optional]
156 profile : str [optional]
157 The name of the profile to use when searching for the connection file,
157 The name of the profile to use when searching for the connection file,
158 if different from the current IPython session or 'default'.
158 if different from the current IPython session or 'default'.
159
159
160 Returns
160 Returns
161 -------
161 -------
162 str : The absolute path of the connection file.
162 str : The absolute path of the connection file.
163 """
163 """
164 from IPython.core.application import BaseIPythonApplication as IPApp
164 from IPython.core.application import BaseIPythonApplication as IPApp
165 try:
165 try:
166 # quick check for absolute path, before going through logic
166 # quick check for absolute path, before going through logic
167 return filefind(filename)
167 return filefind(filename)
168 except IOError:
168 except IOError:
169 pass
169 pass
170
170
171 if profile is None:
171 if profile is None:
172 # profile unspecified, check if running from an IPython app
172 # profile unspecified, check if running from an IPython app
173 if IPApp.initialized():
173 if IPApp.initialized():
174 app = IPApp.instance()
174 app = IPApp.instance()
175 profile_dir = app.profile_dir
175 profile_dir = app.profile_dir
176 else:
176 else:
177 # not running in IPython, use default profile
177 # not running in IPython, use default profile
178 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
178 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
179 else:
179 else:
180 # find profiledir by profile name:
180 # find profiledir by profile name:
181 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
181 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
182 security_dir = profile_dir.security_dir
182 security_dir = profile_dir.security_dir
183
183
184 try:
184 try:
185 # first, try explicit name
185 # first, try explicit name
186 return filefind(filename, ['.', security_dir])
186 return filefind(filename, ['.', security_dir])
187 except IOError:
187 except IOError:
188 pass
188 pass
189
189
190 # not found by full name
190 # not found by full name
191
191
192 if '*' in filename:
192 if '*' in filename:
193 # given as a glob already
193 # given as a glob already
194 pat = filename
194 pat = filename
195 else:
195 else:
196 # accept any substring match
196 # accept any substring match
197 pat = '*%s*' % filename
197 pat = '*%s*' % filename
198 matches = glob.glob( os.path.join(security_dir, pat) )
198 matches = glob.glob( os.path.join(security_dir, pat) )
199 if not matches:
199 if not matches:
200 raise IOError("Could not find %r in %r" % (filename, security_dir))
200 raise IOError("Could not find %r in %r" % (filename, security_dir))
201 elif len(matches) == 1:
201 elif len(matches) == 1:
202 return matches[0]
202 return matches[0]
203 else:
203 else:
204 # get most recent match, by access time:
204 # get most recent match, by access time:
205 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
205 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
206
206
207
207
208 def get_connection_info(connection_file=None, unpack=False, profile=None):
208 def get_connection_info(connection_file=None, unpack=False, profile=None):
209 """Return the connection information for the current Kernel.
209 """Return the connection information for the current Kernel.
210
210
211 Parameters
211 Parameters
212 ----------
212 ----------
213 connection_file : str [optional]
213 connection_file : str [optional]
214 The connection file to be used. Can be given by absolute path, or
214 The connection file to be used. Can be given by absolute path, or
215 IPython will search in the security directory of a given profile.
215 IPython will search in the security directory of a given profile.
216 If run from IPython,
216 If run from IPython,
217
217
218 If unspecified, the connection file for the currently running
218 If unspecified, the connection file for the currently running
219 IPython Kernel will be used, which is only allowed from inside a kernel.
219 IPython Kernel will be used, which is only allowed from inside a kernel.
220 unpack : bool [default: False]
220 unpack : bool [default: False]
221 if True, return the unpacked dict, otherwise just the string contents
221 if True, return the unpacked dict, otherwise just the string contents
222 of the file.
222 of the file.
223 profile : str [optional]
223 profile : str [optional]
224 The name of the profile to use when searching for the connection file,
224 The name of the profile to use when searching for the connection file,
225 if different from the current IPython session or 'default'.
225 if different from the current IPython session or 'default'.
226
226
227
227
228 Returns
228 Returns
229 -------
229 -------
230 The connection dictionary of the current kernel, as string or dict,
230 The connection dictionary of the current kernel, as string or dict,
231 depending on `unpack`.
231 depending on `unpack`.
232 """
232 """
233 if connection_file is None:
233 if connection_file is None:
234 # get connection file from current kernel
234 # get connection file from current kernel
235 cf = get_connection_file()
235 cf = get_connection_file()
236 else:
236 else:
237 # connection file specified, allow shortnames:
237 # connection file specified, allow shortnames:
238 cf = find_connection_file(connection_file, profile=profile)
238 cf = find_connection_file(connection_file, profile=profile)
239
239
240 with open(cf) as f:
240 with open(cf) as f:
241 info = f.read()
241 info = f.read()
242
242
243 if unpack:
243 if unpack:
244 info = json.loads(info)
244 info = json.loads(info)
245 # ensure key is bytes:
245 # ensure key is bytes:
246 info['key'] = str_to_bytes(info.get('key', ''))
246 info['key'] = str_to_bytes(info.get('key', ''))
247 return info
247 return info
248
248
249
249
250 def connect_qtconsole(connection_file=None, argv=None, profile=None):
250 def connect_qtconsole(connection_file=None, argv=None, profile=None):
251 """Connect a qtconsole to the current kernel.
251 """Connect a qtconsole to the current kernel.
252
252
253 This is useful for connecting a second qtconsole to a kernel, or to a
253 This is useful for connecting a second qtconsole to a kernel, or to a
254 local notebook.
254 local notebook.
255
255
256 Parameters
256 Parameters
257 ----------
257 ----------
258 connection_file : str [optional]
258 connection_file : str [optional]
259 The connection file to be used. Can be given by absolute path, or
259 The connection file to be used. Can be given by absolute path, or
260 IPython will search in the security directory of a given profile.
260 IPython will search in the security directory of a given profile.
261 If run from IPython,
261 If run from IPython,
262
262
263 If unspecified, the connection file for the currently running
263 If unspecified, the connection file for the currently running
264 IPython Kernel will be used, which is only allowed from inside a kernel.
264 IPython Kernel will be used, which is only allowed from inside a kernel.
265 argv : list [optional]
265 argv : list [optional]
266 Any extra args to be passed to the console.
266 Any extra args to be passed to the console.
267 profile : str [optional]
267 profile : str [optional]
268 The name of the profile to use when searching for the connection file,
268 The name of the profile to use when searching for the connection file,
269 if different from the current IPython session or 'default'.
269 if different from the current IPython session or 'default'.
270
270
271
271
272 Returns
272 Returns
273 -------
273 -------
274 subprocess.Popen instance running the qtconsole frontend
274 subprocess.Popen instance running the qtconsole frontend
275 """
275 """
276 argv = [] if argv is None else argv
276 argv = [] if argv is None else argv
277
277
278 if connection_file is None:
278 if connection_file is None:
279 # get connection file from current kernel
279 # get connection file from current kernel
280 cf = get_connection_file()
280 cf = get_connection_file()
281 else:
281 else:
282 cf = find_connection_file(connection_file, profile=profile)
282 cf = find_connection_file(connection_file, profile=profile)
283
283
284 cmd = ';'.join([
284 cmd = ';'.join([
285 "from IPython.frontend.qt.console import qtconsoleapp",
285 "from IPython.frontend.qt.console import qtconsoleapp",
286 "qtconsoleapp.main()"
286 "qtconsoleapp.main()"
287 ])
287 ])
288
288
289 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
289 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
290
290
291
291
292 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
292 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
293 """tunnel connections to a kernel via ssh
293 """tunnel connections to a kernel via ssh
294
294
295 This will open four SSH tunnels from localhost on this machine to the
295 This will open four SSH tunnels from localhost on this machine to the
296 ports associated with the kernel. They can be either direct
296 ports associated with the kernel. They can be either direct
297 localhost-localhost tunnels, or if an intermediate server is necessary,
297 localhost-localhost tunnels, or if an intermediate server is necessary,
298 the kernel must be listening on a public IP.
298 the kernel must be listening on a public IP.
299
299
300 Parameters
300 Parameters
301 ----------
301 ----------
302 connection_info : dict or str (path)
302 connection_info : dict or str (path)
303 Either a connection dict, or the path to a JSON connection file
303 Either a connection dict, or the path to a JSON connection file
304 sshserver : str
304 sshserver : str
305 The ssh sever to use to tunnel to the kernel. Can be a full
305 The ssh sever to use to tunnel to the kernel. Can be a full
306 `user@server:port` string. ssh config aliases are respected.
306 `user@server:port` string. ssh config aliases are respected.
307 sshkey : str [optional]
307 sshkey : str [optional]
308 Path to file containing ssh key to use for authentication.
308 Path to file containing ssh key to use for authentication.
309 Only necessary if your ssh config does not already associate
309 Only necessary if your ssh config does not already associate
310 a keyfile with the host.
310 a keyfile with the host.
311
311
312 Returns
312 Returns
313 -------
313 -------
314
314
315 (shell, iopub, stdin, hb) : ints
315 (shell, iopub, stdin, hb) : ints
316 The four ports on localhost that have been forwarded to the kernel.
316 The four ports on localhost that have been forwarded to the kernel.
317 """
317 """
318 if isinstance(connection_info, basestring):
318 if isinstance(connection_info, basestring):
319 # it's a path, unpack it
319 # it's a path, unpack it
320 with open(connection_info) as f:
320 with open(connection_info) as f:
321 connection_info = json.loads(f.read())
321 connection_info = json.loads(f.read())
322
322
323 cf = connection_info
323 cf = connection_info
324
324
325 lports = tunnel.select_random_ports(4)
325 lports = tunnel.select_random_ports(4)
326 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
326 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
327
327
328 remote_ip = cf['ip']
328 remote_ip = cf['ip']
329
329
330 if tunnel.try_passwordless_ssh(sshserver, sshkey):
330 if tunnel.try_passwordless_ssh(sshserver, sshkey):
331 password=False
331 password=False
332 else:
332 else:
333 password = getpass("SSH Password for %s: "%sshserver)
333 password = getpass("SSH Password for %s: "%sshserver)
334
334
335 for lp,rp in zip(lports, rports):
335 for lp,rp in zip(lports, rports):
336 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
336 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
337
337
338 return tuple(lports)
338 return tuple(lports)
339
339
@@ -1,72 +1,72 b''
1 """The IPython ZMQ-based parallel computing interface.
1 """The IPython ZMQ-based parallel computing interface.
2
2
3 Authors:
3 Authors:
4
4
5 * MinRK
5 * MinRK
6 """
6 """
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2011 The IPython Development Team
8 # Copyright (C) 2011 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 import os
18 import os
19 import warnings
19 import warnings
20
20
21 import zmq
21 import zmq
22
22
23 from IPython.config.configurable import MultipleInstanceError
23 from IPython.config.configurable import MultipleInstanceError
24 from IPython.zmq import check_for_zmq
24 from IPython.zmq import check_for_zmq
25
25
26 min_pyzmq = '2.1.11'
26 min_pyzmq = '2.1.11'
27
27
28 check_for_zmq(min_pyzmq, 'IPython.parallel')
28 check_for_zmq(min_pyzmq, 'IPython.parallel')
29
29
30 from IPython.utils.pickleutil import Reference
30 from IPython.utils.pickleutil import Reference
31
31
32 from .client.asyncresult import *
32 from .client.asyncresult import *
33 from .client.client import Client
33 from .client.client import Client
34 from .client.remotefunction import *
34 from .client.remotefunction import *
35 from .client.view import *
35 from .client.view import *
36 from .controller.dependency import *
36 from .controller.dependency import *
37 from .error import *
37 from .error import *
38 from .util import interactive
38 from .util import interactive
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Functions
41 # Functions
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44
44
45 def bind_kernel(**kwargs):
45 def bind_kernel(**kwargs):
46 """Bind an Engine's Kernel to be used as a full IPython kernel.
46 """Bind an Engine's Kernel to be used as a full IPython kernel.
47
47
48 This allows a running Engine to be used simultaneously as a full IPython kernel
48 This allows a running Engine to be used simultaneously as a full IPython kernel
49 with the QtConsole or other frontends.
49 with the QtConsole or other frontends.
50
50
51 This function returns immediately.
51 This function returns immediately.
52 """
52 """
53 from IPython.zmq.ipkernel import IPKernelApp
53 from IPython.zmq.kernelapp import IPKernelApp
54 from IPython.parallel.apps.ipengineapp import IPEngineApp
54 from IPython.parallel.apps.ipengineapp import IPEngineApp
55
55
56 # first check for IPKernelApp, in which case this should be a no-op
56 # first check for IPKernelApp, in which case this should be a no-op
57 # because there is already a bound kernel
57 # because there is already a bound kernel
58 if IPKernelApp.initialized() and isinstance(IPKernelApp._instance, IPKernelApp):
58 if IPKernelApp.initialized() and isinstance(IPKernelApp._instance, IPKernelApp):
59 return
59 return
60
60
61 if IPEngineApp.initialized():
61 if IPEngineApp.initialized():
62 try:
62 try:
63 app = IPEngineApp.instance()
63 app = IPEngineApp.instance()
64 except MultipleInstanceError:
64 except MultipleInstanceError:
65 pass
65 pass
66 else:
66 else:
67 return app.bind_kernel(**kwargs)
67 return app.bind_kernel(**kwargs)
68
68
69 raise RuntimeError("bind_kernel be called from an IPEngineApp instance")
69 raise RuntimeError("bind_kernel be called from an IPEngineApp instance")
70
70
71
71
72
72
@@ -1,399 +1,400 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The IPython engine application
4 The IPython engine application
5
5
6 Authors:
6 Authors:
7
7
8 * Brian Granger
8 * Brian Granger
9 * MinRK
9 * MinRK
10
10
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 import json
24 import json
25 import os
25 import os
26 import sys
26 import sys
27 import time
27 import time
28
28
29 import zmq
29 import zmq
30 from zmq.eventloop import ioloop
30 from zmq.eventloop import ioloop
31
31
32 from IPython.core.profiledir import ProfileDir
32 from IPython.core.profiledir import ProfileDir
33 from IPython.parallel.apps.baseapp import (
33 from IPython.parallel.apps.baseapp import (
34 BaseParallelApplication,
34 BaseParallelApplication,
35 base_aliases,
35 base_aliases,
36 base_flags,
36 base_flags,
37 catch_config_error,
37 catch_config_error,
38 )
38 )
39 from IPython.zmq.log import EnginePUBHandler
39 from IPython.zmq.log import EnginePUBHandler
40 from IPython.zmq.ipkernel import Kernel, IPKernelApp
40 from IPython.zmq.ipkernel import Kernel
41 from IPython.zmq.kernelapp import IPKernelApp
41 from IPython.zmq.session import (
42 from IPython.zmq.session import (
42 Session, session_aliases, session_flags
43 Session, session_aliases, session_flags
43 )
44 )
44 from IPython.zmq.zmqshell import ZMQInteractiveShell
45 from IPython.zmq.zmqshell import ZMQInteractiveShell
45
46
46 from IPython.config.configurable import Configurable
47 from IPython.config.configurable import Configurable
47
48
48 from IPython.parallel.engine.engine import EngineFactory
49 from IPython.parallel.engine.engine import EngineFactory
49 from IPython.parallel.util import disambiguate_ip_address
50 from IPython.parallel.util import disambiguate_ip_address
50
51
51 from IPython.utils.importstring import import_item
52 from IPython.utils.importstring import import_item
52 from IPython.utils.py3compat import cast_bytes
53 from IPython.utils.py3compat import cast_bytes
53 from IPython.utils.traitlets import Bool, Unicode, Dict, List, Float, Instance
54 from IPython.utils.traitlets import Bool, Unicode, Dict, List, Float, Instance
54
55
55
56
56 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
57 # Module level variables
58 # Module level variables
58 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
59
60
60 #: The default config file name for this application
61 #: The default config file name for this application
61 default_config_file_name = u'ipengine_config.py'
62 default_config_file_name = u'ipengine_config.py'
62
63
63 _description = """Start an IPython engine for parallel computing.
64 _description = """Start an IPython engine for parallel computing.
64
65
65 IPython engines run in parallel and perform computations on behalf of a client
66 IPython engines run in parallel and perform computations on behalf of a client
66 and controller. A controller needs to be started before the engines. The
67 and controller. A controller needs to be started before the engines. The
67 engine can be configured using command line options or using a cluster
68 engine can be configured using command line options or using a cluster
68 directory. Cluster directories contain config, log and security files and are
69 directory. Cluster directories contain config, log and security files and are
69 usually located in your ipython directory and named as "profile_name".
70 usually located in your ipython directory and named as "profile_name".
70 See the `profile` and `profile-dir` options for details.
71 See the `profile` and `profile-dir` options for details.
71 """
72 """
72
73
73 _examples = """
74 _examples = """
74 ipengine --ip=192.168.0.1 --port=1000 # connect to hub at ip and port
75 ipengine --ip=192.168.0.1 --port=1000 # connect to hub at ip and port
75 ipengine --log-to-file --log-level=DEBUG # log to a file with DEBUG verbosity
76 ipengine --log-to-file --log-level=DEBUG # log to a file with DEBUG verbosity
76 """
77 """
77
78
78 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
79 # MPI configuration
80 # MPI configuration
80 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
81
82
82 mpi4py_init = """from mpi4py import MPI as mpi
83 mpi4py_init = """from mpi4py import MPI as mpi
83 mpi.size = mpi.COMM_WORLD.Get_size()
84 mpi.size = mpi.COMM_WORLD.Get_size()
84 mpi.rank = mpi.COMM_WORLD.Get_rank()
85 mpi.rank = mpi.COMM_WORLD.Get_rank()
85 """
86 """
86
87
87
88
88 pytrilinos_init = """from PyTrilinos import Epetra
89 pytrilinos_init = """from PyTrilinos import Epetra
89 class SimpleStruct:
90 class SimpleStruct:
90 pass
91 pass
91 mpi = SimpleStruct()
92 mpi = SimpleStruct()
92 mpi.rank = 0
93 mpi.rank = 0
93 mpi.size = 0
94 mpi.size = 0
94 """
95 """
95
96
96 class MPI(Configurable):
97 class MPI(Configurable):
97 """Configurable for MPI initialization"""
98 """Configurable for MPI initialization"""
98 use = Unicode('', config=True,
99 use = Unicode('', config=True,
99 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).'
100 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).'
100 )
101 )
101
102
102 def _use_changed(self, name, old, new):
103 def _use_changed(self, name, old, new):
103 # load default init script if it's not set
104 # load default init script if it's not set
104 if not self.init_script:
105 if not self.init_script:
105 self.init_script = self.default_inits.get(new, '')
106 self.init_script = self.default_inits.get(new, '')
106
107
107 init_script = Unicode('', config=True,
108 init_script = Unicode('', config=True,
108 help="Initialization code for MPI")
109 help="Initialization code for MPI")
109
110
110 default_inits = Dict({'mpi4py' : mpi4py_init, 'pytrilinos':pytrilinos_init},
111 default_inits = Dict({'mpi4py' : mpi4py_init, 'pytrilinos':pytrilinos_init},
111 config=True)
112 config=True)
112
113
113
114
114 #-----------------------------------------------------------------------------
115 #-----------------------------------------------------------------------------
115 # Main application
116 # Main application
116 #-----------------------------------------------------------------------------
117 #-----------------------------------------------------------------------------
117 aliases = dict(
118 aliases = dict(
118 file = 'IPEngineApp.url_file',
119 file = 'IPEngineApp.url_file',
119 c = 'IPEngineApp.startup_command',
120 c = 'IPEngineApp.startup_command',
120 s = 'IPEngineApp.startup_script',
121 s = 'IPEngineApp.startup_script',
121
122
122 url = 'EngineFactory.url',
123 url = 'EngineFactory.url',
123 ssh = 'EngineFactory.sshserver',
124 ssh = 'EngineFactory.sshserver',
124 sshkey = 'EngineFactory.sshkey',
125 sshkey = 'EngineFactory.sshkey',
125 ip = 'EngineFactory.ip',
126 ip = 'EngineFactory.ip',
126 transport = 'EngineFactory.transport',
127 transport = 'EngineFactory.transport',
127 port = 'EngineFactory.regport',
128 port = 'EngineFactory.regport',
128 location = 'EngineFactory.location',
129 location = 'EngineFactory.location',
129
130
130 timeout = 'EngineFactory.timeout',
131 timeout = 'EngineFactory.timeout',
131
132
132 mpi = 'MPI.use',
133 mpi = 'MPI.use',
133
134
134 )
135 )
135 aliases.update(base_aliases)
136 aliases.update(base_aliases)
136 aliases.update(session_aliases)
137 aliases.update(session_aliases)
137 flags = {}
138 flags = {}
138 flags.update(base_flags)
139 flags.update(base_flags)
139 flags.update(session_flags)
140 flags.update(session_flags)
140
141
141 class IPEngineApp(BaseParallelApplication):
142 class IPEngineApp(BaseParallelApplication):
142
143
143 name = 'ipengine'
144 name = 'ipengine'
144 description = _description
145 description = _description
145 examples = _examples
146 examples = _examples
146 config_file_name = Unicode(default_config_file_name)
147 config_file_name = Unicode(default_config_file_name)
147 classes = List([ZMQInteractiveShell, ProfileDir, Session, EngineFactory, Kernel, MPI])
148 classes = List([ZMQInteractiveShell, ProfileDir, Session, EngineFactory, Kernel, MPI])
148
149
149 startup_script = Unicode(u'', config=True,
150 startup_script = Unicode(u'', config=True,
150 help='specify a script to be run at startup')
151 help='specify a script to be run at startup')
151 startup_command = Unicode('', config=True,
152 startup_command = Unicode('', config=True,
152 help='specify a command to be run at startup')
153 help='specify a command to be run at startup')
153
154
154 url_file = Unicode(u'', config=True,
155 url_file = Unicode(u'', config=True,
155 help="""The full location of the file containing the connection information for
156 help="""The full location of the file containing the connection information for
156 the controller. If this is not given, the file must be in the
157 the controller. If this is not given, the file must be in the
157 security directory of the cluster directory. This location is
158 security directory of the cluster directory. This location is
158 resolved using the `profile` or `profile_dir` options.""",
159 resolved using the `profile` or `profile_dir` options.""",
159 )
160 )
160 wait_for_url_file = Float(5, config=True,
161 wait_for_url_file = Float(5, config=True,
161 help="""The maximum number of seconds to wait for url_file to exist.
162 help="""The maximum number of seconds to wait for url_file to exist.
162 This is useful for batch-systems and shared-filesystems where the
163 This is useful for batch-systems and shared-filesystems where the
163 controller and engine are started at the same time and it
164 controller and engine are started at the same time and it
164 may take a moment for the controller to write the connector files.""")
165 may take a moment for the controller to write the connector files.""")
165
166
166 url_file_name = Unicode(u'ipcontroller-engine.json', config=True)
167 url_file_name = Unicode(u'ipcontroller-engine.json', config=True)
167
168
168 def _cluster_id_changed(self, name, old, new):
169 def _cluster_id_changed(self, name, old, new):
169 if new:
170 if new:
170 base = 'ipcontroller-%s' % new
171 base = 'ipcontroller-%s' % new
171 else:
172 else:
172 base = 'ipcontroller'
173 base = 'ipcontroller'
173 self.url_file_name = "%s-engine.json" % base
174 self.url_file_name = "%s-engine.json" % base
174
175
175 log_url = Unicode('', config=True,
176 log_url = Unicode('', config=True,
176 help="""The URL for the iploggerapp instance, for forwarding
177 help="""The URL for the iploggerapp instance, for forwarding
177 logging to a central location.""")
178 logging to a central location.""")
178
179
179 # an IPKernelApp instance, used to setup listening for shell frontends
180 # an IPKernelApp instance, used to setup listening for shell frontends
180 kernel_app = Instance(IPKernelApp)
181 kernel_app = Instance(IPKernelApp)
181
182
182 aliases = Dict(aliases)
183 aliases = Dict(aliases)
183 flags = Dict(flags)
184 flags = Dict(flags)
184
185
185 @property
186 @property
186 def kernel(self):
187 def kernel(self):
187 """allow access to the Kernel object, so I look like IPKernelApp"""
188 """allow access to the Kernel object, so I look like IPKernelApp"""
188 return self.engine.kernel
189 return self.engine.kernel
189
190
190 def find_url_file(self):
191 def find_url_file(self):
191 """Set the url file.
192 """Set the url file.
192
193
193 Here we don't try to actually see if it exists for is valid as that
194 Here we don't try to actually see if it exists for is valid as that
194 is hadled by the connection logic.
195 is hadled by the connection logic.
195 """
196 """
196 config = self.config
197 config = self.config
197 # Find the actual controller key file
198 # Find the actual controller key file
198 if not self.url_file:
199 if not self.url_file:
199 self.url_file = os.path.join(
200 self.url_file = os.path.join(
200 self.profile_dir.security_dir,
201 self.profile_dir.security_dir,
201 self.url_file_name
202 self.url_file_name
202 )
203 )
203
204
204 def load_connector_file(self):
205 def load_connector_file(self):
205 """load config from a JSON connector file,
206 """load config from a JSON connector file,
206 at a *lower* priority than command-line/config files.
207 at a *lower* priority than command-line/config files.
207 """
208 """
208
209
209 self.log.info("Loading url_file %r", self.url_file)
210 self.log.info("Loading url_file %r", self.url_file)
210 config = self.config
211 config = self.config
211
212
212 with open(self.url_file) as f:
213 with open(self.url_file) as f:
213 d = json.loads(f.read())
214 d = json.loads(f.read())
214
215
215 # allow hand-override of location for disambiguation
216 # allow hand-override of location for disambiguation
216 # and ssh-server
217 # and ssh-server
217 try:
218 try:
218 config.EngineFactory.location
219 config.EngineFactory.location
219 except AttributeError:
220 except AttributeError:
220 config.EngineFactory.location = d['location']
221 config.EngineFactory.location = d['location']
221
222
222 try:
223 try:
223 config.EngineFactory.sshserver
224 config.EngineFactory.sshserver
224 except AttributeError:
225 except AttributeError:
225 config.EngineFactory.sshserver = d.get('ssh')
226 config.EngineFactory.sshserver = d.get('ssh')
226
227
227 location = config.EngineFactory.location
228 location = config.EngineFactory.location
228
229
229 proto, ip = d['interface'].split('://')
230 proto, ip = d['interface'].split('://')
230 ip = disambiguate_ip_address(ip, location)
231 ip = disambiguate_ip_address(ip, location)
231 d['interface'] = '%s://%s' % (proto, ip)
232 d['interface'] = '%s://%s' % (proto, ip)
232
233
233 # DO NOT allow override of basic URLs, serialization, or exec_key
234 # DO NOT allow override of basic URLs, serialization, or exec_key
234 # JSON file takes top priority there
235 # JSON file takes top priority there
235 config.Session.key = cast_bytes(d['exec_key'])
236 config.Session.key = cast_bytes(d['exec_key'])
236
237
237 config.EngineFactory.url = d['interface'] + ':%i' % d['registration']
238 config.EngineFactory.url = d['interface'] + ':%i' % d['registration']
238
239
239 config.Session.packer = d['pack']
240 config.Session.packer = d['pack']
240 config.Session.unpacker = d['unpack']
241 config.Session.unpacker = d['unpack']
241
242
242 self.log.debug("Config changed:")
243 self.log.debug("Config changed:")
243 self.log.debug("%r", config)
244 self.log.debug("%r", config)
244 self.connection_info = d
245 self.connection_info = d
245
246
246 def bind_kernel(self, **kwargs):
247 def bind_kernel(self, **kwargs):
247 """Promote engine to listening kernel, accessible to frontends."""
248 """Promote engine to listening kernel, accessible to frontends."""
248 if self.kernel_app is not None:
249 if self.kernel_app is not None:
249 return
250 return
250
251
251 self.log.info("Opening ports for direct connections as an IPython kernel")
252 self.log.info("Opening ports for direct connections as an IPython kernel")
252
253
253 kernel = self.kernel
254 kernel = self.kernel
254
255
255 kwargs.setdefault('config', self.config)
256 kwargs.setdefault('config', self.config)
256 kwargs.setdefault('log', self.log)
257 kwargs.setdefault('log', self.log)
257 kwargs.setdefault('profile_dir', self.profile_dir)
258 kwargs.setdefault('profile_dir', self.profile_dir)
258 kwargs.setdefault('session', self.engine.session)
259 kwargs.setdefault('session', self.engine.session)
259
260
260 app = self.kernel_app = IPKernelApp(**kwargs)
261 app = self.kernel_app = IPKernelApp(**kwargs)
261
262
262 # allow IPKernelApp.instance():
263 # allow IPKernelApp.instance():
263 IPKernelApp._instance = app
264 IPKernelApp._instance = app
264
265
265 app.init_connection_file()
266 app.init_connection_file()
266 # relevant contents of init_sockets:
267 # relevant contents of init_sockets:
267
268
268 app.shell_port = app._bind_socket(kernel.shell_streams[0], app.shell_port)
269 app.shell_port = app._bind_socket(kernel.shell_streams[0], app.shell_port)
269 app.log.debug("shell ROUTER Channel on port: %i", app.shell_port)
270 app.log.debug("shell ROUTER Channel on port: %i", app.shell_port)
270
271
271 app.iopub_port = app._bind_socket(kernel.iopub_socket, app.iopub_port)
272 app.iopub_port = app._bind_socket(kernel.iopub_socket, app.iopub_port)
272 app.log.debug("iopub PUB Channel on port: %i", app.iopub_port)
273 app.log.debug("iopub PUB Channel on port: %i", app.iopub_port)
273
274
274 kernel.stdin_socket = self.engine.context.socket(zmq.ROUTER)
275 kernel.stdin_socket = self.engine.context.socket(zmq.ROUTER)
275 app.stdin_port = app._bind_socket(kernel.stdin_socket, app.stdin_port)
276 app.stdin_port = app._bind_socket(kernel.stdin_socket, app.stdin_port)
276 app.log.debug("stdin ROUTER Channel on port: %i", app.stdin_port)
277 app.log.debug("stdin ROUTER Channel on port: %i", app.stdin_port)
277
278
278 # start the heartbeat, and log connection info:
279 # start the heartbeat, and log connection info:
279
280
280 app.init_heartbeat()
281 app.init_heartbeat()
281
282
282 app.log_connection_info()
283 app.log_connection_info()
283 app.write_connection_file()
284 app.write_connection_file()
284
285
285
286
286 def init_engine(self):
287 def init_engine(self):
287 # This is the working dir by now.
288 # This is the working dir by now.
288 sys.path.insert(0, '')
289 sys.path.insert(0, '')
289 config = self.config
290 config = self.config
290 # print config
291 # print config
291 self.find_url_file()
292 self.find_url_file()
292
293
293 # was the url manually specified?
294 # was the url manually specified?
294 keys = set(self.config.EngineFactory.keys())
295 keys = set(self.config.EngineFactory.keys())
295 keys = keys.union(set(self.config.RegistrationFactory.keys()))
296 keys = keys.union(set(self.config.RegistrationFactory.keys()))
296
297
297 if keys.intersection(set(['ip', 'url', 'port'])):
298 if keys.intersection(set(['ip', 'url', 'port'])):
298 # Connection info was specified, don't wait for the file
299 # Connection info was specified, don't wait for the file
299 url_specified = True
300 url_specified = True
300 self.wait_for_url_file = 0
301 self.wait_for_url_file = 0
301 else:
302 else:
302 url_specified = False
303 url_specified = False
303
304
304 if self.wait_for_url_file and not os.path.exists(self.url_file):
305 if self.wait_for_url_file and not os.path.exists(self.url_file):
305 self.log.warn("url_file %r not found", self.url_file)
306 self.log.warn("url_file %r not found", self.url_file)
306 self.log.warn("Waiting up to %.1f seconds for it to arrive.", self.wait_for_url_file)
307 self.log.warn("Waiting up to %.1f seconds for it to arrive.", self.wait_for_url_file)
307 tic = time.time()
308 tic = time.time()
308 while not os.path.exists(self.url_file) and (time.time()-tic < self.wait_for_url_file):
309 while not os.path.exists(self.url_file) and (time.time()-tic < self.wait_for_url_file):
309 # wait for url_file to exist, or until time limit
310 # wait for url_file to exist, or until time limit
310 time.sleep(0.1)
311 time.sleep(0.1)
311
312
312 if os.path.exists(self.url_file):
313 if os.path.exists(self.url_file):
313 self.load_connector_file()
314 self.load_connector_file()
314 elif not url_specified:
315 elif not url_specified:
315 self.log.fatal("Fatal: url file never arrived: %s", self.url_file)
316 self.log.fatal("Fatal: url file never arrived: %s", self.url_file)
316 self.exit(1)
317 self.exit(1)
317
318
318
319
319 try:
320 try:
320 exec_lines = config.IPKernelApp.exec_lines
321 exec_lines = config.IPKernelApp.exec_lines
321 except AttributeError:
322 except AttributeError:
322 try:
323 try:
323 exec_lines = config.InteractiveShellApp.exec_lines
324 exec_lines = config.InteractiveShellApp.exec_lines
324 except AttributeError:
325 except AttributeError:
325 exec_lines = config.IPKernelApp.exec_lines = []
326 exec_lines = config.IPKernelApp.exec_lines = []
326 try:
327 try:
327 exec_files = config.IPKernelApp.exec_files
328 exec_files = config.IPKernelApp.exec_files
328 except AttributeError:
329 except AttributeError:
329 try:
330 try:
330 exec_files = config.InteractiveShellApp.exec_files
331 exec_files = config.InteractiveShellApp.exec_files
331 except AttributeError:
332 except AttributeError:
332 exec_files = config.IPKernelApp.exec_files = []
333 exec_files = config.IPKernelApp.exec_files = []
333
334
334 if self.startup_script:
335 if self.startup_script:
335 exec_files.append(self.startup_script)
336 exec_files.append(self.startup_script)
336 if self.startup_command:
337 if self.startup_command:
337 exec_lines.append(self.startup_command)
338 exec_lines.append(self.startup_command)
338
339
339 # Create the underlying shell class and Engine
340 # Create the underlying shell class and Engine
340 # shell_class = import_item(self.master_config.Global.shell_class)
341 # shell_class = import_item(self.master_config.Global.shell_class)
341 # print self.config
342 # print self.config
342 try:
343 try:
343 self.engine = EngineFactory(config=config, log=self.log,
344 self.engine = EngineFactory(config=config, log=self.log,
344 connection_info=self.connection_info,
345 connection_info=self.connection_info,
345 )
346 )
346 except:
347 except:
347 self.log.error("Couldn't start the Engine", exc_info=True)
348 self.log.error("Couldn't start the Engine", exc_info=True)
348 self.exit(1)
349 self.exit(1)
349
350
350 def forward_logging(self):
351 def forward_logging(self):
351 if self.log_url:
352 if self.log_url:
352 self.log.info("Forwarding logging to %s", self.log_url)
353 self.log.info("Forwarding logging to %s", self.log_url)
353 context = self.engine.context
354 context = self.engine.context
354 lsock = context.socket(zmq.PUB)
355 lsock = context.socket(zmq.PUB)
355 lsock.connect(self.log_url)
356 lsock.connect(self.log_url)
356 handler = EnginePUBHandler(self.engine, lsock)
357 handler = EnginePUBHandler(self.engine, lsock)
357 handler.setLevel(self.log_level)
358 handler.setLevel(self.log_level)
358 self.log.addHandler(handler)
359 self.log.addHandler(handler)
359
360
360 def init_mpi(self):
361 def init_mpi(self):
361 global mpi
362 global mpi
362 self.mpi = MPI(config=self.config)
363 self.mpi = MPI(config=self.config)
363
364
364 mpi_import_statement = self.mpi.init_script
365 mpi_import_statement = self.mpi.init_script
365 if mpi_import_statement:
366 if mpi_import_statement:
366 try:
367 try:
367 self.log.info("Initializing MPI:")
368 self.log.info("Initializing MPI:")
368 self.log.info(mpi_import_statement)
369 self.log.info(mpi_import_statement)
369 exec mpi_import_statement in globals()
370 exec mpi_import_statement in globals()
370 except:
371 except:
371 mpi = None
372 mpi = None
372 else:
373 else:
373 mpi = None
374 mpi = None
374
375
375 @catch_config_error
376 @catch_config_error
376 def initialize(self, argv=None):
377 def initialize(self, argv=None):
377 super(IPEngineApp, self).initialize(argv)
378 super(IPEngineApp, self).initialize(argv)
378 self.init_mpi()
379 self.init_mpi()
379 self.init_engine()
380 self.init_engine()
380 self.forward_logging()
381 self.forward_logging()
381
382
382 def start(self):
383 def start(self):
383 self.engine.start()
384 self.engine.start()
384 try:
385 try:
385 self.engine.loop.start()
386 self.engine.loop.start()
386 except KeyboardInterrupt:
387 except KeyboardInterrupt:
387 self.log.critical("Engine Interrupted, shutting down...\n")
388 self.log.critical("Engine Interrupted, shutting down...\n")
388
389
389
390
390 def launch_new_instance():
391 def launch_new_instance():
391 """Create and run the IPython engine"""
392 """Create and run the IPython engine"""
392 app = IPEngineApp.instance()
393 app = IPEngineApp.instance()
393 app.initialize()
394 app.initialize()
394 app.start()
395 app.start()
395
396
396
397
397 if __name__ == '__main__':
398 if __name__ == '__main__':
398 launch_new_instance()
399 launch_new_instance()
399
400
@@ -1,304 +1,305 b''
1 """A simple engine that talks to a controller over 0MQ.
1 """A simple engine that talks to a controller over 0MQ.
2 it handles registration, etc. and launches a kernel
2 it handles registration, etc. and launches a kernel
3 connected to the Controller's Schedulers.
3 connected to the Controller's Schedulers.
4
4
5 Authors:
5 Authors:
6
6
7 * Min RK
7 * Min RK
8 """
8 """
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2010-2011 The IPython Development Team
10 # Copyright (C) 2010-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 from __future__ import print_function
16 from __future__ import print_function
17
17
18 import sys
18 import sys
19 import time
19 import time
20 from getpass import getpass
20 from getpass import getpass
21
21
22 import zmq
22 import zmq
23 from zmq.eventloop import ioloop, zmqstream
23 from zmq.eventloop import ioloop, zmqstream
24
24
25 from IPython.external.ssh import tunnel
25 from IPython.external.ssh import tunnel
26 # internal
26 # internal
27 from IPython.utils.localinterfaces import LOCALHOST
27 from IPython.utils.localinterfaces import LOCALHOST
28 from IPython.utils.traitlets import (
28 from IPython.utils.traitlets import (
29 Instance, Dict, Integer, Type, Float, Integer, Unicode, CBytes, Bool
29 Instance, Dict, Integer, Type, Float, Integer, Unicode, CBytes, Bool
30 )
30 )
31 from IPython.utils.py3compat import cast_bytes
31 from IPython.utils.py3compat import cast_bytes
32
32
33 from IPython.parallel.controller.heartmonitor import Heart
33 from IPython.parallel.controller.heartmonitor import Heart
34 from IPython.parallel.factory import RegistrationFactory
34 from IPython.parallel.factory import RegistrationFactory
35 from IPython.parallel.util import disambiguate_url
35 from IPython.parallel.util import disambiguate_url
36
36
37 from IPython.zmq.session import Message
37 from IPython.zmq.session import Message
38 from IPython.zmq.ipkernel import Kernel, IPKernelApp
38 from IPython.zmq.ipkernel import Kernel
39 from IPython.zmq.kernelapp import IPKernelApp
39
40
40 class EngineFactory(RegistrationFactory):
41 class EngineFactory(RegistrationFactory):
41 """IPython engine"""
42 """IPython engine"""
42
43
43 # configurables:
44 # configurables:
44 out_stream_factory=Type('IPython.zmq.iostream.OutStream', config=True,
45 out_stream_factory=Type('IPython.zmq.iostream.OutStream', config=True,
45 help="""The OutStream for handling stdout/err.
46 help="""The OutStream for handling stdout/err.
46 Typically 'IPython.zmq.iostream.OutStream'""")
47 Typically 'IPython.zmq.iostream.OutStream'""")
47 display_hook_factory=Type('IPython.zmq.displayhook.ZMQDisplayHook', config=True,
48 display_hook_factory=Type('IPython.zmq.displayhook.ZMQDisplayHook', config=True,
48 help="""The class for handling displayhook.
49 help="""The class for handling displayhook.
49 Typically 'IPython.zmq.displayhook.ZMQDisplayHook'""")
50 Typically 'IPython.zmq.displayhook.ZMQDisplayHook'""")
50 location=Unicode(config=True,
51 location=Unicode(config=True,
51 help="""The location (an IP address) of the controller. This is
52 help="""The location (an IP address) of the controller. This is
52 used for disambiguating URLs, to determine whether
53 used for disambiguating URLs, to determine whether
53 loopback should be used to connect or the public address.""")
54 loopback should be used to connect or the public address.""")
54 timeout=Float(5.0, config=True,
55 timeout=Float(5.0, config=True,
55 help="""The time (in seconds) to wait for the Controller to respond
56 help="""The time (in seconds) to wait for the Controller to respond
56 to registration requests before giving up.""")
57 to registration requests before giving up.""")
57 max_heartbeat_misses=Integer(50, config=True,
58 max_heartbeat_misses=Integer(50, config=True,
58 help="""The maximum number of times a check for the heartbeat ping of a
59 help="""The maximum number of times a check for the heartbeat ping of a
59 controller can be missed before shutting down the engine.
60 controller can be missed before shutting down the engine.
60
61
61 If set to 0, the check is disabled.""")
62 If set to 0, the check is disabled.""")
62 sshserver=Unicode(config=True,
63 sshserver=Unicode(config=True,
63 help="""The SSH server to use for tunneling connections to the Controller.""")
64 help="""The SSH server to use for tunneling connections to the Controller.""")
64 sshkey=Unicode(config=True,
65 sshkey=Unicode(config=True,
65 help="""The SSH private key file to use when tunneling connections to the Controller.""")
66 help="""The SSH private key file to use when tunneling connections to the Controller.""")
66 paramiko=Bool(sys.platform == 'win32', config=True,
67 paramiko=Bool(sys.platform == 'win32', config=True,
67 help="""Whether to use paramiko instead of openssh for tunnels.""")
68 help="""Whether to use paramiko instead of openssh for tunnels.""")
68
69
69
70
70 # not configurable:
71 # not configurable:
71 connection_info = Dict()
72 connection_info = Dict()
72 user_ns = Dict()
73 user_ns = Dict()
73 id = Integer(allow_none=True)
74 id = Integer(allow_none=True)
74 registrar = Instance('zmq.eventloop.zmqstream.ZMQStream')
75 registrar = Instance('zmq.eventloop.zmqstream.ZMQStream')
75 kernel = Instance(Kernel)
76 kernel = Instance(Kernel)
76 hb_check_period=Integer()
77 hb_check_period=Integer()
77
78
78 # States for the heartbeat monitoring
79 # States for the heartbeat monitoring
79 # Initial values for monitored and pinged must satisfy "monitored > pinged == False" so that
80 # Initial values for monitored and pinged must satisfy "monitored > pinged == False" so that
80 # during the first check no "missed" ping is reported. Must be floats for Python 3 compatibility.
81 # during the first check no "missed" ping is reported. Must be floats for Python 3 compatibility.
81 _hb_last_pinged = 0.0
82 _hb_last_pinged = 0.0
82 _hb_last_monitored = 0.0
83 _hb_last_monitored = 0.0
83 _hb_missed_beats = 0
84 _hb_missed_beats = 0
84 # The zmq Stream which receives the pings from the Heart
85 # The zmq Stream which receives the pings from the Heart
85 _hb_listener = None
86 _hb_listener = None
86
87
87 bident = CBytes()
88 bident = CBytes()
88 ident = Unicode()
89 ident = Unicode()
89 def _ident_changed(self, name, old, new):
90 def _ident_changed(self, name, old, new):
90 self.bident = cast_bytes(new)
91 self.bident = cast_bytes(new)
91 using_ssh=Bool(False)
92 using_ssh=Bool(False)
92
93
93
94
94 def __init__(self, **kwargs):
95 def __init__(self, **kwargs):
95 super(EngineFactory, self).__init__(**kwargs)
96 super(EngineFactory, self).__init__(**kwargs)
96 self.ident = self.session.session
97 self.ident = self.session.session
97
98
98 def init_connector(self):
99 def init_connector(self):
99 """construct connection function, which handles tunnels."""
100 """construct connection function, which handles tunnels."""
100 self.using_ssh = bool(self.sshkey or self.sshserver)
101 self.using_ssh = bool(self.sshkey or self.sshserver)
101
102
102 if self.sshkey and not self.sshserver:
103 if self.sshkey and not self.sshserver:
103 # We are using ssh directly to the controller, tunneling localhost to localhost
104 # We are using ssh directly to the controller, tunneling localhost to localhost
104 self.sshserver = self.url.split('://')[1].split(':')[0]
105 self.sshserver = self.url.split('://')[1].split(':')[0]
105
106
106 if self.using_ssh:
107 if self.using_ssh:
107 if tunnel.try_passwordless_ssh(self.sshserver, self.sshkey, self.paramiko):
108 if tunnel.try_passwordless_ssh(self.sshserver, self.sshkey, self.paramiko):
108 password=False
109 password=False
109 else:
110 else:
110 password = getpass("SSH Password for %s: "%self.sshserver)
111 password = getpass("SSH Password for %s: "%self.sshserver)
111 else:
112 else:
112 password = False
113 password = False
113
114
114 def connect(s, url):
115 def connect(s, url):
115 url = disambiguate_url(url, self.location)
116 url = disambiguate_url(url, self.location)
116 if self.using_ssh:
117 if self.using_ssh:
117 self.log.debug("Tunneling connection to %s via %s", url, self.sshserver)
118 self.log.debug("Tunneling connection to %s via %s", url, self.sshserver)
118 return tunnel.tunnel_connection(s, url, self.sshserver,
119 return tunnel.tunnel_connection(s, url, self.sshserver,
119 keyfile=self.sshkey, paramiko=self.paramiko,
120 keyfile=self.sshkey, paramiko=self.paramiko,
120 password=password,
121 password=password,
121 )
122 )
122 else:
123 else:
123 return s.connect(url)
124 return s.connect(url)
124
125
125 def maybe_tunnel(url):
126 def maybe_tunnel(url):
126 """like connect, but don't complete the connection (for use by heartbeat)"""
127 """like connect, but don't complete the connection (for use by heartbeat)"""
127 url = disambiguate_url(url, self.location)
128 url = disambiguate_url(url, self.location)
128 if self.using_ssh:
129 if self.using_ssh:
129 self.log.debug("Tunneling connection to %s via %s", url, self.sshserver)
130 self.log.debug("Tunneling connection to %s via %s", url, self.sshserver)
130 url,tunnelobj = tunnel.open_tunnel(url, self.sshserver,
131 url,tunnelobj = tunnel.open_tunnel(url, self.sshserver,
131 keyfile=self.sshkey, paramiko=self.paramiko,
132 keyfile=self.sshkey, paramiko=self.paramiko,
132 password=password,
133 password=password,
133 )
134 )
134 return str(url)
135 return str(url)
135 return connect, maybe_tunnel
136 return connect, maybe_tunnel
136
137
137 def register(self):
138 def register(self):
138 """send the registration_request"""
139 """send the registration_request"""
139
140
140 self.log.info("Registering with controller at %s"%self.url)
141 self.log.info("Registering with controller at %s"%self.url)
141 ctx = self.context
142 ctx = self.context
142 connect,maybe_tunnel = self.init_connector()
143 connect,maybe_tunnel = self.init_connector()
143 reg = ctx.socket(zmq.DEALER)
144 reg = ctx.socket(zmq.DEALER)
144 reg.setsockopt(zmq.IDENTITY, self.bident)
145 reg.setsockopt(zmq.IDENTITY, self.bident)
145 connect(reg, self.url)
146 connect(reg, self.url)
146 self.registrar = zmqstream.ZMQStream(reg, self.loop)
147 self.registrar = zmqstream.ZMQStream(reg, self.loop)
147
148
148
149
149 content = dict(uuid=self.ident)
150 content = dict(uuid=self.ident)
150 self.registrar.on_recv(lambda msg: self.complete_registration(msg, connect, maybe_tunnel))
151 self.registrar.on_recv(lambda msg: self.complete_registration(msg, connect, maybe_tunnel))
151 # print (self.session.key)
152 # print (self.session.key)
152 self.session.send(self.registrar, "registration_request", content=content)
153 self.session.send(self.registrar, "registration_request", content=content)
153
154
154 def _report_ping(self, msg):
155 def _report_ping(self, msg):
155 """Callback for when the heartmonitor.Heart receives a ping"""
156 """Callback for when the heartmonitor.Heart receives a ping"""
156 #self.log.debug("Received a ping: %s", msg)
157 #self.log.debug("Received a ping: %s", msg)
157 self._hb_last_pinged = time.time()
158 self._hb_last_pinged = time.time()
158
159
159 def complete_registration(self, msg, connect, maybe_tunnel):
160 def complete_registration(self, msg, connect, maybe_tunnel):
160 # print msg
161 # print msg
161 self._abort_dc.stop()
162 self._abort_dc.stop()
162 ctx = self.context
163 ctx = self.context
163 loop = self.loop
164 loop = self.loop
164 identity = self.bident
165 identity = self.bident
165 idents,msg = self.session.feed_identities(msg)
166 idents,msg = self.session.feed_identities(msg)
166 msg = self.session.unserialize(msg)
167 msg = self.session.unserialize(msg)
167 content = msg['content']
168 content = msg['content']
168 info = self.connection_info
169 info = self.connection_info
169
170
170 def url(key):
171 def url(key):
171 """get zmq url for given channel"""
172 """get zmq url for given channel"""
172 return str(info["interface"] + ":%i" % info[key])
173 return str(info["interface"] + ":%i" % info[key])
173
174
174 if content['status'] == 'ok':
175 if content['status'] == 'ok':
175 self.id = int(content['id'])
176 self.id = int(content['id'])
176
177
177 # launch heartbeat
178 # launch heartbeat
178 # possibly forward hb ports with tunnels
179 # possibly forward hb ports with tunnels
179 hb_ping = maybe_tunnel(url('hb_ping'))
180 hb_ping = maybe_tunnel(url('hb_ping'))
180 hb_pong = maybe_tunnel(url('hb_pong'))
181 hb_pong = maybe_tunnel(url('hb_pong'))
181
182
182 hb_monitor = None
183 hb_monitor = None
183 if self.max_heartbeat_misses > 0:
184 if self.max_heartbeat_misses > 0:
184 # Add a monitor socket which will record the last time a ping was seen
185 # Add a monitor socket which will record the last time a ping was seen
185 mon = self.context.socket(zmq.SUB)
186 mon = self.context.socket(zmq.SUB)
186 mport = mon.bind_to_random_port('tcp://%s' % LOCALHOST)
187 mport = mon.bind_to_random_port('tcp://%s' % LOCALHOST)
187 mon.setsockopt(zmq.SUBSCRIBE, b"")
188 mon.setsockopt(zmq.SUBSCRIBE, b"")
188 self._hb_listener = zmqstream.ZMQStream(mon, self.loop)
189 self._hb_listener = zmqstream.ZMQStream(mon, self.loop)
189 self._hb_listener.on_recv(self._report_ping)
190 self._hb_listener.on_recv(self._report_ping)
190
191
191
192
192 hb_monitor = "tcp://%s:%i" % (LOCALHOST, mport)
193 hb_monitor = "tcp://%s:%i" % (LOCALHOST, mport)
193
194
194 heart = Heart(hb_ping, hb_pong, hb_monitor , heart_id=identity)
195 heart = Heart(hb_ping, hb_pong, hb_monitor , heart_id=identity)
195 heart.start()
196 heart.start()
196
197
197 # create Shell Connections (MUX, Task, etc.):
198 # create Shell Connections (MUX, Task, etc.):
198 shell_addrs = url('mux'), url('task')
199 shell_addrs = url('mux'), url('task')
199
200
200 # Use only one shell stream for mux and tasks
201 # Use only one shell stream for mux and tasks
201 stream = zmqstream.ZMQStream(ctx.socket(zmq.ROUTER), loop)
202 stream = zmqstream.ZMQStream(ctx.socket(zmq.ROUTER), loop)
202 stream.setsockopt(zmq.IDENTITY, identity)
203 stream.setsockopt(zmq.IDENTITY, identity)
203 shell_streams = [stream]
204 shell_streams = [stream]
204 for addr in shell_addrs:
205 for addr in shell_addrs:
205 connect(stream, addr)
206 connect(stream, addr)
206
207
207 # control stream:
208 # control stream:
208 control_addr = url('control')
209 control_addr = url('control')
209 control_stream = zmqstream.ZMQStream(ctx.socket(zmq.ROUTER), loop)
210 control_stream = zmqstream.ZMQStream(ctx.socket(zmq.ROUTER), loop)
210 control_stream.setsockopt(zmq.IDENTITY, identity)
211 control_stream.setsockopt(zmq.IDENTITY, identity)
211 connect(control_stream, control_addr)
212 connect(control_stream, control_addr)
212
213
213 # create iopub stream:
214 # create iopub stream:
214 iopub_addr = url('iopub')
215 iopub_addr = url('iopub')
215 iopub_socket = ctx.socket(zmq.PUB)
216 iopub_socket = ctx.socket(zmq.PUB)
216 iopub_socket.setsockopt(zmq.IDENTITY, identity)
217 iopub_socket.setsockopt(zmq.IDENTITY, identity)
217 connect(iopub_socket, iopub_addr)
218 connect(iopub_socket, iopub_addr)
218
219
219 # disable history:
220 # disable history:
220 self.config.HistoryManager.hist_file = ':memory:'
221 self.config.HistoryManager.hist_file = ':memory:'
221
222
222 # Redirect input streams and set a display hook.
223 # Redirect input streams and set a display hook.
223 if self.out_stream_factory:
224 if self.out_stream_factory:
224 sys.stdout = self.out_stream_factory(self.session, iopub_socket, u'stdout')
225 sys.stdout = self.out_stream_factory(self.session, iopub_socket, u'stdout')
225 sys.stdout.topic = cast_bytes('engine.%i.stdout' % self.id)
226 sys.stdout.topic = cast_bytes('engine.%i.stdout' % self.id)
226 sys.stderr = self.out_stream_factory(self.session, iopub_socket, u'stderr')
227 sys.stderr = self.out_stream_factory(self.session, iopub_socket, u'stderr')
227 sys.stderr.topic = cast_bytes('engine.%i.stderr' % self.id)
228 sys.stderr.topic = cast_bytes('engine.%i.stderr' % self.id)
228 if self.display_hook_factory:
229 if self.display_hook_factory:
229 sys.displayhook = self.display_hook_factory(self.session, iopub_socket)
230 sys.displayhook = self.display_hook_factory(self.session, iopub_socket)
230 sys.displayhook.topic = cast_bytes('engine.%i.pyout' % self.id)
231 sys.displayhook.topic = cast_bytes('engine.%i.pyout' % self.id)
231
232
232 self.kernel = Kernel(config=self.config, int_id=self.id, ident=self.ident, session=self.session,
233 self.kernel = Kernel(config=self.config, int_id=self.id, ident=self.ident, session=self.session,
233 control_stream=control_stream, shell_streams=shell_streams, iopub_socket=iopub_socket,
234 control_stream=control_stream, shell_streams=shell_streams, iopub_socket=iopub_socket,
234 loop=loop, user_ns=self.user_ns, log=self.log)
235 loop=loop, user_ns=self.user_ns, log=self.log)
235
236
236 self.kernel.shell.display_pub.topic = cast_bytes('engine.%i.displaypub' % self.id)
237 self.kernel.shell.display_pub.topic = cast_bytes('engine.%i.displaypub' % self.id)
237
238
238
239
239 # periodically check the heartbeat pings of the controller
240 # periodically check the heartbeat pings of the controller
240 # Should be started here and not in "start()" so that the right period can be taken
241 # Should be started here and not in "start()" so that the right period can be taken
241 # from the hubs HeartBeatMonitor.period
242 # from the hubs HeartBeatMonitor.period
242 if self.max_heartbeat_misses > 0:
243 if self.max_heartbeat_misses > 0:
243 # Use a slightly bigger check period than the hub signal period to not warn unnecessary
244 # Use a slightly bigger check period than the hub signal period to not warn unnecessary
244 self.hb_check_period = int(content['hb_period'])+10
245 self.hb_check_period = int(content['hb_period'])+10
245 self.log.info("Starting to monitor the heartbeat signal from the hub every %i ms." , self.hb_check_period)
246 self.log.info("Starting to monitor the heartbeat signal from the hub every %i ms." , self.hb_check_period)
246 self._hb_reporter = ioloop.PeriodicCallback(self._hb_monitor, self.hb_check_period, self.loop)
247 self._hb_reporter = ioloop.PeriodicCallback(self._hb_monitor, self.hb_check_period, self.loop)
247 self._hb_reporter.start()
248 self._hb_reporter.start()
248 else:
249 else:
249 self.log.info("Monitoring of the heartbeat signal from the hub is not enabled.")
250 self.log.info("Monitoring of the heartbeat signal from the hub is not enabled.")
250
251
251
252
252 # FIXME: This is a hack until IPKernelApp and IPEngineApp can be fully merged
253 # FIXME: This is a hack until IPKernelApp and IPEngineApp can be fully merged
253 app = IPKernelApp(config=self.config, shell=self.kernel.shell, kernel=self.kernel, log=self.log)
254 app = IPKernelApp(config=self.config, shell=self.kernel.shell, kernel=self.kernel, log=self.log)
254 app.init_profile_dir()
255 app.init_profile_dir()
255 app.init_code()
256 app.init_code()
256
257
257 self.kernel.start()
258 self.kernel.start()
258 else:
259 else:
259 self.log.fatal("Registration Failed: %s"%msg)
260 self.log.fatal("Registration Failed: %s"%msg)
260 raise Exception("Registration Failed: %s"%msg)
261 raise Exception("Registration Failed: %s"%msg)
261
262
262 self.log.info("Completed registration with id %i"%self.id)
263 self.log.info("Completed registration with id %i"%self.id)
263
264
264
265
265 def abort(self):
266 def abort(self):
266 self.log.fatal("Registration timed out after %.1f seconds"%self.timeout)
267 self.log.fatal("Registration timed out after %.1f seconds"%self.timeout)
267 if self.url.startswith('127.'):
268 if self.url.startswith('127.'):
268 self.log.fatal("""
269 self.log.fatal("""
269 If the controller and engines are not on the same machine,
270 If the controller and engines are not on the same machine,
270 you will have to instruct the controller to listen on an external IP (in ipcontroller_config.py):
271 you will have to instruct the controller to listen on an external IP (in ipcontroller_config.py):
271 c.HubFactory.ip='*' # for all interfaces, internal and external
272 c.HubFactory.ip='*' # for all interfaces, internal and external
272 c.HubFactory.ip='192.168.1.101' # or any interface that the engines can see
273 c.HubFactory.ip='192.168.1.101' # or any interface that the engines can see
273 or tunnel connections via ssh.
274 or tunnel connections via ssh.
274 """)
275 """)
275 self.session.send(self.registrar, "unregistration_request", content=dict(id=self.id))
276 self.session.send(self.registrar, "unregistration_request", content=dict(id=self.id))
276 time.sleep(1)
277 time.sleep(1)
277 sys.exit(255)
278 sys.exit(255)
278
279
279 def _hb_monitor(self):
280 def _hb_monitor(self):
280 """Callback to monitor the heartbeat from the controller"""
281 """Callback to monitor the heartbeat from the controller"""
281 self._hb_listener.flush()
282 self._hb_listener.flush()
282 if self._hb_last_monitored > self._hb_last_pinged:
283 if self._hb_last_monitored > self._hb_last_pinged:
283 self._hb_missed_beats += 1
284 self._hb_missed_beats += 1
284 self.log.warn("No heartbeat in the last %s ms (%s time(s) in a row).", self.hb_check_period, self._hb_missed_beats)
285 self.log.warn("No heartbeat in the last %s ms (%s time(s) in a row).", self.hb_check_period, self._hb_missed_beats)
285 else:
286 else:
286 #self.log.debug("Heartbeat received (after missing %s beats).", self._hb_missed_beats)
287 #self.log.debug("Heartbeat received (after missing %s beats).", self._hb_missed_beats)
287 self._hb_missed_beats = 0
288 self._hb_missed_beats = 0
288
289
289 if self._hb_missed_beats >= self.max_heartbeat_misses:
290 if self._hb_missed_beats >= self.max_heartbeat_misses:
290 self.log.fatal("Maximum number of heartbeats misses reached (%s times %s ms), shutting down.",
291 self.log.fatal("Maximum number of heartbeats misses reached (%s times %s ms), shutting down.",
291 self.max_heartbeat_misses, self.hb_check_period)
292 self.max_heartbeat_misses, self.hb_check_period)
292 self.session.send(self.registrar, "unregistration_request", content=dict(id=self.id))
293 self.session.send(self.registrar, "unregistration_request", content=dict(id=self.id))
293 self.loop.stop()
294 self.loop.stop()
294
295
295 self._hb_last_monitored = time.time()
296 self._hb_last_monitored = time.time()
296
297
297
298
298 def start(self):
299 def start(self):
299 dc = ioloop.DelayedCallback(self.register, 0, self.loop)
300 dc = ioloop.DelayedCallback(self.register, 0, self.loop)
300 dc.start()
301 dc.start()
301 self._abort_dc = ioloop.DelayedCallback(self.abort, self.timeout*1000, self.loop)
302 self._abort_dc = ioloop.DelayedCallback(self.abort, self.timeout*1000, self.loop)
302 self._abort_dc.start()
303 self._abort_dc.start()
303
304
304
305
@@ -1,923 +1,779 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """A simple interactive kernel that talks to a frontend over 0MQ.
2 """A simple interactive kernel that talks to a frontend over 0MQ.
3
3
4 Things to do:
4 Things to do:
5
5
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 call set_parent on all the PUB objects with the message about to be executed.
7 call set_parent on all the PUB objects with the message about to be executed.
8 * Implement random port and security key logic.
8 * Implement random port and security key logic.
9 * Implement control messages.
9 * Implement control messages.
10 * Implement event loop and poll version.
10 * Implement event loop and poll version.
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Standard library imports
18 # Standard library imports
19 import __builtin__
19 import __builtin__
20 import atexit
21 import sys
20 import sys
22 import time
21 import time
23 import traceback
22 import traceback
24 import logging
23 import logging
25 import uuid
24 import uuid
26
25
27 from datetime import datetime
26 from datetime import datetime
28 from signal import (
27 from signal import (
29 signal, getsignal, default_int_handler, SIGINT, SIG_IGN
28 signal, getsignal, default_int_handler, SIGINT, SIG_IGN
30 )
29 )
31
30
32 # System library imports
31 # System library imports
33 import zmq
32 import zmq
34 from zmq.eventloop import ioloop
33 from zmq.eventloop import ioloop
35 from zmq.eventloop.zmqstream import ZMQStream
34 from zmq.eventloop.zmqstream import ZMQStream
36
35
37 # Local imports
36 # Local imports
38 from IPython.config.configurable import Configurable
37 from IPython.config.configurable import Configurable
39 from IPython.config.application import boolean_flag, catch_config_error
40 from IPython.core.application import ProfileDir
41 from IPython.core.error import StdinNotImplementedError
38 from IPython.core.error import StdinNotImplementedError
42 from IPython.core import release
39 from IPython.core import release
43 from IPython.core.shellapp import (
44 InteractiveShellApp, shell_flags, shell_aliases
45 )
46 from IPython.utils import io
40 from IPython.utils import io
47 from IPython.utils import py3compat
41 from IPython.utils import py3compat
48 from IPython.utils.frame import extract_module_locals
49 from IPython.utils.jsonutil import json_clean
42 from IPython.utils.jsonutil import json_clean
50 from IPython.utils.traitlets import (
43 from IPython.utils.traitlets import (
51 Any, Instance, Float, Dict, CaselessStrEnum, List, Set, Integer, Unicode,
44 Any, Instance, Float, Dict, CaselessStrEnum, List, Set, Integer, Unicode,
52 Type
45 Type
53 )
46 )
54
47
55 from kernelapp import KernelApp, kernel_flags, kernel_aliases
56 from serialize import serialize_object, unpack_apply_message
48 from serialize import serialize_object, unpack_apply_message
57 from session import Session, Message
49 from session import Session
58 from zmqshell import ZMQInteractiveShell
50 from zmqshell import ZMQInteractiveShell
59
51
60
52
61 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
62 # Main kernel class
54 # Main kernel class
63 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
64
56
65 protocol_version = list(release.kernel_protocol_version_info)
57 protocol_version = list(release.kernel_protocol_version_info)
66 ipython_version = list(release.version_info)
58 ipython_version = list(release.version_info)
67 language_version = list(sys.version_info[:3])
59 language_version = list(sys.version_info[:3])
68
60
69
61
70 class Kernel(Configurable):
62 class Kernel(Configurable):
71
63
72 #---------------------------------------------------------------------------
64 #---------------------------------------------------------------------------
73 # Kernel interface
65 # Kernel interface
74 #---------------------------------------------------------------------------
66 #---------------------------------------------------------------------------
75
67
76 # attribute to override with a GUI
68 # attribute to override with a GUI
77 eventloop = Any(None)
69 eventloop = Any(None)
78 def _eventloop_changed(self, name, old, new):
70 def _eventloop_changed(self, name, old, new):
79 """schedule call to eventloop from IOLoop"""
71 """schedule call to eventloop from IOLoop"""
80 loop = ioloop.IOLoop.instance()
72 loop = ioloop.IOLoop.instance()
81 loop.add_timeout(time.time()+0.1, self.enter_eventloop)
73 loop.add_timeout(time.time()+0.1, self.enter_eventloop)
82
74
83 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
75 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
84 shell_class = Type(ZMQInteractiveShell)
76 shell_class = Type(ZMQInteractiveShell)
85
77
86 session = Instance(Session)
78 session = Instance(Session)
87 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
79 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
88 shell_streams = List()
80 shell_streams = List()
89 control_stream = Instance(ZMQStream)
81 control_stream = Instance(ZMQStream)
90 iopub_socket = Instance(zmq.Socket)
82 iopub_socket = Instance(zmq.Socket)
91 stdin_socket = Instance(zmq.Socket)
83 stdin_socket = Instance(zmq.Socket)
92 log = Instance(logging.Logger)
84 log = Instance(logging.Logger)
93
85
94 user_module = Any()
86 user_module = Any()
95 def _user_module_changed(self, name, old, new):
87 def _user_module_changed(self, name, old, new):
96 if self.shell is not None:
88 if self.shell is not None:
97 self.shell.user_module = new
89 self.shell.user_module = new
98
90
99 user_ns = Dict(default_value=None)
91 user_ns = Dict(default_value=None)
100 def _user_ns_changed(self, name, old, new):
92 def _user_ns_changed(self, name, old, new):
101 if self.shell is not None:
93 if self.shell is not None:
102 self.shell.user_ns = new
94 self.shell.user_ns = new
103 self.shell.init_user_ns()
95 self.shell.init_user_ns()
104
96
105 # identities:
97 # identities:
106 int_id = Integer(-1)
98 int_id = Integer(-1)
107 ident = Unicode()
99 ident = Unicode()
108
100
109 def _ident_default(self):
101 def _ident_default(self):
110 return unicode(uuid.uuid4())
102 return unicode(uuid.uuid4())
111
103
112
104
113 # Private interface
105 # Private interface
114
106
115 # Time to sleep after flushing the stdout/err buffers in each execute
107 # Time to sleep after flushing the stdout/err buffers in each execute
116 # cycle. While this introduces a hard limit on the minimal latency of the
108 # cycle. While this introduces a hard limit on the minimal latency of the
117 # execute cycle, it helps prevent output synchronization problems for
109 # execute cycle, it helps prevent output synchronization problems for
118 # clients.
110 # clients.
119 # Units are in seconds. The minimum zmq latency on local host is probably
111 # Units are in seconds. The minimum zmq latency on local host is probably
120 # ~150 microseconds, set this to 500us for now. We may need to increase it
112 # ~150 microseconds, set this to 500us for now. We may need to increase it
121 # a little if it's not enough after more interactive testing.
113 # a little if it's not enough after more interactive testing.
122 _execute_sleep = Float(0.0005, config=True)
114 _execute_sleep = Float(0.0005, config=True)
123
115
124 # Frequency of the kernel's event loop.
116 # Frequency of the kernel's event loop.
125 # Units are in seconds, kernel subclasses for GUI toolkits may need to
117 # Units are in seconds, kernel subclasses for GUI toolkits may need to
126 # adapt to milliseconds.
118 # adapt to milliseconds.
127 _poll_interval = Float(0.05, config=True)
119 _poll_interval = Float(0.05, config=True)
128
120
129 # If the shutdown was requested over the network, we leave here the
121 # If the shutdown was requested over the network, we leave here the
130 # necessary reply message so it can be sent by our registered atexit
122 # necessary reply message so it can be sent by our registered atexit
131 # handler. This ensures that the reply is only sent to clients truly at
123 # handler. This ensures that the reply is only sent to clients truly at
132 # the end of our shutdown process (which happens after the underlying
124 # the end of our shutdown process (which happens after the underlying
133 # IPython shell's own shutdown).
125 # IPython shell's own shutdown).
134 _shutdown_message = None
126 _shutdown_message = None
135
127
136 # This is a dict of port number that the kernel is listening on. It is set
128 # This is a dict of port number that the kernel is listening on. It is set
137 # by record_ports and used by connect_request.
129 # by record_ports and used by connect_request.
138 _recorded_ports = Dict()
130 _recorded_ports = Dict()
139
131
140 # A reference to the Python builtin 'raw_input' function.
132 # A reference to the Python builtin 'raw_input' function.
141 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
133 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
142 _sys_raw_input = Any()
134 _sys_raw_input = Any()
143
135
144 # set of aborted msg_ids
136 # set of aborted msg_ids
145 aborted = Set()
137 aborted = Set()
146
138
147
139
148 def __init__(self, **kwargs):
140 def __init__(self, **kwargs):
149 super(Kernel, self).__init__(**kwargs)
141 super(Kernel, self).__init__(**kwargs)
150
142
151 # Initialize the InteractiveShell subclass
143 # Initialize the InteractiveShell subclass
152 self.shell = self.shell_class.instance(config=self.config,
144 self.shell = self.shell_class.instance(config=self.config,
153 profile_dir = self.profile_dir,
145 profile_dir = self.profile_dir,
154 user_module = self.user_module,
146 user_module = self.user_module,
155 user_ns = self.user_ns,
147 user_ns = self.user_ns,
156 )
148 )
157 self.shell.displayhook.session = self.session
149 self.shell.displayhook.session = self.session
158 self.shell.displayhook.pub_socket = self.iopub_socket
150 self.shell.displayhook.pub_socket = self.iopub_socket
159 self.shell.displayhook.topic = self._topic('pyout')
151 self.shell.displayhook.topic = self._topic('pyout')
160 self.shell.display_pub.session = self.session
152 self.shell.display_pub.session = self.session
161 self.shell.display_pub.pub_socket = self.iopub_socket
153 self.shell.display_pub.pub_socket = self.iopub_socket
162 self.shell.data_pub.session = self.session
154 self.shell.data_pub.session = self.session
163 self.shell.data_pub.pub_socket = self.iopub_socket
155 self.shell.data_pub.pub_socket = self.iopub_socket
164
156
165 # TMP - hack while developing
157 # TMP - hack while developing
166 self.shell._reply_content = None
158 self.shell._reply_content = None
167
159
168 # Build dict of handlers for message types
160 # Build dict of handlers for message types
169 msg_types = [ 'execute_request', 'complete_request',
161 msg_types = [ 'execute_request', 'complete_request',
170 'object_info_request', 'history_request',
162 'object_info_request', 'history_request',
171 'kernel_info_request',
163 'kernel_info_request',
172 'connect_request', 'shutdown_request',
164 'connect_request', 'shutdown_request',
173 'apply_request',
165 'apply_request',
174 ]
166 ]
175 self.shell_handlers = {}
167 self.shell_handlers = {}
176 for msg_type in msg_types:
168 for msg_type in msg_types:
177 self.shell_handlers[msg_type] = getattr(self, msg_type)
169 self.shell_handlers[msg_type] = getattr(self, msg_type)
178
170
179 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
171 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
180 self.control_handlers = {}
172 self.control_handlers = {}
181 for msg_type in control_msg_types:
173 for msg_type in control_msg_types:
182 self.control_handlers[msg_type] = getattr(self, msg_type)
174 self.control_handlers[msg_type] = getattr(self, msg_type)
183
175
184 def dispatch_control(self, msg):
176 def dispatch_control(self, msg):
185 """dispatch control requests"""
177 """dispatch control requests"""
186 idents,msg = self.session.feed_identities(msg, copy=False)
178 idents,msg = self.session.feed_identities(msg, copy=False)
187 try:
179 try:
188 msg = self.session.unserialize(msg, content=True, copy=False)
180 msg = self.session.unserialize(msg, content=True, copy=False)
189 except:
181 except:
190 self.log.error("Invalid Control Message", exc_info=True)
182 self.log.error("Invalid Control Message", exc_info=True)
191 return
183 return
192
184
193 self.log.debug("Control received: %s", msg)
185 self.log.debug("Control received: %s", msg)
194
186
195 header = msg['header']
187 header = msg['header']
196 msg_id = header['msg_id']
188 msg_id = header['msg_id']
197 msg_type = header['msg_type']
189 msg_type = header['msg_type']
198
190
199 handler = self.control_handlers.get(msg_type, None)
191 handler = self.control_handlers.get(msg_type, None)
200 if handler is None:
192 if handler is None:
201 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
193 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
202 else:
194 else:
203 try:
195 try:
204 handler(self.control_stream, idents, msg)
196 handler(self.control_stream, idents, msg)
205 except Exception:
197 except Exception:
206 self.log.error("Exception in control handler:", exc_info=True)
198 self.log.error("Exception in control handler:", exc_info=True)
207
199
208 def dispatch_shell(self, stream, msg):
200 def dispatch_shell(self, stream, msg):
209 """dispatch shell requests"""
201 """dispatch shell requests"""
210 # flush control requests first
202 # flush control requests first
211 if self.control_stream:
203 if self.control_stream:
212 self.control_stream.flush()
204 self.control_stream.flush()
213
205
214 idents,msg = self.session.feed_identities(msg, copy=False)
206 idents,msg = self.session.feed_identities(msg, copy=False)
215 try:
207 try:
216 msg = self.session.unserialize(msg, content=True, copy=False)
208 msg = self.session.unserialize(msg, content=True, copy=False)
217 except:
209 except:
218 self.log.error("Invalid Message", exc_info=True)
210 self.log.error("Invalid Message", exc_info=True)
219 return
211 return
220
212
221 header = msg['header']
213 header = msg['header']
222 msg_id = header['msg_id']
214 msg_id = header['msg_id']
223 msg_type = msg['header']['msg_type']
215 msg_type = msg['header']['msg_type']
224
216
225 # Print some info about this message and leave a '--->' marker, so it's
217 # Print some info about this message and leave a '--->' marker, so it's
226 # easier to trace visually the message chain when debugging. Each
218 # easier to trace visually the message chain when debugging. Each
227 # handler prints its message at the end.
219 # handler prints its message at the end.
228 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
220 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
229 self.log.debug(' Content: %s\n --->\n ', msg['content'])
221 self.log.debug(' Content: %s\n --->\n ', msg['content'])
230
222
231 if msg_id in self.aborted:
223 if msg_id in self.aborted:
232 self.aborted.remove(msg_id)
224 self.aborted.remove(msg_id)
233 # is it safe to assume a msg_id will not be resubmitted?
225 # is it safe to assume a msg_id will not be resubmitted?
234 reply_type = msg_type.split('_')[0] + '_reply'
226 reply_type = msg_type.split('_')[0] + '_reply'
235 status = {'status' : 'aborted'}
227 status = {'status' : 'aborted'}
236 md = {'engine' : self.ident}
228 md = {'engine' : self.ident}
237 md.update(status)
229 md.update(status)
238 reply_msg = self.session.send(stream, reply_type, metadata=md,
230 reply_msg = self.session.send(stream, reply_type, metadata=md,
239 content=status, parent=msg, ident=idents)
231 content=status, parent=msg, ident=idents)
240 return
232 return
241
233
242 handler = self.shell_handlers.get(msg_type, None)
234 handler = self.shell_handlers.get(msg_type, None)
243 if handler is None:
235 if handler is None:
244 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
236 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
245 else:
237 else:
246 # ensure default_int_handler during handler call
238 # ensure default_int_handler during handler call
247 sig = signal(SIGINT, default_int_handler)
239 sig = signal(SIGINT, default_int_handler)
248 try:
240 try:
249 handler(stream, idents, msg)
241 handler(stream, idents, msg)
250 except Exception:
242 except Exception:
251 self.log.error("Exception in message handler:", exc_info=True)
243 self.log.error("Exception in message handler:", exc_info=True)
252 finally:
244 finally:
253 signal(SIGINT, sig)
245 signal(SIGINT, sig)
254
246
255 def enter_eventloop(self):
247 def enter_eventloop(self):
256 """enter eventloop"""
248 """enter eventloop"""
257 self.log.info("entering eventloop")
249 self.log.info("entering eventloop")
258 # restore default_int_handler
250 # restore default_int_handler
259 signal(SIGINT, default_int_handler)
251 signal(SIGINT, default_int_handler)
260 while self.eventloop is not None:
252 while self.eventloop is not None:
261 try:
253 try:
262 self.eventloop(self)
254 self.eventloop(self)
263 except KeyboardInterrupt:
255 except KeyboardInterrupt:
264 # Ctrl-C shouldn't crash the kernel
256 # Ctrl-C shouldn't crash the kernel
265 self.log.error("KeyboardInterrupt caught in kernel")
257 self.log.error("KeyboardInterrupt caught in kernel")
266 continue
258 continue
267 else:
259 else:
268 # eventloop exited cleanly, this means we should stop (right?)
260 # eventloop exited cleanly, this means we should stop (right?)
269 self.eventloop = None
261 self.eventloop = None
270 break
262 break
271 self.log.info("exiting eventloop")
263 self.log.info("exiting eventloop")
272
264
273 def start(self):
265 def start(self):
274 """register dispatchers for streams"""
266 """register dispatchers for streams"""
275 self.shell.exit_now = False
267 self.shell.exit_now = False
276 if self.control_stream:
268 if self.control_stream:
277 self.control_stream.on_recv(self.dispatch_control, copy=False)
269 self.control_stream.on_recv(self.dispatch_control, copy=False)
278
270
279 def make_dispatcher(stream):
271 def make_dispatcher(stream):
280 def dispatcher(msg):
272 def dispatcher(msg):
281 return self.dispatch_shell(stream, msg)
273 return self.dispatch_shell(stream, msg)
282 return dispatcher
274 return dispatcher
283
275
284 for s in self.shell_streams:
276 for s in self.shell_streams:
285 s.on_recv(make_dispatcher(s), copy=False)
277 s.on_recv(make_dispatcher(s), copy=False)
286
278
287 def do_one_iteration(self):
279 def do_one_iteration(self):
288 """step eventloop just once"""
280 """step eventloop just once"""
289 if self.control_stream:
281 if self.control_stream:
290 self.control_stream.flush()
282 self.control_stream.flush()
291 for stream in self.shell_streams:
283 for stream in self.shell_streams:
292 # handle at most one request per iteration
284 # handle at most one request per iteration
293 stream.flush(zmq.POLLIN, 1)
285 stream.flush(zmq.POLLIN, 1)
294 stream.flush(zmq.POLLOUT)
286 stream.flush(zmq.POLLOUT)
295
287
296
288
297 def record_ports(self, ports):
289 def record_ports(self, ports):
298 """Record the ports that this kernel is using.
290 """Record the ports that this kernel is using.
299
291
300 The creator of the Kernel instance must call this methods if they
292 The creator of the Kernel instance must call this methods if they
301 want the :meth:`connect_request` method to return the port numbers.
293 want the :meth:`connect_request` method to return the port numbers.
302 """
294 """
303 self._recorded_ports = ports
295 self._recorded_ports = ports
304
296
305 #---------------------------------------------------------------------------
297 #---------------------------------------------------------------------------
306 # Kernel request handlers
298 # Kernel request handlers
307 #---------------------------------------------------------------------------
299 #---------------------------------------------------------------------------
308
300
309 def _make_metadata(self, other=None):
301 def _make_metadata(self, other=None):
310 """init metadata dict, for execute/apply_reply"""
302 """init metadata dict, for execute/apply_reply"""
311 new_md = {
303 new_md = {
312 'dependencies_met' : True,
304 'dependencies_met' : True,
313 'engine' : self.ident,
305 'engine' : self.ident,
314 'started': datetime.now(),
306 'started': datetime.now(),
315 }
307 }
316 if other:
308 if other:
317 new_md.update(other)
309 new_md.update(other)
318 return new_md
310 return new_md
319
311
320 def _publish_pyin(self, code, parent, execution_count):
312 def _publish_pyin(self, code, parent, execution_count):
321 """Publish the code request on the pyin stream."""
313 """Publish the code request on the pyin stream."""
322
314
323 self.session.send(self.iopub_socket, u'pyin',
315 self.session.send(self.iopub_socket, u'pyin',
324 {u'code':code, u'execution_count': execution_count},
316 {u'code':code, u'execution_count': execution_count},
325 parent=parent, ident=self._topic('pyin')
317 parent=parent, ident=self._topic('pyin')
326 )
318 )
327
319
328 def _publish_status(self, status, parent=None):
320 def _publish_status(self, status, parent=None):
329 """send status (busy/idle) on IOPub"""
321 """send status (busy/idle) on IOPub"""
330 self.session.send(self.iopub_socket,
322 self.session.send(self.iopub_socket,
331 u'status',
323 u'status',
332 {u'execution_state': status},
324 {u'execution_state': status},
333 parent=parent,
325 parent=parent,
334 ident=self._topic('status'),
326 ident=self._topic('status'),
335 )
327 )
336
328
337
329
338 def execute_request(self, stream, ident, parent):
330 def execute_request(self, stream, ident, parent):
339 """handle an execute_request"""
331 """handle an execute_request"""
340
332
341 self._publish_status(u'busy', parent)
333 self._publish_status(u'busy', parent)
342
334
343 try:
335 try:
344 content = parent[u'content']
336 content = parent[u'content']
345 code = content[u'code']
337 code = content[u'code']
346 silent = content[u'silent']
338 silent = content[u'silent']
347 store_history = content.get(u'store_history', not silent)
339 store_history = content.get(u'store_history', not silent)
348 except:
340 except:
349 self.log.error("Got bad msg: ")
341 self.log.error("Got bad msg: ")
350 self.log.error("%s", parent)
342 self.log.error("%s", parent)
351 return
343 return
352
344
353 md = self._make_metadata(parent['metadata'])
345 md = self._make_metadata(parent['metadata'])
354
346
355 shell = self.shell # we'll need this a lot here
347 shell = self.shell # we'll need this a lot here
356
348
357 # Replace raw_input. Note that is not sufficient to replace
349 # Replace raw_input. Note that is not sufficient to replace
358 # raw_input in the user namespace.
350 # raw_input in the user namespace.
359 if content.get('allow_stdin', False):
351 if content.get('allow_stdin', False):
360 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
352 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
361 else:
353 else:
362 raw_input = lambda prompt='' : self._no_raw_input()
354 raw_input = lambda prompt='' : self._no_raw_input()
363
355
364 if py3compat.PY3:
356 if py3compat.PY3:
365 self._sys_raw_input = __builtin__.input
357 self._sys_raw_input = __builtin__.input
366 __builtin__.input = raw_input
358 __builtin__.input = raw_input
367 else:
359 else:
368 self._sys_raw_input = __builtin__.raw_input
360 self._sys_raw_input = __builtin__.raw_input
369 __builtin__.raw_input = raw_input
361 __builtin__.raw_input = raw_input
370
362
371 # Set the parent message of the display hook and out streams.
363 # Set the parent message of the display hook and out streams.
372 shell.displayhook.set_parent(parent)
364 shell.displayhook.set_parent(parent)
373 shell.display_pub.set_parent(parent)
365 shell.display_pub.set_parent(parent)
374 shell.data_pub.set_parent(parent)
366 shell.data_pub.set_parent(parent)
375 sys.stdout.set_parent(parent)
367 sys.stdout.set_parent(parent)
376 sys.stderr.set_parent(parent)
368 sys.stderr.set_parent(parent)
377
369
378 # Re-broadcast our input for the benefit of listening clients, and
370 # Re-broadcast our input for the benefit of listening clients, and
379 # start computing output
371 # start computing output
380 if not silent:
372 if not silent:
381 self._publish_pyin(code, parent, shell.execution_count)
373 self._publish_pyin(code, parent, shell.execution_count)
382
374
383 reply_content = {}
375 reply_content = {}
384 try:
376 try:
385 # FIXME: the shell calls the exception handler itself.
377 # FIXME: the shell calls the exception handler itself.
386 shell.run_cell(code, store_history=store_history, silent=silent)
378 shell.run_cell(code, store_history=store_history, silent=silent)
387 except:
379 except:
388 status = u'error'
380 status = u'error'
389 # FIXME: this code right now isn't being used yet by default,
381 # FIXME: this code right now isn't being used yet by default,
390 # because the run_cell() call above directly fires off exception
382 # because the run_cell() call above directly fires off exception
391 # reporting. This code, therefore, is only active in the scenario
383 # reporting. This code, therefore, is only active in the scenario
392 # where runlines itself has an unhandled exception. We need to
384 # where runlines itself has an unhandled exception. We need to
393 # uniformize this, for all exception construction to come from a
385 # uniformize this, for all exception construction to come from a
394 # single location in the codbase.
386 # single location in the codbase.
395 etype, evalue, tb = sys.exc_info()
387 etype, evalue, tb = sys.exc_info()
396 tb_list = traceback.format_exception(etype, evalue, tb)
388 tb_list = traceback.format_exception(etype, evalue, tb)
397 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
389 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
398 else:
390 else:
399 status = u'ok'
391 status = u'ok'
400 finally:
392 finally:
401 # Restore raw_input.
393 # Restore raw_input.
402 if py3compat.PY3:
394 if py3compat.PY3:
403 __builtin__.input = self._sys_raw_input
395 __builtin__.input = self._sys_raw_input
404 else:
396 else:
405 __builtin__.raw_input = self._sys_raw_input
397 __builtin__.raw_input = self._sys_raw_input
406
398
407 reply_content[u'status'] = status
399 reply_content[u'status'] = status
408
400
409 # Return the execution counter so clients can display prompts
401 # Return the execution counter so clients can display prompts
410 reply_content['execution_count'] = shell.execution_count - 1
402 reply_content['execution_count'] = shell.execution_count - 1
411
403
412 # FIXME - fish exception info out of shell, possibly left there by
404 # FIXME - fish exception info out of shell, possibly left there by
413 # runlines. We'll need to clean up this logic later.
405 # runlines. We'll need to clean up this logic later.
414 if shell._reply_content is not None:
406 if shell._reply_content is not None:
415 reply_content.update(shell._reply_content)
407 reply_content.update(shell._reply_content)
416 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
408 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
417 reply_content['engine_info'] = e_info
409 reply_content['engine_info'] = e_info
418 # reset after use
410 # reset after use
419 shell._reply_content = None
411 shell._reply_content = None
420
412
421 if 'traceback' in reply_content:
413 if 'traceback' in reply_content:
422 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
414 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
423
415
424
416
425 # At this point, we can tell whether the main code execution succeeded
417 # At this point, we can tell whether the main code execution succeeded
426 # or not. If it did, we proceed to evaluate user_variables/expressions
418 # or not. If it did, we proceed to evaluate user_variables/expressions
427 if reply_content['status'] == 'ok':
419 if reply_content['status'] == 'ok':
428 reply_content[u'user_variables'] = \
420 reply_content[u'user_variables'] = \
429 shell.user_variables(content.get(u'user_variables', []))
421 shell.user_variables(content.get(u'user_variables', []))
430 reply_content[u'user_expressions'] = \
422 reply_content[u'user_expressions'] = \
431 shell.user_expressions(content.get(u'user_expressions', {}))
423 shell.user_expressions(content.get(u'user_expressions', {}))
432 else:
424 else:
433 # If there was an error, don't even try to compute variables or
425 # If there was an error, don't even try to compute variables or
434 # expressions
426 # expressions
435 reply_content[u'user_variables'] = {}
427 reply_content[u'user_variables'] = {}
436 reply_content[u'user_expressions'] = {}
428 reply_content[u'user_expressions'] = {}
437
429
438 # Payloads should be retrieved regardless of outcome, so we can both
430 # Payloads should be retrieved regardless of outcome, so we can both
439 # recover partial output (that could have been generated early in a
431 # recover partial output (that could have been generated early in a
440 # block, before an error) and clear the payload system always.
432 # block, before an error) and clear the payload system always.
441 reply_content[u'payload'] = shell.payload_manager.read_payload()
433 reply_content[u'payload'] = shell.payload_manager.read_payload()
442 # Be agressive about clearing the payload because we don't want
434 # Be agressive about clearing the payload because we don't want
443 # it to sit in memory until the next execute_request comes in.
435 # it to sit in memory until the next execute_request comes in.
444 shell.payload_manager.clear_payload()
436 shell.payload_manager.clear_payload()
445
437
446 # Flush output before sending the reply.
438 # Flush output before sending the reply.
447 sys.stdout.flush()
439 sys.stdout.flush()
448 sys.stderr.flush()
440 sys.stderr.flush()
449 # FIXME: on rare occasions, the flush doesn't seem to make it to the
441 # FIXME: on rare occasions, the flush doesn't seem to make it to the
450 # clients... This seems to mitigate the problem, but we definitely need
442 # clients... This seems to mitigate the problem, but we definitely need
451 # to better understand what's going on.
443 # to better understand what's going on.
452 if self._execute_sleep:
444 if self._execute_sleep:
453 time.sleep(self._execute_sleep)
445 time.sleep(self._execute_sleep)
454
446
455 # Send the reply.
447 # Send the reply.
456 reply_content = json_clean(reply_content)
448 reply_content = json_clean(reply_content)
457
449
458 md['status'] = reply_content['status']
450 md['status'] = reply_content['status']
459 if reply_content['status'] == 'error' and \
451 if reply_content['status'] == 'error' and \
460 reply_content['ename'] == 'UnmetDependency':
452 reply_content['ename'] == 'UnmetDependency':
461 md['dependencies_met'] = False
453 md['dependencies_met'] = False
462
454
463 reply_msg = self.session.send(stream, u'execute_reply',
455 reply_msg = self.session.send(stream, u'execute_reply',
464 reply_content, parent, metadata=md,
456 reply_content, parent, metadata=md,
465 ident=ident)
457 ident=ident)
466
458
467 self.log.debug("%s", reply_msg)
459 self.log.debug("%s", reply_msg)
468
460
469 if not silent and reply_msg['content']['status'] == u'error':
461 if not silent and reply_msg['content']['status'] == u'error':
470 self._abort_queues()
462 self._abort_queues()
471
463
472 self._publish_status(u'idle', parent)
464 self._publish_status(u'idle', parent)
473
465
474 def complete_request(self, stream, ident, parent):
466 def complete_request(self, stream, ident, parent):
475 txt, matches = self._complete(parent)
467 txt, matches = self._complete(parent)
476 matches = {'matches' : matches,
468 matches = {'matches' : matches,
477 'matched_text' : txt,
469 'matched_text' : txt,
478 'status' : 'ok'}
470 'status' : 'ok'}
479 matches = json_clean(matches)
471 matches = json_clean(matches)
480 completion_msg = self.session.send(stream, 'complete_reply',
472 completion_msg = self.session.send(stream, 'complete_reply',
481 matches, parent, ident)
473 matches, parent, ident)
482 self.log.debug("%s", completion_msg)
474 self.log.debug("%s", completion_msg)
483
475
484 def object_info_request(self, stream, ident, parent):
476 def object_info_request(self, stream, ident, parent):
485 content = parent['content']
477 content = parent['content']
486 object_info = self.shell.object_inspect(content['oname'],
478 object_info = self.shell.object_inspect(content['oname'],
487 detail_level = content.get('detail_level', 0)
479 detail_level = content.get('detail_level', 0)
488 )
480 )
489 # Before we send this object over, we scrub it for JSON usage
481 # Before we send this object over, we scrub it for JSON usage
490 oinfo = json_clean(object_info)
482 oinfo = json_clean(object_info)
491 msg = self.session.send(stream, 'object_info_reply',
483 msg = self.session.send(stream, 'object_info_reply',
492 oinfo, parent, ident)
484 oinfo, parent, ident)
493 self.log.debug("%s", msg)
485 self.log.debug("%s", msg)
494
486
495 def history_request(self, stream, ident, parent):
487 def history_request(self, stream, ident, parent):
496 # We need to pull these out, as passing **kwargs doesn't work with
488 # We need to pull these out, as passing **kwargs doesn't work with
497 # unicode keys before Python 2.6.5.
489 # unicode keys before Python 2.6.5.
498 hist_access_type = parent['content']['hist_access_type']
490 hist_access_type = parent['content']['hist_access_type']
499 raw = parent['content']['raw']
491 raw = parent['content']['raw']
500 output = parent['content']['output']
492 output = parent['content']['output']
501 if hist_access_type == 'tail':
493 if hist_access_type == 'tail':
502 n = parent['content']['n']
494 n = parent['content']['n']
503 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
495 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
504 include_latest=True)
496 include_latest=True)
505
497
506 elif hist_access_type == 'range':
498 elif hist_access_type == 'range':
507 session = parent['content']['session']
499 session = parent['content']['session']
508 start = parent['content']['start']
500 start = parent['content']['start']
509 stop = parent['content']['stop']
501 stop = parent['content']['stop']
510 hist = self.shell.history_manager.get_range(session, start, stop,
502 hist = self.shell.history_manager.get_range(session, start, stop,
511 raw=raw, output=output)
503 raw=raw, output=output)
512
504
513 elif hist_access_type == 'search':
505 elif hist_access_type == 'search':
514 n = parent['content'].get('n')
506 n = parent['content'].get('n')
515 pattern = parent['content']['pattern']
507 pattern = parent['content']['pattern']
516 hist = self.shell.history_manager.search(pattern, raw=raw,
508 hist = self.shell.history_manager.search(pattern, raw=raw,
517 output=output, n=n)
509 output=output, n=n)
518
510
519 else:
511 else:
520 hist = []
512 hist = []
521 hist = list(hist)
513 hist = list(hist)
522 content = {'history' : hist}
514 content = {'history' : hist}
523 content = json_clean(content)
515 content = json_clean(content)
524 msg = self.session.send(stream, 'history_reply',
516 msg = self.session.send(stream, 'history_reply',
525 content, parent, ident)
517 content, parent, ident)
526 self.log.debug("Sending history reply with %i entries", len(hist))
518 self.log.debug("Sending history reply with %i entries", len(hist))
527
519
528 def connect_request(self, stream, ident, parent):
520 def connect_request(self, stream, ident, parent):
529 if self._recorded_ports is not None:
521 if self._recorded_ports is not None:
530 content = self._recorded_ports.copy()
522 content = self._recorded_ports.copy()
531 else:
523 else:
532 content = {}
524 content = {}
533 msg = self.session.send(stream, 'connect_reply',
525 msg = self.session.send(stream, 'connect_reply',
534 content, parent, ident)
526 content, parent, ident)
535 self.log.debug("%s", msg)
527 self.log.debug("%s", msg)
536
528
537 def kernel_info_request(self, stream, ident, parent):
529 def kernel_info_request(self, stream, ident, parent):
538 vinfo = {
530 vinfo = {
539 'protocol_version': protocol_version,
531 'protocol_version': protocol_version,
540 'ipython_version': ipython_version,
532 'ipython_version': ipython_version,
541 'language_version': language_version,
533 'language_version': language_version,
542 'language': 'python',
534 'language': 'python',
543 }
535 }
544 msg = self.session.send(stream, 'kernel_info_reply',
536 msg = self.session.send(stream, 'kernel_info_reply',
545 vinfo, parent, ident)
537 vinfo, parent, ident)
546 self.log.debug("%s", msg)
538 self.log.debug("%s", msg)
547
539
548 def shutdown_request(self, stream, ident, parent):
540 def shutdown_request(self, stream, ident, parent):
549 self.shell.exit_now = True
541 self.shell.exit_now = True
550 content = dict(status='ok')
542 content = dict(status='ok')
551 content.update(parent['content'])
543 content.update(parent['content'])
552 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
544 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
553 # same content, but different msg_id for broadcasting on IOPub
545 # same content, but different msg_id for broadcasting on IOPub
554 self._shutdown_message = self.session.msg(u'shutdown_reply',
546 self._shutdown_message = self.session.msg(u'shutdown_reply',
555 content, parent
547 content, parent
556 )
548 )
557
549
558 self._at_shutdown()
550 self._at_shutdown()
559 # call sys.exit after a short delay
551 # call sys.exit after a short delay
560 loop = ioloop.IOLoop.instance()
552 loop = ioloop.IOLoop.instance()
561 loop.add_timeout(time.time()+0.1, loop.stop)
553 loop.add_timeout(time.time()+0.1, loop.stop)
562
554
563 #---------------------------------------------------------------------------
555 #---------------------------------------------------------------------------
564 # Engine methods
556 # Engine methods
565 #---------------------------------------------------------------------------
557 #---------------------------------------------------------------------------
566
558
567 def apply_request(self, stream, ident, parent):
559 def apply_request(self, stream, ident, parent):
568 try:
560 try:
569 content = parent[u'content']
561 content = parent[u'content']
570 bufs = parent[u'buffers']
562 bufs = parent[u'buffers']
571 msg_id = parent['header']['msg_id']
563 msg_id = parent['header']['msg_id']
572 except:
564 except:
573 self.log.error("Got bad msg: %s", parent, exc_info=True)
565 self.log.error("Got bad msg: %s", parent, exc_info=True)
574 return
566 return
575
567
576 self._publish_status(u'busy', parent)
568 self._publish_status(u'busy', parent)
577
569
578 # Set the parent message of the display hook and out streams.
570 # Set the parent message of the display hook and out streams.
579 shell = self.shell
571 shell = self.shell
580 shell.displayhook.set_parent(parent)
572 shell.displayhook.set_parent(parent)
581 shell.display_pub.set_parent(parent)
573 shell.display_pub.set_parent(parent)
582 shell.data_pub.set_parent(parent)
574 shell.data_pub.set_parent(parent)
583 sys.stdout.set_parent(parent)
575 sys.stdout.set_parent(parent)
584 sys.stderr.set_parent(parent)
576 sys.stderr.set_parent(parent)
585
577
586 # pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
578 # pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
587 # self.iopub_socket.send(pyin_msg)
579 # self.iopub_socket.send(pyin_msg)
588 # self.session.send(self.iopub_socket, u'pyin', {u'code':code},parent=parent)
580 # self.session.send(self.iopub_socket, u'pyin', {u'code':code},parent=parent)
589 md = self._make_metadata(parent['metadata'])
581 md = self._make_metadata(parent['metadata'])
590 try:
582 try:
591 working = shell.user_ns
583 working = shell.user_ns
592
584
593 prefix = "_"+str(msg_id).replace("-","")+"_"
585 prefix = "_"+str(msg_id).replace("-","")+"_"
594
586
595 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
587 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
596
588
597 fname = getattr(f, '__name__', 'f')
589 fname = getattr(f, '__name__', 'f')
598
590
599 fname = prefix+"f"
591 fname = prefix+"f"
600 argname = prefix+"args"
592 argname = prefix+"args"
601 kwargname = prefix+"kwargs"
593 kwargname = prefix+"kwargs"
602 resultname = prefix+"result"
594 resultname = prefix+"result"
603
595
604 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
596 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
605 # print ns
597 # print ns
606 working.update(ns)
598 working.update(ns)
607 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
599 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
608 try:
600 try:
609 exec code in shell.user_global_ns, shell.user_ns
601 exec code in shell.user_global_ns, shell.user_ns
610 result = working.get(resultname)
602 result = working.get(resultname)
611 finally:
603 finally:
612 for key in ns.iterkeys():
604 for key in ns.iterkeys():
613 working.pop(key)
605 working.pop(key)
614
606
615 result_buf = serialize_object(result,
607 result_buf = serialize_object(result,
616 buffer_threshold=self.session.buffer_threshold,
608 buffer_threshold=self.session.buffer_threshold,
617 item_threshold=self.session.item_threshold,
609 item_threshold=self.session.item_threshold,
618 )
610 )
619
611
620 except:
612 except:
621 # invoke IPython traceback formatting
613 # invoke IPython traceback formatting
622 shell.showtraceback()
614 shell.showtraceback()
623 # FIXME - fish exception info out of shell, possibly left there by
615 # FIXME - fish exception info out of shell, possibly left there by
624 # run_code. We'll need to clean up this logic later.
616 # run_code. We'll need to clean up this logic later.
625 reply_content = {}
617 reply_content = {}
626 if shell._reply_content is not None:
618 if shell._reply_content is not None:
627 reply_content.update(shell._reply_content)
619 reply_content.update(shell._reply_content)
628 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
620 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
629 reply_content['engine_info'] = e_info
621 reply_content['engine_info'] = e_info
630 # reset after use
622 # reset after use
631 shell._reply_content = None
623 shell._reply_content = None
632
624
633 self.session.send(self.iopub_socket, u'pyerr', reply_content, parent=parent,
625 self.session.send(self.iopub_socket, u'pyerr', reply_content, parent=parent,
634 ident=self._topic('pyerr'))
626 ident=self._topic('pyerr'))
635 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
627 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
636 result_buf = []
628 result_buf = []
637
629
638 if reply_content['ename'] == 'UnmetDependency':
630 if reply_content['ename'] == 'UnmetDependency':
639 md['dependencies_met'] = False
631 md['dependencies_met'] = False
640 else:
632 else:
641 reply_content = {'status' : 'ok'}
633 reply_content = {'status' : 'ok'}
642
634
643 # put 'ok'/'error' status in header, for scheduler introspection:
635 # put 'ok'/'error' status in header, for scheduler introspection:
644 md['status'] = reply_content['status']
636 md['status'] = reply_content['status']
645
637
646 # flush i/o
638 # flush i/o
647 sys.stdout.flush()
639 sys.stdout.flush()
648 sys.stderr.flush()
640 sys.stderr.flush()
649
641
650 reply_msg = self.session.send(stream, u'apply_reply', reply_content,
642 reply_msg = self.session.send(stream, u'apply_reply', reply_content,
651 parent=parent, ident=ident,buffers=result_buf, metadata=md)
643 parent=parent, ident=ident,buffers=result_buf, metadata=md)
652
644
653 self._publish_status(u'idle', parent)
645 self._publish_status(u'idle', parent)
654
646
655 #---------------------------------------------------------------------------
647 #---------------------------------------------------------------------------
656 # Control messages
648 # Control messages
657 #---------------------------------------------------------------------------
649 #---------------------------------------------------------------------------
658
650
659 def abort_request(self, stream, ident, parent):
651 def abort_request(self, stream, ident, parent):
660 """abort a specifig msg by id"""
652 """abort a specifig msg by id"""
661 msg_ids = parent['content'].get('msg_ids', None)
653 msg_ids = parent['content'].get('msg_ids', None)
662 if isinstance(msg_ids, basestring):
654 if isinstance(msg_ids, basestring):
663 msg_ids = [msg_ids]
655 msg_ids = [msg_ids]
664 if not msg_ids:
656 if not msg_ids:
665 self.abort_queues()
657 self.abort_queues()
666 for mid in msg_ids:
658 for mid in msg_ids:
667 self.aborted.add(str(mid))
659 self.aborted.add(str(mid))
668
660
669 content = dict(status='ok')
661 content = dict(status='ok')
670 reply_msg = self.session.send(stream, 'abort_reply', content=content,
662 reply_msg = self.session.send(stream, 'abort_reply', content=content,
671 parent=parent, ident=ident)
663 parent=parent, ident=ident)
672 self.log.debug("%s", reply_msg)
664 self.log.debug("%s", reply_msg)
673
665
674 def clear_request(self, stream, idents, parent):
666 def clear_request(self, stream, idents, parent):
675 """Clear our namespace."""
667 """Clear our namespace."""
676 self.shell.reset(False)
668 self.shell.reset(False)
677 msg = self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
669 msg = self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
678 content = dict(status='ok'))
670 content = dict(status='ok'))
679
671
680
672
681 #---------------------------------------------------------------------------
673 #---------------------------------------------------------------------------
682 # Protected interface
674 # Protected interface
683 #---------------------------------------------------------------------------
675 #---------------------------------------------------------------------------
684
676
685 def _wrap_exception(self, method=None):
677 def _wrap_exception(self, method=None):
686 # import here, because _wrap_exception is only used in parallel,
678 # import here, because _wrap_exception is only used in parallel,
687 # and parallel has higher min pyzmq version
679 # and parallel has higher min pyzmq version
688 from IPython.parallel.error import wrap_exception
680 from IPython.parallel.error import wrap_exception
689 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method=method)
681 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method=method)
690 content = wrap_exception(e_info)
682 content = wrap_exception(e_info)
691 return content
683 return content
692
684
693 def _topic(self, topic):
685 def _topic(self, topic):
694 """prefixed topic for IOPub messages"""
686 """prefixed topic for IOPub messages"""
695 if self.int_id >= 0:
687 if self.int_id >= 0:
696 base = "engine.%i" % self.int_id
688 base = "engine.%i" % self.int_id
697 else:
689 else:
698 base = "kernel.%s" % self.ident
690 base = "kernel.%s" % self.ident
699
691
700 return py3compat.cast_bytes("%s.%s" % (base, topic))
692 return py3compat.cast_bytes("%s.%s" % (base, topic))
701
693
702 def _abort_queues(self):
694 def _abort_queues(self):
703 for stream in self.shell_streams:
695 for stream in self.shell_streams:
704 if stream:
696 if stream:
705 self._abort_queue(stream)
697 self._abort_queue(stream)
706
698
707 def _abort_queue(self, stream):
699 def _abort_queue(self, stream):
708 poller = zmq.Poller()
700 poller = zmq.Poller()
709 poller.register(stream.socket, zmq.POLLIN)
701 poller.register(stream.socket, zmq.POLLIN)
710 while True:
702 while True:
711 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
703 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
712 if msg is None:
704 if msg is None:
713 return
705 return
714
706
715 self.log.info("Aborting:")
707 self.log.info("Aborting:")
716 self.log.info("%s", msg)
708 self.log.info("%s", msg)
717 msg_type = msg['header']['msg_type']
709 msg_type = msg['header']['msg_type']
718 reply_type = msg_type.split('_')[0] + '_reply'
710 reply_type = msg_type.split('_')[0] + '_reply'
719
711
720 status = {'status' : 'aborted'}
712 status = {'status' : 'aborted'}
721 md = {'engine' : self.ident}
713 md = {'engine' : self.ident}
722 md.update(status)
714 md.update(status)
723 reply_msg = self.session.send(stream, reply_type, metadata=md,
715 reply_msg = self.session.send(stream, reply_type, metadata=md,
724 content=status, parent=msg, ident=idents)
716 content=status, parent=msg, ident=idents)
725 self.log.debug("%s", reply_msg)
717 self.log.debug("%s", reply_msg)
726 # We need to wait a bit for requests to come in. This can probably
718 # We need to wait a bit for requests to come in. This can probably
727 # be set shorter for true asynchronous clients.
719 # be set shorter for true asynchronous clients.
728 poller.poll(50)
720 poller.poll(50)
729
721
730
722
731 def _no_raw_input(self):
723 def _no_raw_input(self):
732 """Raise StdinNotImplentedError if active frontend doesn't support
724 """Raise StdinNotImplentedError if active frontend doesn't support
733 stdin."""
725 stdin."""
734 raise StdinNotImplementedError("raw_input was called, but this "
726 raise StdinNotImplementedError("raw_input was called, but this "
735 "frontend does not support stdin.")
727 "frontend does not support stdin.")
736
728
737 def _raw_input(self, prompt, ident, parent):
729 def _raw_input(self, prompt, ident, parent):
738 # Flush output before making the request.
730 # Flush output before making the request.
739 sys.stderr.flush()
731 sys.stderr.flush()
740 sys.stdout.flush()
732 sys.stdout.flush()
741
733
742 # Send the input request.
734 # Send the input request.
743 content = json_clean(dict(prompt=prompt))
735 content = json_clean(dict(prompt=prompt))
744 self.session.send(self.stdin_socket, u'input_request', content, parent,
736 self.session.send(self.stdin_socket, u'input_request', content, parent,
745 ident=ident)
737 ident=ident)
746
738
747 # Await a response.
739 # Await a response.
748 while True:
740 while True:
749 try:
741 try:
750 ident, reply = self.session.recv(self.stdin_socket, 0)
742 ident, reply = self.session.recv(self.stdin_socket, 0)
751 except Exception:
743 except Exception:
752 self.log.warn("Invalid Message:", exc_info=True)
744 self.log.warn("Invalid Message:", exc_info=True)
753 else:
745 else:
754 break
746 break
755 try:
747 try:
756 value = reply['content']['value']
748 value = reply['content']['value']
757 except:
749 except:
758 self.log.error("Got bad raw_input reply: ")
750 self.log.error("Got bad raw_input reply: ")
759 self.log.error("%s", parent)
751 self.log.error("%s", parent)
760 value = ''
752 value = ''
761 if value == '\x04':
753 if value == '\x04':
762 # EOF
754 # EOF
763 raise EOFError
755 raise EOFError
764 return value
756 return value
765
757
766 def _complete(self, msg):
758 def _complete(self, msg):
767 c = msg['content']
759 c = msg['content']
768 try:
760 try:
769 cpos = int(c['cursor_pos'])
761 cpos = int(c['cursor_pos'])
770 except:
762 except:
771 # If we don't get something that we can convert to an integer, at
763 # If we don't get something that we can convert to an integer, at
772 # least attempt the completion guessing the cursor is at the end of
764 # least attempt the completion guessing the cursor is at the end of
773 # the text, if there's any, and otherwise of the line
765 # the text, if there's any, and otherwise of the line
774 cpos = len(c['text'])
766 cpos = len(c['text'])
775 if cpos==0:
767 if cpos==0:
776 cpos = len(c['line'])
768 cpos = len(c['line'])
777 return self.shell.complete(c['text'], c['line'], cpos)
769 return self.shell.complete(c['text'], c['line'], cpos)
778
770
779 def _at_shutdown(self):
771 def _at_shutdown(self):
780 """Actions taken at shutdown by the kernel, called by python's atexit.
772 """Actions taken at shutdown by the kernel, called by python's atexit.
781 """
773 """
782 # io.rprint("Kernel at_shutdown") # dbg
774 # io.rprint("Kernel at_shutdown") # dbg
783 if self._shutdown_message is not None:
775 if self._shutdown_message is not None:
784 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
776 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
785 self.log.debug("%s", self._shutdown_message)
777 self.log.debug("%s", self._shutdown_message)
786 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
778 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
787
779
788 #-----------------------------------------------------------------------------
789 # Aliases and Flags for the IPKernelApp
790 #-----------------------------------------------------------------------------
791
792 flags = dict(kernel_flags)
793 flags.update(shell_flags)
794
795 addflag = lambda *args: flags.update(boolean_flag(*args))
796
797 flags['pylab'] = (
798 {'IPKernelApp' : {'pylab' : 'auto'}},
799 """Pre-load matplotlib and numpy for interactive use with
800 the default matplotlib backend."""
801 )
802
803 aliases = dict(kernel_aliases)
804 aliases.update(shell_aliases)
805
806 #-----------------------------------------------------------------------------
807 # The IPKernelApp class
808 #-----------------------------------------------------------------------------
809
810 class IPKernelApp(KernelApp, InteractiveShellApp):
811 name = 'ipkernel'
812
813 aliases = Dict(aliases)
814 flags = Dict(flags)
815 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
816
817 @catch_config_error
818 def initialize(self, argv=None):
819 super(IPKernelApp, self).initialize(argv)
820 self.init_path()
821 self.init_shell()
822 self.init_gui_pylab()
823 self.init_extensions()
824 self.init_code()
825
826 def init_kernel(self):
827
828 shell_stream = ZMQStream(self.shell_socket)
829
830 kernel = Kernel(config=self.config, session=self.session,
831 shell_streams=[shell_stream],
832 iopub_socket=self.iopub_socket,
833 stdin_socket=self.stdin_socket,
834 log=self.log,
835 profile_dir=self.profile_dir,
836 )
837 self.kernel = kernel
838 kernel.record_ports(self.ports)
839 shell = kernel.shell
840
841 def init_gui_pylab(self):
842 """Enable GUI event loop integration, taking pylab into account."""
843
844 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
845 # to ensure that any exception is printed straight to stderr.
846 # Normally _showtraceback associates the reply with an execution,
847 # which means frontends will never draw it, as this exception
848 # is not associated with any execute request.
849
850 shell = self.shell
851 _showtraceback = shell._showtraceback
852 try:
853 # replace pyerr-sending traceback with stderr
854 def print_tb(etype, evalue, stb):
855 print ("GUI event loop or pylab initialization failed",
856 file=io.stderr)
857 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
858 shell._showtraceback = print_tb
859 InteractiveShellApp.init_gui_pylab(self)
860 finally:
861 shell._showtraceback = _showtraceback
862
863 def init_shell(self):
864 self.shell = self.kernel.shell
865 self.shell.configurables.append(self)
866
867
868 #-----------------------------------------------------------------------------
869 # Kernel main and launch functions
870 #-----------------------------------------------------------------------------
871
872
873 def embed_kernel(module=None, local_ns=None, **kwargs):
874 """Embed and start an IPython kernel in a given scope.
875
876 Parameters
877 ----------
878 module : ModuleType, optional
879 The module to load into IPython globals (default: caller)
880 local_ns : dict, optional
881 The namespace to load into IPython user namespace (default: caller)
882
883 kwargs : various, optional
884 Further keyword args are relayed to the KernelApp constructor,
885 allowing configuration of the Kernel. Will only have an effect
886 on the first embed_kernel call for a given process.
887
888 """
889 # get the app if it exists, or set it up if it doesn't
890 if IPKernelApp.initialized():
891 app = IPKernelApp.instance()
892 else:
893 app = IPKernelApp.instance(**kwargs)
894 app.initialize([])
895 # Undo unnecessary sys module mangling from init_sys_modules.
896 # This would not be necessary if we could prevent it
897 # in the first place by using a different InteractiveShell
898 # subclass, as in the regular embed case.
899 main = app.kernel.shell._orig_sys_modules_main_mod
900 if main is not None:
901 sys.modules[app.kernel.shell._orig_sys_modules_main_name] = main
902
903 # load the calling scope if not given
904 (caller_module, caller_locals) = extract_module_locals(1)
905 if module is None:
906 module = caller_module
907 if local_ns is None:
908 local_ns = caller_locals
909
910 app.kernel.user_module = module
911 app.kernel.user_ns = local_ns
912 app.shell.set_completer_frame()
913 app.start()
914
915 def main():
916 """Run an IPKernel as an application"""
917 app = IPKernelApp.instance()
918 app.initialize()
919 app.start()
920
921
922 if __name__ == '__main__':
923 main()
@@ -1,372 +1,432 b''
1 """An Application for launching a kernel
1 """An Application for launching a kernel
2
2
3 Authors
3 Authors
4 -------
4 -------
5 * MinRK
5 * MinRK
6 """
6 """
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2011 The IPython Development Team
8 # Copyright (C) 2011 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING.txt, distributed as part of this software.
11 # the file COPYING.txt, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 from __future__ import print_function
19
18 # Standard library imports
20 # Standard library imports
19 import atexit
21 import atexit
20 import json
22 import json
21 import os
23 import os
22 import sys
24 import sys
23 import signal
25 import signal
24
26
25 # System library imports
27 # System library imports
26 import zmq
28 import zmq
27 from zmq.eventloop import ioloop
29 from zmq.eventloop import ioloop
30 from zmq.eventloop.zmqstream import ZMQStream
28
31
29 # IPython imports
32 # IPython imports
30 from IPython.core.ultratb import FormattedTB
33 from IPython.core.ultratb import FormattedTB
31 from IPython.core.application import (
34 from IPython.core.application import (
32 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
35 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
33 )
36 )
37 from IPython.core.profiledir import ProfileDir
38 from IPython.core.shellapp import (
39 InteractiveShellApp, shell_flags, shell_aliases
40 )
34 from IPython.utils import io
41 from IPython.utils import io
35 from IPython.utils.localinterfaces import LOCALHOST
42 from IPython.utils.localinterfaces import LOCALHOST
36 from IPython.utils.path import filefind
43 from IPython.utils.path import filefind
37 from IPython.utils.py3compat import str_to_bytes
44 from IPython.utils.py3compat import str_to_bytes
38 from IPython.utils.traitlets import (
45 from IPython.utils.traitlets import (
39 Any, Instance, Dict, Unicode, Integer, Bool, CaselessStrEnum,
46 Any, Instance, Dict, Unicode, Integer, Bool, CaselessStrEnum,
40 DottedObjectName,
47 DottedObjectName,
41 )
48 )
42 from IPython.utils.importstring import import_item
49 from IPython.utils.importstring import import_item
43 from IPython.kernel import write_connection_file
50 from IPython.kernel import write_connection_file
51
44 # local imports
52 # local imports
45 from IPython.zmq.heartbeat import Heartbeat
53 from heartbeat import Heartbeat
46 from IPython.zmq.parentpoller import ParentPollerUnix, ParentPollerWindows
54 from ipkernel import Kernel
47 from IPython.zmq.session import (
55 from parentpoller import ParentPollerUnix, ParentPollerWindows
56 from session import (
48 Session, session_flags, session_aliases, default_secure,
57 Session, session_flags, session_aliases, default_secure,
49 )
58 )
50
59 from zmqshell import ZMQInteractiveShell
51
60
52 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
53 # Flags and Aliases
62 # Flags and Aliases
54 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
55
64
56 kernel_aliases = dict(base_aliases)
65 kernel_aliases = dict(base_aliases)
57 kernel_aliases.update({
66 kernel_aliases.update({
58 'ip' : 'KernelApp.ip',
67 'ip' : 'IPKernelApp.ip',
59 'hb' : 'KernelApp.hb_port',
68 'hb' : 'IPKernelApp.hb_port',
60 'shell' : 'KernelApp.shell_port',
69 'shell' : 'IPKernelApp.shell_port',
61 'iopub' : 'KernelApp.iopub_port',
70 'iopub' : 'IPKernelApp.iopub_port',
62 'stdin' : 'KernelApp.stdin_port',
71 'stdin' : 'IPKernelApp.stdin_port',
63 'f' : 'KernelApp.connection_file',
72 'f' : 'IPKernelApp.connection_file',
64 'parent': 'KernelApp.parent',
73 'parent': 'IPKernelApp.parent',
65 'transport': 'KernelApp.transport',
74 'transport': 'IPKernelApp.transport',
66 })
75 })
67 if sys.platform.startswith('win'):
76 if sys.platform.startswith('win'):
68 kernel_aliases['interrupt'] = 'KernelApp.interrupt'
77 kernel_aliases['interrupt'] = 'KernelApp.interrupt'
69
78
70 kernel_flags = dict(base_flags)
79 kernel_flags = dict(base_flags)
71 kernel_flags.update({
80 kernel_flags.update({
72 'no-stdout' : (
81 'no-stdout' : (
73 {'KernelApp' : {'no_stdout' : True}},
82 {'IPKernelApp' : {'no_stdout' : True}},
74 "redirect stdout to the null device"),
83 "redirect stdout to the null device"),
75 'no-stderr' : (
84 'no-stderr' : (
76 {'KernelApp' : {'no_stderr' : True}},
85 {'IPKernelApp' : {'no_stderr' : True}},
77 "redirect stderr to the null device"),
86 "redirect stderr to the null device"),
87 'pylab' : (
88 {'IPKernelApp' : {'pylab' : 'auto'}},
89 """Pre-load matplotlib and numpy for interactive use with
90 the default matplotlib backend."""),
78 })
91 })
79
92
93 # inherit flags&aliases for any IPython shell apps
94 kernel_aliases.update(shell_aliases)
95 kernel_flags.update(shell_flags)
96
80 # inherit flags&aliases for Sessions
97 # inherit flags&aliases for Sessions
81 kernel_aliases.update(session_aliases)
98 kernel_aliases.update(session_aliases)
82 kernel_flags.update(session_flags)
99 kernel_flags.update(session_flags)
83
100
84
85
86 #-----------------------------------------------------------------------------
101 #-----------------------------------------------------------------------------
87 # Application class for starting a Kernel
102 # Application class for starting an IPython Kernel
88 #-----------------------------------------------------------------------------
103 #-----------------------------------------------------------------------------
89
104
90 class KernelApp(BaseIPythonApplication):
105 class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):
91 name='ipkernel'
106 name='ipkernel'
92 aliases = Dict(kernel_aliases)
107 aliases = Dict(kernel_aliases)
93 flags = Dict(kernel_flags)
108 flags = Dict(kernel_flags)
94 classes = [Session]
109 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
95 # the kernel class, as an importstring
110 # the kernel class, as an importstring
96 kernel_class = DottedObjectName('IPython.zmq.ipkernel.Kernel')
111 kernel_class = DottedObjectName('IPython.zmq.ipkernel.Kernel')
97 kernel = Any()
112 kernel = Any()
98 poller = Any() # don't restrict this even though current pollers are all Threads
113 poller = Any() # don't restrict this even though current pollers are all Threads
99 heartbeat = Instance(Heartbeat)
114 heartbeat = Instance(Heartbeat)
100 session = Instance('IPython.zmq.session.Session')
115 session = Instance('IPython.zmq.session.Session')
101 ports = Dict()
116 ports = Dict()
102
117
103 # inherit config file name from parent:
118 # inherit config file name from parent:
104 parent_appname = Unicode(config=True)
119 parent_appname = Unicode(config=True)
105 def _parent_appname_changed(self, name, old, new):
120 def _parent_appname_changed(self, name, old, new):
106 if self.config_file_specified:
121 if self.config_file_specified:
107 # it was manually specified, ignore
122 # it was manually specified, ignore
108 return
123 return
109 self.config_file_name = new.replace('-','_') + u'_config.py'
124 self.config_file_name = new.replace('-','_') + u'_config.py'
110 # don't let this count as specifying the config file
125 # don't let this count as specifying the config file
111 self.config_file_specified = False
126 self.config_file_specified = False
112
127
113 # connection info:
128 # connection info:
114 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
129 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
115 ip = Unicode(config=True,
130 ip = Unicode(config=True,
116 help="Set the IP or interface on which the kernel will listen.")
131 help="Set the IP or interface on which the kernel will listen.")
117 def _ip_default(self):
132 def _ip_default(self):
118 if self.transport == 'ipc':
133 if self.transport == 'ipc':
119 if self.connection_file:
134 if self.connection_file:
120 return os.path.splitext(self.abs_connection_file)[0] + '-ipc'
135 return os.path.splitext(self.abs_connection_file)[0] + '-ipc'
121 else:
136 else:
122 return 'kernel-ipc'
137 return 'kernel-ipc'
123 else:
138 else:
124 return LOCALHOST
139 return LOCALHOST
125 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
140 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
126 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
141 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
127 iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
142 iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
128 stdin_port = Integer(0, config=True, help="set the stdin (DEALER) port [default: random]")
143 stdin_port = Integer(0, config=True, help="set the stdin (DEALER) port [default: random]")
129 connection_file = Unicode('', config=True,
144 connection_file = Unicode('', config=True,
130 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
145 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
131
146
132 This file will contain the IP, ports, and authentication key needed to connect
147 This file will contain the IP, ports, and authentication key needed to connect
133 clients to this kernel. By default, this file will be created in the security dir
148 clients to this kernel. By default, this file will be created in the security dir
134 of the current profile, but can be specified by absolute path.
149 of the current profile, but can be specified by absolute path.
135 """)
150 """)
136 @property
151 @property
137 def abs_connection_file(self):
152 def abs_connection_file(self):
138 if os.path.basename(self.connection_file) == self.connection_file:
153 if os.path.basename(self.connection_file) == self.connection_file:
139 return os.path.join(self.profile_dir.security_dir, self.connection_file)
154 return os.path.join(self.profile_dir.security_dir, self.connection_file)
140 else:
155 else:
141 return self.connection_file
156 return self.connection_file
142
157
143
158
144 # streams, etc.
159 # streams, etc.
145 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
160 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
146 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
161 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
147 outstream_class = DottedObjectName('IPython.zmq.iostream.OutStream',
162 outstream_class = DottedObjectName('IPython.zmq.iostream.OutStream',
148 config=True, help="The importstring for the OutStream factory")
163 config=True, help="The importstring for the OutStream factory")
149 displayhook_class = DottedObjectName('IPython.zmq.displayhook.ZMQDisplayHook',
164 displayhook_class = DottedObjectName('IPython.zmq.displayhook.ZMQDisplayHook',
150 config=True, help="The importstring for the DisplayHook factory")
165 config=True, help="The importstring for the DisplayHook factory")
151
166
152 # polling
167 # polling
153 parent = Integer(0, config=True,
168 parent = Integer(0, config=True,
154 help="""kill this process if its parent dies. On Windows, the argument
169 help="""kill this process if its parent dies. On Windows, the argument
155 specifies the HANDLE of the parent process, otherwise it is simply boolean.
170 specifies the HANDLE of the parent process, otherwise it is simply boolean.
156 """)
171 """)
157 interrupt = Integer(0, config=True,
172 interrupt = Integer(0, config=True,
158 help="""ONLY USED ON WINDOWS
173 help="""ONLY USED ON WINDOWS
159 Interrupt this process when the parent is signaled.
174 Interrupt this process when the parent is signaled.
160 """)
175 """)
161
176
162 def init_crash_handler(self):
177 def init_crash_handler(self):
163 # Install minimal exception handling
178 # Install minimal exception handling
164 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
179 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
165 ostream=sys.__stdout__)
180 ostream=sys.__stdout__)
166
181
167 def init_poller(self):
182 def init_poller(self):
168 if sys.platform == 'win32':
183 if sys.platform == 'win32':
169 if self.interrupt or self.parent:
184 if self.interrupt or self.parent:
170 self.poller = ParentPollerWindows(self.interrupt, self.parent)
185 self.poller = ParentPollerWindows(self.interrupt, self.parent)
171 elif self.parent:
186 elif self.parent:
172 self.poller = ParentPollerUnix()
187 self.poller = ParentPollerUnix()
173
188
174 def _bind_socket(self, s, port):
189 def _bind_socket(self, s, port):
175 iface = '%s://%s' % (self.transport, self.ip)
190 iface = '%s://%s' % (self.transport, self.ip)
176 if self.transport == 'tcp':
191 if self.transport == 'tcp':
177 if port <= 0:
192 if port <= 0:
178 port = s.bind_to_random_port(iface)
193 port = s.bind_to_random_port(iface)
179 else:
194 else:
180 s.bind("tcp://%s:%i" % (self.ip, port))
195 s.bind("tcp://%s:%i" % (self.ip, port))
181 elif self.transport == 'ipc':
196 elif self.transport == 'ipc':
182 if port <= 0:
197 if port <= 0:
183 port = 1
198 port = 1
184 path = "%s-%i" % (self.ip, port)
199 path = "%s-%i" % (self.ip, port)
185 while os.path.exists(path):
200 while os.path.exists(path):
186 port = port + 1
201 port = port + 1
187 path = "%s-%i" % (self.ip, port)
202 path = "%s-%i" % (self.ip, port)
188 else:
203 else:
189 path = "%s-%i" % (self.ip, port)
204 path = "%s-%i" % (self.ip, port)
190 s.bind("ipc://%s" % path)
205 s.bind("ipc://%s" % path)
191 return port
206 return port
192
207
193 def load_connection_file(self):
208 def load_connection_file(self):
194 """load ip/port/hmac config from JSON connection file"""
209 """load ip/port/hmac config from JSON connection file"""
195 try:
210 try:
196 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
211 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
197 except IOError:
212 except IOError:
198 self.log.debug("Connection file not found: %s", self.connection_file)
213 self.log.debug("Connection file not found: %s", self.connection_file)
199 # This means I own it, so I will clean it up:
214 # This means I own it, so I will clean it up:
200 atexit.register(self.cleanup_connection_file)
215 atexit.register(self.cleanup_connection_file)
201 return
216 return
202 self.log.debug(u"Loading connection file %s", fname)
217 self.log.debug(u"Loading connection file %s", fname)
203 with open(fname) as f:
218 with open(fname) as f:
204 s = f.read()
219 s = f.read()
205 cfg = json.loads(s)
220 cfg = json.loads(s)
206 self.transport = cfg.get('transport', self.transport)
221 self.transport = cfg.get('transport', self.transport)
207 if self.ip == self._ip_default() and 'ip' in cfg:
222 if self.ip == self._ip_default() and 'ip' in cfg:
208 # not overridden by config or cl_args
223 # not overridden by config or cl_args
209 self.ip = cfg['ip']
224 self.ip = cfg['ip']
210 for channel in ('hb', 'shell', 'iopub', 'stdin'):
225 for channel in ('hb', 'shell', 'iopub', 'stdin'):
211 name = channel + '_port'
226 name = channel + '_port'
212 if getattr(self, name) == 0 and name in cfg:
227 if getattr(self, name) == 0 and name in cfg:
213 # not overridden by config or cl_args
228 # not overridden by config or cl_args
214 setattr(self, name, cfg[name])
229 setattr(self, name, cfg[name])
215 if 'key' in cfg:
230 if 'key' in cfg:
216 self.config.Session.key = str_to_bytes(cfg['key'])
231 self.config.Session.key = str_to_bytes(cfg['key'])
217
232
218 def write_connection_file(self):
233 def write_connection_file(self):
219 """write connection info to JSON file"""
234 """write connection info to JSON file"""
220 cf = self.abs_connection_file
235 cf = self.abs_connection_file
221 self.log.debug("Writing connection file: %s", cf)
236 self.log.debug("Writing connection file: %s", cf)
222 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
237 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
223 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
238 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
224 iopub_port=self.iopub_port)
239 iopub_port=self.iopub_port)
225
240
226 def cleanup_connection_file(self):
241 def cleanup_connection_file(self):
227 cf = self.abs_connection_file
242 cf = self.abs_connection_file
228 self.log.debug("Cleaning up connection file: %s", cf)
243 self.log.debug("Cleaning up connection file: %s", cf)
229 try:
244 try:
230 os.remove(cf)
245 os.remove(cf)
231 except (IOError, OSError):
246 except (IOError, OSError):
232 pass
247 pass
233
248
234 self.cleanup_ipc_files()
249 self.cleanup_ipc_files()
235
250
236 def cleanup_ipc_files(self):
251 def cleanup_ipc_files(self):
237 """cleanup ipc files if we wrote them"""
252 """cleanup ipc files if we wrote them"""
238 if self.transport != 'ipc':
253 if self.transport != 'ipc':
239 return
254 return
240 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
255 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
241 ipcfile = "%s-%i" % (self.ip, port)
256 ipcfile = "%s-%i" % (self.ip, port)
242 try:
257 try:
243 os.remove(ipcfile)
258 os.remove(ipcfile)
244 except (IOError, OSError):
259 except (IOError, OSError):
245 pass
260 pass
246
261
247 def init_connection_file(self):
262 def init_connection_file(self):
248 if not self.connection_file:
263 if not self.connection_file:
249 self.connection_file = "kernel-%s.json"%os.getpid()
264 self.connection_file = "kernel-%s.json"%os.getpid()
250 try:
265 try:
251 self.load_connection_file()
266 self.load_connection_file()
252 except Exception:
267 except Exception:
253 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
268 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
254 self.exit(1)
269 self.exit(1)
255
270
256 def init_sockets(self):
271 def init_sockets(self):
257 # Create a context, a session, and the kernel sockets.
272 # Create a context, a session, and the kernel sockets.
258 self.log.info("Starting the kernel at pid: %i", os.getpid())
273 self.log.info("Starting the kernel at pid: %i", os.getpid())
259 context = zmq.Context.instance()
274 context = zmq.Context.instance()
260 # Uncomment this to try closing the context.
275 # Uncomment this to try closing the context.
261 # atexit.register(context.term)
276 # atexit.register(context.term)
262
277
263 self.shell_socket = context.socket(zmq.ROUTER)
278 self.shell_socket = context.socket(zmq.ROUTER)
264 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
279 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
265 self.log.debug("shell ROUTER Channel on port: %i"%self.shell_port)
280 self.log.debug("shell ROUTER Channel on port: %i"%self.shell_port)
266
281
267 self.iopub_socket = context.socket(zmq.PUB)
282 self.iopub_socket = context.socket(zmq.PUB)
268 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
283 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
269 self.log.debug("iopub PUB Channel on port: %i"%self.iopub_port)
284 self.log.debug("iopub PUB Channel on port: %i"%self.iopub_port)
270
285
271 self.stdin_socket = context.socket(zmq.ROUTER)
286 self.stdin_socket = context.socket(zmq.ROUTER)
272 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
287 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
273 self.log.debug("stdin ROUTER Channel on port: %i"%self.stdin_port)
288 self.log.debug("stdin ROUTER Channel on port: %i"%self.stdin_port)
274
289
275 def init_heartbeat(self):
290 def init_heartbeat(self):
276 """start the heart beating"""
291 """start the heart beating"""
277 # heartbeat doesn't share context, because it mustn't be blocked
292 # heartbeat doesn't share context, because it mustn't be blocked
278 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
293 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
279 hb_ctx = zmq.Context()
294 hb_ctx = zmq.Context()
280 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
295 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
281 self.hb_port = self.heartbeat.port
296 self.hb_port = self.heartbeat.port
282 self.log.debug("Heartbeat REP Channel on port: %i"%self.hb_port)
297 self.log.debug("Heartbeat REP Channel on port: %i"%self.hb_port)
283 self.heartbeat.start()
298 self.heartbeat.start()
284
299
285 # Helper to make it easier to connect to an existing kernel.
300 # Helper to make it easier to connect to an existing kernel.
286 # set log-level to critical, to make sure it is output
301 # set log-level to critical, to make sure it is output
287 self.log.critical("To connect another client to this kernel, use:")
302 self.log.critical("To connect another client to this kernel, use:")
288
303
289 def log_connection_info(self):
304 def log_connection_info(self):
290 """display connection info, and store ports"""
305 """display connection info, and store ports"""
291 basename = os.path.basename(self.connection_file)
306 basename = os.path.basename(self.connection_file)
292 if basename == self.connection_file or \
307 if basename == self.connection_file or \
293 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
308 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
294 # use shortname
309 # use shortname
295 tail = basename
310 tail = basename
296 if self.profile != 'default':
311 if self.profile != 'default':
297 tail += " --profile %s" % self.profile
312 tail += " --profile %s" % self.profile
298 else:
313 else:
299 tail = self.connection_file
314 tail = self.connection_file
300 self.log.critical("--existing %s", tail)
315 self.log.critical("--existing %s", tail)
301
316
302
317
303 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
318 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
304 stdin=self.stdin_port, hb=self.hb_port)
319 stdin=self.stdin_port, hb=self.hb_port)
305
320
306 def init_session(self):
321 def init_session(self):
307 """create our session object"""
322 """create our session object"""
308 default_secure(self.config)
323 default_secure(self.config)
309 self.session = Session(config=self.config, username=u'kernel')
324 self.session = Session(config=self.config, username=u'kernel')
310
325
311 def init_blackhole(self):
326 def init_blackhole(self):
312 """redirects stdout/stderr to devnull if necessary"""
327 """redirects stdout/stderr to devnull if necessary"""
313 if self.no_stdout or self.no_stderr:
328 if self.no_stdout or self.no_stderr:
314 blackhole = open(os.devnull, 'w')
329 blackhole = open(os.devnull, 'w')
315 if self.no_stdout:
330 if self.no_stdout:
316 sys.stdout = sys.__stdout__ = blackhole
331 sys.stdout = sys.__stdout__ = blackhole
317 if self.no_stderr:
332 if self.no_stderr:
318 sys.stderr = sys.__stderr__ = blackhole
333 sys.stderr = sys.__stderr__ = blackhole
319
334
320 def init_io(self):
335 def init_io(self):
321 """Redirect input streams and set a display hook."""
336 """Redirect input streams and set a display hook."""
322 if self.outstream_class:
337 if self.outstream_class:
323 outstream_factory = import_item(str(self.outstream_class))
338 outstream_factory = import_item(str(self.outstream_class))
324 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
339 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
325 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
340 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
326 if self.displayhook_class:
341 if self.displayhook_class:
327 displayhook_factory = import_item(str(self.displayhook_class))
342 displayhook_factory = import_item(str(self.displayhook_class))
328 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
343 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
329
344
330 def init_signal(self):
345 def init_signal(self):
331 signal.signal(signal.SIGINT, signal.SIG_IGN)
346 signal.signal(signal.SIGINT, signal.SIG_IGN)
332
347
333 def init_kernel(self):
348 def init_kernel(self):
334 """Create the Kernel object itself"""
349 """Create the Kernel object itself"""
335 kernel_factory = import_item(str(self.kernel_class))
350 shell_stream = ZMQStream(self.shell_socket)
336 self.kernel = kernel_factory(config=self.config, session=self.session,
351
337 shell_socket=self.shell_socket,
352 kernel = Kernel(config=self.config, session=self.session,
353 shell_streams=[shell_stream],
338 iopub_socket=self.iopub_socket,
354 iopub_socket=self.iopub_socket,
339 stdin_socket=self.stdin_socket,
355 stdin_socket=self.stdin_socket,
340 log=self.log
356 log=self.log,
357 profile_dir=self.profile_dir,
341 )
358 )
342 self.kernel.record_ports(self.ports)
359 kernel.record_ports(self.ports)
360 self.kernel = kernel
361
362 def init_gui_pylab(self):
363 """Enable GUI event loop integration, taking pylab into account."""
364
365 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
366 # to ensure that any exception is printed straight to stderr.
367 # Normally _showtraceback associates the reply with an execution,
368 # which means frontends will never draw it, as this exception
369 # is not associated with any execute request.
370
371 shell = self.shell
372 _showtraceback = shell._showtraceback
373 try:
374 # replace pyerr-sending traceback with stderr
375 def print_tb(etype, evalue, stb):
376 print ("GUI event loop or pylab initialization failed",
377 file=io.stderr)
378 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
379 shell._showtraceback = print_tb
380 InteractiveShellApp.init_gui_pylab(self)
381 finally:
382 shell._showtraceback = _showtraceback
383
384 def init_shell(self):
385 self.shell = self.kernel.shell
386 self.shell.configurables.append(self)
343
387
344 @catch_config_error
388 @catch_config_error
345 def initialize(self, argv=None):
389 def initialize(self, argv=None):
346 super(KernelApp, self).initialize(argv)
390 super(IPKernelApp, self).initialize(argv)
347 self.init_blackhole()
391 self.init_blackhole()
348 self.init_connection_file()
392 self.init_connection_file()
349 self.init_session()
393 self.init_session()
350 self.init_poller()
394 self.init_poller()
351 self.init_sockets()
395 self.init_sockets()
352 self.init_heartbeat()
396 self.init_heartbeat()
353 # writing/displaying connection info must be *after* init_sockets/heartbeat
397 # writing/displaying connection info must be *after* init_sockets/heartbeat
354 self.log_connection_info()
398 self.log_connection_info()
355 self.write_connection_file()
399 self.write_connection_file()
356 self.init_io()
400 self.init_io()
357 self.init_signal()
401 self.init_signal()
358 self.init_kernel()
402 self.init_kernel()
403 # shell init steps
404 self.init_path()
405 self.init_shell()
406 self.init_gui_pylab()
407 self.init_extensions()
408 self.init_code()
359 # flush stdout/stderr, so that anything written to these streams during
409 # flush stdout/stderr, so that anything written to these streams during
360 # initialization do not get associated with the first execution request
410 # initialization do not get associated with the first execution request
361 sys.stdout.flush()
411 sys.stdout.flush()
362 sys.stderr.flush()
412 sys.stderr.flush()
363
413
364 def start(self):
414 def start(self):
365 if self.poller is not None:
415 if self.poller is not None:
366 self.poller.start()
416 self.poller.start()
367 self.kernel.start()
417 self.kernel.start()
368 try:
418 try:
369 ioloop.IOLoop.instance().start()
419 ioloop.IOLoop.instance().start()
370 except KeyboardInterrupt:
420 except KeyboardInterrupt:
371 pass
421 pass
372
422
423
424 def main():
425 """Run an IPKernel as an application"""
426 app = IPKernelApp.instance()
427 app.initialize()
428 app.start()
429
430
431 if __name__ == '__main__':
432 main()
@@ -1,1129 +1,1129 b''
1 """Base classes to manage the interaction with a running kernel.
1 """Base classes to manage the interaction with a running kernel.
2
2
3 TODO
3 TODO
4 * Create logger to handle debugging and console messages.
4 * Create logger to handle debugging and console messages.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2011 The IPython Development Team
8 # Copyright (C) 2008-2011 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import atexit
19 import atexit
20 import errno
20 import errno
21 import json
21 import json
22 from subprocess import Popen
22 from subprocess import Popen
23 import os
23 import os
24 import signal
24 import signal
25 import sys
25 import sys
26 from threading import Thread
26 from threading import Thread
27 import time
27 import time
28
28
29 # System library imports.
29 # System library imports.
30 import zmq
30 import zmq
31 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
31 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
32 # during garbage collection of threads at exit:
32 # during garbage collection of threads at exit:
33 from zmq import ZMQError
33 from zmq import ZMQError
34 from zmq.eventloop import ioloop, zmqstream
34 from zmq.eventloop import ioloop, zmqstream
35
35
36 # Local imports.
36 # Local imports.
37 from IPython.config.configurable import Configurable
37 from IPython.config.configurable import Configurable
38 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
38 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
39 from IPython.utils.traitlets import (
39 from IPython.utils.traitlets import (
40 Any, Instance, Type, Unicode, List, Integer, Bool, CaselessStrEnum
40 Any, Instance, Type, Unicode, List, Integer, Bool, CaselessStrEnum
41 )
41 )
42 from IPython.utils.py3compat import str_to_bytes
42 from IPython.utils.py3compat import str_to_bytes
43 from IPython.kernel import (
43 from IPython.kernel import (
44 write_connection_file,
44 write_connection_file,
45 make_ipkernel_cmd,
45 make_ipkernel_cmd,
46 launch_kernel,
46 launch_kernel,
47 )
47 )
48 from session import Session
48 from session import Session
49 from IPython.kernel import (
49 from IPython.kernel import (
50 ShellChannelABC, IOPubChannelABC,
50 ShellChannelABC, IOPubChannelABC,
51 HBChannelABC, StdInChannelABC,
51 HBChannelABC, StdInChannelABC,
52 KernelManagerABC
52 KernelManagerABC
53 )
53 )
54
54
55
55
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57 # Constants and exceptions
57 # Constants and exceptions
58 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
59
59
60 class InvalidPortNumber(Exception):
60 class InvalidPortNumber(Exception):
61 pass
61 pass
62
62
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64 # Utility functions
64 # Utility functions
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66
66
67 # some utilities to validate message structure, these might get moved elsewhere
67 # some utilities to validate message structure, these might get moved elsewhere
68 # if they prove to have more generic utility
68 # if they prove to have more generic utility
69
69
70 def validate_string_list(lst):
70 def validate_string_list(lst):
71 """Validate that the input is a list of strings.
71 """Validate that the input is a list of strings.
72
72
73 Raises ValueError if not."""
73 Raises ValueError if not."""
74 if not isinstance(lst, list):
74 if not isinstance(lst, list):
75 raise ValueError('input %r must be a list' % lst)
75 raise ValueError('input %r must be a list' % lst)
76 for x in lst:
76 for x in lst:
77 if not isinstance(x, basestring):
77 if not isinstance(x, basestring):
78 raise ValueError('element %r in list must be a string' % x)
78 raise ValueError('element %r in list must be a string' % x)
79
79
80
80
81 def validate_string_dict(dct):
81 def validate_string_dict(dct):
82 """Validate that the input is a dict with string keys and values.
82 """Validate that the input is a dict with string keys and values.
83
83
84 Raises ValueError if not."""
84 Raises ValueError if not."""
85 for k,v in dct.iteritems():
85 for k,v in dct.iteritems():
86 if not isinstance(k, basestring):
86 if not isinstance(k, basestring):
87 raise ValueError('key %r in dict must be a string' % k)
87 raise ValueError('key %r in dict must be a string' % k)
88 if not isinstance(v, basestring):
88 if not isinstance(v, basestring):
89 raise ValueError('value %r in dict must be a string' % v)
89 raise ValueError('value %r in dict must be a string' % v)
90
90
91
91
92 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
93 # ZMQ Socket Channel classes
93 # ZMQ Socket Channel classes
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95
95
96 class ZMQSocketChannel(Thread):
96 class ZMQSocketChannel(Thread):
97 """The base class for the channels that use ZMQ sockets."""
97 """The base class for the channels that use ZMQ sockets."""
98 context = None
98 context = None
99 session = None
99 session = None
100 socket = None
100 socket = None
101 ioloop = None
101 ioloop = None
102 stream = None
102 stream = None
103 _address = None
103 _address = None
104 _exiting = False
104 _exiting = False
105
105
106 def __init__(self, context, session, address):
106 def __init__(self, context, session, address):
107 """Create a channel.
107 """Create a channel.
108
108
109 Parameters
109 Parameters
110 ----------
110 ----------
111 context : :class:`zmq.Context`
111 context : :class:`zmq.Context`
112 The ZMQ context to use.
112 The ZMQ context to use.
113 session : :class:`session.Session`
113 session : :class:`session.Session`
114 The session to use.
114 The session to use.
115 address : zmq url
115 address : zmq url
116 Standard (ip, port) tuple that the kernel is listening on.
116 Standard (ip, port) tuple that the kernel is listening on.
117 """
117 """
118 super(ZMQSocketChannel, self).__init__()
118 super(ZMQSocketChannel, self).__init__()
119 self.daemon = True
119 self.daemon = True
120
120
121 self.context = context
121 self.context = context
122 self.session = session
122 self.session = session
123 if isinstance(address, tuple):
123 if isinstance(address, tuple):
124 if address[1] == 0:
124 if address[1] == 0:
125 message = 'The port number for a channel cannot be 0.'
125 message = 'The port number for a channel cannot be 0.'
126 raise InvalidPortNumber(message)
126 raise InvalidPortNumber(message)
127 address = "tcp://%s:%i" % address
127 address = "tcp://%s:%i" % address
128 self._address = address
128 self._address = address
129 atexit.register(self._notice_exit)
129 atexit.register(self._notice_exit)
130
130
131 def _notice_exit(self):
131 def _notice_exit(self):
132 self._exiting = True
132 self._exiting = True
133
133
134 def _run_loop(self):
134 def _run_loop(self):
135 """Run my loop, ignoring EINTR events in the poller"""
135 """Run my loop, ignoring EINTR events in the poller"""
136 while True:
136 while True:
137 try:
137 try:
138 self.ioloop.start()
138 self.ioloop.start()
139 except ZMQError as e:
139 except ZMQError as e:
140 if e.errno == errno.EINTR:
140 if e.errno == errno.EINTR:
141 continue
141 continue
142 else:
142 else:
143 raise
143 raise
144 except Exception:
144 except Exception:
145 if self._exiting:
145 if self._exiting:
146 break
146 break
147 else:
147 else:
148 raise
148 raise
149 else:
149 else:
150 break
150 break
151
151
152 def stop(self):
152 def stop(self):
153 """Stop the channel's event loop and join its thread.
153 """Stop the channel's event loop and join its thread.
154
154
155 This calls :method:`Thread.join` and returns when the thread
155 This calls :method:`Thread.join` and returns when the thread
156 terminates. :class:`RuntimeError` will be raised if
156 terminates. :class:`RuntimeError` will be raised if
157 :method:`self.start` is called again.
157 :method:`self.start` is called again.
158 """
158 """
159 self.join()
159 self.join()
160
160
161 @property
161 @property
162 def address(self):
162 def address(self):
163 """Get the channel's address as a zmq url string.
163 """Get the channel's address as a zmq url string.
164
164
165 These URLS have the form: 'tcp://127.0.0.1:5555'.
165 These URLS have the form: 'tcp://127.0.0.1:5555'.
166 """
166 """
167 return self._address
167 return self._address
168
168
169 def _queue_send(self, msg):
169 def _queue_send(self, msg):
170 """Queue a message to be sent from the IOLoop's thread.
170 """Queue a message to be sent from the IOLoop's thread.
171
171
172 Parameters
172 Parameters
173 ----------
173 ----------
174 msg : message to send
174 msg : message to send
175
175
176 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
176 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
177 thread control of the action.
177 thread control of the action.
178 """
178 """
179 def thread_send():
179 def thread_send():
180 self.session.send(self.stream, msg)
180 self.session.send(self.stream, msg)
181 self.ioloop.add_callback(thread_send)
181 self.ioloop.add_callback(thread_send)
182
182
183 def _handle_recv(self, msg):
183 def _handle_recv(self, msg):
184 """Callback for stream.on_recv.
184 """Callback for stream.on_recv.
185
185
186 Unpacks message, and calls handlers with it.
186 Unpacks message, and calls handlers with it.
187 """
187 """
188 ident,smsg = self.session.feed_identities(msg)
188 ident,smsg = self.session.feed_identities(msg)
189 self.call_handlers(self.session.unserialize(smsg))
189 self.call_handlers(self.session.unserialize(smsg))
190
190
191
191
192
192
193 class ShellChannel(ZMQSocketChannel):
193 class ShellChannel(ZMQSocketChannel):
194 """The shell channel for issuing request/replies to the kernel."""
194 """The shell channel for issuing request/replies to the kernel."""
195
195
196 command_queue = None
196 command_queue = None
197 # flag for whether execute requests should be allowed to call raw_input:
197 # flag for whether execute requests should be allowed to call raw_input:
198 allow_stdin = True
198 allow_stdin = True
199
199
200 def __init__(self, context, session, address):
200 def __init__(self, context, session, address):
201 super(ShellChannel, self).__init__(context, session, address)
201 super(ShellChannel, self).__init__(context, session, address)
202 self.ioloop = ioloop.IOLoop()
202 self.ioloop = ioloop.IOLoop()
203
203
204 def run(self):
204 def run(self):
205 """The thread's main activity. Call start() instead."""
205 """The thread's main activity. Call start() instead."""
206 self.socket = self.context.socket(zmq.DEALER)
206 self.socket = self.context.socket(zmq.DEALER)
207 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
207 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
208 self.socket.connect(self.address)
208 self.socket.connect(self.address)
209 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
209 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
210 self.stream.on_recv(self._handle_recv)
210 self.stream.on_recv(self._handle_recv)
211 self._run_loop()
211 self._run_loop()
212 try:
212 try:
213 self.socket.close()
213 self.socket.close()
214 except:
214 except:
215 pass
215 pass
216
216
217 def stop(self):
217 def stop(self):
218 """Stop the channel's event loop and join its thread."""
218 """Stop the channel's event loop and join its thread."""
219 self.ioloop.stop()
219 self.ioloop.stop()
220 super(ShellChannel, self).stop()
220 super(ShellChannel, self).stop()
221
221
222 def call_handlers(self, msg):
222 def call_handlers(self, msg):
223 """This method is called in the ioloop thread when a message arrives.
223 """This method is called in the ioloop thread when a message arrives.
224
224
225 Subclasses should override this method to handle incoming messages.
225 Subclasses should override this method to handle incoming messages.
226 It is important to remember that this method is called in the thread
226 It is important to remember that this method is called in the thread
227 so that some logic must be done to ensure that the application leve
227 so that some logic must be done to ensure that the application leve
228 handlers are called in the application thread.
228 handlers are called in the application thread.
229 """
229 """
230 raise NotImplementedError('call_handlers must be defined in a subclass.')
230 raise NotImplementedError('call_handlers must be defined in a subclass.')
231
231
232 def execute(self, code, silent=False, store_history=True,
232 def execute(self, code, silent=False, store_history=True,
233 user_variables=None, user_expressions=None, allow_stdin=None):
233 user_variables=None, user_expressions=None, allow_stdin=None):
234 """Execute code in the kernel.
234 """Execute code in the kernel.
235
235
236 Parameters
236 Parameters
237 ----------
237 ----------
238 code : str
238 code : str
239 A string of Python code.
239 A string of Python code.
240
240
241 silent : bool, optional (default False)
241 silent : bool, optional (default False)
242 If set, the kernel will execute the code as quietly possible, and
242 If set, the kernel will execute the code as quietly possible, and
243 will force store_history to be False.
243 will force store_history to be False.
244
244
245 store_history : bool, optional (default True)
245 store_history : bool, optional (default True)
246 If set, the kernel will store command history. This is forced
246 If set, the kernel will store command history. This is forced
247 to be False if silent is True.
247 to be False if silent is True.
248
248
249 user_variables : list, optional
249 user_variables : list, optional
250 A list of variable names to pull from the user's namespace. They
250 A list of variable names to pull from the user's namespace. They
251 will come back as a dict with these names as keys and their
251 will come back as a dict with these names as keys and their
252 :func:`repr` as values.
252 :func:`repr` as values.
253
253
254 user_expressions : dict, optional
254 user_expressions : dict, optional
255 A dict mapping names to expressions to be evaluated in the user's
255 A dict mapping names to expressions to be evaluated in the user's
256 dict. The expression values are returned as strings formatted using
256 dict. The expression values are returned as strings formatted using
257 :func:`repr`.
257 :func:`repr`.
258
258
259 allow_stdin : bool, optional (default self.allow_stdin)
259 allow_stdin : bool, optional (default self.allow_stdin)
260 Flag for whether the kernel can send stdin requests to frontends.
260 Flag for whether the kernel can send stdin requests to frontends.
261
261
262 Some frontends (e.g. the Notebook) do not support stdin requests.
262 Some frontends (e.g. the Notebook) do not support stdin requests.
263 If raw_input is called from code executed from such a frontend, a
263 If raw_input is called from code executed from such a frontend, a
264 StdinNotImplementedError will be raised.
264 StdinNotImplementedError will be raised.
265
265
266 Returns
266 Returns
267 -------
267 -------
268 The msg_id of the message sent.
268 The msg_id of the message sent.
269 """
269 """
270 if user_variables is None:
270 if user_variables is None:
271 user_variables = []
271 user_variables = []
272 if user_expressions is None:
272 if user_expressions is None:
273 user_expressions = {}
273 user_expressions = {}
274 if allow_stdin is None:
274 if allow_stdin is None:
275 allow_stdin = self.allow_stdin
275 allow_stdin = self.allow_stdin
276
276
277
277
278 # Don't waste network traffic if inputs are invalid
278 # Don't waste network traffic if inputs are invalid
279 if not isinstance(code, basestring):
279 if not isinstance(code, basestring):
280 raise ValueError('code %r must be a string' % code)
280 raise ValueError('code %r must be a string' % code)
281 validate_string_list(user_variables)
281 validate_string_list(user_variables)
282 validate_string_dict(user_expressions)
282 validate_string_dict(user_expressions)
283
283
284 # Create class for content/msg creation. Related to, but possibly
284 # Create class for content/msg creation. Related to, but possibly
285 # not in Session.
285 # not in Session.
286 content = dict(code=code, silent=silent, store_history=store_history,
286 content = dict(code=code, silent=silent, store_history=store_history,
287 user_variables=user_variables,
287 user_variables=user_variables,
288 user_expressions=user_expressions,
288 user_expressions=user_expressions,
289 allow_stdin=allow_stdin,
289 allow_stdin=allow_stdin,
290 )
290 )
291 msg = self.session.msg('execute_request', content)
291 msg = self.session.msg('execute_request', content)
292 self._queue_send(msg)
292 self._queue_send(msg)
293 return msg['header']['msg_id']
293 return msg['header']['msg_id']
294
294
295 def complete(self, text, line, cursor_pos, block=None):
295 def complete(self, text, line, cursor_pos, block=None):
296 """Tab complete text in the kernel's namespace.
296 """Tab complete text in the kernel's namespace.
297
297
298 Parameters
298 Parameters
299 ----------
299 ----------
300 text : str
300 text : str
301 The text to complete.
301 The text to complete.
302 line : str
302 line : str
303 The full line of text that is the surrounding context for the
303 The full line of text that is the surrounding context for the
304 text to complete.
304 text to complete.
305 cursor_pos : int
305 cursor_pos : int
306 The position of the cursor in the line where the completion was
306 The position of the cursor in the line where the completion was
307 requested.
307 requested.
308 block : str, optional
308 block : str, optional
309 The full block of code in which the completion is being requested.
309 The full block of code in which the completion is being requested.
310
310
311 Returns
311 Returns
312 -------
312 -------
313 The msg_id of the message sent.
313 The msg_id of the message sent.
314 """
314 """
315 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
315 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
316 msg = self.session.msg('complete_request', content)
316 msg = self.session.msg('complete_request', content)
317 self._queue_send(msg)
317 self._queue_send(msg)
318 return msg['header']['msg_id']
318 return msg['header']['msg_id']
319
319
320 def object_info(self, oname, detail_level=0):
320 def object_info(self, oname, detail_level=0):
321 """Get metadata information about an object in the kernel's namespace.
321 """Get metadata information about an object in the kernel's namespace.
322
322
323 Parameters
323 Parameters
324 ----------
324 ----------
325 oname : str
325 oname : str
326 A string specifying the object name.
326 A string specifying the object name.
327 detail_level : int, optional
327 detail_level : int, optional
328 The level of detail for the introspection (0-2)
328 The level of detail for the introspection (0-2)
329
329
330 Returns
330 Returns
331 -------
331 -------
332 The msg_id of the message sent.
332 The msg_id of the message sent.
333 """
333 """
334 content = dict(oname=oname, detail_level=detail_level)
334 content = dict(oname=oname, detail_level=detail_level)
335 msg = self.session.msg('object_info_request', content)
335 msg = self.session.msg('object_info_request', content)
336 self._queue_send(msg)
336 self._queue_send(msg)
337 return msg['header']['msg_id']
337 return msg['header']['msg_id']
338
338
339 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
339 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
340 """Get entries from the kernel's history list.
340 """Get entries from the kernel's history list.
341
341
342 Parameters
342 Parameters
343 ----------
343 ----------
344 raw : bool
344 raw : bool
345 If True, return the raw input.
345 If True, return the raw input.
346 output : bool
346 output : bool
347 If True, then return the output as well.
347 If True, then return the output as well.
348 hist_access_type : str
348 hist_access_type : str
349 'range' (fill in session, start and stop params), 'tail' (fill in n)
349 'range' (fill in session, start and stop params), 'tail' (fill in n)
350 or 'search' (fill in pattern param).
350 or 'search' (fill in pattern param).
351
351
352 session : int
352 session : int
353 For a range request, the session from which to get lines. Session
353 For a range request, the session from which to get lines. Session
354 numbers are positive integers; negative ones count back from the
354 numbers are positive integers; negative ones count back from the
355 current session.
355 current session.
356 start : int
356 start : int
357 The first line number of a history range.
357 The first line number of a history range.
358 stop : int
358 stop : int
359 The final (excluded) line number of a history range.
359 The final (excluded) line number of a history range.
360
360
361 n : int
361 n : int
362 The number of lines of history to get for a tail request.
362 The number of lines of history to get for a tail request.
363
363
364 pattern : str
364 pattern : str
365 The glob-syntax pattern for a search request.
365 The glob-syntax pattern for a search request.
366
366
367 Returns
367 Returns
368 -------
368 -------
369 The msg_id of the message sent.
369 The msg_id of the message sent.
370 """
370 """
371 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
371 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
372 **kwargs)
372 **kwargs)
373 msg = self.session.msg('history_request', content)
373 msg = self.session.msg('history_request', content)
374 self._queue_send(msg)
374 self._queue_send(msg)
375 return msg['header']['msg_id']
375 return msg['header']['msg_id']
376
376
377 def kernel_info(self):
377 def kernel_info(self):
378 """Request kernel info."""
378 """Request kernel info."""
379 msg = self.session.msg('kernel_info_request')
379 msg = self.session.msg('kernel_info_request')
380 self._queue_send(msg)
380 self._queue_send(msg)
381 return msg['header']['msg_id']
381 return msg['header']['msg_id']
382
382
383 def shutdown(self, restart=False):
383 def shutdown(self, restart=False):
384 """Request an immediate kernel shutdown.
384 """Request an immediate kernel shutdown.
385
385
386 Upon receipt of the (empty) reply, client code can safely assume that
386 Upon receipt of the (empty) reply, client code can safely assume that
387 the kernel has shut down and it's safe to forcefully terminate it if
387 the kernel has shut down and it's safe to forcefully terminate it if
388 it's still alive.
388 it's still alive.
389
389
390 The kernel will send the reply via a function registered with Python's
390 The kernel will send the reply via a function registered with Python's
391 atexit module, ensuring it's truly done as the kernel is done with all
391 atexit module, ensuring it's truly done as the kernel is done with all
392 normal operation.
392 normal operation.
393 """
393 """
394 # Send quit message to kernel. Once we implement kernel-side setattr,
394 # Send quit message to kernel. Once we implement kernel-side setattr,
395 # this should probably be done that way, but for now this will do.
395 # this should probably be done that way, but for now this will do.
396 msg = self.session.msg('shutdown_request', {'restart':restart})
396 msg = self.session.msg('shutdown_request', {'restart':restart})
397 self._queue_send(msg)
397 self._queue_send(msg)
398 return msg['header']['msg_id']
398 return msg['header']['msg_id']
399
399
400
400
401
401
402 class IOPubChannel(ZMQSocketChannel):
402 class IOPubChannel(ZMQSocketChannel):
403 """The iopub channel which listens for messages that the kernel publishes.
403 """The iopub channel which listens for messages that the kernel publishes.
404
404
405 This channel is where all output is published to frontends.
405 This channel is where all output is published to frontends.
406 """
406 """
407
407
408 def __init__(self, context, session, address):
408 def __init__(self, context, session, address):
409 super(IOPubChannel, self).__init__(context, session, address)
409 super(IOPubChannel, self).__init__(context, session, address)
410 self.ioloop = ioloop.IOLoop()
410 self.ioloop = ioloop.IOLoop()
411
411
412 def run(self):
412 def run(self):
413 """The thread's main activity. Call start() instead."""
413 """The thread's main activity. Call start() instead."""
414 self.socket = self.context.socket(zmq.SUB)
414 self.socket = self.context.socket(zmq.SUB)
415 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
415 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
416 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
416 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
417 self.socket.connect(self.address)
417 self.socket.connect(self.address)
418 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
418 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
419 self.stream.on_recv(self._handle_recv)
419 self.stream.on_recv(self._handle_recv)
420 self._run_loop()
420 self._run_loop()
421 try:
421 try:
422 self.socket.close()
422 self.socket.close()
423 except:
423 except:
424 pass
424 pass
425
425
426 def stop(self):
426 def stop(self):
427 """Stop the channel's event loop and join its thread."""
427 """Stop the channel's event loop and join its thread."""
428 self.ioloop.stop()
428 self.ioloop.stop()
429 super(IOPubChannel, self).stop()
429 super(IOPubChannel, self).stop()
430
430
431 def call_handlers(self, msg):
431 def call_handlers(self, msg):
432 """This method is called in the ioloop thread when a message arrives.
432 """This method is called in the ioloop thread when a message arrives.
433
433
434 Subclasses should override this method to handle incoming messages.
434 Subclasses should override this method to handle incoming messages.
435 It is important to remember that this method is called in the thread
435 It is important to remember that this method is called in the thread
436 so that some logic must be done to ensure that the application leve
436 so that some logic must be done to ensure that the application leve
437 handlers are called in the application thread.
437 handlers are called in the application thread.
438 """
438 """
439 raise NotImplementedError('call_handlers must be defined in a subclass.')
439 raise NotImplementedError('call_handlers must be defined in a subclass.')
440
440
441 def flush(self, timeout=1.0):
441 def flush(self, timeout=1.0):
442 """Immediately processes all pending messages on the iopub channel.
442 """Immediately processes all pending messages on the iopub channel.
443
443
444 Callers should use this method to ensure that :method:`call_handlers`
444 Callers should use this method to ensure that :method:`call_handlers`
445 has been called for all messages that have been received on the
445 has been called for all messages that have been received on the
446 0MQ SUB socket of this channel.
446 0MQ SUB socket of this channel.
447
447
448 This method is thread safe.
448 This method is thread safe.
449
449
450 Parameters
450 Parameters
451 ----------
451 ----------
452 timeout : float, optional
452 timeout : float, optional
453 The maximum amount of time to spend flushing, in seconds. The
453 The maximum amount of time to spend flushing, in seconds. The
454 default is one second.
454 default is one second.
455 """
455 """
456 # We do the IOLoop callback process twice to ensure that the IOLoop
456 # We do the IOLoop callback process twice to ensure that the IOLoop
457 # gets to perform at least one full poll.
457 # gets to perform at least one full poll.
458 stop_time = time.time() + timeout
458 stop_time = time.time() + timeout
459 for i in xrange(2):
459 for i in xrange(2):
460 self._flushed = False
460 self._flushed = False
461 self.ioloop.add_callback(self._flush)
461 self.ioloop.add_callback(self._flush)
462 while not self._flushed and time.time() < stop_time:
462 while not self._flushed and time.time() < stop_time:
463 time.sleep(0.01)
463 time.sleep(0.01)
464
464
465 def _flush(self):
465 def _flush(self):
466 """Callback for :method:`self.flush`."""
466 """Callback for :method:`self.flush`."""
467 self.stream.flush()
467 self.stream.flush()
468 self._flushed = True
468 self._flushed = True
469
469
470
470
471 class StdInChannel(ZMQSocketChannel):
471 class StdInChannel(ZMQSocketChannel):
472 """The stdin channel to handle raw_input requests that the kernel makes."""
472 """The stdin channel to handle raw_input requests that the kernel makes."""
473
473
474 msg_queue = None
474 msg_queue = None
475
475
476 def __init__(self, context, session, address):
476 def __init__(self, context, session, address):
477 super(StdInChannel, self).__init__(context, session, address)
477 super(StdInChannel, self).__init__(context, session, address)
478 self.ioloop = ioloop.IOLoop()
478 self.ioloop = ioloop.IOLoop()
479
479
480 def run(self):
480 def run(self):
481 """The thread's main activity. Call start() instead."""
481 """The thread's main activity. Call start() instead."""
482 self.socket = self.context.socket(zmq.DEALER)
482 self.socket = self.context.socket(zmq.DEALER)
483 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
483 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
484 self.socket.connect(self.address)
484 self.socket.connect(self.address)
485 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
485 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
486 self.stream.on_recv(self._handle_recv)
486 self.stream.on_recv(self._handle_recv)
487 self._run_loop()
487 self._run_loop()
488 try:
488 try:
489 self.socket.close()
489 self.socket.close()
490 except:
490 except:
491 pass
491 pass
492
492
493 def stop(self):
493 def stop(self):
494 """Stop the channel's event loop and join its thread."""
494 """Stop the channel's event loop and join its thread."""
495 self.ioloop.stop()
495 self.ioloop.stop()
496 super(StdInChannel, self).stop()
496 super(StdInChannel, self).stop()
497
497
498 def call_handlers(self, msg):
498 def call_handlers(self, msg):
499 """This method is called in the ioloop thread when a message arrives.
499 """This method is called in the ioloop thread when a message arrives.
500
500
501 Subclasses should override this method to handle incoming messages.
501 Subclasses should override this method to handle incoming messages.
502 It is important to remember that this method is called in the thread
502 It is important to remember that this method is called in the thread
503 so that some logic must be done to ensure that the application leve
503 so that some logic must be done to ensure that the application leve
504 handlers are called in the application thread.
504 handlers are called in the application thread.
505 """
505 """
506 raise NotImplementedError('call_handlers must be defined in a subclass.')
506 raise NotImplementedError('call_handlers must be defined in a subclass.')
507
507
508 def input(self, string):
508 def input(self, string):
509 """Send a string of raw input to the kernel."""
509 """Send a string of raw input to the kernel."""
510 content = dict(value=string)
510 content = dict(value=string)
511 msg = self.session.msg('input_reply', content)
511 msg = self.session.msg('input_reply', content)
512 self._queue_send(msg)
512 self._queue_send(msg)
513
513
514
514
515 class HBChannel(ZMQSocketChannel):
515 class HBChannel(ZMQSocketChannel):
516 """The heartbeat channel which monitors the kernel heartbeat.
516 """The heartbeat channel which monitors the kernel heartbeat.
517
517
518 Note that the heartbeat channel is paused by default. As long as you start
518 Note that the heartbeat channel is paused by default. As long as you start
519 this channel, the kernel manager will ensure that it is paused and un-paused
519 this channel, the kernel manager will ensure that it is paused and un-paused
520 as appropriate.
520 as appropriate.
521 """
521 """
522
522
523 time_to_dead = 3.0
523 time_to_dead = 3.0
524 socket = None
524 socket = None
525 poller = None
525 poller = None
526 _running = None
526 _running = None
527 _pause = None
527 _pause = None
528 _beating = None
528 _beating = None
529
529
530 def __init__(self, context, session, address):
530 def __init__(self, context, session, address):
531 super(HBChannel, self).__init__(context, session, address)
531 super(HBChannel, self).__init__(context, session, address)
532 self._running = False
532 self._running = False
533 self._pause =True
533 self._pause =True
534 self.poller = zmq.Poller()
534 self.poller = zmq.Poller()
535
535
536 def _create_socket(self):
536 def _create_socket(self):
537 if self.socket is not None:
537 if self.socket is not None:
538 # close previous socket, before opening a new one
538 # close previous socket, before opening a new one
539 self.poller.unregister(self.socket)
539 self.poller.unregister(self.socket)
540 self.socket.close()
540 self.socket.close()
541 self.socket = self.context.socket(zmq.REQ)
541 self.socket = self.context.socket(zmq.REQ)
542 self.socket.setsockopt(zmq.LINGER, 0)
542 self.socket.setsockopt(zmq.LINGER, 0)
543 self.socket.connect(self.address)
543 self.socket.connect(self.address)
544
544
545 self.poller.register(self.socket, zmq.POLLIN)
545 self.poller.register(self.socket, zmq.POLLIN)
546
546
547 def _poll(self, start_time):
547 def _poll(self, start_time):
548 """poll for heartbeat replies until we reach self.time_to_dead.
548 """poll for heartbeat replies until we reach self.time_to_dead.
549
549
550 Ignores interrupts, and returns the result of poll(), which
550 Ignores interrupts, and returns the result of poll(), which
551 will be an empty list if no messages arrived before the timeout,
551 will be an empty list if no messages arrived before the timeout,
552 or the event tuple if there is a message to receive.
552 or the event tuple if there is a message to receive.
553 """
553 """
554
554
555 until_dead = self.time_to_dead - (time.time() - start_time)
555 until_dead = self.time_to_dead - (time.time() - start_time)
556 # ensure poll at least once
556 # ensure poll at least once
557 until_dead = max(until_dead, 1e-3)
557 until_dead = max(until_dead, 1e-3)
558 events = []
558 events = []
559 while True:
559 while True:
560 try:
560 try:
561 events = self.poller.poll(1000 * until_dead)
561 events = self.poller.poll(1000 * until_dead)
562 except ZMQError as e:
562 except ZMQError as e:
563 if e.errno == errno.EINTR:
563 if e.errno == errno.EINTR:
564 # ignore interrupts during heartbeat
564 # ignore interrupts during heartbeat
565 # this may never actually happen
565 # this may never actually happen
566 until_dead = self.time_to_dead - (time.time() - start_time)
566 until_dead = self.time_to_dead - (time.time() - start_time)
567 until_dead = max(until_dead, 1e-3)
567 until_dead = max(until_dead, 1e-3)
568 pass
568 pass
569 else:
569 else:
570 raise
570 raise
571 except Exception:
571 except Exception:
572 if self._exiting:
572 if self._exiting:
573 break
573 break
574 else:
574 else:
575 raise
575 raise
576 else:
576 else:
577 break
577 break
578 return events
578 return events
579
579
580 def run(self):
580 def run(self):
581 """The thread's main activity. Call start() instead."""
581 """The thread's main activity. Call start() instead."""
582 self._create_socket()
582 self._create_socket()
583 self._running = True
583 self._running = True
584 self._beating = True
584 self._beating = True
585
585
586 while self._running:
586 while self._running:
587 if self._pause:
587 if self._pause:
588 # just sleep, and skip the rest of the loop
588 # just sleep, and skip the rest of the loop
589 time.sleep(self.time_to_dead)
589 time.sleep(self.time_to_dead)
590 continue
590 continue
591
591
592 since_last_heartbeat = 0.0
592 since_last_heartbeat = 0.0
593 # io.rprint('Ping from HB channel') # dbg
593 # io.rprint('Ping from HB channel') # dbg
594 # no need to catch EFSM here, because the previous event was
594 # no need to catch EFSM here, because the previous event was
595 # either a recv or connect, which cannot be followed by EFSM
595 # either a recv or connect, which cannot be followed by EFSM
596 self.socket.send(b'ping')
596 self.socket.send(b'ping')
597 request_time = time.time()
597 request_time = time.time()
598 ready = self._poll(request_time)
598 ready = self._poll(request_time)
599 if ready:
599 if ready:
600 self._beating = True
600 self._beating = True
601 # the poll above guarantees we have something to recv
601 # the poll above guarantees we have something to recv
602 self.socket.recv()
602 self.socket.recv()
603 # sleep the remainder of the cycle
603 # sleep the remainder of the cycle
604 remainder = self.time_to_dead - (time.time() - request_time)
604 remainder = self.time_to_dead - (time.time() - request_time)
605 if remainder > 0:
605 if remainder > 0:
606 time.sleep(remainder)
606 time.sleep(remainder)
607 continue
607 continue
608 else:
608 else:
609 # nothing was received within the time limit, signal heart failure
609 # nothing was received within the time limit, signal heart failure
610 self._beating = False
610 self._beating = False
611 since_last_heartbeat = time.time() - request_time
611 since_last_heartbeat = time.time() - request_time
612 self.call_handlers(since_last_heartbeat)
612 self.call_handlers(since_last_heartbeat)
613 # and close/reopen the socket, because the REQ/REP cycle has been broken
613 # and close/reopen the socket, because the REQ/REP cycle has been broken
614 self._create_socket()
614 self._create_socket()
615 continue
615 continue
616 try:
616 try:
617 self.socket.close()
617 self.socket.close()
618 except:
618 except:
619 pass
619 pass
620
620
621 def pause(self):
621 def pause(self):
622 """Pause the heartbeat."""
622 """Pause the heartbeat."""
623 self._pause = True
623 self._pause = True
624
624
625 def unpause(self):
625 def unpause(self):
626 """Unpause the heartbeat."""
626 """Unpause the heartbeat."""
627 self._pause = False
627 self._pause = False
628
628
629 def is_beating(self):
629 def is_beating(self):
630 """Is the heartbeat running and responsive (and not paused)."""
630 """Is the heartbeat running and responsive (and not paused)."""
631 if self.is_alive() and not self._pause and self._beating:
631 if self.is_alive() and not self._pause and self._beating:
632 return True
632 return True
633 else:
633 else:
634 return False
634 return False
635
635
636 def stop(self):
636 def stop(self):
637 """Stop the channel's event loop and join its thread."""
637 """Stop the channel's event loop and join its thread."""
638 self._running = False
638 self._running = False
639 super(HBChannel, self).stop()
639 super(HBChannel, self).stop()
640
640
641 def call_handlers(self, since_last_heartbeat):
641 def call_handlers(self, since_last_heartbeat):
642 """This method is called in the ioloop thread when a message arrives.
642 """This method is called in the ioloop thread when a message arrives.
643
643
644 Subclasses should override this method to handle incoming messages.
644 Subclasses should override this method to handle incoming messages.
645 It is important to remember that this method is called in the thread
645 It is important to remember that this method is called in the thread
646 so that some logic must be done to ensure that the application level
646 so that some logic must be done to ensure that the application level
647 handlers are called in the application thread.
647 handlers are called in the application thread.
648 """
648 """
649 raise NotImplementedError('call_handlers must be defined in a subclass.')
649 raise NotImplementedError('call_handlers must be defined in a subclass.')
650
650
651
651
652 #-----------------------------------------------------------------------------
652 #-----------------------------------------------------------------------------
653 # Main kernel manager class
653 # Main kernel manager class
654 #-----------------------------------------------------------------------------
654 #-----------------------------------------------------------------------------
655
655
656 class KernelManager(Configurable):
656 class KernelManager(Configurable):
657 """Manages a single kernel on this host along with its channels.
657 """Manages a single kernel on this host along with its channels.
658
658
659 There are four channels associated with each kernel:
659 There are four channels associated with each kernel:
660
660
661 * shell: for request/reply calls to the kernel.
661 * shell: for request/reply calls to the kernel.
662 * iopub: for the kernel to publish results to frontends.
662 * iopub: for the kernel to publish results to frontends.
663 * hb: for monitoring the kernel's heartbeat.
663 * hb: for monitoring the kernel's heartbeat.
664 * stdin: for frontends to reply to raw_input calls in the kernel.
664 * stdin: for frontends to reply to raw_input calls in the kernel.
665
665
666 The usage of the channels that this class manages is optional. It is
666 The usage of the channels that this class manages is optional. It is
667 entirely possible to connect to the kernels directly using ZeroMQ
667 entirely possible to connect to the kernels directly using ZeroMQ
668 sockets. These channels are useful primarily for talking to a kernel
668 sockets. These channels are useful primarily for talking to a kernel
669 whose :class:`KernelManager` is in the same process.
669 whose :class:`KernelManager` is in the same process.
670
670
671 This version manages kernels started using Popen.
671 This version manages kernels started using Popen.
672 """
672 """
673 # The PyZMQ Context to use for communication with the kernel.
673 # The PyZMQ Context to use for communication with the kernel.
674 context = Instance(zmq.Context)
674 context = Instance(zmq.Context)
675 def _context_default(self):
675 def _context_default(self):
676 return zmq.Context.instance()
676 return zmq.Context.instance()
677
677
678 # The Session to use for communication with the kernel.
678 # The Session to use for communication with the kernel.
679 session = Instance(Session)
679 session = Instance(Session)
680 def _session_default(self):
680 def _session_default(self):
681 return Session(config=self.config)
681 return Session(config=self.config)
682
682
683 # The kernel process with which the KernelManager is communicating.
683 # The kernel process with which the KernelManager is communicating.
684 # generally a Popen instance
684 # generally a Popen instance
685 kernel = Any()
685 kernel = Any()
686
686
687 kernel_cmd = List(Unicode, config=True,
687 kernel_cmd = List(Unicode, config=True,
688 help="""The Popen Command to launch the kernel.
688 help="""The Popen Command to launch the kernel.
689 Override this if you have a custom
689 Override this if you have a custom
690 """
690 """
691 )
691 )
692 def _kernel_cmd_changed(self, name, old, new):
692 def _kernel_cmd_changed(self, name, old, new):
693 print 'kernel cmd changed', new
693 print 'kernel cmd changed', new
694 self.ipython_kernel = False
694 self.ipython_kernel = False
695
695
696 ipython_kernel = Bool(True)
696 ipython_kernel = Bool(True)
697
697
698
698
699 # The addresses for the communication channels.
699 # The addresses for the communication channels.
700 connection_file = Unicode('')
700 connection_file = Unicode('')
701
701
702 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
702 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
703
703
704 ip = Unicode(LOCALHOST, config=True,
704 ip = Unicode(LOCALHOST, config=True,
705 help="""Set the kernel\'s IP address [default localhost].
705 help="""Set the kernel\'s IP address [default localhost].
706 If the IP address is something other than localhost, then
706 If the IP address is something other than localhost, then
707 Consoles on other machines will be able to connect
707 Consoles on other machines will be able to connect
708 to the Kernel, so be careful!"""
708 to the Kernel, so be careful!"""
709 )
709 )
710 def _ip_default(self):
710 def _ip_default(self):
711 if self.transport == 'ipc':
711 if self.transport == 'ipc':
712 if self.connection_file:
712 if self.connection_file:
713 return os.path.splitext(self.connection_file)[0] + '-ipc'
713 return os.path.splitext(self.connection_file)[0] + '-ipc'
714 else:
714 else:
715 return 'kernel-ipc'
715 return 'kernel-ipc'
716 else:
716 else:
717 return LOCALHOST
717 return LOCALHOST
718 def _ip_changed(self, name, old, new):
718 def _ip_changed(self, name, old, new):
719 if new == '*':
719 if new == '*':
720 self.ip = '0.0.0.0'
720 self.ip = '0.0.0.0'
721 shell_port = Integer(0)
721 shell_port = Integer(0)
722 iopub_port = Integer(0)
722 iopub_port = Integer(0)
723 stdin_port = Integer(0)
723 stdin_port = Integer(0)
724 hb_port = Integer(0)
724 hb_port = Integer(0)
725
725
726 # The classes to use for the various channels.
726 # The classes to use for the various channels.
727 shell_channel_class = Type(ShellChannel)
727 shell_channel_class = Type(ShellChannel)
728 iopub_channel_class = Type(IOPubChannel)
728 iopub_channel_class = Type(IOPubChannel)
729 stdin_channel_class = Type(StdInChannel)
729 stdin_channel_class = Type(StdInChannel)
730 hb_channel_class = Type(HBChannel)
730 hb_channel_class = Type(HBChannel)
731
731
732 # Protected traits.
732 # Protected traits.
733 _launch_args = Any
733 _launch_args = Any
734 _shell_channel = Any
734 _shell_channel = Any
735 _iopub_channel = Any
735 _iopub_channel = Any
736 _stdin_channel = Any
736 _stdin_channel = Any
737 _hb_channel = Any
737 _hb_channel = Any
738 _connection_file_written=Bool(False)
738 _connection_file_written=Bool(False)
739
739
740 def __del__(self):
740 def __del__(self):
741 self.cleanup_connection_file()
741 self.cleanup_connection_file()
742
742
743 #--------------------------------------------------------------------------
743 #--------------------------------------------------------------------------
744 # Channel management methods:
744 # Channel management methods:
745 #--------------------------------------------------------------------------
745 #--------------------------------------------------------------------------
746
746
747 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
747 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
748 """Starts the channels for this kernel.
748 """Starts the channels for this kernel.
749
749
750 This will create the channels if they do not exist and then start
750 This will create the channels if they do not exist and then start
751 them (their activity runs in a thread). If port numbers of 0 are
751 them (their activity runs in a thread). If port numbers of 0 are
752 being used (random ports) then you must first call
752 being used (random ports) then you must first call
753 :method:`start_kernel`. If the channels have been stopped and you
753 :method:`start_kernel`. If the channels have been stopped and you
754 call this, :class:`RuntimeError` will be raised.
754 call this, :class:`RuntimeError` will be raised.
755 """
755 """
756 if shell:
756 if shell:
757 self.shell_channel.start()
757 self.shell_channel.start()
758 if iopub:
758 if iopub:
759 self.iopub_channel.start()
759 self.iopub_channel.start()
760 if stdin:
760 if stdin:
761 self.stdin_channel.start()
761 self.stdin_channel.start()
762 self.shell_channel.allow_stdin = True
762 self.shell_channel.allow_stdin = True
763 else:
763 else:
764 self.shell_channel.allow_stdin = False
764 self.shell_channel.allow_stdin = False
765 if hb:
765 if hb:
766 self.hb_channel.start()
766 self.hb_channel.start()
767
767
768 def stop_channels(self):
768 def stop_channels(self):
769 """Stops all the running channels for this kernel.
769 """Stops all the running channels for this kernel.
770
770
771 This stops their event loops and joins their threads.
771 This stops their event loops and joins their threads.
772 """
772 """
773 if self.shell_channel.is_alive():
773 if self.shell_channel.is_alive():
774 self.shell_channel.stop()
774 self.shell_channel.stop()
775 if self.iopub_channel.is_alive():
775 if self.iopub_channel.is_alive():
776 self.iopub_channel.stop()
776 self.iopub_channel.stop()
777 if self.stdin_channel.is_alive():
777 if self.stdin_channel.is_alive():
778 self.stdin_channel.stop()
778 self.stdin_channel.stop()
779 if self.hb_channel.is_alive():
779 if self.hb_channel.is_alive():
780 self.hb_channel.stop()
780 self.hb_channel.stop()
781
781
782 @property
782 @property
783 def channels_running(self):
783 def channels_running(self):
784 """Are any of the channels created and running?"""
784 """Are any of the channels created and running?"""
785 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
785 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
786 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
786 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
787
787
788 def _make_url(self, port):
788 def _make_url(self, port):
789 """Make a zmq url with a port.
789 """Make a zmq url with a port.
790
790
791 There are two cases that this handles:
791 There are two cases that this handles:
792
792
793 * tcp: tcp://ip:port
793 * tcp: tcp://ip:port
794 * ipc: ipc://ip-port
794 * ipc: ipc://ip-port
795 """
795 """
796 if self.transport == 'tcp':
796 if self.transport == 'tcp':
797 return "tcp://%s:%i" % (self.ip, port)
797 return "tcp://%s:%i" % (self.ip, port)
798 else:
798 else:
799 return "%s://%s-%s" % (self.transport, self.ip, port)
799 return "%s://%s-%s" % (self.transport, self.ip, port)
800
800
801 @property
801 @property
802 def shell_channel(self):
802 def shell_channel(self):
803 """Get the shell channel object for this kernel."""
803 """Get the shell channel object for this kernel."""
804 if self._shell_channel is None:
804 if self._shell_channel is None:
805 self._shell_channel = self.shell_channel_class(
805 self._shell_channel = self.shell_channel_class(
806 self.context, self.session, self._make_url(self.shell_port)
806 self.context, self.session, self._make_url(self.shell_port)
807 )
807 )
808 return self._shell_channel
808 return self._shell_channel
809
809
810 @property
810 @property
811 def iopub_channel(self):
811 def iopub_channel(self):
812 """Get the iopub channel object for this kernel."""
812 """Get the iopub channel object for this kernel."""
813 if self._iopub_channel is None:
813 if self._iopub_channel is None:
814 self._iopub_channel = self.iopub_channel_class(
814 self._iopub_channel = self.iopub_channel_class(
815 self.context, self.session, self._make_url(self.iopub_port)
815 self.context, self.session, self._make_url(self.iopub_port)
816 )
816 )
817 return self._iopub_channel
817 return self._iopub_channel
818
818
819 @property
819 @property
820 def stdin_channel(self):
820 def stdin_channel(self):
821 """Get the stdin channel object for this kernel."""
821 """Get the stdin channel object for this kernel."""
822 if self._stdin_channel is None:
822 if self._stdin_channel is None:
823 self._stdin_channel = self.stdin_channel_class(
823 self._stdin_channel = self.stdin_channel_class(
824 self.context, self.session, self._make_url(self.stdin_port)
824 self.context, self.session, self._make_url(self.stdin_port)
825 )
825 )
826 return self._stdin_channel
826 return self._stdin_channel
827
827
828 @property
828 @property
829 def hb_channel(self):
829 def hb_channel(self):
830 """Get the hb channel object for this kernel."""
830 """Get the hb channel object for this kernel."""
831 if self._hb_channel is None:
831 if self._hb_channel is None:
832 self._hb_channel = self.hb_channel_class(
832 self._hb_channel = self.hb_channel_class(
833 self.context, self.session, self._make_url(self.hb_port)
833 self.context, self.session, self._make_url(self.hb_port)
834 )
834 )
835 return self._hb_channel
835 return self._hb_channel
836
836
837 #--------------------------------------------------------------------------
837 #--------------------------------------------------------------------------
838 # Connection and ipc file management
838 # Connection and ipc file management
839 #--------------------------------------------------------------------------
839 #--------------------------------------------------------------------------
840
840
841 def cleanup_connection_file(self):
841 def cleanup_connection_file(self):
842 """Cleanup connection file *if we wrote it*
842 """Cleanup connection file *if we wrote it*
843
843
844 Will not raise if the connection file was already removed somehow.
844 Will not raise if the connection file was already removed somehow.
845 """
845 """
846 if self._connection_file_written:
846 if self._connection_file_written:
847 # cleanup connection files on full shutdown of kernel we started
847 # cleanup connection files on full shutdown of kernel we started
848 self._connection_file_written = False
848 self._connection_file_written = False
849 try:
849 try:
850 os.remove(self.connection_file)
850 os.remove(self.connection_file)
851 except (IOError, OSError):
851 except (IOError, OSError):
852 pass
852 pass
853
853
854 def cleanup_ipc_files(self):
854 def cleanup_ipc_files(self):
855 """Cleanup ipc files if we wrote them."""
855 """Cleanup ipc files if we wrote them."""
856 if self.transport != 'ipc':
856 if self.transport != 'ipc':
857 return
857 return
858 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
858 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
859 ipcfile = "%s-%i" % (self.ip, port)
859 ipcfile = "%s-%i" % (self.ip, port)
860 try:
860 try:
861 os.remove(ipcfile)
861 os.remove(ipcfile)
862 except (IOError, OSError):
862 except (IOError, OSError):
863 pass
863 pass
864
864
865 def load_connection_file(self):
865 def load_connection_file(self):
866 """Load connection info from JSON dict in self.connection_file."""
866 """Load connection info from JSON dict in self.connection_file."""
867 with open(self.connection_file) as f:
867 with open(self.connection_file) as f:
868 cfg = json.loads(f.read())
868 cfg = json.loads(f.read())
869
869
870 from pprint import pprint
870 from pprint import pprint
871 pprint(cfg)
871 pprint(cfg)
872 self.transport = cfg.get('transport', 'tcp')
872 self.transport = cfg.get('transport', 'tcp')
873 self.ip = cfg['ip']
873 self.ip = cfg['ip']
874 self.shell_port = cfg['shell_port']
874 self.shell_port = cfg['shell_port']
875 self.stdin_port = cfg['stdin_port']
875 self.stdin_port = cfg['stdin_port']
876 self.iopub_port = cfg['iopub_port']
876 self.iopub_port = cfg['iopub_port']
877 self.hb_port = cfg['hb_port']
877 self.hb_port = cfg['hb_port']
878 self.session.key = str_to_bytes(cfg['key'])
878 self.session.key = str_to_bytes(cfg['key'])
879
879
880 def write_connection_file(self):
880 def write_connection_file(self):
881 """Write connection info to JSON dict in self.connection_file."""
881 """Write connection info to JSON dict in self.connection_file."""
882 if self._connection_file_written:
882 if self._connection_file_written:
883 return
883 return
884 self.connection_file,cfg = write_connection_file(self.connection_file,
884 self.connection_file,cfg = write_connection_file(self.connection_file,
885 transport=self.transport, ip=self.ip, key=self.session.key,
885 transport=self.transport, ip=self.ip, key=self.session.key,
886 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
886 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
887 shell_port=self.shell_port, hb_port=self.hb_port)
887 shell_port=self.shell_port, hb_port=self.hb_port)
888 # write_connection_file also sets default ports:
888 # write_connection_file also sets default ports:
889 self.shell_port = cfg['shell_port']
889 self.shell_port = cfg['shell_port']
890 self.stdin_port = cfg['stdin_port']
890 self.stdin_port = cfg['stdin_port']
891 self.iopub_port = cfg['iopub_port']
891 self.iopub_port = cfg['iopub_port']
892 self.hb_port = cfg['hb_port']
892 self.hb_port = cfg['hb_port']
893
893
894 self._connection_file_written = True
894 self._connection_file_written = True
895
895
896 #--------------------------------------------------------------------------
896 #--------------------------------------------------------------------------
897 # Kernel management
897 # Kernel management
898 #--------------------------------------------------------------------------
898 #--------------------------------------------------------------------------
899
899
900 def format_kernel_cmd(self, **kw):
900 def format_kernel_cmd(self, **kw):
901 """format templated args (e.g. {connection_file})"""
901 """format templated args (e.g. {connection_file})"""
902 if self.kernel_cmd:
902 if self.kernel_cmd:
903 cmd = self.kernel_cmd
903 cmd = self.kernel_cmd
904 else:
904 else:
905 cmd = make_ipkernel_cmd(
905 cmd = make_ipkernel_cmd(
906 'from IPython.zmq.ipkernel import main; main()',
906 'from IPython.zmq.kernelapp import main; main()',
907 **kw
907 **kw
908 )
908 )
909 ns = dict(connection_file=self.connection_file)
909 ns = dict(connection_file=self.connection_file)
910 ns.update(self._launch_args)
910 ns.update(self._launch_args)
911 return [ c.format(**ns) for c in cmd ]
911 return [ c.format(**ns) for c in cmd ]
912
912
913 def _launch_kernel(self, kernel_cmd, **kw):
913 def _launch_kernel(self, kernel_cmd, **kw):
914 """actually launch the kernel
914 """actually launch the kernel
915
915
916 override in a subclass to launch kernel subprocesses differently
916 override in a subclass to launch kernel subprocesses differently
917 """
917 """
918 return launch_kernel(kernel_cmd, **kw)
918 return launch_kernel(kernel_cmd, **kw)
919
919
920 def start_kernel(self, **kw):
920 def start_kernel(self, **kw):
921 """Starts a kernel on this host in a separate process.
921 """Starts a kernel on this host in a separate process.
922
922
923 If random ports (port=0) are being used, this method must be called
923 If random ports (port=0) are being used, this method must be called
924 before the channels are created.
924 before the channels are created.
925
925
926 Parameters:
926 Parameters:
927 -----------
927 -----------
928 **kw : optional
928 **kw : optional
929 keyword arguments that are passed down to build the kernel_cmd
929 keyword arguments that are passed down to build the kernel_cmd
930 and launching the kernel (e.g. Popen kwargs).
930 and launching the kernel (e.g. Popen kwargs).
931 """
931 """
932 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
932 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
933 raise RuntimeError("Can only launch a kernel on a local interface. "
933 raise RuntimeError("Can only launch a kernel on a local interface. "
934 "Make sure that the '*_address' attributes are "
934 "Make sure that the '*_address' attributes are "
935 "configured properly. "
935 "configured properly. "
936 "Currently valid addresses are: %s"%LOCAL_IPS
936 "Currently valid addresses are: %s"%LOCAL_IPS
937 )
937 )
938
938
939 # write connection file / get default ports
939 # write connection file / get default ports
940 self.write_connection_file()
940 self.write_connection_file()
941
941
942 # save kwargs for use in restart
942 # save kwargs for use in restart
943 self._launch_args = kw.copy()
943 self._launch_args = kw.copy()
944 # build the Popen cmd
944 # build the Popen cmd
945 kernel_cmd = self.format_kernel_cmd(**kw)
945 kernel_cmd = self.format_kernel_cmd(**kw)
946 # launch the kernel subprocess
946 # launch the kernel subprocess
947 self.kernel = self._launch_kernel(kernel_cmd,
947 self.kernel = self._launch_kernel(kernel_cmd,
948 ipython_kernel=self.ipython_kernel,
948 ipython_kernel=self.ipython_kernel,
949 **kw)
949 **kw)
950
950
951 def shutdown_kernel(self, now=False, restart=False):
951 def shutdown_kernel(self, now=False, restart=False):
952 """Attempts to the stop the kernel process cleanly.
952 """Attempts to the stop the kernel process cleanly.
953
953
954 This attempts to shutdown the kernels cleanly by:
954 This attempts to shutdown the kernels cleanly by:
955
955
956 1. Sending it a shutdown message over the shell channel.
956 1. Sending it a shutdown message over the shell channel.
957 2. If that fails, the kernel is shutdown forcibly by sending it
957 2. If that fails, the kernel is shutdown forcibly by sending it
958 a signal.
958 a signal.
959
959
960 Parameters:
960 Parameters:
961 -----------
961 -----------
962 now : bool
962 now : bool
963 Should the kernel be forcible killed *now*. This skips the
963 Should the kernel be forcible killed *now*. This skips the
964 first, nice shutdown attempt.
964 first, nice shutdown attempt.
965 restart: bool
965 restart: bool
966 Will this kernel be restarted after it is shutdown. When this
966 Will this kernel be restarted after it is shutdown. When this
967 is True, connection files will not be cleaned up.
967 is True, connection files will not be cleaned up.
968 """
968 """
969 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
969 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
970 if sys.platform == 'win32':
970 if sys.platform == 'win32':
971 self._kill_kernel()
971 self._kill_kernel()
972 return
972 return
973
973
974 # Pause the heart beat channel if it exists.
974 # Pause the heart beat channel if it exists.
975 if self._hb_channel is not None:
975 if self._hb_channel is not None:
976 self._hb_channel.pause()
976 self._hb_channel.pause()
977
977
978 if now:
978 if now:
979 if self.has_kernel:
979 if self.has_kernel:
980 self._kill_kernel()
980 self._kill_kernel()
981 else:
981 else:
982 # Don't send any additional kernel kill messages immediately, to give
982 # Don't send any additional kernel kill messages immediately, to give
983 # the kernel a chance to properly execute shutdown actions. Wait for at
983 # the kernel a chance to properly execute shutdown actions. Wait for at
984 # most 1s, checking every 0.1s.
984 # most 1s, checking every 0.1s.
985 self.shell_channel.shutdown(restart=restart)
985 self.shell_channel.shutdown(restart=restart)
986 for i in range(10):
986 for i in range(10):
987 if self.is_alive:
987 if self.is_alive:
988 time.sleep(0.1)
988 time.sleep(0.1)
989 else:
989 else:
990 break
990 break
991 else:
991 else:
992 # OK, we've waited long enough.
992 # OK, we've waited long enough.
993 if self.has_kernel:
993 if self.has_kernel:
994 self._kill_kernel()
994 self._kill_kernel()
995
995
996 if not restart:
996 if not restart:
997 self.cleanup_connection_file()
997 self.cleanup_connection_file()
998 self.cleanup_ipc_files()
998 self.cleanup_ipc_files()
999 else:
999 else:
1000 self.cleanup_ipc_files()
1000 self.cleanup_ipc_files()
1001
1001
1002 def restart_kernel(self, now=False, **kw):
1002 def restart_kernel(self, now=False, **kw):
1003 """Restarts a kernel with the arguments that were used to launch it.
1003 """Restarts a kernel with the arguments that were used to launch it.
1004
1004
1005 If the old kernel was launched with random ports, the same ports will be
1005 If the old kernel was launched with random ports, the same ports will be
1006 used for the new kernel. The same connection file is used again.
1006 used for the new kernel. The same connection file is used again.
1007
1007
1008 Parameters
1008 Parameters
1009 ----------
1009 ----------
1010 now : bool, optional
1010 now : bool, optional
1011 If True, the kernel is forcefully restarted *immediately*, without
1011 If True, the kernel is forcefully restarted *immediately*, without
1012 having a chance to do any cleanup action. Otherwise the kernel is
1012 having a chance to do any cleanup action. Otherwise the kernel is
1013 given 1s to clean up before a forceful restart is issued.
1013 given 1s to clean up before a forceful restart is issued.
1014
1014
1015 In all cases the kernel is restarted, the only difference is whether
1015 In all cases the kernel is restarted, the only difference is whether
1016 it is given a chance to perform a clean shutdown or not.
1016 it is given a chance to perform a clean shutdown or not.
1017
1017
1018 **kw : optional
1018 **kw : optional
1019 Any options specified here will overwrite those used to launch the
1019 Any options specified here will overwrite those used to launch the
1020 kernel.
1020 kernel.
1021 """
1021 """
1022 if self._launch_args is None:
1022 if self._launch_args is None:
1023 raise RuntimeError("Cannot restart the kernel. "
1023 raise RuntimeError("Cannot restart the kernel. "
1024 "No previous call to 'start_kernel'.")
1024 "No previous call to 'start_kernel'.")
1025 else:
1025 else:
1026 # Stop currently running kernel.
1026 # Stop currently running kernel.
1027 self.shutdown_kernel(now=now, restart=True)
1027 self.shutdown_kernel(now=now, restart=True)
1028
1028
1029 # Start new kernel.
1029 # Start new kernel.
1030 self._launch_args.update(kw)
1030 self._launch_args.update(kw)
1031 self.start_kernel(**self._launch_args)
1031 self.start_kernel(**self._launch_args)
1032
1032
1033 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
1033 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
1034 # unless there is some delay here.
1034 # unless there is some delay here.
1035 if sys.platform == 'win32':
1035 if sys.platform == 'win32':
1036 time.sleep(0.2)
1036 time.sleep(0.2)
1037
1037
1038 @property
1038 @property
1039 def has_kernel(self):
1039 def has_kernel(self):
1040 """Has a kernel been started that we are managing."""
1040 """Has a kernel been started that we are managing."""
1041 return self.kernel is not None
1041 return self.kernel is not None
1042
1042
1043 def _kill_kernel(self):
1043 def _kill_kernel(self):
1044 """Kill the running kernel.
1044 """Kill the running kernel.
1045
1045
1046 This is a private method, callers should use shutdown_kernel(now=True).
1046 This is a private method, callers should use shutdown_kernel(now=True).
1047 """
1047 """
1048 if self.has_kernel:
1048 if self.has_kernel:
1049 # Pause the heart beat channel if it exists.
1049 # Pause the heart beat channel if it exists.
1050 if self._hb_channel is not None:
1050 if self._hb_channel is not None:
1051 self._hb_channel.pause()
1051 self._hb_channel.pause()
1052
1052
1053 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1053 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1054 # TerminateProcess() on Win32).
1054 # TerminateProcess() on Win32).
1055 try:
1055 try:
1056 self.kernel.kill()
1056 self.kernel.kill()
1057 except OSError as e:
1057 except OSError as e:
1058 # In Windows, we will get an Access Denied error if the process
1058 # In Windows, we will get an Access Denied error if the process
1059 # has already terminated. Ignore it.
1059 # has already terminated. Ignore it.
1060 if sys.platform == 'win32':
1060 if sys.platform == 'win32':
1061 if e.winerror != 5:
1061 if e.winerror != 5:
1062 raise
1062 raise
1063 # On Unix, we may get an ESRCH error if the process has already
1063 # On Unix, we may get an ESRCH error if the process has already
1064 # terminated. Ignore it.
1064 # terminated. Ignore it.
1065 else:
1065 else:
1066 from errno import ESRCH
1066 from errno import ESRCH
1067 if e.errno != ESRCH:
1067 if e.errno != ESRCH:
1068 raise
1068 raise
1069
1069
1070 # Block until the kernel terminates.
1070 # Block until the kernel terminates.
1071 self.kernel.wait()
1071 self.kernel.wait()
1072 self.kernel = None
1072 self.kernel = None
1073 else:
1073 else:
1074 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1074 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1075
1075
1076 def interrupt_kernel(self):
1076 def interrupt_kernel(self):
1077 """Interrupts the kernel by sending it a signal.
1077 """Interrupts the kernel by sending it a signal.
1078
1078
1079 Unlike ``signal_kernel``, this operation is well supported on all
1079 Unlike ``signal_kernel``, this operation is well supported on all
1080 platforms.
1080 platforms.
1081 """
1081 """
1082 if self.has_kernel:
1082 if self.has_kernel:
1083 if sys.platform == 'win32':
1083 if sys.platform == 'win32':
1084 from parentpoller import ParentPollerWindows as Poller
1084 from parentpoller import ParentPollerWindows as Poller
1085 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1085 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1086 else:
1086 else:
1087 self.kernel.send_signal(signal.SIGINT)
1087 self.kernel.send_signal(signal.SIGINT)
1088 else:
1088 else:
1089 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1089 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1090
1090
1091 def signal_kernel(self, signum):
1091 def signal_kernel(self, signum):
1092 """Sends a signal to the kernel.
1092 """Sends a signal to the kernel.
1093
1093
1094 Note that since only SIGTERM is supported on Windows, this function is
1094 Note that since only SIGTERM is supported on Windows, this function is
1095 only useful on Unix systems.
1095 only useful on Unix systems.
1096 """
1096 """
1097 if self.has_kernel:
1097 if self.has_kernel:
1098 self.kernel.send_signal(signum)
1098 self.kernel.send_signal(signum)
1099 else:
1099 else:
1100 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1100 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1101
1101
1102 @property
1102 @property
1103 def is_alive(self):
1103 def is_alive(self):
1104 """Is the kernel process still running?"""
1104 """Is the kernel process still running?"""
1105 if self.has_kernel:
1105 if self.has_kernel:
1106 if self.kernel.poll() is None:
1106 if self.kernel.poll() is None:
1107 return True
1107 return True
1108 else:
1108 else:
1109 return False
1109 return False
1110 elif self._hb_channel is not None:
1110 elif self._hb_channel is not None:
1111 # We didn't start the kernel with this KernelManager so we
1111 # We didn't start the kernel with this KernelManager so we
1112 # use the heartbeat.
1112 # use the heartbeat.
1113 return self._hb_channel.is_beating()
1113 return self._hb_channel.is_beating()
1114 else:
1114 else:
1115 # no heartbeat and not local, we can't tell if it's running,
1115 # no heartbeat and not local, we can't tell if it's running,
1116 # so naively return True
1116 # so naively return True
1117 return True
1117 return True
1118
1118
1119
1119
1120 #-----------------------------------------------------------------------------
1120 #-----------------------------------------------------------------------------
1121 # ABC Registration
1121 # ABC Registration
1122 #-----------------------------------------------------------------------------
1122 #-----------------------------------------------------------------------------
1123
1123
1124 ShellChannelABC.register(ShellChannel)
1124 ShellChannelABC.register(ShellChannel)
1125 IOPubChannelABC.register(IOPubChannel)
1125 IOPubChannelABC.register(IOPubChannel)
1126 HBChannelABC.register(HBChannel)
1126 HBChannelABC.register(HBChannel)
1127 StdInChannelABC.register(StdInChannel)
1127 StdInChannelABC.register(StdInChannel)
1128 KernelManagerABC.register(KernelManager)
1128 KernelManagerABC.register(KernelManager)
1129
1129
General Comments 0
You need to be logged in to leave comments. Login now