##// 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 35 from IPython.kernel.blocking import BlockingKernelClient
36 36 from IPython.kernel import KernelManager
37 37 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
38 from IPython.kernel.kernelspec import NoSuchKernel
38 39 from IPython.utils.path import filefind
39 40 from IPython.utils.py3compat import str_to_bytes
40 41 from IPython.utils.traitlets import (
@@ -98,6 +99,7 b' app_aliases = dict('
98 99 existing = 'IPythonConsoleApp.existing',
99 100 f = 'IPythonConsoleApp.connection_file',
100 101
102 kernel = 'IPythonConsoleApp.kernel_name',
101 103
102 104 ssh = 'IPythonConsoleApp.sshserver',
103 105 )
@@ -174,6 +176,9 b' class IPythonConsoleApp(ConnectionFileMixin):'
174 176 existing = CUnicode('', config=True,
175 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 182 confirm_exit = CBool(True, config=True,
178 183 help="""
179 184 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
@@ -327,16 +332,23 b' class IPythonConsoleApp(ConnectionFileMixin):'
327 332 signal.signal(signal.SIGINT, signal.SIG_DFL)
328 333
329 334 # Create a KernelManager and start a kernel.
330 self.kernel_manager = self.kernel_manager_class(
331 ip=self.ip,
332 transport=self.transport,
333 shell_port=self.shell_port,
334 iopub_port=self.iopub_port,
335 stdin_port=self.stdin_port,
336 hb_port=self.hb_port,
337 connection_file=self.connection_file,
338 parent=self,
339 )
335 try:
336 self.kernel_manager = self.kernel_manager_class(
337 ip=self.ip,
338 transport=self.transport,
339 shell_port=self.shell_port,
340 iopub_port=self.iopub_port,
341 stdin_port=self.stdin_port,
342 hb_port=self.hb_port,
343 connection_file=self.connection_file,
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 352 self.kernel_manager.client_factory = self.kernel_client_class
341 353 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
342 354 atexit.register(self.kernel_manager.cleanup_ipc_files)
@@ -1,21 +1,8 b''
1 1 """Utilities for launching kernels
2
3 Authors:
4
5 * Min Ragan-Kelley
6
7 2 """
8 3
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2013 The IPython Development Team
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 #-----------------------------------------------------------------------------
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
19 6
20 7 import os
21 8 import sys
@@ -24,9 +11,6 b' from subprocess import Popen, PIPE'
24 11 from IPython.utils.encoding import getdefaultencoding
25 12 from IPython.utils.py3compat import cast_bytes_py2
26 13
27 #-----------------------------------------------------------------------------
28 # Launching Kernels
29 #-----------------------------------------------------------------------------
30 14
31 15 def swallow_argv(argv, aliases=None, flags=None):
32 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 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 124 independent=False,
141 125 cwd=None, ipython_kernel=True,
142 126 **kw
@@ -221,7 +205,7 b' def launch_kernel(cmd, stdin=None, stdout=None, stderr=None,'
221 205 if independent:
222 206 proc = Popen(cmd,
223 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 209 else:
226 210 if ipython_kernel:
227 211 try:
@@ -238,7 +222,7 b' def launch_kernel(cmd, stdin=None, stdout=None, stderr=None,'
238 222
239 223
240 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 227 # Attach the interrupt event to the Popen objet so it can be used later.
244 228 proc.win32_interrupt_event = interrupt_event
@@ -246,12 +230,12 b' def launch_kernel(cmd, stdin=None, stdout=None, stderr=None,'
246 230 else:
247 231 if independent:
248 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 234 else:
251 235 if ipython_kernel:
252 236 cmd += ['--parent=1']
253 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 240 # Clean up pipes created to work around Popen bug.
257 241 if redirect_in:
@@ -1,23 +1,17 b''
1 1 """Base class to manage a running kernel"""
2 2
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
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 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
13 5
14 6 from __future__ import absolute_import
15 7
16 8 # Standard library imports
9 import os
17 10 import re
18 11 import signal
19 12 import sys
20 13 import time
14 import warnings
21 15
22 16 import zmq
23 17
@@ -25,12 +19,14 b' import zmq'
25 19 from IPython.config.configurable import LoggingConfigurable
26 20 from IPython.utils.importstring import import_item
27 21 from IPython.utils.localinterfaces import is_local_ip, local_ips
22 from IPython.utils.path import get_ipython_dir
28 23 from IPython.utils.traitlets import (
29 24 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
30 25 )
31 26 from IPython.kernel import (
32 27 make_ipkernel_cmd,
33 28 launch_kernel,
29 kernelspec,
34 30 )
35 31 from .connect import ConnectionFileMixin
36 32 from .zmq.session import Session
@@ -38,9 +34,6 b' from .managerabc import ('
38 34 KernelManagerABC
39 35 )
40 36
41 #-----------------------------------------------------------------------------
42 # Main kernel manager class
43 #-----------------------------------------------------------------------------
44 37
45 38 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
46 39 """Manages a single kernel in a subprocess on this host.
@@ -67,9 +60,27 b' class KernelManager(LoggingConfigurable, ConnectionFileMixin):'
67 60 # The kernel process with which the KernelManager is communicating.
68 61 # generally a Popen instance
69 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 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 84 Override this if you have a custom kernel.
74 85 If kernel_cmd is specified in a configuration file,
75 86 IPython does not pass any arguments to the kernel,
@@ -81,9 +92,15 b' class KernelManager(LoggingConfigurable, ConnectionFileMixin):'
81 92 )
82 93
83 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 97 self.ipython_kernel = False
85 98
86 99 ipython_kernel = Bool(True)
100
101 ipython_dir = Unicode()
102 def _ipython_dir_default(self):
103 return get_ipython_dir()
87 104
88 105 # Protected traits
89 106 _launch_args = Any()
@@ -150,11 +167,15 b' class KernelManager(LoggingConfigurable, ConnectionFileMixin):'
150 167 """replace templated args (e.g. {connection_file})"""
151 168 if self.kernel_cmd:
152 169 cmd = self.kernel_cmd
153 else:
170 elif self.kernel_name == 'python':
171 # The native kernel gets special handling
154 172 cmd = make_ipkernel_cmd(
155 173 'from IPython.kernel.zmq.kernelapp import main; main()',
156 174 **kw
157 175 )
176 else:
177 cmd = self.kernel_spec.argv
178
158 179 ns = dict(connection_file=self.connection_file)
159 180 ns.update(self._launch_args)
160 181
@@ -211,8 +232,15 b' class KernelManager(LoggingConfigurable, ConnectionFileMixin):'
211 232 self._launch_args = kw.copy()
212 233 # build the Popen cmd
213 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 242 # launch the kernel subprocess
215 self.kernel = self._launch_kernel(kernel_cmd,
243 self.kernel = self._launch_kernel(kernel_cmd, env=env,
216 244 ipython_kernel=self.ipython_kernel,
217 245 **kw)
218 246 self.start_restarter()
@@ -381,9 +409,5 b' class KernelManager(LoggingConfigurable, ConnectionFileMixin):'
381 409 return False
382 410
383 411
384 #-----------------------------------------------------------------------------
385 # ABC Registration
386 #-----------------------------------------------------------------------------
387
388 412 KernelManagerABC.register(KernelManager)
389 413
General Comments 0
You need to be logged in to leave comments. Login now