##// END OF EJS Templates
Merge pull request #5598 from takluyver/kernelspec...
Min RK -
r16479:52f7cd40 merge
parent child Browse files
Show More
@@ -0,0 +1,142 b''
1 import io
2 import json
3 import os
4 import sys
5
6 pjoin = os.path.join
7
8 from IPython.utils.path import get_ipython_dir
9 from IPython.utils.py3compat import PY3
10 from IPython.utils.traitlets import HasTraits, List, Unicode, Dict
11
12 if os.name == 'nt':
13 programdata = os.environ.get('PROGRAMDATA', None)
14 if programdata:
15 SYSTEM_KERNEL_DIR = pjoin(programdata, 'ipython', 'kernels')
16 else: # PROGRAMDATA is not defined by default on XP.
17 SYSTEM_KERNEL_DIR = None
18 else:
19 SYSTEM_KERNEL_DIR = "/usr/share/ipython/kernels"
20
21 NATIVE_KERNEL_NAME = 'python3' if PY3 else 'python2'
22
23 class KernelSpec(HasTraits):
24 argv = List()
25 display_name = Unicode()
26 language = Unicode()
27 codemirror_mode = None
28 env = Dict()
29
30 resource_dir = Unicode()
31
32 def __init__(self, resource_dir, argv, display_name, language,
33 codemirror_mode=None):
34 super(KernelSpec, self).__init__(resource_dir=resource_dir, argv=argv,
35 display_name=display_name, language=language,
36 codemirror_mode=codemirror_mode)
37 if not self.codemirror_mode:
38 self.codemirror_mode = self.language
39
40 @classmethod
41 def from_resource_dir(cls, resource_dir):
42 """Create a KernelSpec object by reading kernel.json
43
44 Pass the path to the *directory* containing kernel.json.
45 """
46 kernel_file = pjoin(resource_dir, 'kernel.json')
47 with io.open(kernel_file, 'r', encoding='utf-8') as f:
48 kernel_dict = json.load(f)
49 return cls(resource_dir=resource_dir, **kernel_dict)
50
51 def _is_kernel_dir(path):
52 """Is ``path`` a kernel directory?"""
53 return os.path.isdir(path) and os.path.isfile(pjoin(path, 'kernel.json'))
54
55 def _list_kernels_in(dir):
56 """Return a mapping of kernel names to resource directories from dir.
57
58 If dir is None or does not exist, returns an empty dict.
59 """
60 if dir is None or not os.path.isdir(dir):
61 return {}
62 return {f.lower(): pjoin(dir, f) for f in os.listdir(dir)
63 if _is_kernel_dir(pjoin(dir, f))}
64
65 class NoSuchKernel(KeyError):
66 def __init__(self, name):
67 self.name = name
68
69 class KernelSpecManager(HasTraits):
70 ipython_dir = Unicode()
71 def _ipython_dir_default(self):
72 return get_ipython_dir()
73
74 user_kernel_dir = Unicode()
75 def _user_kernel_dir_default(self):
76 return pjoin(self.ipython_dir, 'kernels')
77
78 kernel_dirs = List(
79 help="List of kernel directories to search. Later ones take priority over earlier."
80 )
81 def _kernel_dirs_default(self):
82 return [
83 SYSTEM_KERNEL_DIR,
84 self.user_kernel_dir,
85 ]
86
87 def _make_native_kernel_dir(self):
88 """Makes a kernel directory for the native kernel.
89
90 The native kernel is the kernel using the same Python runtime as this
91 process. This will put its informatino in the user kernels directory.
92 """
93 path = pjoin(self.user_kernel_dir, NATIVE_KERNEL_NAME)
94 os.makedirs(path, mode=0o755)
95 with open(pjoin(path, 'kernel.json'), 'w') as f:
96 json.dump({'argv':[NATIVE_KERNEL_NAME, '-c',
97 'from IPython.kernel.zmq.kernelapp import main; main()',
98 '-f', '{connection_file}'],
99 'display_name': 'Python 3' if PY3 else 'Python 2',
100 'language': 'python',
101 'codemirror_mode': {'name': 'python',
102 'version': sys.version_info[0]},
103 },
104 f, indent=1)
105 # TODO: Copy icons into directory
106 return path
107
108 def find_kernel_specs(self):
109 """Returns a dict mapping kernel names to resource directories."""
110 d = {}
111 for kernel_dir in self.kernel_dirs:
112 d.update(_list_kernels_in(kernel_dir))
113
114 if NATIVE_KERNEL_NAME not in d:
115 d[NATIVE_KERNEL_NAME] = self._make_native_kernel_dir()
116 return d
117 # TODO: Caching?
118
119 def get_kernel_spec(self, kernel_name):
120 """Returns a :class:`KernelSpec` instance for the given kernel_name.
121
122 Raises :exc:`NoSuchKernel` if the given kernel name is not found.
123 """
124 if kernel_name == 'python':
125 kernel_name = NATIVE_KERNEL_NAME
126 d = self.find_kernel_specs()
127 try:
128 resource_dir = d[kernel_name.lower()]
129 except KeyError:
130 raise NoSuchKernel(kernel_name)
131 return KernelSpec.from_resource_dir(resource_dir)
132
133 def find_kernel_specs():
134 """Returns a dict mapping kernel names to resource directories."""
135 return KernelSpecManager().find_kernel_specs()
136
137 def get_kernel_spec(kernel_name):
138 """Returns a :class:`KernelSpec` instance for the given kernel_name.
139
140 Raises KeyError if the given kernel name is not found.
141 """
142 return KernelSpecManager().get_kernel_spec(kernel_name) No newline at end of file
@@ -0,0 +1,39 b''
1 import json
2 import os
3 from os.path import join as pjoin
4 import unittest
5
6 from IPython.utils.tempdir import TemporaryDirectory
7 from IPython.kernel import kernelspec
8
9 sample_kernel_json = {'argv':['cat', '{connection_file}'],
10 'display_name':'Test kernel',
11 'language':'bash',
12 }
13
14 class KernelSpecTests(unittest.TestCase):
15 def setUp(self):
16 self.tempdir = td = TemporaryDirectory()
17 self.sample_kernel_dir = pjoin(td.name, 'kernels', 'Sample')
18 os.makedirs(self.sample_kernel_dir)
19 json_file = pjoin(self.sample_kernel_dir, 'kernel.json')
20 with open(json_file, 'w') as f:
21 json.dump(sample_kernel_json, f)
22
23 self.ksm = kernelspec.KernelSpecManager(ipython_dir=td.name)
24
25 def tearDown(self):
26 self.tempdir.cleanup()
27
28 def test_find_kernel_specs(self):
29 kernels = self.ksm.find_kernel_specs()
30 self.assertEqual(kernels['sample'], self.sample_kernel_dir)
31
32 def test_get_kernel_spec(self):
33 ks = self.ksm.get_kernel_spec('SAMPLE') # Case insensitive
34 self.assertEqual(ks.resource_dir, self.sample_kernel_dir)
35 self.assertEqual(ks.argv, sample_kernel_json['argv'])
36 self.assertEqual(ks.display_name, sample_kernel_json['display_name'])
37 self.assertEqual(ks.language, sample_kernel_json['language'])
38 self.assertEqual(ks.codemirror_mode, sample_kernel_json['language'])
39 self.assertEqual(ks.env, {}) No newline at end of file
@@ -35,6 +35,7 b' from IPython.core.profiledir import ProfileDir'
35 from IPython.kernel.blocking import BlockingKernelClient
35 from IPython.kernel.blocking import BlockingKernelClient
36 from IPython.kernel import KernelManager
36 from IPython.kernel import KernelManager
37 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
37 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
38 from IPython.kernel.kernelspec import NoSuchKernel
38 from IPython.utils.path import filefind
39 from IPython.utils.path import filefind
39 from IPython.utils.py3compat import str_to_bytes
40 from IPython.utils.py3compat import str_to_bytes
40 from IPython.utils.traitlets import (
41 from IPython.utils.traitlets import (
@@ -98,6 +99,7 b' app_aliases = dict('
98 existing = 'IPythonConsoleApp.existing',
99 existing = 'IPythonConsoleApp.existing',
99 f = 'IPythonConsoleApp.connection_file',
100 f = 'IPythonConsoleApp.connection_file',
100
101
102 kernel = 'IPythonConsoleApp.kernel_name',
101
103
102 ssh = 'IPythonConsoleApp.sshserver',
104 ssh = 'IPythonConsoleApp.sshserver',
103 )
105 )
@@ -174,6 +176,9 b' class IPythonConsoleApp(ConnectionFileMixin):'
174 existing = CUnicode('', config=True,
176 existing = CUnicode('', config=True,
175 help="""Connect to an already running kernel""")
177 help="""Connect to an already running kernel""")
176
178
179 kernel_name = Unicode('python', config=True,
180 help="""The name of the default kernel to start.""")
181
177 confirm_exit = CBool(True, config=True,
182 confirm_exit = CBool(True, config=True,
178 help="""
183 help="""
179 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
184 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
@@ -327,16 +332,23 b' class IPythonConsoleApp(ConnectionFileMixin):'
327 signal.signal(signal.SIGINT, signal.SIG_DFL)
332 signal.signal(signal.SIGINT, signal.SIG_DFL)
328
333
329 # Create a KernelManager and start a kernel.
334 # Create a KernelManager and start a kernel.
330 self.kernel_manager = self.kernel_manager_class(
335 try:
331 ip=self.ip,
336 self.kernel_manager = self.kernel_manager_class(
332 transport=self.transport,
337 ip=self.ip,
333 shell_port=self.shell_port,
338 transport=self.transport,
334 iopub_port=self.iopub_port,
339 shell_port=self.shell_port,
335 stdin_port=self.stdin_port,
340 iopub_port=self.iopub_port,
336 hb_port=self.hb_port,
341 stdin_port=self.stdin_port,
337 connection_file=self.connection_file,
342 hb_port=self.hb_port,
338 parent=self,
343 connection_file=self.connection_file,
339 )
344 kernel_name=self.kernel_name,
345 parent=self,
346 ipython_dir=self.ipython_dir,
347 )
348 except NoSuchKernel:
349 self.log.critical("Could not find kernel %s", self.kernel_name)
350 self.exit(1)
351
340 self.kernel_manager.client_factory = self.kernel_client_class
352 self.kernel_manager.client_factory = self.kernel_client_class
341 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
353 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
342 atexit.register(self.kernel_manager.cleanup_ipc_files)
354 atexit.register(self.kernel_manager.cleanup_ipc_files)
@@ -1,21 +1,8 b''
1 """Utilities for launching kernels
1 """Utilities for launching kernels
2
3 Authors:
4
5 * Min Ragan-Kelley
6
7 """
2 """
8
3
9 #-----------------------------------------------------------------------------
4 # Copyright (c) IPython Development Team.
10 # Copyright (C) 2013 The IPython Development Team
5 # Distributed under the terms of the Modified BSD License.
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
15
16 #-----------------------------------------------------------------------------
17 # Imports
18 #-----------------------------------------------------------------------------
19
6
20 import os
7 import os
21 import sys
8 import sys
@@ -24,9 +11,6 b' from subprocess import Popen, PIPE'
24 from IPython.utils.encoding import getdefaultencoding
11 from IPython.utils.encoding import getdefaultencoding
25 from IPython.utils.py3compat import cast_bytes_py2
12 from IPython.utils.py3compat import cast_bytes_py2
26
13
27 #-----------------------------------------------------------------------------
28 # Launching Kernels
29 #-----------------------------------------------------------------------------
30
14
31 def swallow_argv(argv, aliases=None, flags=None):
15 def swallow_argv(argv, aliases=None, flags=None):
32 """strip frontend-specific aliases and flags from an argument list
16 """strip frontend-specific aliases and flags from an argument list
@@ -136,7 +120,7 b' def make_ipkernel_cmd(code, executable=None, extra_arguments=[], **kw):'
136 return arguments
120 return arguments
137
121
138
122
139 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None,
123 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None, env=None,
140 independent=False,
124 independent=False,
141 cwd=None, ipython_kernel=True,
125 cwd=None, ipython_kernel=True,
142 **kw
126 **kw
@@ -221,7 +205,7 b' def launch_kernel(cmd, stdin=None, stdout=None, stderr=None,'
221 if independent:
205 if independent:
222 proc = Popen(cmd,
206 proc = Popen(cmd,
223 creationflags=512, # CREATE_NEW_PROCESS_GROUP
207 creationflags=512, # CREATE_NEW_PROCESS_GROUP
224 stdin=_stdin, stdout=_stdout, stderr=_stderr, env=os.environ)
208 stdin=_stdin, stdout=_stdout, stderr=_stderr, env=env)
225 else:
209 else:
226 if ipython_kernel:
210 if ipython_kernel:
227 try:
211 try:
@@ -238,7 +222,7 b' def launch_kernel(cmd, stdin=None, stdout=None, stderr=None,'
238
222
239
223
240 proc = Popen(cmd,
224 proc = Popen(cmd,
241 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=os.environ)
225 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
242
226
243 # Attach the interrupt event to the Popen objet so it can be used later.
227 # Attach the interrupt event to the Popen objet so it can be used later.
244 proc.win32_interrupt_event = interrupt_event
228 proc.win32_interrupt_event = interrupt_event
@@ -246,12 +230,12 b' def launch_kernel(cmd, stdin=None, stdout=None, stderr=None,'
246 else:
230 else:
247 if independent:
231 if independent:
248 proc = Popen(cmd, preexec_fn=lambda: os.setsid(),
232 proc = Popen(cmd, preexec_fn=lambda: os.setsid(),
249 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=os.environ)
233 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
250 else:
234 else:
251 if ipython_kernel:
235 if ipython_kernel:
252 cmd += ['--parent=1']
236 cmd += ['--parent=1']
253 proc = Popen(cmd,
237 proc = Popen(cmd,
254 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=os.environ)
238 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=env)
255
239
256 # Clean up pipes created to work around Popen bug.
240 # Clean up pipes created to work around Popen bug.
257 if redirect_in:
241 if redirect_in:
@@ -1,23 +1,17 b''
1 """Base class to manage a running kernel"""
1 """Base class to manage a running kernel"""
2
2
3 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
4 # Copyright (C) 2013 The IPython Development Team
4 # Distributed under the terms of the Modified BSD License.
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
5
14 from __future__ import absolute_import
6 from __future__ import absolute_import
15
7
16 # Standard library imports
8 # Standard library imports
9 import os
17 import re
10 import re
18 import signal
11 import signal
19 import sys
12 import sys
20 import time
13 import time
14 import warnings
21
15
22 import zmq
16 import zmq
23
17
@@ -25,12 +19,14 b' import zmq'
25 from IPython.config.configurable import LoggingConfigurable
19 from IPython.config.configurable import LoggingConfigurable
26 from IPython.utils.importstring import import_item
20 from IPython.utils.importstring import import_item
27 from IPython.utils.localinterfaces import is_local_ip, local_ips
21 from IPython.utils.localinterfaces import is_local_ip, local_ips
22 from IPython.utils.path import get_ipython_dir
28 from IPython.utils.traitlets import (
23 from IPython.utils.traitlets import (
29 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
24 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
30 )
25 )
31 from IPython.kernel import (
26 from IPython.kernel import (
32 make_ipkernel_cmd,
27 make_ipkernel_cmd,
33 launch_kernel,
28 launch_kernel,
29 kernelspec,
34 )
30 )
35 from .connect import ConnectionFileMixin
31 from .connect import ConnectionFileMixin
36 from .zmq.session import Session
32 from .zmq.session import Session
@@ -38,9 +34,6 b' from .managerabc import ('
38 KernelManagerABC
34 KernelManagerABC
39 )
35 )
40
36
41 #-----------------------------------------------------------------------------
42 # Main kernel manager class
43 #-----------------------------------------------------------------------------
44
37
45 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
38 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
46 """Manages a single kernel in a subprocess on this host.
39 """Manages a single kernel in a subprocess on this host.
@@ -67,9 +60,27 b' class KernelManager(LoggingConfigurable, ConnectionFileMixin):'
67 # The kernel process with which the KernelManager is communicating.
60 # The kernel process with which the KernelManager is communicating.
68 # generally a Popen instance
61 # generally a Popen instance
69 kernel = Any()
62 kernel = Any()
63
64 kernel_spec_manager = Instance(kernelspec.KernelSpecManager)
65
66 def _kernel_spec_manager_default(self):
67 return kernelspec.KernelSpecManager(ipython_dir=self.ipython_dir)
68
69 kernel_name = Unicode('python')
70
71 kernel_spec = Instance(kernelspec.KernelSpec)
72
73 def _kernel_spec_default(self):
74 return self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
75
76 def _kernel_name_changed(self, name, old, new):
77 self.kernel_spec = self.kernel_spec_manager.get_kernel_spec(new)
78 self.ipython_kernel = new in {'python', 'python2', 'python3'}
70
79
71 kernel_cmd = List(Unicode, config=True,
80 kernel_cmd = List(Unicode, config=True,
72 help="""The Popen Command to launch the kernel.
81 help="""DEPRECATED: Use kernel_name instead.
82
83 The Popen Command to launch the kernel.
73 Override this if you have a custom kernel.
84 Override this if you have a custom kernel.
74 If kernel_cmd is specified in a configuration file,
85 If kernel_cmd is specified in a configuration file,
75 IPython does not pass any arguments to the kernel,
86 IPython does not pass any arguments to the kernel,
@@ -81,9 +92,15 b' class KernelManager(LoggingConfigurable, ConnectionFileMixin):'
81 )
92 )
82
93
83 def _kernel_cmd_changed(self, name, old, new):
94 def _kernel_cmd_changed(self, name, old, new):
95 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
96 "start different kernels.")
84 self.ipython_kernel = False
97 self.ipython_kernel = False
85
98
86 ipython_kernel = Bool(True)
99 ipython_kernel = Bool(True)
100
101 ipython_dir = Unicode()
102 def _ipython_dir_default(self):
103 return get_ipython_dir()
87
104
88 # Protected traits
105 # Protected traits
89 _launch_args = Any()
106 _launch_args = Any()
@@ -150,11 +167,15 b' class KernelManager(LoggingConfigurable, ConnectionFileMixin):'
150 """replace templated args (e.g. {connection_file})"""
167 """replace templated args (e.g. {connection_file})"""
151 if self.kernel_cmd:
168 if self.kernel_cmd:
152 cmd = self.kernel_cmd
169 cmd = self.kernel_cmd
153 else:
170 elif self.kernel_name == 'python':
171 # The native kernel gets special handling
154 cmd = make_ipkernel_cmd(
172 cmd = make_ipkernel_cmd(
155 'from IPython.kernel.zmq.kernelapp import main; main()',
173 'from IPython.kernel.zmq.kernelapp import main; main()',
156 **kw
174 **kw
157 )
175 )
176 else:
177 cmd = self.kernel_spec.argv
178
158 ns = dict(connection_file=self.connection_file)
179 ns = dict(connection_file=self.connection_file)
159 ns.update(self._launch_args)
180 ns.update(self._launch_args)
160
181
@@ -211,8 +232,15 b' class KernelManager(LoggingConfigurable, ConnectionFileMixin):'
211 self._launch_args = kw.copy()
232 self._launch_args = kw.copy()
212 # build the Popen cmd
233 # build the Popen cmd
213 kernel_cmd = self.format_kernel_cmd(**kw)
234 kernel_cmd = self.format_kernel_cmd(**kw)
235 if self.kernel_cmd:
236 # If kernel_cmd has been set manually, don't refer to a kernel spec
237 env = os.environ
238 else:
239 # Environment variables from kernel spec are added to os.environ
240 env = os.environ.copy()
241 env.update(self.kernel_spec.env or {})
214 # launch the kernel subprocess
242 # launch the kernel subprocess
215 self.kernel = self._launch_kernel(kernel_cmd,
243 self.kernel = self._launch_kernel(kernel_cmd, env=env,
216 ipython_kernel=self.ipython_kernel,
244 ipython_kernel=self.ipython_kernel,
217 **kw)
245 **kw)
218 self.start_restarter()
246 self.start_restarter()
@@ -381,9 +409,5 b' class KernelManager(LoggingConfigurable, ConnectionFileMixin):'
381 return False
409 return False
382
410
383
411
384 #-----------------------------------------------------------------------------
385 # ABC Registration
386 #-----------------------------------------------------------------------------
387
388 KernelManagerABC.register(KernelManager)
412 KernelManagerABC.register(KernelManager)
389
413
General Comments 0
You need to be logged in to leave comments. Login now