##// END OF EJS Templates
define and test IPython.kernel public API
MinRK -
Show More
@@ -0,0 +1,41 b''
1 """Test the IPython.kernel public API
2
3 Authors
4 -------
5 * MinRK
6 """
7 #-----------------------------------------------------------------------------
8 # Copyright (c) 2013, the IPython Development Team.
9 #
10 # Distributed under the terms of the Modified BSD License.
11 #
12 # The full license is in the file COPYING.txt, distributed with this software.
13 #-----------------------------------------------------------------------------
14
15 import nose.tools as nt
16
17 from IPython.testing import decorators as dec
18
19 from IPython.kernel import launcher, connect
20 from IPython import kernel
21
22 #-----------------------------------------------------------------------------
23 # Classes and functions
24 #-----------------------------------------------------------------------------
25
26 @dec.parametric
27 def test_kms():
28 for base in ("", "Blocking", "Multi"):
29 KM = base + "KernelManager"
30 yield nt.assert_true(KM in dir(kernel), KM)
31
32 @dec.parametric
33 def test_launcher():
34 for name in launcher.__all__:
35 yield nt.assert_true(name in dir(kernel), name)
36
37 @dec.parametric
38 def test_connect():
39 for name in connect.__all__:
40 yield nt.assert_true(name in dir(kernel), name)
41
@@ -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.kernel import swallow_argv
64 from IPython.kernel.zmq.session import Session, default_secure
65 from IPython.kernel.zmq.session import Session, default_secure
65 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
66 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
66 from IPython.kernel.zmq.kernelapp import (
67 from IPython.kernel.zmq.kernelapp import (
67 kernel_flags,
68 kernel_flags,
68 kernel_aliases,
69 kernel_aliases,
69 IPKernelApp
70 IPKernelApp
70 )
71 )
71 from IPython.utils.importstring import import_item
72 from IPython.utils.importstring import import_item
72 from IPython.utils.localinterfaces import LOCALHOST
73 from IPython.utils.localinterfaces import LOCALHOST
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(kernel_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(kernel_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,5 +1,7 b''
1 """IPython kernel bases and utilities"""
1 """IPython kernels and associated utilities"""
2
2
3 from .connect import *
3 from .connect import *
4 from .launcher import *
4 from .launcher import *
5 from .kernelmanagerabc import *
5 from .kernelmanager import KernelManager
6 from .blockingkernelmanager import BlockingKernelManager
7 from .multikernelmanager import MultiKernelManager
@@ -1,339 +1,347 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.kernel.zmq.kernelapp import IPKernelApp
130 from IPython.kernel.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
340 __all__ = [
341 'write_connection_file',
342 'get_connection_file',
343 'find_connection_file',
344 'get_connection_info',
345 'connect_qtconsole',
346 'tunnel_to_kernel',
347 ] No newline at end of file
@@ -1,314 +1,314 b''
1 """ A kernel manager for in-process kernels. """
1 """ A kernel manager for in-process kernels. """
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2012 The IPython Development Team
4 # Copyright (C) 2012 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 # Local imports.
14 # Local imports.
15 from IPython.config.configurable import Configurable
15 from IPython.config.configurable import Configurable
16 from IPython.utils.traitlets import Any, Instance, Type
16 from IPython.utils.traitlets import Any, Instance, Type
17 from IPython.kernel import (
17 from IPython.kernel.kernelmanagerabc import (
18 ShellChannelABC, IOPubChannelABC,
18 ShellChannelABC, IOPubChannelABC,
19 HBChannelABC, StdInChannelABC,
19 HBChannelABC, StdInChannelABC,
20 KernelManagerABC
20 KernelManagerABC
21 )
21 )
22
22
23 from .socket import DummySocket
23 from .socket import DummySocket
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Channel classes
26 # Channel classes
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 class InProcessChannel(object):
29 class InProcessChannel(object):
30 """Base class for in-process channels."""
30 """Base class for in-process channels."""
31
31
32 def __init__(self, manager):
32 def __init__(self, manager):
33 super(InProcessChannel, self).__init__()
33 super(InProcessChannel, self).__init__()
34 self.manager = manager
34 self.manager = manager
35 self._is_alive = False
35 self._is_alive = False
36
36
37 #--------------------------------------------------------------------------
37 #--------------------------------------------------------------------------
38 # Channel interface
38 # Channel interface
39 #--------------------------------------------------------------------------
39 #--------------------------------------------------------------------------
40
40
41 def is_alive(self):
41 def is_alive(self):
42 return self._is_alive
42 return self._is_alive
43
43
44 def start(self):
44 def start(self):
45 self._is_alive = True
45 self._is_alive = True
46
46
47 def stop(self):
47 def stop(self):
48 self._is_alive = False
48 self._is_alive = False
49
49
50 def call_handlers(self, msg):
50 def call_handlers(self, msg):
51 """ This method is called in the main thread when a message arrives.
51 """ This method is called in the main thread when a message arrives.
52
52
53 Subclasses should override this method to handle incoming messages.
53 Subclasses should override this method to handle incoming messages.
54 """
54 """
55 raise NotImplementedError('call_handlers must be defined in a subclass.')
55 raise NotImplementedError('call_handlers must be defined in a subclass.')
56
56
57 #--------------------------------------------------------------------------
57 #--------------------------------------------------------------------------
58 # InProcessChannel interface
58 # InProcessChannel interface
59 #--------------------------------------------------------------------------
59 #--------------------------------------------------------------------------
60
60
61 def call_handlers_later(self, *args, **kwds):
61 def call_handlers_later(self, *args, **kwds):
62 """ Call the message handlers later.
62 """ Call the message handlers later.
63
63
64 The default implementation just calls the handlers immediately, but this
64 The default implementation just calls the handlers immediately, but this
65 method exists so that GUI toolkits can defer calling the handlers until
65 method exists so that GUI toolkits can defer calling the handlers until
66 after the event loop has run, as expected by GUI frontends.
66 after the event loop has run, as expected by GUI frontends.
67 """
67 """
68 self.call_handlers(*args, **kwds)
68 self.call_handlers(*args, **kwds)
69
69
70 def process_events(self):
70 def process_events(self):
71 """ Process any pending GUI events.
71 """ Process any pending GUI events.
72
72
73 This method will be never be called from a frontend without an event
73 This method will be never be called from a frontend without an event
74 loop (e.g., a terminal frontend).
74 loop (e.g., a terminal frontend).
75 """
75 """
76 raise NotImplementedError
76 raise NotImplementedError
77
77
78
78
79 class InProcessShellChannel(InProcessChannel):
79 class InProcessShellChannel(InProcessChannel):
80 """See `IPython.kernel.kernelmanager.ShellChannel` for docstrings."""
80 """See `IPython.kernel.kernelmanager.ShellChannel` for docstrings."""
81
81
82 # flag for whether execute requests should be allowed to call raw_input
82 # flag for whether execute requests should be allowed to call raw_input
83 allow_stdin = True
83 allow_stdin = True
84
84
85 #--------------------------------------------------------------------------
85 #--------------------------------------------------------------------------
86 # ShellChannel interface
86 # ShellChannel interface
87 #--------------------------------------------------------------------------
87 #--------------------------------------------------------------------------
88
88
89 def execute(self, code, silent=False, store_history=True,
89 def execute(self, code, silent=False, store_history=True,
90 user_variables=[], user_expressions={}, allow_stdin=None):
90 user_variables=[], user_expressions={}, allow_stdin=None):
91 if allow_stdin is None:
91 if allow_stdin is None:
92 allow_stdin = self.allow_stdin
92 allow_stdin = self.allow_stdin
93 content = dict(code=code, silent=silent, store_history=store_history,
93 content = dict(code=code, silent=silent, store_history=store_history,
94 user_variables=user_variables,
94 user_variables=user_variables,
95 user_expressions=user_expressions,
95 user_expressions=user_expressions,
96 allow_stdin=allow_stdin)
96 allow_stdin=allow_stdin)
97 msg = self.manager.session.msg('execute_request', content)
97 msg = self.manager.session.msg('execute_request', content)
98 self._dispatch_to_kernel(msg)
98 self._dispatch_to_kernel(msg)
99 return msg['header']['msg_id']
99 return msg['header']['msg_id']
100
100
101 def complete(self, text, line, cursor_pos, block=None):
101 def complete(self, text, line, cursor_pos, block=None):
102 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
102 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
103 msg = self.manager.session.msg('complete_request', content)
103 msg = self.manager.session.msg('complete_request', content)
104 self._dispatch_to_kernel(msg)
104 self._dispatch_to_kernel(msg)
105 return msg['header']['msg_id']
105 return msg['header']['msg_id']
106
106
107 def object_info(self, oname, detail_level=0):
107 def object_info(self, oname, detail_level=0):
108 content = dict(oname=oname, detail_level=detail_level)
108 content = dict(oname=oname, detail_level=detail_level)
109 msg = self.manager.session.msg('object_info_request', content)
109 msg = self.manager.session.msg('object_info_request', content)
110 self._dispatch_to_kernel(msg)
110 self._dispatch_to_kernel(msg)
111 return msg['header']['msg_id']
111 return msg['header']['msg_id']
112
112
113 def history(self, raw=True, output=False, hist_access_type='range', **kwds):
113 def history(self, raw=True, output=False, hist_access_type='range', **kwds):
114 content = dict(raw=raw, output=output,
114 content = dict(raw=raw, output=output,
115 hist_access_type=hist_access_type, **kwds)
115 hist_access_type=hist_access_type, **kwds)
116 msg = self.manager.session.msg('history_request', content)
116 msg = self.manager.session.msg('history_request', content)
117 self._dispatch_to_kernel(msg)
117 self._dispatch_to_kernel(msg)
118 return msg['header']['msg_id']
118 return msg['header']['msg_id']
119
119
120 def shutdown(self, restart=False):
120 def shutdown(self, restart=False):
121 # FIXME: What to do here?
121 # FIXME: What to do here?
122 raise NotImplementedError('Cannot shutdown in-process kernel')
122 raise NotImplementedError('Cannot shutdown in-process kernel')
123
123
124 #--------------------------------------------------------------------------
124 #--------------------------------------------------------------------------
125 # Protected interface
125 # Protected interface
126 #--------------------------------------------------------------------------
126 #--------------------------------------------------------------------------
127
127
128 def _dispatch_to_kernel(self, msg):
128 def _dispatch_to_kernel(self, msg):
129 """ Send a message to the kernel and handle a reply.
129 """ Send a message to the kernel and handle a reply.
130 """
130 """
131 kernel = self.manager.kernel
131 kernel = self.manager.kernel
132 if kernel is None:
132 if kernel is None:
133 raise RuntimeError('Cannot send request. No kernel exists.')
133 raise RuntimeError('Cannot send request. No kernel exists.')
134
134
135 stream = DummySocket()
135 stream = DummySocket()
136 self.manager.session.send(stream, msg)
136 self.manager.session.send(stream, msg)
137 msg_parts = stream.recv_multipart()
137 msg_parts = stream.recv_multipart()
138 kernel.dispatch_shell(stream, msg_parts)
138 kernel.dispatch_shell(stream, msg_parts)
139
139
140 idents, reply_msg = self.manager.session.recv(stream, copy=False)
140 idents, reply_msg = self.manager.session.recv(stream, copy=False)
141 self.call_handlers_later(reply_msg)
141 self.call_handlers_later(reply_msg)
142
142
143
143
144 class InProcessIOPubChannel(InProcessChannel):
144 class InProcessIOPubChannel(InProcessChannel):
145 """See `IPython.kernel.kernelmanager.IOPubChannel` for docstrings."""
145 """See `IPython.kernel.kernelmanager.IOPubChannel` for docstrings."""
146
146
147 def flush(self, timeout=1.0):
147 def flush(self, timeout=1.0):
148 pass
148 pass
149
149
150
150
151 class InProcessStdInChannel(InProcessChannel):
151 class InProcessStdInChannel(InProcessChannel):
152 """See `IPython.kernel.kernelmanager.StdInChannel` for docstrings."""
152 """See `IPython.kernel.kernelmanager.StdInChannel` for docstrings."""
153
153
154 def input(self, string):
154 def input(self, string):
155 kernel = self.manager.kernel
155 kernel = self.manager.kernel
156 if kernel is None:
156 if kernel is None:
157 raise RuntimeError('Cannot send input reply. No kernel exists.')
157 raise RuntimeError('Cannot send input reply. No kernel exists.')
158 kernel.raw_input_str = string
158 kernel.raw_input_str = string
159
159
160
160
161 class InProcessHBChannel(InProcessChannel):
161 class InProcessHBChannel(InProcessChannel):
162 """See `IPython.kernel.kernelmanager.HBChannel` for docstrings."""
162 """See `IPython.kernel.kernelmanager.HBChannel` for docstrings."""
163
163
164 time_to_dead = 3.0
164 time_to_dead = 3.0
165
165
166 def __init__(self, *args, **kwds):
166 def __init__(self, *args, **kwds):
167 super(InProcessHBChannel, self).__init__(*args, **kwds)
167 super(InProcessHBChannel, self).__init__(*args, **kwds)
168 self._pause = True
168 self._pause = True
169
169
170 def pause(self):
170 def pause(self):
171 self._pause = True
171 self._pause = True
172
172
173 def unpause(self):
173 def unpause(self):
174 self._pause = False
174 self._pause = False
175
175
176 def is_beating(self):
176 def is_beating(self):
177 return not self._pause
177 return not self._pause
178
178
179
179
180 #-----------------------------------------------------------------------------
180 #-----------------------------------------------------------------------------
181 # Main kernel manager class
181 # Main kernel manager class
182 #-----------------------------------------------------------------------------
182 #-----------------------------------------------------------------------------
183
183
184 class InProcessKernelManager(Configurable):
184 class InProcessKernelManager(Configurable):
185 """A manager for an in-process kernel.
185 """A manager for an in-process kernel.
186
186
187 This class implements the interface of
187 This class implements the interface of
188 `IPython.kernel.kernelmanagerabc.KernelManagerABC` and allows
188 `IPython.kernel.kernelmanagerabc.KernelManagerABC` and allows
189 (asynchronous) frontends to be used seamlessly with an in-process kernel.
189 (asynchronous) frontends to be used seamlessly with an in-process kernel.
190
190
191 See `IPython.kernel.kernelmanager.KernelManager` for docstrings.
191 See `IPython.kernel.kernelmanager.KernelManager` for docstrings.
192 """
192 """
193
193
194 # The Session to use for building messages.
194 # The Session to use for building messages.
195 session = Instance('IPython.kernel.zmq.session.Session')
195 session = Instance('IPython.kernel.zmq.session.Session')
196 def _session_default(self):
196 def _session_default(self):
197 from IPython.kernel.zmq.session import Session
197 from IPython.kernel.zmq.session import Session
198 return Session(config=self.config)
198 return Session(config=self.config)
199
199
200 # The kernel process with which the KernelManager is communicating.
200 # The kernel process with which the KernelManager is communicating.
201 kernel = Instance('IPython.kernel.inprocess.ipkernel.InProcessKernel')
201 kernel = Instance('IPython.kernel.inprocess.ipkernel.InProcessKernel')
202
202
203 # The classes to use for the various channels.
203 # The classes to use for the various channels.
204 shell_channel_class = Type(InProcessShellChannel)
204 shell_channel_class = Type(InProcessShellChannel)
205 iopub_channel_class = Type(InProcessIOPubChannel)
205 iopub_channel_class = Type(InProcessIOPubChannel)
206 stdin_channel_class = Type(InProcessStdInChannel)
206 stdin_channel_class = Type(InProcessStdInChannel)
207 hb_channel_class = Type(InProcessHBChannel)
207 hb_channel_class = Type(InProcessHBChannel)
208
208
209 # Protected traits.
209 # Protected traits.
210 _shell_channel = Any
210 _shell_channel = Any
211 _iopub_channel = Any
211 _iopub_channel = Any
212 _stdin_channel = Any
212 _stdin_channel = Any
213 _hb_channel = Any
213 _hb_channel = Any
214
214
215 #--------------------------------------------------------------------------
215 #--------------------------------------------------------------------------
216 # Channel management methods.
216 # Channel management methods.
217 #--------------------------------------------------------------------------
217 #--------------------------------------------------------------------------
218
218
219 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
219 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
220 if shell:
220 if shell:
221 self.shell_channel.start()
221 self.shell_channel.start()
222 if iopub:
222 if iopub:
223 self.iopub_channel.start()
223 self.iopub_channel.start()
224 if stdin:
224 if stdin:
225 self.stdin_channel.start()
225 self.stdin_channel.start()
226 self.shell_channel.allow_stdin = True
226 self.shell_channel.allow_stdin = True
227 else:
227 else:
228 self.shell_channel.allow_stdin = False
228 self.shell_channel.allow_stdin = False
229 if hb:
229 if hb:
230 self.hb_channel.start()
230 self.hb_channel.start()
231
231
232 def stop_channels(self):
232 def stop_channels(self):
233 if self.shell_channel.is_alive():
233 if self.shell_channel.is_alive():
234 self.shell_channel.stop()
234 self.shell_channel.stop()
235 if self.iopub_channel.is_alive():
235 if self.iopub_channel.is_alive():
236 self.iopub_channel.stop()
236 self.iopub_channel.stop()
237 if self.stdin_channel.is_alive():
237 if self.stdin_channel.is_alive():
238 self.stdin_channel.stop()
238 self.stdin_channel.stop()
239 if self.hb_channel.is_alive():
239 if self.hb_channel.is_alive():
240 self.hb_channel.stop()
240 self.hb_channel.stop()
241
241
242 @property
242 @property
243 def channels_running(self):
243 def channels_running(self):
244 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
244 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
245 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
245 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
246
246
247 @property
247 @property
248 def shell_channel(self):
248 def shell_channel(self):
249 if self._shell_channel is None:
249 if self._shell_channel is None:
250 self._shell_channel = self.shell_channel_class(self)
250 self._shell_channel = self.shell_channel_class(self)
251 return self._shell_channel
251 return self._shell_channel
252
252
253 @property
253 @property
254 def iopub_channel(self):
254 def iopub_channel(self):
255 if self._iopub_channel is None:
255 if self._iopub_channel is None:
256 self._iopub_channel = self.iopub_channel_class(self)
256 self._iopub_channel = self.iopub_channel_class(self)
257 return self._iopub_channel
257 return self._iopub_channel
258
258
259 @property
259 @property
260 def stdin_channel(self):
260 def stdin_channel(self):
261 if self._stdin_channel is None:
261 if self._stdin_channel is None:
262 self._stdin_channel = self.stdin_channel_class(self)
262 self._stdin_channel = self.stdin_channel_class(self)
263 return self._stdin_channel
263 return self._stdin_channel
264
264
265 @property
265 @property
266 def hb_channel(self):
266 def hb_channel(self):
267 if self._hb_channel is None:
267 if self._hb_channel is None:
268 self._hb_channel = self.hb_channel_class(self)
268 self._hb_channel = self.hb_channel_class(self)
269 return self._hb_channel
269 return self._hb_channel
270
270
271 #--------------------------------------------------------------------------
271 #--------------------------------------------------------------------------
272 # Kernel management methods:
272 # Kernel management methods:
273 #--------------------------------------------------------------------------
273 #--------------------------------------------------------------------------
274
274
275 def start_kernel(self, **kwds):
275 def start_kernel(self, **kwds):
276 from IPython.kernel.inprocess.ipkernel import InProcessKernel
276 from IPython.kernel.inprocess.ipkernel import InProcessKernel
277 self.kernel = InProcessKernel()
277 self.kernel = InProcessKernel()
278 self.kernel.frontends.append(self)
278 self.kernel.frontends.append(self)
279
279
280 def shutdown_kernel(self):
280 def shutdown_kernel(self):
281 self._kill_kernel()
281 self._kill_kernel()
282
282
283 def restart_kernel(self, now=False, **kwds):
283 def restart_kernel(self, now=False, **kwds):
284 self.shutdown_kernel()
284 self.shutdown_kernel()
285 self.start_kernel(**kwds)
285 self.start_kernel(**kwds)
286
286
287 @property
287 @property
288 def has_kernel(self):
288 def has_kernel(self):
289 return self.kernel is not None
289 return self.kernel is not None
290
290
291 def _kill_kernel(self):
291 def _kill_kernel(self):
292 self.kernel.frontends.remove(self)
292 self.kernel.frontends.remove(self)
293 self.kernel = None
293 self.kernel = None
294
294
295 def interrupt_kernel(self):
295 def interrupt_kernel(self):
296 raise NotImplementedError("Cannot interrupt in-process kernel.")
296 raise NotImplementedError("Cannot interrupt in-process kernel.")
297
297
298 def signal_kernel(self, signum):
298 def signal_kernel(self, signum):
299 raise NotImplementedError("Cannot signal in-process kernel.")
299 raise NotImplementedError("Cannot signal in-process kernel.")
300
300
301 @property
301 @property
302 def is_alive(self):
302 def is_alive(self):
303 return True
303 return True
304
304
305
305
306 #-----------------------------------------------------------------------------
306 #-----------------------------------------------------------------------------
307 # ABC Registration
307 # ABC Registration
308 #-----------------------------------------------------------------------------
308 #-----------------------------------------------------------------------------
309
309
310 ShellChannelABC.register(InProcessShellChannel)
310 ShellChannelABC.register(InProcessShellChannel)
311 IOPubChannelABC.register(InProcessIOPubChannel)
311 IOPubChannelABC.register(InProcessIOPubChannel)
312 HBChannelABC.register(InProcessHBChannel)
312 HBChannelABC.register(InProcessHBChannel)
313 StdInChannelABC.register(InProcessStdInChannel)
313 StdInChannelABC.register(InProcessStdInChannel)
314 KernelManagerABC.register(InProcessKernelManager)
314 KernelManagerABC.register(InProcessKernelManager)
@@ -1,1131 +1,1131 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 from __future__ import absolute_import
18 from __future__ import absolute_import
19
19
20 # Standard library imports
20 # Standard library imports
21 import atexit
21 import atexit
22 import errno
22 import errno
23 import json
23 import json
24 from subprocess import Popen
24 from subprocess import Popen
25 import os
25 import os
26 import signal
26 import signal
27 import sys
27 import sys
28 from threading import Thread
28 from threading import Thread
29 import time
29 import time
30
30
31 # System library imports
31 # System library imports
32 import zmq
32 import zmq
33 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
33 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
34 # during garbage collection of threads at exit:
34 # during garbage collection of threads at exit:
35 from zmq import ZMQError
35 from zmq import ZMQError
36 from zmq.eventloop import ioloop, zmqstream
36 from zmq.eventloop import ioloop, zmqstream
37
37
38 # Local imports
38 # Local imports
39 from IPython.config.configurable import Configurable
39 from IPython.config.configurable import Configurable
40 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
40 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
41 from IPython.utils.traitlets import (
41 from IPython.utils.traitlets import (
42 Any, Instance, Type, Unicode, List, Integer, Bool, CaselessStrEnum
42 Any, Instance, Type, Unicode, List, Integer, Bool, CaselessStrEnum
43 )
43 )
44 from IPython.utils.py3compat import str_to_bytes
44 from IPython.utils.py3compat import str_to_bytes
45 from IPython.kernel import (
45 from IPython.kernel import (
46 write_connection_file,
46 write_connection_file,
47 make_ipkernel_cmd,
47 make_ipkernel_cmd,
48 launch_kernel,
48 launch_kernel,
49 )
49 )
50 from IPython.kernel.zmq.session import Session
50 from .zmq.session import Session
51 from IPython.kernel import (
51 from .kernelmanagerabc import (
52 ShellChannelABC, IOPubChannelABC,
52 ShellChannelABC, IOPubChannelABC,
53 HBChannelABC, StdInChannelABC,
53 HBChannelABC, StdInChannelABC,
54 KernelManagerABC
54 KernelManagerABC
55 )
55 )
56
56
57
57
58 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
59 # Constants and exceptions
59 # Constants and exceptions
60 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
61
61
62 class InvalidPortNumber(Exception):
62 class InvalidPortNumber(Exception):
63 pass
63 pass
64
64
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66 # Utility functions
66 # Utility functions
67 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68
68
69 # some utilities to validate message structure, these might get moved elsewhere
69 # some utilities to validate message structure, these might get moved elsewhere
70 # if they prove to have more generic utility
70 # if they prove to have more generic utility
71
71
72 def validate_string_list(lst):
72 def validate_string_list(lst):
73 """Validate that the input is a list of strings.
73 """Validate that the input is a list of strings.
74
74
75 Raises ValueError if not."""
75 Raises ValueError if not."""
76 if not isinstance(lst, list):
76 if not isinstance(lst, list):
77 raise ValueError('input %r must be a list' % lst)
77 raise ValueError('input %r must be a list' % lst)
78 for x in lst:
78 for x in lst:
79 if not isinstance(x, basestring):
79 if not isinstance(x, basestring):
80 raise ValueError('element %r in list must be a string' % x)
80 raise ValueError('element %r in list must be a string' % x)
81
81
82
82
83 def validate_string_dict(dct):
83 def validate_string_dict(dct):
84 """Validate that the input is a dict with string keys and values.
84 """Validate that the input is a dict with string keys and values.
85
85
86 Raises ValueError if not."""
86 Raises ValueError if not."""
87 for k,v in dct.iteritems():
87 for k,v in dct.iteritems():
88 if not isinstance(k, basestring):
88 if not isinstance(k, basestring):
89 raise ValueError('key %r in dict must be a string' % k)
89 raise ValueError('key %r in dict must be a string' % k)
90 if not isinstance(v, basestring):
90 if not isinstance(v, basestring):
91 raise ValueError('value %r in dict must be a string' % v)
91 raise ValueError('value %r in dict must be a string' % v)
92
92
93
93
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95 # ZMQ Socket Channel classes
95 # ZMQ Socket Channel classes
96 #-----------------------------------------------------------------------------
96 #-----------------------------------------------------------------------------
97
97
98 class ZMQSocketChannel(Thread):
98 class ZMQSocketChannel(Thread):
99 """The base class for the channels that use ZMQ sockets."""
99 """The base class for the channels that use ZMQ sockets."""
100 context = None
100 context = None
101 session = None
101 session = None
102 socket = None
102 socket = None
103 ioloop = None
103 ioloop = None
104 stream = None
104 stream = None
105 _address = None
105 _address = None
106 _exiting = False
106 _exiting = False
107
107
108 def __init__(self, context, session, address):
108 def __init__(self, context, session, address):
109 """Create a channel.
109 """Create a channel.
110
110
111 Parameters
111 Parameters
112 ----------
112 ----------
113 context : :class:`zmq.Context`
113 context : :class:`zmq.Context`
114 The ZMQ context to use.
114 The ZMQ context to use.
115 session : :class:`session.Session`
115 session : :class:`session.Session`
116 The session to use.
116 The session to use.
117 address : zmq url
117 address : zmq url
118 Standard (ip, port) tuple that the kernel is listening on.
118 Standard (ip, port) tuple that the kernel is listening on.
119 """
119 """
120 super(ZMQSocketChannel, self).__init__()
120 super(ZMQSocketChannel, self).__init__()
121 self.daemon = True
121 self.daemon = True
122
122
123 self.context = context
123 self.context = context
124 self.session = session
124 self.session = session
125 if isinstance(address, tuple):
125 if isinstance(address, tuple):
126 if address[1] == 0:
126 if address[1] == 0:
127 message = 'The port number for a channel cannot be 0.'
127 message = 'The port number for a channel cannot be 0.'
128 raise InvalidPortNumber(message)
128 raise InvalidPortNumber(message)
129 address = "tcp://%s:%i" % address
129 address = "tcp://%s:%i" % address
130 self._address = address
130 self._address = address
131 atexit.register(self._notice_exit)
131 atexit.register(self._notice_exit)
132
132
133 def _notice_exit(self):
133 def _notice_exit(self):
134 self._exiting = True
134 self._exiting = True
135
135
136 def _run_loop(self):
136 def _run_loop(self):
137 """Run my loop, ignoring EINTR events in the poller"""
137 """Run my loop, ignoring EINTR events in the poller"""
138 while True:
138 while True:
139 try:
139 try:
140 self.ioloop.start()
140 self.ioloop.start()
141 except ZMQError as e:
141 except ZMQError as e:
142 if e.errno == errno.EINTR:
142 if e.errno == errno.EINTR:
143 continue
143 continue
144 else:
144 else:
145 raise
145 raise
146 except Exception:
146 except Exception:
147 if self._exiting:
147 if self._exiting:
148 break
148 break
149 else:
149 else:
150 raise
150 raise
151 else:
151 else:
152 break
152 break
153
153
154 def stop(self):
154 def stop(self):
155 """Stop the channel's event loop and join its thread.
155 """Stop the channel's event loop and join its thread.
156
156
157 This calls :method:`Thread.join` and returns when the thread
157 This calls :method:`Thread.join` and returns when the thread
158 terminates. :class:`RuntimeError` will be raised if
158 terminates. :class:`RuntimeError` will be raised if
159 :method:`self.start` is called again.
159 :method:`self.start` is called again.
160 """
160 """
161 self.join()
161 self.join()
162
162
163 @property
163 @property
164 def address(self):
164 def address(self):
165 """Get the channel's address as a zmq url string.
165 """Get the channel's address as a zmq url string.
166
166
167 These URLS have the form: 'tcp://127.0.0.1:5555'.
167 These URLS have the form: 'tcp://127.0.0.1:5555'.
168 """
168 """
169 return self._address
169 return self._address
170
170
171 def _queue_send(self, msg):
171 def _queue_send(self, msg):
172 """Queue a message to be sent from the IOLoop's thread.
172 """Queue a message to be sent from the IOLoop's thread.
173
173
174 Parameters
174 Parameters
175 ----------
175 ----------
176 msg : message to send
176 msg : message to send
177
177
178 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
178 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
179 thread control of the action.
179 thread control of the action.
180 """
180 """
181 def thread_send():
181 def thread_send():
182 self.session.send(self.stream, msg)
182 self.session.send(self.stream, msg)
183 self.ioloop.add_callback(thread_send)
183 self.ioloop.add_callback(thread_send)
184
184
185 def _handle_recv(self, msg):
185 def _handle_recv(self, msg):
186 """Callback for stream.on_recv.
186 """Callback for stream.on_recv.
187
187
188 Unpacks message, and calls handlers with it.
188 Unpacks message, and calls handlers with it.
189 """
189 """
190 ident,smsg = self.session.feed_identities(msg)
190 ident,smsg = self.session.feed_identities(msg)
191 self.call_handlers(self.session.unserialize(smsg))
191 self.call_handlers(self.session.unserialize(smsg))
192
192
193
193
194
194
195 class ShellChannel(ZMQSocketChannel):
195 class ShellChannel(ZMQSocketChannel):
196 """The shell channel for issuing request/replies to the kernel."""
196 """The shell channel for issuing request/replies to the kernel."""
197
197
198 command_queue = None
198 command_queue = None
199 # flag for whether execute requests should be allowed to call raw_input:
199 # flag for whether execute requests should be allowed to call raw_input:
200 allow_stdin = True
200 allow_stdin = True
201
201
202 def __init__(self, context, session, address):
202 def __init__(self, context, session, address):
203 super(ShellChannel, self).__init__(context, session, address)
203 super(ShellChannel, self).__init__(context, session, address)
204 self.ioloop = ioloop.IOLoop()
204 self.ioloop = ioloop.IOLoop()
205
205
206 def run(self):
206 def run(self):
207 """The thread's main activity. Call start() instead."""
207 """The thread's main activity. Call start() instead."""
208 self.socket = self.context.socket(zmq.DEALER)
208 self.socket = self.context.socket(zmq.DEALER)
209 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
209 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
210 self.socket.connect(self.address)
210 self.socket.connect(self.address)
211 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
211 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
212 self.stream.on_recv(self._handle_recv)
212 self.stream.on_recv(self._handle_recv)
213 self._run_loop()
213 self._run_loop()
214 try:
214 try:
215 self.socket.close()
215 self.socket.close()
216 except:
216 except:
217 pass
217 pass
218
218
219 def stop(self):
219 def stop(self):
220 """Stop the channel's event loop and join its thread."""
220 """Stop the channel's event loop and join its thread."""
221 self.ioloop.stop()
221 self.ioloop.stop()
222 super(ShellChannel, self).stop()
222 super(ShellChannel, self).stop()
223
223
224 def call_handlers(self, msg):
224 def call_handlers(self, msg):
225 """This method is called in the ioloop thread when a message arrives.
225 """This method is called in the ioloop thread when a message arrives.
226
226
227 Subclasses should override this method to handle incoming messages.
227 Subclasses should override this method to handle incoming messages.
228 It is important to remember that this method is called in the thread
228 It is important to remember that this method is called in the thread
229 so that some logic must be done to ensure that the application leve
229 so that some logic must be done to ensure that the application leve
230 handlers are called in the application thread.
230 handlers are called in the application thread.
231 """
231 """
232 raise NotImplementedError('call_handlers must be defined in a subclass.')
232 raise NotImplementedError('call_handlers must be defined in a subclass.')
233
233
234 def execute(self, code, silent=False, store_history=True,
234 def execute(self, code, silent=False, store_history=True,
235 user_variables=None, user_expressions=None, allow_stdin=None):
235 user_variables=None, user_expressions=None, allow_stdin=None):
236 """Execute code in the kernel.
236 """Execute code in the kernel.
237
237
238 Parameters
238 Parameters
239 ----------
239 ----------
240 code : str
240 code : str
241 A string of Python code.
241 A string of Python code.
242
242
243 silent : bool, optional (default False)
243 silent : bool, optional (default False)
244 If set, the kernel will execute the code as quietly possible, and
244 If set, the kernel will execute the code as quietly possible, and
245 will force store_history to be False.
245 will force store_history to be False.
246
246
247 store_history : bool, optional (default True)
247 store_history : bool, optional (default True)
248 If set, the kernel will store command history. This is forced
248 If set, the kernel will store command history. This is forced
249 to be False if silent is True.
249 to be False if silent is True.
250
250
251 user_variables : list, optional
251 user_variables : list, optional
252 A list of variable names to pull from the user's namespace. They
252 A list of variable names to pull from the user's namespace. They
253 will come back as a dict with these names as keys and their
253 will come back as a dict with these names as keys and their
254 :func:`repr` as values.
254 :func:`repr` as values.
255
255
256 user_expressions : dict, optional
256 user_expressions : dict, optional
257 A dict mapping names to expressions to be evaluated in the user's
257 A dict mapping names to expressions to be evaluated in the user's
258 dict. The expression values are returned as strings formatted using
258 dict. The expression values are returned as strings formatted using
259 :func:`repr`.
259 :func:`repr`.
260
260
261 allow_stdin : bool, optional (default self.allow_stdin)
261 allow_stdin : bool, optional (default self.allow_stdin)
262 Flag for whether the kernel can send stdin requests to frontends.
262 Flag for whether the kernel can send stdin requests to frontends.
263
263
264 Some frontends (e.g. the Notebook) do not support stdin requests.
264 Some frontends (e.g. the Notebook) do not support stdin requests.
265 If raw_input is called from code executed from such a frontend, a
265 If raw_input is called from code executed from such a frontend, a
266 StdinNotImplementedError will be raised.
266 StdinNotImplementedError will be raised.
267
267
268 Returns
268 Returns
269 -------
269 -------
270 The msg_id of the message sent.
270 The msg_id of the message sent.
271 """
271 """
272 if user_variables is None:
272 if user_variables is None:
273 user_variables = []
273 user_variables = []
274 if user_expressions is None:
274 if user_expressions is None:
275 user_expressions = {}
275 user_expressions = {}
276 if allow_stdin is None:
276 if allow_stdin is None:
277 allow_stdin = self.allow_stdin
277 allow_stdin = self.allow_stdin
278
278
279
279
280 # Don't waste network traffic if inputs are invalid
280 # Don't waste network traffic if inputs are invalid
281 if not isinstance(code, basestring):
281 if not isinstance(code, basestring):
282 raise ValueError('code %r must be a string' % code)
282 raise ValueError('code %r must be a string' % code)
283 validate_string_list(user_variables)
283 validate_string_list(user_variables)
284 validate_string_dict(user_expressions)
284 validate_string_dict(user_expressions)
285
285
286 # Create class for content/msg creation. Related to, but possibly
286 # Create class for content/msg creation. Related to, but possibly
287 # not in Session.
287 # not in Session.
288 content = dict(code=code, silent=silent, store_history=store_history,
288 content = dict(code=code, silent=silent, store_history=store_history,
289 user_variables=user_variables,
289 user_variables=user_variables,
290 user_expressions=user_expressions,
290 user_expressions=user_expressions,
291 allow_stdin=allow_stdin,
291 allow_stdin=allow_stdin,
292 )
292 )
293 msg = self.session.msg('execute_request', content)
293 msg = self.session.msg('execute_request', content)
294 self._queue_send(msg)
294 self._queue_send(msg)
295 return msg['header']['msg_id']
295 return msg['header']['msg_id']
296
296
297 def complete(self, text, line, cursor_pos, block=None):
297 def complete(self, text, line, cursor_pos, block=None):
298 """Tab complete text in the kernel's namespace.
298 """Tab complete text in the kernel's namespace.
299
299
300 Parameters
300 Parameters
301 ----------
301 ----------
302 text : str
302 text : str
303 The text to complete.
303 The text to complete.
304 line : str
304 line : str
305 The full line of text that is the surrounding context for the
305 The full line of text that is the surrounding context for the
306 text to complete.
306 text to complete.
307 cursor_pos : int
307 cursor_pos : int
308 The position of the cursor in the line where the completion was
308 The position of the cursor in the line where the completion was
309 requested.
309 requested.
310 block : str, optional
310 block : str, optional
311 The full block of code in which the completion is being requested.
311 The full block of code in which the completion is being requested.
312
312
313 Returns
313 Returns
314 -------
314 -------
315 The msg_id of the message sent.
315 The msg_id of the message sent.
316 """
316 """
317 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
317 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
318 msg = self.session.msg('complete_request', content)
318 msg = self.session.msg('complete_request', content)
319 self._queue_send(msg)
319 self._queue_send(msg)
320 return msg['header']['msg_id']
320 return msg['header']['msg_id']
321
321
322 def object_info(self, oname, detail_level=0):
322 def object_info(self, oname, detail_level=0):
323 """Get metadata information about an object in the kernel's namespace.
323 """Get metadata information about an object in the kernel's namespace.
324
324
325 Parameters
325 Parameters
326 ----------
326 ----------
327 oname : str
327 oname : str
328 A string specifying the object name.
328 A string specifying the object name.
329 detail_level : int, optional
329 detail_level : int, optional
330 The level of detail for the introspection (0-2)
330 The level of detail for the introspection (0-2)
331
331
332 Returns
332 Returns
333 -------
333 -------
334 The msg_id of the message sent.
334 The msg_id of the message sent.
335 """
335 """
336 content = dict(oname=oname, detail_level=detail_level)
336 content = dict(oname=oname, detail_level=detail_level)
337 msg = self.session.msg('object_info_request', content)
337 msg = self.session.msg('object_info_request', content)
338 self._queue_send(msg)
338 self._queue_send(msg)
339 return msg['header']['msg_id']
339 return msg['header']['msg_id']
340
340
341 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
341 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
342 """Get entries from the kernel's history list.
342 """Get entries from the kernel's history list.
343
343
344 Parameters
344 Parameters
345 ----------
345 ----------
346 raw : bool
346 raw : bool
347 If True, return the raw input.
347 If True, return the raw input.
348 output : bool
348 output : bool
349 If True, then return the output as well.
349 If True, then return the output as well.
350 hist_access_type : str
350 hist_access_type : str
351 'range' (fill in session, start and stop params), 'tail' (fill in n)
351 'range' (fill in session, start and stop params), 'tail' (fill in n)
352 or 'search' (fill in pattern param).
352 or 'search' (fill in pattern param).
353
353
354 session : int
354 session : int
355 For a range request, the session from which to get lines. Session
355 For a range request, the session from which to get lines. Session
356 numbers are positive integers; negative ones count back from the
356 numbers are positive integers; negative ones count back from the
357 current session.
357 current session.
358 start : int
358 start : int
359 The first line number of a history range.
359 The first line number of a history range.
360 stop : int
360 stop : int
361 The final (excluded) line number of a history range.
361 The final (excluded) line number of a history range.
362
362
363 n : int
363 n : int
364 The number of lines of history to get for a tail request.
364 The number of lines of history to get for a tail request.
365
365
366 pattern : str
366 pattern : str
367 The glob-syntax pattern for a search request.
367 The glob-syntax pattern for a search request.
368
368
369 Returns
369 Returns
370 -------
370 -------
371 The msg_id of the message sent.
371 The msg_id of the message sent.
372 """
372 """
373 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
373 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
374 **kwargs)
374 **kwargs)
375 msg = self.session.msg('history_request', content)
375 msg = self.session.msg('history_request', content)
376 self._queue_send(msg)
376 self._queue_send(msg)
377 return msg['header']['msg_id']
377 return msg['header']['msg_id']
378
378
379 def kernel_info(self):
379 def kernel_info(self):
380 """Request kernel info."""
380 """Request kernel info."""
381 msg = self.session.msg('kernel_info_request')
381 msg = self.session.msg('kernel_info_request')
382 self._queue_send(msg)
382 self._queue_send(msg)
383 return msg['header']['msg_id']
383 return msg['header']['msg_id']
384
384
385 def shutdown(self, restart=False):
385 def shutdown(self, restart=False):
386 """Request an immediate kernel shutdown.
386 """Request an immediate kernel shutdown.
387
387
388 Upon receipt of the (empty) reply, client code can safely assume that
388 Upon receipt of the (empty) reply, client code can safely assume that
389 the kernel has shut down and it's safe to forcefully terminate it if
389 the kernel has shut down and it's safe to forcefully terminate it if
390 it's still alive.
390 it's still alive.
391
391
392 The kernel will send the reply via a function registered with Python's
392 The kernel will send the reply via a function registered with Python's
393 atexit module, ensuring it's truly done as the kernel is done with all
393 atexit module, ensuring it's truly done as the kernel is done with all
394 normal operation.
394 normal operation.
395 """
395 """
396 # Send quit message to kernel. Once we implement kernel-side setattr,
396 # Send quit message to kernel. Once we implement kernel-side setattr,
397 # this should probably be done that way, but for now this will do.
397 # this should probably be done that way, but for now this will do.
398 msg = self.session.msg('shutdown_request', {'restart':restart})
398 msg = self.session.msg('shutdown_request', {'restart':restart})
399 self._queue_send(msg)
399 self._queue_send(msg)
400 return msg['header']['msg_id']
400 return msg['header']['msg_id']
401
401
402
402
403
403
404 class IOPubChannel(ZMQSocketChannel):
404 class IOPubChannel(ZMQSocketChannel):
405 """The iopub channel which listens for messages that the kernel publishes.
405 """The iopub channel which listens for messages that the kernel publishes.
406
406
407 This channel is where all output is published to frontends.
407 This channel is where all output is published to frontends.
408 """
408 """
409
409
410 def __init__(self, context, session, address):
410 def __init__(self, context, session, address):
411 super(IOPubChannel, self).__init__(context, session, address)
411 super(IOPubChannel, self).__init__(context, session, address)
412 self.ioloop = ioloop.IOLoop()
412 self.ioloop = ioloop.IOLoop()
413
413
414 def run(self):
414 def run(self):
415 """The thread's main activity. Call start() instead."""
415 """The thread's main activity. Call start() instead."""
416 self.socket = self.context.socket(zmq.SUB)
416 self.socket = self.context.socket(zmq.SUB)
417 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
417 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
418 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
418 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
419 self.socket.connect(self.address)
419 self.socket.connect(self.address)
420 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
420 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
421 self.stream.on_recv(self._handle_recv)
421 self.stream.on_recv(self._handle_recv)
422 self._run_loop()
422 self._run_loop()
423 try:
423 try:
424 self.socket.close()
424 self.socket.close()
425 except:
425 except:
426 pass
426 pass
427
427
428 def stop(self):
428 def stop(self):
429 """Stop the channel's event loop and join its thread."""
429 """Stop the channel's event loop and join its thread."""
430 self.ioloop.stop()
430 self.ioloop.stop()
431 super(IOPubChannel, self).stop()
431 super(IOPubChannel, self).stop()
432
432
433 def call_handlers(self, msg):
433 def call_handlers(self, msg):
434 """This method is called in the ioloop thread when a message arrives.
434 """This method is called in the ioloop thread when a message arrives.
435
435
436 Subclasses should override this method to handle incoming messages.
436 Subclasses should override this method to handle incoming messages.
437 It is important to remember that this method is called in the thread
437 It is important to remember that this method is called in the thread
438 so that some logic must be done to ensure that the application leve
438 so that some logic must be done to ensure that the application leve
439 handlers are called in the application thread.
439 handlers are called in the application thread.
440 """
440 """
441 raise NotImplementedError('call_handlers must be defined in a subclass.')
441 raise NotImplementedError('call_handlers must be defined in a subclass.')
442
442
443 def flush(self, timeout=1.0):
443 def flush(self, timeout=1.0):
444 """Immediately processes all pending messages on the iopub channel.
444 """Immediately processes all pending messages on the iopub channel.
445
445
446 Callers should use this method to ensure that :method:`call_handlers`
446 Callers should use this method to ensure that :method:`call_handlers`
447 has been called for all messages that have been received on the
447 has been called for all messages that have been received on the
448 0MQ SUB socket of this channel.
448 0MQ SUB socket of this channel.
449
449
450 This method is thread safe.
450 This method is thread safe.
451
451
452 Parameters
452 Parameters
453 ----------
453 ----------
454 timeout : float, optional
454 timeout : float, optional
455 The maximum amount of time to spend flushing, in seconds. The
455 The maximum amount of time to spend flushing, in seconds. The
456 default is one second.
456 default is one second.
457 """
457 """
458 # We do the IOLoop callback process twice to ensure that the IOLoop
458 # We do the IOLoop callback process twice to ensure that the IOLoop
459 # gets to perform at least one full poll.
459 # gets to perform at least one full poll.
460 stop_time = time.time() + timeout
460 stop_time = time.time() + timeout
461 for i in xrange(2):
461 for i in xrange(2):
462 self._flushed = False
462 self._flushed = False
463 self.ioloop.add_callback(self._flush)
463 self.ioloop.add_callback(self._flush)
464 while not self._flushed and time.time() < stop_time:
464 while not self._flushed and time.time() < stop_time:
465 time.sleep(0.01)
465 time.sleep(0.01)
466
466
467 def _flush(self):
467 def _flush(self):
468 """Callback for :method:`self.flush`."""
468 """Callback for :method:`self.flush`."""
469 self.stream.flush()
469 self.stream.flush()
470 self._flushed = True
470 self._flushed = True
471
471
472
472
473 class StdInChannel(ZMQSocketChannel):
473 class StdInChannel(ZMQSocketChannel):
474 """The stdin channel to handle raw_input requests that the kernel makes."""
474 """The stdin channel to handle raw_input requests that the kernel makes."""
475
475
476 msg_queue = None
476 msg_queue = None
477
477
478 def __init__(self, context, session, address):
478 def __init__(self, context, session, address):
479 super(StdInChannel, self).__init__(context, session, address)
479 super(StdInChannel, self).__init__(context, session, address)
480 self.ioloop = ioloop.IOLoop()
480 self.ioloop = ioloop.IOLoop()
481
481
482 def run(self):
482 def run(self):
483 """The thread's main activity. Call start() instead."""
483 """The thread's main activity. Call start() instead."""
484 self.socket = self.context.socket(zmq.DEALER)
484 self.socket = self.context.socket(zmq.DEALER)
485 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
485 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
486 self.socket.connect(self.address)
486 self.socket.connect(self.address)
487 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
487 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
488 self.stream.on_recv(self._handle_recv)
488 self.stream.on_recv(self._handle_recv)
489 self._run_loop()
489 self._run_loop()
490 try:
490 try:
491 self.socket.close()
491 self.socket.close()
492 except:
492 except:
493 pass
493 pass
494
494
495 def stop(self):
495 def stop(self):
496 """Stop the channel's event loop and join its thread."""
496 """Stop the channel's event loop and join its thread."""
497 self.ioloop.stop()
497 self.ioloop.stop()
498 super(StdInChannel, self).stop()
498 super(StdInChannel, self).stop()
499
499
500 def call_handlers(self, msg):
500 def call_handlers(self, msg):
501 """This method is called in the ioloop thread when a message arrives.
501 """This method is called in the ioloop thread when a message arrives.
502
502
503 Subclasses should override this method to handle incoming messages.
503 Subclasses should override this method to handle incoming messages.
504 It is important to remember that this method is called in the thread
504 It is important to remember that this method is called in the thread
505 so that some logic must be done to ensure that the application leve
505 so that some logic must be done to ensure that the application leve
506 handlers are called in the application thread.
506 handlers are called in the application thread.
507 """
507 """
508 raise NotImplementedError('call_handlers must be defined in a subclass.')
508 raise NotImplementedError('call_handlers must be defined in a subclass.')
509
509
510 def input(self, string):
510 def input(self, string):
511 """Send a string of raw input to the kernel."""
511 """Send a string of raw input to the kernel."""
512 content = dict(value=string)
512 content = dict(value=string)
513 msg = self.session.msg('input_reply', content)
513 msg = self.session.msg('input_reply', content)
514 self._queue_send(msg)
514 self._queue_send(msg)
515
515
516
516
517 class HBChannel(ZMQSocketChannel):
517 class HBChannel(ZMQSocketChannel):
518 """The heartbeat channel which monitors the kernel heartbeat.
518 """The heartbeat channel which monitors the kernel heartbeat.
519
519
520 Note that the heartbeat channel is paused by default. As long as you start
520 Note that the heartbeat channel is paused by default. As long as you start
521 this channel, the kernel manager will ensure that it is paused and un-paused
521 this channel, the kernel manager will ensure that it is paused and un-paused
522 as appropriate.
522 as appropriate.
523 """
523 """
524
524
525 time_to_dead = 3.0
525 time_to_dead = 3.0
526 socket = None
526 socket = None
527 poller = None
527 poller = None
528 _running = None
528 _running = None
529 _pause = None
529 _pause = None
530 _beating = None
530 _beating = None
531
531
532 def __init__(self, context, session, address):
532 def __init__(self, context, session, address):
533 super(HBChannel, self).__init__(context, session, address)
533 super(HBChannel, self).__init__(context, session, address)
534 self._running = False
534 self._running = False
535 self._pause =True
535 self._pause =True
536 self.poller = zmq.Poller()
536 self.poller = zmq.Poller()
537
537
538 def _create_socket(self):
538 def _create_socket(self):
539 if self.socket is not None:
539 if self.socket is not None:
540 # close previous socket, before opening a new one
540 # close previous socket, before opening a new one
541 self.poller.unregister(self.socket)
541 self.poller.unregister(self.socket)
542 self.socket.close()
542 self.socket.close()
543 self.socket = self.context.socket(zmq.REQ)
543 self.socket = self.context.socket(zmq.REQ)
544 self.socket.setsockopt(zmq.LINGER, 0)
544 self.socket.setsockopt(zmq.LINGER, 0)
545 self.socket.connect(self.address)
545 self.socket.connect(self.address)
546
546
547 self.poller.register(self.socket, zmq.POLLIN)
547 self.poller.register(self.socket, zmq.POLLIN)
548
548
549 def _poll(self, start_time):
549 def _poll(self, start_time):
550 """poll for heartbeat replies until we reach self.time_to_dead.
550 """poll for heartbeat replies until we reach self.time_to_dead.
551
551
552 Ignores interrupts, and returns the result of poll(), which
552 Ignores interrupts, and returns the result of poll(), which
553 will be an empty list if no messages arrived before the timeout,
553 will be an empty list if no messages arrived before the timeout,
554 or the event tuple if there is a message to receive.
554 or the event tuple if there is a message to receive.
555 """
555 """
556
556
557 until_dead = self.time_to_dead - (time.time() - start_time)
557 until_dead = self.time_to_dead - (time.time() - start_time)
558 # ensure poll at least once
558 # ensure poll at least once
559 until_dead = max(until_dead, 1e-3)
559 until_dead = max(until_dead, 1e-3)
560 events = []
560 events = []
561 while True:
561 while True:
562 try:
562 try:
563 events = self.poller.poll(1000 * until_dead)
563 events = self.poller.poll(1000 * until_dead)
564 except ZMQError as e:
564 except ZMQError as e:
565 if e.errno == errno.EINTR:
565 if e.errno == errno.EINTR:
566 # ignore interrupts during heartbeat
566 # ignore interrupts during heartbeat
567 # this may never actually happen
567 # this may never actually happen
568 until_dead = self.time_to_dead - (time.time() - start_time)
568 until_dead = self.time_to_dead - (time.time() - start_time)
569 until_dead = max(until_dead, 1e-3)
569 until_dead = max(until_dead, 1e-3)
570 pass
570 pass
571 else:
571 else:
572 raise
572 raise
573 except Exception:
573 except Exception:
574 if self._exiting:
574 if self._exiting:
575 break
575 break
576 else:
576 else:
577 raise
577 raise
578 else:
578 else:
579 break
579 break
580 return events
580 return events
581
581
582 def run(self):
582 def run(self):
583 """The thread's main activity. Call start() instead."""
583 """The thread's main activity. Call start() instead."""
584 self._create_socket()
584 self._create_socket()
585 self._running = True
585 self._running = True
586 self._beating = True
586 self._beating = True
587
587
588 while self._running:
588 while self._running:
589 if self._pause:
589 if self._pause:
590 # just sleep, and skip the rest of the loop
590 # just sleep, and skip the rest of the loop
591 time.sleep(self.time_to_dead)
591 time.sleep(self.time_to_dead)
592 continue
592 continue
593
593
594 since_last_heartbeat = 0.0
594 since_last_heartbeat = 0.0
595 # io.rprint('Ping from HB channel') # dbg
595 # io.rprint('Ping from HB channel') # dbg
596 # no need to catch EFSM here, because the previous event was
596 # no need to catch EFSM here, because the previous event was
597 # either a recv or connect, which cannot be followed by EFSM
597 # either a recv or connect, which cannot be followed by EFSM
598 self.socket.send(b'ping')
598 self.socket.send(b'ping')
599 request_time = time.time()
599 request_time = time.time()
600 ready = self._poll(request_time)
600 ready = self._poll(request_time)
601 if ready:
601 if ready:
602 self._beating = True
602 self._beating = True
603 # the poll above guarantees we have something to recv
603 # the poll above guarantees we have something to recv
604 self.socket.recv()
604 self.socket.recv()
605 # sleep the remainder of the cycle
605 # sleep the remainder of the cycle
606 remainder = self.time_to_dead - (time.time() - request_time)
606 remainder = self.time_to_dead - (time.time() - request_time)
607 if remainder > 0:
607 if remainder > 0:
608 time.sleep(remainder)
608 time.sleep(remainder)
609 continue
609 continue
610 else:
610 else:
611 # nothing was received within the time limit, signal heart failure
611 # nothing was received within the time limit, signal heart failure
612 self._beating = False
612 self._beating = False
613 since_last_heartbeat = time.time() - request_time
613 since_last_heartbeat = time.time() - request_time
614 self.call_handlers(since_last_heartbeat)
614 self.call_handlers(since_last_heartbeat)
615 # and close/reopen the socket, because the REQ/REP cycle has been broken
615 # and close/reopen the socket, because the REQ/REP cycle has been broken
616 self._create_socket()
616 self._create_socket()
617 continue
617 continue
618 try:
618 try:
619 self.socket.close()
619 self.socket.close()
620 except:
620 except:
621 pass
621 pass
622
622
623 def pause(self):
623 def pause(self):
624 """Pause the heartbeat."""
624 """Pause the heartbeat."""
625 self._pause = True
625 self._pause = True
626
626
627 def unpause(self):
627 def unpause(self):
628 """Unpause the heartbeat."""
628 """Unpause the heartbeat."""
629 self._pause = False
629 self._pause = False
630
630
631 def is_beating(self):
631 def is_beating(self):
632 """Is the heartbeat running and responsive (and not paused)."""
632 """Is the heartbeat running and responsive (and not paused)."""
633 if self.is_alive() and not self._pause and self._beating:
633 if self.is_alive() and not self._pause and self._beating:
634 return True
634 return True
635 else:
635 else:
636 return False
636 return False
637
637
638 def stop(self):
638 def stop(self):
639 """Stop the channel's event loop and join its thread."""
639 """Stop the channel's event loop and join its thread."""
640 self._running = False
640 self._running = False
641 super(HBChannel, self).stop()
641 super(HBChannel, self).stop()
642
642
643 def call_handlers(self, since_last_heartbeat):
643 def call_handlers(self, since_last_heartbeat):
644 """This method is called in the ioloop thread when a message arrives.
644 """This method is called in the ioloop thread when a message arrives.
645
645
646 Subclasses should override this method to handle incoming messages.
646 Subclasses should override this method to handle incoming messages.
647 It is important to remember that this method is called in the thread
647 It is important to remember that this method is called in the thread
648 so that some logic must be done to ensure that the application level
648 so that some logic must be done to ensure that the application level
649 handlers are called in the application thread.
649 handlers are called in the application thread.
650 """
650 """
651 raise NotImplementedError('call_handlers must be defined in a subclass.')
651 raise NotImplementedError('call_handlers must be defined in a subclass.')
652
652
653
653
654 #-----------------------------------------------------------------------------
654 #-----------------------------------------------------------------------------
655 # Main kernel manager class
655 # Main kernel manager class
656 #-----------------------------------------------------------------------------
656 #-----------------------------------------------------------------------------
657
657
658 class KernelManager(Configurable):
658 class KernelManager(Configurable):
659 """Manages a single kernel on this host along with its channels.
659 """Manages a single kernel on this host along with its channels.
660
660
661 There are four channels associated with each kernel:
661 There are four channels associated with each kernel:
662
662
663 * shell: for request/reply calls to the kernel.
663 * shell: for request/reply calls to the kernel.
664 * iopub: for the kernel to publish results to frontends.
664 * iopub: for the kernel to publish results to frontends.
665 * hb: for monitoring the kernel's heartbeat.
665 * hb: for monitoring the kernel's heartbeat.
666 * stdin: for frontends to reply to raw_input calls in the kernel.
666 * stdin: for frontends to reply to raw_input calls in the kernel.
667
667
668 The usage of the channels that this class manages is optional. It is
668 The usage of the channels that this class manages is optional. It is
669 entirely possible to connect to the kernels directly using ZeroMQ
669 entirely possible to connect to the kernels directly using ZeroMQ
670 sockets. These channels are useful primarily for talking to a kernel
670 sockets. These channels are useful primarily for talking to a kernel
671 whose :class:`KernelManager` is in the same process.
671 whose :class:`KernelManager` is in the same process.
672
672
673 This version manages kernels started using Popen.
673 This version manages kernels started using Popen.
674 """
674 """
675 # The PyZMQ Context to use for communication with the kernel.
675 # The PyZMQ Context to use for communication with the kernel.
676 context = Instance(zmq.Context)
676 context = Instance(zmq.Context)
677 def _context_default(self):
677 def _context_default(self):
678 return zmq.Context.instance()
678 return zmq.Context.instance()
679
679
680 # The Session to use for communication with the kernel.
680 # The Session to use for communication with the kernel.
681 session = Instance(Session)
681 session = Instance(Session)
682 def _session_default(self):
682 def _session_default(self):
683 return Session(config=self.config)
683 return Session(config=self.config)
684
684
685 # The kernel process with which the KernelManager is communicating.
685 # The kernel process with which the KernelManager is communicating.
686 # generally a Popen instance
686 # generally a Popen instance
687 kernel = Any()
687 kernel = Any()
688
688
689 kernel_cmd = List(Unicode, config=True,
689 kernel_cmd = List(Unicode, config=True,
690 help="""The Popen Command to launch the kernel.
690 help="""The Popen Command to launch the kernel.
691 Override this if you have a custom
691 Override this if you have a custom
692 """
692 """
693 )
693 )
694 def _kernel_cmd_changed(self, name, old, new):
694 def _kernel_cmd_changed(self, name, old, new):
695 print 'kernel cmd changed', new
695 print 'kernel cmd changed', new
696 self.ipython_kernel = False
696 self.ipython_kernel = False
697
697
698 ipython_kernel = Bool(True)
698 ipython_kernel = Bool(True)
699
699
700
700
701 # The addresses for the communication channels.
701 # The addresses for the communication channels.
702 connection_file = Unicode('')
702 connection_file = Unicode('')
703
703
704 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
704 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
705
705
706 ip = Unicode(LOCALHOST, config=True,
706 ip = Unicode(LOCALHOST, config=True,
707 help="""Set the kernel\'s IP address [default localhost].
707 help="""Set the kernel\'s IP address [default localhost].
708 If the IP address is something other than localhost, then
708 If the IP address is something other than localhost, then
709 Consoles on other machines will be able to connect
709 Consoles on other machines will be able to connect
710 to the Kernel, so be careful!"""
710 to the Kernel, so be careful!"""
711 )
711 )
712 def _ip_default(self):
712 def _ip_default(self):
713 if self.transport == 'ipc':
713 if self.transport == 'ipc':
714 if self.connection_file:
714 if self.connection_file:
715 return os.path.splitext(self.connection_file)[0] + '-ipc'
715 return os.path.splitext(self.connection_file)[0] + '-ipc'
716 else:
716 else:
717 return 'kernel-ipc'
717 return 'kernel-ipc'
718 else:
718 else:
719 return LOCALHOST
719 return LOCALHOST
720 def _ip_changed(self, name, old, new):
720 def _ip_changed(self, name, old, new):
721 if new == '*':
721 if new == '*':
722 self.ip = '0.0.0.0'
722 self.ip = '0.0.0.0'
723 shell_port = Integer(0)
723 shell_port = Integer(0)
724 iopub_port = Integer(0)
724 iopub_port = Integer(0)
725 stdin_port = Integer(0)
725 stdin_port = Integer(0)
726 hb_port = Integer(0)
726 hb_port = Integer(0)
727
727
728 # The classes to use for the various channels.
728 # The classes to use for the various channels.
729 shell_channel_class = Type(ShellChannel)
729 shell_channel_class = Type(ShellChannel)
730 iopub_channel_class = Type(IOPubChannel)
730 iopub_channel_class = Type(IOPubChannel)
731 stdin_channel_class = Type(StdInChannel)
731 stdin_channel_class = Type(StdInChannel)
732 hb_channel_class = Type(HBChannel)
732 hb_channel_class = Type(HBChannel)
733
733
734 # Protected traits.
734 # Protected traits.
735 _launch_args = Any
735 _launch_args = Any
736 _shell_channel = Any
736 _shell_channel = Any
737 _iopub_channel = Any
737 _iopub_channel = Any
738 _stdin_channel = Any
738 _stdin_channel = Any
739 _hb_channel = Any
739 _hb_channel = Any
740 _connection_file_written=Bool(False)
740 _connection_file_written=Bool(False)
741
741
742 def __del__(self):
742 def __del__(self):
743 self.cleanup_connection_file()
743 self.cleanup_connection_file()
744
744
745 #--------------------------------------------------------------------------
745 #--------------------------------------------------------------------------
746 # Channel management methods:
746 # Channel management methods:
747 #--------------------------------------------------------------------------
747 #--------------------------------------------------------------------------
748
748
749 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
749 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
750 """Starts the channels for this kernel.
750 """Starts the channels for this kernel.
751
751
752 This will create the channels if they do not exist and then start
752 This will create the channels if they do not exist and then start
753 them (their activity runs in a thread). If port numbers of 0 are
753 them (their activity runs in a thread). If port numbers of 0 are
754 being used (random ports) then you must first call
754 being used (random ports) then you must first call
755 :method:`start_kernel`. If the channels have been stopped and you
755 :method:`start_kernel`. If the channels have been stopped and you
756 call this, :class:`RuntimeError` will be raised.
756 call this, :class:`RuntimeError` will be raised.
757 """
757 """
758 if shell:
758 if shell:
759 self.shell_channel.start()
759 self.shell_channel.start()
760 if iopub:
760 if iopub:
761 self.iopub_channel.start()
761 self.iopub_channel.start()
762 if stdin:
762 if stdin:
763 self.stdin_channel.start()
763 self.stdin_channel.start()
764 self.shell_channel.allow_stdin = True
764 self.shell_channel.allow_stdin = True
765 else:
765 else:
766 self.shell_channel.allow_stdin = False
766 self.shell_channel.allow_stdin = False
767 if hb:
767 if hb:
768 self.hb_channel.start()
768 self.hb_channel.start()
769
769
770 def stop_channels(self):
770 def stop_channels(self):
771 """Stops all the running channels for this kernel.
771 """Stops all the running channels for this kernel.
772
772
773 This stops their event loops and joins their threads.
773 This stops their event loops and joins their threads.
774 """
774 """
775 if self.shell_channel.is_alive():
775 if self.shell_channel.is_alive():
776 self.shell_channel.stop()
776 self.shell_channel.stop()
777 if self.iopub_channel.is_alive():
777 if self.iopub_channel.is_alive():
778 self.iopub_channel.stop()
778 self.iopub_channel.stop()
779 if self.stdin_channel.is_alive():
779 if self.stdin_channel.is_alive():
780 self.stdin_channel.stop()
780 self.stdin_channel.stop()
781 if self.hb_channel.is_alive():
781 if self.hb_channel.is_alive():
782 self.hb_channel.stop()
782 self.hb_channel.stop()
783
783
784 @property
784 @property
785 def channels_running(self):
785 def channels_running(self):
786 """Are any of the channels created and running?"""
786 """Are any of the channels created and running?"""
787 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
787 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
788 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
788 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
789
789
790 def _make_url(self, port):
790 def _make_url(self, port):
791 """Make a zmq url with a port.
791 """Make a zmq url with a port.
792
792
793 There are two cases that this handles:
793 There are two cases that this handles:
794
794
795 * tcp: tcp://ip:port
795 * tcp: tcp://ip:port
796 * ipc: ipc://ip-port
796 * ipc: ipc://ip-port
797 """
797 """
798 if self.transport == 'tcp':
798 if self.transport == 'tcp':
799 return "tcp://%s:%i" % (self.ip, port)
799 return "tcp://%s:%i" % (self.ip, port)
800 else:
800 else:
801 return "%s://%s-%s" % (self.transport, self.ip, port)
801 return "%s://%s-%s" % (self.transport, self.ip, port)
802
802
803 @property
803 @property
804 def shell_channel(self):
804 def shell_channel(self):
805 """Get the shell channel object for this kernel."""
805 """Get the shell channel object for this kernel."""
806 if self._shell_channel is None:
806 if self._shell_channel is None:
807 self._shell_channel = self.shell_channel_class(
807 self._shell_channel = self.shell_channel_class(
808 self.context, self.session, self._make_url(self.shell_port)
808 self.context, self.session, self._make_url(self.shell_port)
809 )
809 )
810 return self._shell_channel
810 return self._shell_channel
811
811
812 @property
812 @property
813 def iopub_channel(self):
813 def iopub_channel(self):
814 """Get the iopub channel object for this kernel."""
814 """Get the iopub channel object for this kernel."""
815 if self._iopub_channel is None:
815 if self._iopub_channel is None:
816 self._iopub_channel = self.iopub_channel_class(
816 self._iopub_channel = self.iopub_channel_class(
817 self.context, self.session, self._make_url(self.iopub_port)
817 self.context, self.session, self._make_url(self.iopub_port)
818 )
818 )
819 return self._iopub_channel
819 return self._iopub_channel
820
820
821 @property
821 @property
822 def stdin_channel(self):
822 def stdin_channel(self):
823 """Get the stdin channel object for this kernel."""
823 """Get the stdin channel object for this kernel."""
824 if self._stdin_channel is None:
824 if self._stdin_channel is None:
825 self._stdin_channel = self.stdin_channel_class(
825 self._stdin_channel = self.stdin_channel_class(
826 self.context, self.session, self._make_url(self.stdin_port)
826 self.context, self.session, self._make_url(self.stdin_port)
827 )
827 )
828 return self._stdin_channel
828 return self._stdin_channel
829
829
830 @property
830 @property
831 def hb_channel(self):
831 def hb_channel(self):
832 """Get the hb channel object for this kernel."""
832 """Get the hb channel object for this kernel."""
833 if self._hb_channel is None:
833 if self._hb_channel is None:
834 self._hb_channel = self.hb_channel_class(
834 self._hb_channel = self.hb_channel_class(
835 self.context, self.session, self._make_url(self.hb_port)
835 self.context, self.session, self._make_url(self.hb_port)
836 )
836 )
837 return self._hb_channel
837 return self._hb_channel
838
838
839 #--------------------------------------------------------------------------
839 #--------------------------------------------------------------------------
840 # Connection and ipc file management
840 # Connection and ipc file management
841 #--------------------------------------------------------------------------
841 #--------------------------------------------------------------------------
842
842
843 def cleanup_connection_file(self):
843 def cleanup_connection_file(self):
844 """Cleanup connection file *if we wrote it*
844 """Cleanup connection file *if we wrote it*
845
845
846 Will not raise if the connection file was already removed somehow.
846 Will not raise if the connection file was already removed somehow.
847 """
847 """
848 if self._connection_file_written:
848 if self._connection_file_written:
849 # cleanup connection files on full shutdown of kernel we started
849 # cleanup connection files on full shutdown of kernel we started
850 self._connection_file_written = False
850 self._connection_file_written = False
851 try:
851 try:
852 os.remove(self.connection_file)
852 os.remove(self.connection_file)
853 except (IOError, OSError):
853 except (IOError, OSError):
854 pass
854 pass
855
855
856 def cleanup_ipc_files(self):
856 def cleanup_ipc_files(self):
857 """Cleanup ipc files if we wrote them."""
857 """Cleanup ipc files if we wrote them."""
858 if self.transport != 'ipc':
858 if self.transport != 'ipc':
859 return
859 return
860 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
860 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
861 ipcfile = "%s-%i" % (self.ip, port)
861 ipcfile = "%s-%i" % (self.ip, port)
862 try:
862 try:
863 os.remove(ipcfile)
863 os.remove(ipcfile)
864 except (IOError, OSError):
864 except (IOError, OSError):
865 pass
865 pass
866
866
867 def load_connection_file(self):
867 def load_connection_file(self):
868 """Load connection info from JSON dict in self.connection_file."""
868 """Load connection info from JSON dict in self.connection_file."""
869 with open(self.connection_file) as f:
869 with open(self.connection_file) as f:
870 cfg = json.loads(f.read())
870 cfg = json.loads(f.read())
871
871
872 from pprint import pprint
872 from pprint import pprint
873 pprint(cfg)
873 pprint(cfg)
874 self.transport = cfg.get('transport', 'tcp')
874 self.transport = cfg.get('transport', 'tcp')
875 self.ip = cfg['ip']
875 self.ip = cfg['ip']
876 self.shell_port = cfg['shell_port']
876 self.shell_port = cfg['shell_port']
877 self.stdin_port = cfg['stdin_port']
877 self.stdin_port = cfg['stdin_port']
878 self.iopub_port = cfg['iopub_port']
878 self.iopub_port = cfg['iopub_port']
879 self.hb_port = cfg['hb_port']
879 self.hb_port = cfg['hb_port']
880 self.session.key = str_to_bytes(cfg['key'])
880 self.session.key = str_to_bytes(cfg['key'])
881
881
882 def write_connection_file(self):
882 def write_connection_file(self):
883 """Write connection info to JSON dict in self.connection_file."""
883 """Write connection info to JSON dict in self.connection_file."""
884 if self._connection_file_written:
884 if self._connection_file_written:
885 return
885 return
886 self.connection_file,cfg = write_connection_file(self.connection_file,
886 self.connection_file,cfg = write_connection_file(self.connection_file,
887 transport=self.transport, ip=self.ip, key=self.session.key,
887 transport=self.transport, ip=self.ip, key=self.session.key,
888 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
888 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
889 shell_port=self.shell_port, hb_port=self.hb_port)
889 shell_port=self.shell_port, hb_port=self.hb_port)
890 # write_connection_file also sets default ports:
890 # write_connection_file also sets default ports:
891 self.shell_port = cfg['shell_port']
891 self.shell_port = cfg['shell_port']
892 self.stdin_port = cfg['stdin_port']
892 self.stdin_port = cfg['stdin_port']
893 self.iopub_port = cfg['iopub_port']
893 self.iopub_port = cfg['iopub_port']
894 self.hb_port = cfg['hb_port']
894 self.hb_port = cfg['hb_port']
895
895
896 self._connection_file_written = True
896 self._connection_file_written = True
897
897
898 #--------------------------------------------------------------------------
898 #--------------------------------------------------------------------------
899 # Kernel management
899 # Kernel management
900 #--------------------------------------------------------------------------
900 #--------------------------------------------------------------------------
901
901
902 def format_kernel_cmd(self, **kw):
902 def format_kernel_cmd(self, **kw):
903 """format templated args (e.g. {connection_file})"""
903 """format templated args (e.g. {connection_file})"""
904 if self.kernel_cmd:
904 if self.kernel_cmd:
905 cmd = self.kernel_cmd
905 cmd = self.kernel_cmd
906 else:
906 else:
907 cmd = make_ipkernel_cmd(
907 cmd = make_ipkernel_cmd(
908 'from IPython.kernel.zmq.kernelapp import main; main()',
908 'from IPython.kernel.zmq.kernelapp import main; main()',
909 **kw
909 **kw
910 )
910 )
911 ns = dict(connection_file=self.connection_file)
911 ns = dict(connection_file=self.connection_file)
912 ns.update(self._launch_args)
912 ns.update(self._launch_args)
913 return [ c.format(**ns) for c in cmd ]
913 return [ c.format(**ns) for c in cmd ]
914
914
915 def _launch_kernel(self, kernel_cmd, **kw):
915 def _launch_kernel(self, kernel_cmd, **kw):
916 """actually launch the kernel
916 """actually launch the kernel
917
917
918 override in a subclass to launch kernel subprocesses differently
918 override in a subclass to launch kernel subprocesses differently
919 """
919 """
920 return launch_kernel(kernel_cmd, **kw)
920 return launch_kernel(kernel_cmd, **kw)
921
921
922 def start_kernel(self, **kw):
922 def start_kernel(self, **kw):
923 """Starts a kernel on this host in a separate process.
923 """Starts a kernel on this host in a separate process.
924
924
925 If random ports (port=0) are being used, this method must be called
925 If random ports (port=0) are being used, this method must be called
926 before the channels are created.
926 before the channels are created.
927
927
928 Parameters:
928 Parameters:
929 -----------
929 -----------
930 **kw : optional
930 **kw : optional
931 keyword arguments that are passed down to build the kernel_cmd
931 keyword arguments that are passed down to build the kernel_cmd
932 and launching the kernel (e.g. Popen kwargs).
932 and launching the kernel (e.g. Popen kwargs).
933 """
933 """
934 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
934 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
935 raise RuntimeError("Can only launch a kernel on a local interface. "
935 raise RuntimeError("Can only launch a kernel on a local interface. "
936 "Make sure that the '*_address' attributes are "
936 "Make sure that the '*_address' attributes are "
937 "configured properly. "
937 "configured properly. "
938 "Currently valid addresses are: %s"%LOCAL_IPS
938 "Currently valid addresses are: %s"%LOCAL_IPS
939 )
939 )
940
940
941 # write connection file / get default ports
941 # write connection file / get default ports
942 self.write_connection_file()
942 self.write_connection_file()
943
943
944 # save kwargs for use in restart
944 # save kwargs for use in restart
945 self._launch_args = kw.copy()
945 self._launch_args = kw.copy()
946 # build the Popen cmd
946 # build the Popen cmd
947 kernel_cmd = self.format_kernel_cmd(**kw)
947 kernel_cmd = self.format_kernel_cmd(**kw)
948 # launch the kernel subprocess
948 # launch the kernel subprocess
949 self.kernel = self._launch_kernel(kernel_cmd,
949 self.kernel = self._launch_kernel(kernel_cmd,
950 ipython_kernel=self.ipython_kernel,
950 ipython_kernel=self.ipython_kernel,
951 **kw)
951 **kw)
952
952
953 def shutdown_kernel(self, now=False, restart=False):
953 def shutdown_kernel(self, now=False, restart=False):
954 """Attempts to the stop the kernel process cleanly.
954 """Attempts to the stop the kernel process cleanly.
955
955
956 This attempts to shutdown the kernels cleanly by:
956 This attempts to shutdown the kernels cleanly by:
957
957
958 1. Sending it a shutdown message over the shell channel.
958 1. Sending it a shutdown message over the shell channel.
959 2. If that fails, the kernel is shutdown forcibly by sending it
959 2. If that fails, the kernel is shutdown forcibly by sending it
960 a signal.
960 a signal.
961
961
962 Parameters:
962 Parameters:
963 -----------
963 -----------
964 now : bool
964 now : bool
965 Should the kernel be forcible killed *now*. This skips the
965 Should the kernel be forcible killed *now*. This skips the
966 first, nice shutdown attempt.
966 first, nice shutdown attempt.
967 restart: bool
967 restart: bool
968 Will this kernel be restarted after it is shutdown. When this
968 Will this kernel be restarted after it is shutdown. When this
969 is True, connection files will not be cleaned up.
969 is True, connection files will not be cleaned up.
970 """
970 """
971 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
971 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
972 if sys.platform == 'win32':
972 if sys.platform == 'win32':
973 self._kill_kernel()
973 self._kill_kernel()
974 return
974 return
975
975
976 # Pause the heart beat channel if it exists.
976 # Pause the heart beat channel if it exists.
977 if self._hb_channel is not None:
977 if self._hb_channel is not None:
978 self._hb_channel.pause()
978 self._hb_channel.pause()
979
979
980 if now:
980 if now:
981 if self.has_kernel:
981 if self.has_kernel:
982 self._kill_kernel()
982 self._kill_kernel()
983 else:
983 else:
984 # Don't send any additional kernel kill messages immediately, to give
984 # Don't send any additional kernel kill messages immediately, to give
985 # the kernel a chance to properly execute shutdown actions. Wait for at
985 # the kernel a chance to properly execute shutdown actions. Wait for at
986 # most 1s, checking every 0.1s.
986 # most 1s, checking every 0.1s.
987 self.shell_channel.shutdown(restart=restart)
987 self.shell_channel.shutdown(restart=restart)
988 for i in range(10):
988 for i in range(10):
989 if self.is_alive:
989 if self.is_alive:
990 time.sleep(0.1)
990 time.sleep(0.1)
991 else:
991 else:
992 break
992 break
993 else:
993 else:
994 # OK, we've waited long enough.
994 # OK, we've waited long enough.
995 if self.has_kernel:
995 if self.has_kernel:
996 self._kill_kernel()
996 self._kill_kernel()
997
997
998 if not restart:
998 if not restart:
999 self.cleanup_connection_file()
999 self.cleanup_connection_file()
1000 self.cleanup_ipc_files()
1000 self.cleanup_ipc_files()
1001 else:
1001 else:
1002 self.cleanup_ipc_files()
1002 self.cleanup_ipc_files()
1003
1003
1004 def restart_kernel(self, now=False, **kw):
1004 def restart_kernel(self, now=False, **kw):
1005 """Restarts a kernel with the arguments that were used to launch it.
1005 """Restarts a kernel with the arguments that were used to launch it.
1006
1006
1007 If the old kernel was launched with random ports, the same ports will be
1007 If the old kernel was launched with random ports, the same ports will be
1008 used for the new kernel. The same connection file is used again.
1008 used for the new kernel. The same connection file is used again.
1009
1009
1010 Parameters
1010 Parameters
1011 ----------
1011 ----------
1012 now : bool, optional
1012 now : bool, optional
1013 If True, the kernel is forcefully restarted *immediately*, without
1013 If True, the kernel is forcefully restarted *immediately*, without
1014 having a chance to do any cleanup action. Otherwise the kernel is
1014 having a chance to do any cleanup action. Otherwise the kernel is
1015 given 1s to clean up before a forceful restart is issued.
1015 given 1s to clean up before a forceful restart is issued.
1016
1016
1017 In all cases the kernel is restarted, the only difference is whether
1017 In all cases the kernel is restarted, the only difference is whether
1018 it is given a chance to perform a clean shutdown or not.
1018 it is given a chance to perform a clean shutdown or not.
1019
1019
1020 **kw : optional
1020 **kw : optional
1021 Any options specified here will overwrite those used to launch the
1021 Any options specified here will overwrite those used to launch the
1022 kernel.
1022 kernel.
1023 """
1023 """
1024 if self._launch_args is None:
1024 if self._launch_args is None:
1025 raise RuntimeError("Cannot restart the kernel. "
1025 raise RuntimeError("Cannot restart the kernel. "
1026 "No previous call to 'start_kernel'.")
1026 "No previous call to 'start_kernel'.")
1027 else:
1027 else:
1028 # Stop currently running kernel.
1028 # Stop currently running kernel.
1029 self.shutdown_kernel(now=now, restart=True)
1029 self.shutdown_kernel(now=now, restart=True)
1030
1030
1031 # Start new kernel.
1031 # Start new kernel.
1032 self._launch_args.update(kw)
1032 self._launch_args.update(kw)
1033 self.start_kernel(**self._launch_args)
1033 self.start_kernel(**self._launch_args)
1034
1034
1035 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
1035 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
1036 # unless there is some delay here.
1036 # unless there is some delay here.
1037 if sys.platform == 'win32':
1037 if sys.platform == 'win32':
1038 time.sleep(0.2)
1038 time.sleep(0.2)
1039
1039
1040 @property
1040 @property
1041 def has_kernel(self):
1041 def has_kernel(self):
1042 """Has a kernel been started that we are managing."""
1042 """Has a kernel been started that we are managing."""
1043 return self.kernel is not None
1043 return self.kernel is not None
1044
1044
1045 def _kill_kernel(self):
1045 def _kill_kernel(self):
1046 """Kill the running kernel.
1046 """Kill the running kernel.
1047
1047
1048 This is a private method, callers should use shutdown_kernel(now=True).
1048 This is a private method, callers should use shutdown_kernel(now=True).
1049 """
1049 """
1050 if self.has_kernel:
1050 if self.has_kernel:
1051 # Pause the heart beat channel if it exists.
1051 # Pause the heart beat channel if it exists.
1052 if self._hb_channel is not None:
1052 if self._hb_channel is not None:
1053 self._hb_channel.pause()
1053 self._hb_channel.pause()
1054
1054
1055 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1055 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1056 # TerminateProcess() on Win32).
1056 # TerminateProcess() on Win32).
1057 try:
1057 try:
1058 self.kernel.kill()
1058 self.kernel.kill()
1059 except OSError as e:
1059 except OSError as e:
1060 # In Windows, we will get an Access Denied error if the process
1060 # In Windows, we will get an Access Denied error if the process
1061 # has already terminated. Ignore it.
1061 # has already terminated. Ignore it.
1062 if sys.platform == 'win32':
1062 if sys.platform == 'win32':
1063 if e.winerror != 5:
1063 if e.winerror != 5:
1064 raise
1064 raise
1065 # On Unix, we may get an ESRCH error if the process has already
1065 # On Unix, we may get an ESRCH error if the process has already
1066 # terminated. Ignore it.
1066 # terminated. Ignore it.
1067 else:
1067 else:
1068 from errno import ESRCH
1068 from errno import ESRCH
1069 if e.errno != ESRCH:
1069 if e.errno != ESRCH:
1070 raise
1070 raise
1071
1071
1072 # Block until the kernel terminates.
1072 # Block until the kernel terminates.
1073 self.kernel.wait()
1073 self.kernel.wait()
1074 self.kernel = None
1074 self.kernel = None
1075 else:
1075 else:
1076 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1076 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1077
1077
1078 def interrupt_kernel(self):
1078 def interrupt_kernel(self):
1079 """Interrupts the kernel by sending it a signal.
1079 """Interrupts the kernel by sending it a signal.
1080
1080
1081 Unlike ``signal_kernel``, this operation is well supported on all
1081 Unlike ``signal_kernel``, this operation is well supported on all
1082 platforms.
1082 platforms.
1083 """
1083 """
1084 if self.has_kernel:
1084 if self.has_kernel:
1085 if sys.platform == 'win32':
1085 if sys.platform == 'win32':
1086 from parentpoller import ParentPollerWindows as Poller
1086 from parentpoller import ParentPollerWindows as Poller
1087 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1087 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1088 else:
1088 else:
1089 self.kernel.send_signal(signal.SIGINT)
1089 self.kernel.send_signal(signal.SIGINT)
1090 else:
1090 else:
1091 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1091 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1092
1092
1093 def signal_kernel(self, signum):
1093 def signal_kernel(self, signum):
1094 """Sends a signal to the kernel.
1094 """Sends a signal to the kernel.
1095
1095
1096 Note that since only SIGTERM is supported on Windows, this function is
1096 Note that since only SIGTERM is supported on Windows, this function is
1097 only useful on Unix systems.
1097 only useful on Unix systems.
1098 """
1098 """
1099 if self.has_kernel:
1099 if self.has_kernel:
1100 self.kernel.send_signal(signum)
1100 self.kernel.send_signal(signum)
1101 else:
1101 else:
1102 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1102 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1103
1103
1104 @property
1104 @property
1105 def is_alive(self):
1105 def is_alive(self):
1106 """Is the kernel process still running?"""
1106 """Is the kernel process still running?"""
1107 if self.has_kernel:
1107 if self.has_kernel:
1108 if self.kernel.poll() is None:
1108 if self.kernel.poll() is None:
1109 return True
1109 return True
1110 else:
1110 else:
1111 return False
1111 return False
1112 elif self._hb_channel is not None:
1112 elif self._hb_channel is not None:
1113 # We didn't start the kernel with this KernelManager so we
1113 # We didn't start the kernel with this KernelManager so we
1114 # use the heartbeat.
1114 # use the heartbeat.
1115 return self._hb_channel.is_beating()
1115 return self._hb_channel.is_beating()
1116 else:
1116 else:
1117 # no heartbeat and not local, we can't tell if it's running,
1117 # no heartbeat and not local, we can't tell if it's running,
1118 # so naively return True
1118 # so naively return True
1119 return True
1119 return True
1120
1120
1121
1121
1122 #-----------------------------------------------------------------------------
1122 #-----------------------------------------------------------------------------
1123 # ABC Registration
1123 # ABC Registration
1124 #-----------------------------------------------------------------------------
1124 #-----------------------------------------------------------------------------
1125
1125
1126 ShellChannelABC.register(ShellChannel)
1126 ShellChannelABC.register(ShellChannel)
1127 IOPubChannelABC.register(IOPubChannel)
1127 IOPubChannelABC.register(IOPubChannel)
1128 HBChannelABC.register(HBChannel)
1128 HBChannelABC.register(HBChannel)
1129 StdInChannelABC.register(StdInChannel)
1129 StdInChannelABC.register(StdInChannel)
1130 KernelManagerABC.register(KernelManager)
1130 KernelManagerABC.register(KernelManager)
1131
1131
@@ -1,253 +1,258 b''
1 """Utilities for launching kernels
1 """Utilities for launching 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 os
20 import os
21 import sys
21 import sys
22 from subprocess import Popen, PIPE
22 from subprocess import Popen, PIPE
23
23
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Launching Kernels
26 # Launching Kernels
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 def swallow_argv(argv, aliases=None, flags=None):
29 def swallow_argv(argv, aliases=None, flags=None):
30 """strip frontend-specific aliases and flags from an argument list
30 """strip frontend-specific aliases and flags from an argument list
31
31
32 For use primarily in frontend apps that want to pass a subset of command-line
32 For use primarily in frontend apps that want to pass a subset of command-line
33 arguments through to a subprocess, where frontend-specific flags and aliases
33 arguments through to a subprocess, where frontend-specific flags and aliases
34 should be removed from the list.
34 should be removed from the list.
35
35
36 Parameters
36 Parameters
37 ----------
37 ----------
38
38
39 argv : list(str)
39 argv : list(str)
40 The starting argv, to be filtered
40 The starting argv, to be filtered
41 aliases : container of aliases (dict, list, set, etc.)
41 aliases : container of aliases (dict, list, set, etc.)
42 The frontend-specific aliases to be removed
42 The frontend-specific aliases to be removed
43 flags : container of flags (dict, list, set, etc.)
43 flags : container of flags (dict, list, set, etc.)
44 The frontend-specific flags to be removed
44 The frontend-specific flags to be removed
45
45
46 Returns
46 Returns
47 -------
47 -------
48
48
49 argv : list(str)
49 argv : list(str)
50 The argv list, excluding flags and aliases that have been stripped
50 The argv list, excluding flags and aliases that have been stripped
51 """
51 """
52
52
53 if aliases is None:
53 if aliases is None:
54 aliases = set()
54 aliases = set()
55 if flags is None:
55 if flags is None:
56 flags = set()
56 flags = set()
57
57
58 stripped = list(argv) # copy
58 stripped = list(argv) # copy
59
59
60 swallow_next = False
60 swallow_next = False
61 was_flag = False
61 was_flag = False
62 for a in argv:
62 for a in argv:
63 if swallow_next:
63 if swallow_next:
64 swallow_next = False
64 swallow_next = False
65 # last arg was an alias, remove the next one
65 # last arg was an alias, remove the next one
66 # *unless* the last alias has a no-arg flag version, in which
66 # *unless* the last alias has a no-arg flag version, in which
67 # case, don't swallow the next arg if it's also a flag:
67 # case, don't swallow the next arg if it's also a flag:
68 if not (was_flag and a.startswith('-')):
68 if not (was_flag and a.startswith('-')):
69 stripped.remove(a)
69 stripped.remove(a)
70 continue
70 continue
71 if a.startswith('-'):
71 if a.startswith('-'):
72 split = a.lstrip('-').split('=')
72 split = a.lstrip('-').split('=')
73 alias = split[0]
73 alias = split[0]
74 if alias in aliases:
74 if alias in aliases:
75 stripped.remove(a)
75 stripped.remove(a)
76 if len(split) == 1:
76 if len(split) == 1:
77 # alias passed with arg via space
77 # alias passed with arg via space
78 swallow_next = True
78 swallow_next = True
79 # could have been a flag that matches an alias, e.g. `existing`
79 # could have been a flag that matches an alias, e.g. `existing`
80 # in which case, we might not swallow the next arg
80 # in which case, we might not swallow the next arg
81 was_flag = alias in flags
81 was_flag = alias in flags
82 elif alias in flags and len(split) == 1:
82 elif alias in flags and len(split) == 1:
83 # strip flag, but don't swallow next, as flags don't take args
83 # strip flag, but don't swallow next, as flags don't take args
84 stripped.remove(a)
84 stripped.remove(a)
85
85
86 # return shortened list
86 # return shortened list
87 return stripped
87 return stripped
88
88
89
89
90 def make_ipkernel_cmd(code, executable=None, extra_arguments=[], **kw):
90 def make_ipkernel_cmd(code, executable=None, extra_arguments=[], **kw):
91 """Build Popen command list for launching an IPython kernel.
91 """Build Popen command list for launching an IPython kernel.
92
92
93 Parameters
93 Parameters
94 ----------
94 ----------
95 code : str,
95 code : str,
96 A string of Python code that imports and executes a kernel entry point.
96 A string of Python code that imports and executes a kernel entry point.
97
97
98 executable : str, optional (default sys.executable)
98 executable : str, optional (default sys.executable)
99 The Python executable to use for the kernel process.
99 The Python executable to use for the kernel process.
100
100
101 extra_arguments : list, optional
101 extra_arguments : list, optional
102 A list of extra arguments to pass when executing the launch code.
102 A list of extra arguments to pass when executing the launch code.
103
103
104 Returns
104 Returns
105 -------
105 -------
106
106
107 A Popen command list
107 A Popen command list
108 """
108 """
109
109
110 # Build the kernel launch command.
110 # Build the kernel launch command.
111 if executable is None:
111 if executable is None:
112 executable = sys.executable
112 executable = sys.executable
113 arguments = [ executable, '-c', code, '-f', '{connection_file}' ]
113 arguments = [ executable, '-c', code, '-f', '{connection_file}' ]
114 arguments.extend(extra_arguments)
114 arguments.extend(extra_arguments)
115
115
116 # Spawn a kernel.
116 # Spawn a kernel.
117 if sys.platform == 'win32':
117 if sys.platform == 'win32':
118
118
119 # If the kernel is running on pythonw and stdout/stderr are not been
119 # If the kernel is running on pythonw and stdout/stderr are not been
120 # re-directed, it will crash when more than 4KB of data is written to
120 # re-directed, it will crash when more than 4KB of data is written to
121 # stdout or stderr. This is a bug that has been with Python for a very
121 # stdout or stderr. This is a bug that has been with Python for a very
122 # long time; see http://bugs.python.org/issue706263.
122 # long time; see http://bugs.python.org/issue706263.
123 # A cleaner solution to this problem would be to pass os.devnull to
123 # A cleaner solution to this problem would be to pass os.devnull to
124 # Popen directly. Unfortunately, that does not work.
124 # Popen directly. Unfortunately, that does not work.
125 if executable.endswith('pythonw.exe'):
125 if executable.endswith('pythonw.exe'):
126 arguments.append('--no-stdout')
126 arguments.append('--no-stdout')
127 arguments.append('--no-stderr')
127 arguments.append('--no-stderr')
128
128
129 return arguments
129 return arguments
130
130
131
131
132 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None,
132 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None,
133 independent=False,
133 independent=False,
134 cwd=None, ipython_kernel=True,
134 cwd=None, ipython_kernel=True,
135 **kw
135 **kw
136 ):
136 ):
137 """ Launches a localhost kernel, binding to the specified ports.
137 """ Launches a localhost kernel, binding to the specified ports.
138
138
139 Parameters
139 Parameters
140 ----------
140 ----------
141 cmd : Popen list,
141 cmd : Popen list,
142 A string of Python code that imports and executes a kernel entry point.
142 A string of Python code that imports and executes a kernel entry point.
143
143
144 stdin, stdout, stderr : optional (default None)
144 stdin, stdout, stderr : optional (default None)
145 Standards streams, as defined in subprocess.Popen.
145 Standards streams, as defined in subprocess.Popen.
146
146
147 independent : bool, optional (default False)
147 independent : bool, optional (default False)
148 If set, the kernel process is guaranteed to survive if this process
148 If set, the kernel process is guaranteed to survive if this process
149 dies. If not set, an effort is made to ensure that the kernel is killed
149 dies. If not set, an effort is made to ensure that the kernel is killed
150 when this process dies. Note that in this case it is still good practice
150 when this process dies. Note that in this case it is still good practice
151 to kill kernels manually before exiting.
151 to kill kernels manually before exiting.
152
152
153 cwd : path, optional
153 cwd : path, optional
154 The working dir of the kernel process (default: cwd of this process).
154 The working dir of the kernel process (default: cwd of this process).
155
155
156 ipython_kernel : bool, optional
156 ipython_kernel : bool, optional
157 Whether the kernel is an official IPython one,
157 Whether the kernel is an official IPython one,
158 and should get a bit of special treatment.
158 and should get a bit of special treatment.
159
159
160 Returns
160 Returns
161 -------
161 -------
162
162
163 Popen instance for the kernel subprocess
163 Popen instance for the kernel subprocess
164 """
164 """
165
165
166 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
166 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
167 # are invalid. Unfortunately, there is in general no way to detect whether
167 # are invalid. Unfortunately, there is in general no way to detect whether
168 # they are valid. The following two blocks redirect them to (temporary)
168 # they are valid. The following two blocks redirect them to (temporary)
169 # pipes in certain important cases.
169 # pipes in certain important cases.
170
170
171 # If this process has been backgrounded, our stdin is invalid. Since there
171 # If this process has been backgrounded, our stdin is invalid. Since there
172 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
172 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
173 # place this one safe and always redirect.
173 # place this one safe and always redirect.
174 redirect_in = True
174 redirect_in = True
175 _stdin = PIPE if stdin is None else stdin
175 _stdin = PIPE if stdin is None else stdin
176
176
177 # If this process in running on pythonw, we know that stdin, stdout, and
177 # If this process in running on pythonw, we know that stdin, stdout, and
178 # stderr are all invalid.
178 # stderr are all invalid.
179 redirect_out = sys.executable.endswith('pythonw.exe')
179 redirect_out = sys.executable.endswith('pythonw.exe')
180 if redirect_out:
180 if redirect_out:
181 _stdout = PIPE if stdout is None else stdout
181 _stdout = PIPE if stdout is None else stdout
182 _stderr = PIPE if stderr is None else stderr
182 _stderr = PIPE if stderr is None else stderr
183 else:
183 else:
184 _stdout, _stderr = stdout, stderr
184 _stdout, _stderr = stdout, stderr
185
185
186 # Spawn a kernel.
186 # Spawn a kernel.
187 if sys.platform == 'win32':
187 if sys.platform == 'win32':
188 from IPython.kernel.zmq.parentpoller import ParentPollerWindows
188 from IPython.kernel.zmq.parentpoller import ParentPollerWindows
189 # Create a Win32 event for interrupting the kernel.
189 # Create a Win32 event for interrupting the kernel.
190 interrupt_event = ParentPollerWindows.create_interrupt_event()
190 interrupt_event = ParentPollerWindows.create_interrupt_event()
191 if ipython_kernel:
191 if ipython_kernel:
192 cmd += [ '--interrupt=%i' % interrupt_event ]
192 cmd += [ '--interrupt=%i' % interrupt_event ]
193
193
194 # If the kernel is running on pythonw and stdout/stderr are not been
194 # If the kernel is running on pythonw and stdout/stderr are not been
195 # re-directed, it will crash when more than 4KB of data is written to
195 # re-directed, it will crash when more than 4KB of data is written to
196 # stdout or stderr. This is a bug that has been with Python for a very
196 # stdout or stderr. This is a bug that has been with Python for a very
197 # long time; see http://bugs.python.org/issue706263.
197 # long time; see http://bugs.python.org/issue706263.
198 # A cleaner solution to this problem would be to pass os.devnull to
198 # A cleaner solution to this problem would be to pass os.devnull to
199 # Popen directly. Unfortunately, that does not work.
199 # Popen directly. Unfortunately, that does not work.
200 if cmd[0].endswith('pythonw.exe'):
200 if cmd[0].endswith('pythonw.exe'):
201 if stdout is None:
201 if stdout is None:
202 cmd.append('--no-stdout')
202 cmd.append('--no-stdout')
203 if stderr is None:
203 if stderr is None:
204 cmd.append('--no-stderr')
204 cmd.append('--no-stderr')
205
205
206 # Launch the kernel process.
206 # Launch the kernel process.
207 if independent:
207 if independent:
208 proc = Popen(cmd,
208 proc = Popen(cmd,
209 creationflags=512, # CREATE_NEW_PROCESS_GROUP
209 creationflags=512, # CREATE_NEW_PROCESS_GROUP
210 stdin=_stdin, stdout=_stdout, stderr=_stderr)
210 stdin=_stdin, stdout=_stdout, stderr=_stderr)
211 else:
211 else:
212 if ipython_kernel:
212 if ipython_kernel:
213 try:
213 try:
214 from _winapi import DuplicateHandle, GetCurrentProcess, \
214 from _winapi import DuplicateHandle, GetCurrentProcess, \
215 DUPLICATE_SAME_ACCESS
215 DUPLICATE_SAME_ACCESS
216 except:
216 except:
217 from _subprocess import DuplicateHandle, GetCurrentProcess, \
217 from _subprocess import DuplicateHandle, GetCurrentProcess, \
218 DUPLICATE_SAME_ACCESS
218 DUPLICATE_SAME_ACCESS
219 pid = GetCurrentProcess()
219 pid = GetCurrentProcess()
220 handle = DuplicateHandle(pid, pid, pid, 0,
220 handle = DuplicateHandle(pid, pid, pid, 0,
221 True, # Inheritable by new processes.
221 True, # Inheritable by new processes.
222 DUPLICATE_SAME_ACCESS)
222 DUPLICATE_SAME_ACCESS)
223 cmd +=[ '--parent=%i' % handle ]
223 cmd +=[ '--parent=%i' % handle ]
224
224
225
225
226 proc = Popen(cmd,
226 proc = Popen(cmd,
227 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
227 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
228
228
229 # Attach the interrupt event to the Popen objet so it can be used later.
229 # Attach the interrupt event to the Popen objet so it can be used later.
230 proc.win32_interrupt_event = interrupt_event
230 proc.win32_interrupt_event = interrupt_event
231
231
232 else:
232 else:
233 if independent:
233 if independent:
234 proc = Popen(cmd, preexec_fn=lambda: os.setsid(),
234 proc = Popen(cmd, preexec_fn=lambda: os.setsid(),
235 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
235 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
236 else:
236 else:
237 if ipython_kernel:
237 if ipython_kernel:
238 cmd += ['--parent=1']
238 cmd += ['--parent=1']
239 proc = Popen(cmd,
239 proc = Popen(cmd,
240 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
240 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd)
241
241
242 # Clean up pipes created to work around Popen bug.
242 # Clean up pipes created to work around Popen bug.
243 if redirect_in:
243 if redirect_in:
244 if stdin is None:
244 if stdin is None:
245 proc.stdin.close()
245 proc.stdin.close()
246 if redirect_out:
246 if redirect_out:
247 if stdout is None:
247 if stdout is None:
248 proc.stdout.close()
248 proc.stdout.close()
249 if stderr is None:
249 if stderr is None:
250 proc.stderr.close()
250 proc.stderr.close()
251
251
252 return proc
252 return proc
253
253
254 __all__ = [
255 'swallow_argv',
256 'make_ipkernel_cmd',
257 'launch_kernel',
258 ] No newline at end of file
@@ -1,48 +1,48 b''
1 """Tests for the notebook kernel and session manager"""
1 """Tests for the notebook kernel and session manager"""
2
2
3 from subprocess import PIPE
3 from subprocess import PIPE
4 import time
4 import time
5 from unittest import TestCase
5 from unittest import TestCase
6
6
7 from IPython.testing import decorators as dec
7 from IPython.testing import decorators as dec
8
8
9 from IPython.config.loader import Config
9 from IPython.config.loader import Config
10 from IPython.kernel.kernelmanager import KernelManager
10 from IPython.kernel.kernelmanager import KernelManager
11
11
12 class TestKernelManager(TestCase):
12 class TestKernelManager(TestCase):
13
13
14 def _get_tcp_km(self):
14 def _get_tcp_km(self):
15 return KernelManager()
15 return KernelManager()
16
16
17 def _get_ipc_km(self):
17 def _get_ipc_km(self):
18 c = Config()
18 c = Config()
19 c.KernelManager.transport = 'ipc'
19 c.KernelManager.transport = 'ipc'
20 c.KernelManager.ip = 'test'
20 c.KernelManager.ip = 'test'
21 km = KernelManager(config=c)
21 km = KernelManager(config=c)
22 return km
22 return km
23
23
24 def _run_lifecycle(self, km):
24 def _run_lifecycle(self, km):
25 km.start_kernel(stdout=PIPE, stderr=PIPE)
25 km.start_kernel(stdout=PIPE, stderr=PIPE)
26 km.start_channels(shell=True, iopub=False, stdin=False, hb=False)
26 km.start_channels(shell=True, iopub=False, stdin=False, hb=False)
27 km.restart_kernel()
27 km.restart_kernel()
28 # We need a delay here to give the restarting kernel a chance to
28 # We need a delay here to give the restarting kernel a chance to
29 # restart. Otherwise, the interrupt will kill it, causing the test
29 # restart. Otherwise, the interrupt will kill it, causing the test
30 # suite to hang. The reason it *hangs* is that the shutdown
30 # suite to hang. The reason it *hangs* is that the shutdown
31 # message for the restart sometimes hasn't been sent to the kernel.
31 # message for the restart sometimes hasn't been sent to the kernel.
32 # Because linger is oo on the shell channel, the context can't
32 # Because linger is oo on the shell channel, the context can't
33 # close until the message is sent to the kernel, which is not dead.
33 # close until the message is sent to the kernel, which is not dead.
34 time.sleep(1.0)
34 time.sleep(1.0)
35 km.interrupt_kernel()
35 km.interrupt_kernel()
36 self.assertTrue(isinstance(km, KernelManager))
36 self.assertTrue(isinstance(km, KernelManager))
37 km.shutdown_kernel()
37 km.shutdown_kernel()
38 km.shell_channel.stop()
38 km.shell_channel.stop()
39
39
40 def test_tcp_lifecycle(self):
40 def test_tcp_lifecycle(self):
41 km = self._get_tcp_km()
41 km = self._get_tcp_km()
42 self._run_lifecycle(km)
42 self._run_lifecycle(km)
43
43
44 @dec.skip_win32
44 @dec.skip_win32
45 def testipc_lifecycle(self):
45 def test_ipc_lifecycle(self):
46 km = self._get_ipc_km()
46 km = self._get_ipc_km()
47 self._run_lifecycle(km)
47 self._run_lifecycle(km)
48
48
General Comments 0
You need to be logged in to leave comments. Login now