##// END OF EJS Templates
Make installation of native kernelspec explicit...
Thomas Kluyver -
Show More
@@ -1,196 +1,222 b''
1 1 import io
2 2 import json
3 3 import os
4 4 import shutil
5 5 import sys
6 6
7 7 pjoin = os.path.join
8 8
9 9 from IPython.utils.path import get_ipython_dir
10 10 from IPython.utils.py3compat import PY3
11 11 from IPython.utils.traitlets import HasTraits, List, Unicode, Dict, Any
12 12
13 13 if os.name == 'nt':
14 14 programdata = os.environ.get('PROGRAMDATA', None)
15 15 if programdata:
16 16 SYSTEM_KERNEL_DIRS = [pjoin(programdata, 'ipython', 'kernels')]
17 17 else: # PROGRAMDATA is not defined by default on XP.
18 18 SYSTEM_KERNEL_DIRS = []
19 19 else:
20 20 SYSTEM_KERNEL_DIRS = ["/usr/share/ipython/kernels",
21 21 "/usr/local/share/ipython/kernels",
22 22 ]
23 23
24 24 NATIVE_KERNEL_NAME = 'python3' if PY3 else 'python2'
25 25
26 26 def _pythonfirst(s):
27 27 "Sort key function that will put strings starting with 'python' first."
28 28 if s == NATIVE_KERNEL_NAME:
29 29 return ' ' + s # Two spaces to sort this first of all
30 30 elif s.startswith('python'):
31 31 # Space is not valid in kernel names, so this should sort first
32 32 return ' ' + s
33 33 return s
34 34
35 35 class KernelSpec(HasTraits):
36 36 argv = List()
37 37 display_name = Unicode()
38 38 language = Unicode()
39 39 codemirror_mode = Any() # can be unicode or dict
40 40 env = Dict()
41 41 resource_dir = Unicode()
42 42
43 43 def _codemirror_mode_default(self):
44 44 return self.language
45 45
46 46 @classmethod
47 47 def from_resource_dir(cls, resource_dir):
48 48 """Create a KernelSpec object by reading kernel.json
49 49
50 50 Pass the path to the *directory* containing kernel.json.
51 51 """
52 52 kernel_file = pjoin(resource_dir, 'kernel.json')
53 53 with io.open(kernel_file, 'r', encoding='utf-8') as f:
54 54 kernel_dict = json.load(f)
55 55 return cls(resource_dir=resource_dir, **kernel_dict)
56 56
57 57 def to_dict(self):
58 58 return dict(argv=self.argv,
59 59 env=self.env,
60 60 display_name=self.display_name,
61 61 language=self.language,
62 62 codemirror_mode=self.codemirror_mode,
63 63 )
64 64
65 65 def to_json(self):
66 66 return json.dumps(self.to_dict())
67 67
68 68 def _is_kernel_dir(path):
69 69 """Is ``path`` a kernel directory?"""
70 70 return os.path.isdir(path) and os.path.isfile(pjoin(path, 'kernel.json'))
71 71
72 72 def _list_kernels_in(dir):
73 73 """Return a mapping of kernel names to resource directories from dir.
74 74
75 75 If dir is None or does not exist, returns an empty dict.
76 76 """
77 77 if dir is None or not os.path.isdir(dir):
78 78 return {}
79 79 return {f.lower(): pjoin(dir, f) for f in os.listdir(dir)
80 80 if _is_kernel_dir(pjoin(dir, f))}
81 81
82 82 class NoSuchKernel(KeyError):
83 83 def __init__(self, name):
84 84 self.name = name
85 85
86 86 class KernelSpecManager(HasTraits):
87 87 ipython_dir = Unicode()
88 88 def _ipython_dir_default(self):
89 89 return get_ipython_dir()
90 90
91 91 user_kernel_dir = Unicode()
92 92 def _user_kernel_dir_default(self):
93 93 return pjoin(self.ipython_dir, 'kernels')
94 94
95 95 kernel_dirs = List(
96 96 help="List of kernel directories to search. Later ones take priority over earlier."
97 97 )
98 98 def _kernel_dirs_default(self):
99 99 return SYSTEM_KERNEL_DIRS + [
100 100 self.user_kernel_dir,
101 101 ]
102 102
103 def _make_native_kernel_dir(self):
103 @property
104 def _native_kernel_dict(self):
104 105 """Makes a kernel directory for the native kernel.
105 106
106 107 The native kernel is the kernel using the same Python runtime as this
107 108 process. This will put its informatino in the user kernels directory.
108 109 """
109 path = pjoin(self.user_kernel_dir, NATIVE_KERNEL_NAME)
110 os.makedirs(path, mode=0o755)
111 with open(pjoin(path, 'kernel.json'), 'w') as f:
112 json.dump({'argv':[NATIVE_KERNEL_NAME, '-c',
110 return {'argv':[sys.executable, '-c',
113 111 'from IPython.kernel.zmq.kernelapp import main; main()',
114 112 '-f', '{connection_file}'],
115 113 'display_name': 'IPython (Python %d)' % (3 if PY3 else 2),
116 114 'language': 'python',
117 115 'codemirror_mode': {'name': 'ipython',
118 116 'version': sys.version_info[0]},
119 },
120 f, indent=1)
121 # TODO: Copy icons into directory
122 return path
117 }
118
119 @property
120 def _native_kernel_resource_dir(self):
121 # TODO: This may be different when we actually have any resources
122 return os.path.dirname(__file__)
123 123
124 124 def find_kernel_specs(self):
125 125 """Returns a dict mapping kernel names to resource directories."""
126 126 d = {}
127 127 for kernel_dir in self.kernel_dirs:
128 128 d.update(_list_kernels_in(kernel_dir))
129 129
130 if NATIVE_KERNEL_NAME not in d:
131 d[NATIVE_KERNEL_NAME] = self._make_native_kernel_dir()
130 d[NATIVE_KERNEL_NAME] = self._native_kernel_resource_dir
132 131 return d
133 132 # TODO: Caching?
134 133
135 134 def get_kernel_spec(self, kernel_name):
136 135 """Returns a :class:`KernelSpec` instance for the given kernel_name.
137 136
138 137 Raises :exc:`NoSuchKernel` if the given kernel name is not found.
139 138 """
140 if kernel_name == 'python':
141 kernel_name = NATIVE_KERNEL_NAME
139 if kernel_name in {'python', NATIVE_KERNEL_NAME}:
140 return KernelSpec(self._native_kernel_resource_dir, **self._native_kernel_dict)
141
142 142 d = self.find_kernel_specs()
143 143 try:
144 144 resource_dir = d[kernel_name.lower()]
145 145 except KeyError:
146 146 raise NoSuchKernel(kernel_name)
147 147 return KernelSpec.from_resource_dir(resource_dir)
148 148
149 def _get_destination_dir(self, kernel_name, system=False):
150 if system:
151 if SYSTEM_KERNEL_DIRS:
152 return os.path.join(SYSTEM_KERNEL_DIRS[-1], kernel_name)
153 else:
154 raise EnvironmentError("No system kernel directory is available")
155 else:
156 return os.path.join(self.user_kernel_dir, kernel_name)
157
149 158 def install_kernel_spec(self, source_dir, kernel_name=None, system=False,
150 159 replace=False):
151 160 """Install a kernel spec by copying its directory.
152 161
153 162 If ``kernel_name`` is not given, the basename of ``source_dir`` will
154 163 be used.
155 164
156 165 If ``system`` is True, it will attempt to install into the systemwide
157 166 kernel registry. If the process does not have appropriate permissions,
158 167 an :exc:`OSError` will be raised.
159 168
160 169 If ``replace`` is True, this will replace an existing kernel of the same
161 170 name. Otherwise, if the destination already exists, an :exc:`OSError`
162 171 will be raised.
163 172 """
164 173 if not kernel_name:
165 174 kernel_name = os.path.basename(source_dir)
166 175 kernel_name = kernel_name.lower()
167 176
168 if system:
169 if SYSTEM_KERNEL_DIRS:
170 destination = os.path.join(SYSTEM_KERNEL_DIRS[-1], kernel_name)
171 else:
172 raise EnvironmentError("No system kernel directory is available")
173 else:
174 destination = os.path.join(self.user_kernel_dir, kernel_name)
177 destination = self._get_destination_dir(kernel_name, system=system)
175 178
176 179 if replace and os.path.isdir(destination):
177 180 shutil.rmtree(destination)
178 181
179 182 shutil.copytree(source_dir, destination)
180 183
184 def install_native_kernel_spec(self, system=False):
185 """Install the native kernel spec to the filesystem
186
187 This allows a Python 3 frontend to use a Python 2 kernel, or vice versa.
188 The kernelspec will be written pointing to the Python executable on
189 which this is run.
190
191 If ``system`` is True, it will attempt to install into the systemwide
192 kernel registry. If the process does not have appropriate permissions,
193 an :exc:`OSError` will be raised.
194 """
195 path = self._get_destination_dir(NATIVE_KERNEL_NAME, system=system)
196 os.makedirs(path, mode=0o755)
197 with open(pjoin(path, 'kernel.json'), 'w') as f:
198 json.dump(self._native_kernel_dict, f, indent=1)
199 # TODO: Copy icons into directory
200 return path
201
181 202 def find_kernel_specs():
182 203 """Returns a dict mapping kernel names to resource directories."""
183 204 return KernelSpecManager().find_kernel_specs()
184 205
185 206 def get_kernel_spec(kernel_name):
186 207 """Returns a :class:`KernelSpec` instance for the given kernel_name.
187 208
188 209 Raises KeyError if the given kernel name is not found.
189 210 """
190 211 return KernelSpecManager().get_kernel_spec(kernel_name)
191 212
192 213 def install_kernel_spec(source_dir, kernel_name=None, system=False, replace=False):
193 214 return KernelSpecManager().install_kernel_spec(source_dir, kernel_name,
194 215 system, replace)
195 216
196 217 install_kernel_spec.__doc__ = KernelSpecManager.install_kernel_spec.__doc__
218
219 def install_native_kernel_spec(self, system=False):
220 return KernelSpecManager().install_native_kernel_spec(system=system)
221
222 install_native_kernel_spec.__doc__ = KernelSpecManager.install_native_kernel_spec.__doc__ No newline at end of file
@@ -1,114 +1,145 b''
1 1
2 2 # Copyright (c) IPython Development Team.
3 3 # Distributed under the terms of the Modified BSD License.
4 4
5 5 import errno
6 6 import os.path
7 7
8 8 from IPython.config.application import Application
9 9 from IPython.core.application import (
10 10 BaseIPythonApplication, base_flags, base_aliases
11 11 )
12 12 from IPython.utils.traitlets import Instance, Dict, Unicode, Bool
13 13
14 14 from .kernelspec import KernelSpecManager, _pythonfirst
15 15
16 16 class ListKernelSpecs(BaseIPythonApplication):
17 17 description = """List installed kernel specifications."""
18 18 kernel_spec_manager = Instance(KernelSpecManager)
19 19
20 20 # Not all of the base aliases are meaningful (e.g. profile)
21 21 aliases = {k: base_aliases[k] for k in ['ipython-dir', 'log-level']}
22 22 flags = {'debug': base_flags['debug'],}
23 23
24 24 def _kernel_spec_manager_default(self):
25 25 return KernelSpecManager(ipython_dir=self.ipython_dir)
26 26
27 27 def start(self):
28 28 print("Available kernels:")
29 29 for kernelname in sorted(self.kernel_spec_manager.find_kernel_specs(),
30 30 key=_pythonfirst):
31 31 print(" %s" % kernelname)
32 32
33 33
34 34 class InstallKernelSpec(BaseIPythonApplication):
35 35 description = """Install a kernel specification directory."""
36 36 kernel_spec_manager = Instance(KernelSpecManager)
37 37
38 38 def _kernel_spec_manager_default(self):
39 39 return KernelSpecManager(ipython_dir=self.ipython_dir)
40 40
41 41 sourcedir = Unicode()
42 42 kernel_name = Unicode("", config=True,
43 43 help="Install the kernel spec with this name"
44 44 )
45 45 def _kernel_name_default(self):
46 46 return os.path.basename(self.sourcedir)
47 47
48 48 system = Bool(False, config=True,
49 49 help="""
50 50 Try to install the kernel spec to the systemwide directory instead of
51 51 the per-user directory.
52 52 """
53 53 )
54 54 replace = Bool(False, config=True,
55 55 help="Replace any existing kernel spec with this name."
56 56 )
57 57
58 58 aliases = {'name': 'InstallKernelSpec.kernel_name'}
59 59 for k in ['ipython-dir', 'log-level']:
60 60 aliases[k] = base_aliases[k]
61 61
62 62 flags = {'system': ({'InstallKernelSpec': {'system': True}},
63 63 "Install to the systemwide kernel registry"),
64 64 'replace': ({'InstallKernelSpec': {'replace': True}},
65 65 "Replace any existing kernel spec with this name."),
66 66 'debug': base_flags['debug'],
67 67 }
68 68
69 69 def parse_command_line(self, argv):
70 70 super(InstallKernelSpec, self).parse_command_line(argv)
71 71 # accept positional arg as profile name
72 72 if self.extra_args:
73 73 self.sourcedir = self.extra_args[0]
74 74 else:
75 75 print("No source directory specified.")
76 76 self.exit(1)
77 77
78 78 def start(self):
79 79 try:
80 80 self.kernel_spec_manager.install_kernel_spec(self.sourcedir,
81 81 kernel_name=self.kernel_name,
82 82 system=self.system,
83 83 replace=self.replace,
84 84 )
85 85 except OSError as e:
86 86 if e.errno == errno.EACCES:
87 87 print("Permission denied")
88 88 self.exit(1)
89 89 elif e.errno == errno.EEXIST:
90 90 print("A kernel spec is already present at %s" % e.filename)
91 91 self.exit(1)
92 92 raise
93 93
94 class InstallNativeKernelSpec(BaseIPythonApplication):
95 description = """Install the native kernel spec directory for this Python."""
96 kernel_spec_manager = Instance(KernelSpecManager)
97
98 def _kernel_spec_manager_default(self):
99 return KernelSpecManager(ipython_dir=self.ipython_dir)
100
101 system = Bool(False, config=True,
102 help="""
103 Try to install the kernel spec to the systemwide directory instead of
104 the per-user directory.
105 """
106 )
107
108 # Not all of the base aliases are meaningful (e.g. profile)
109 aliases = {k: base_aliases[k] for k in ['ipython-dir', 'log-level']}
110 flags = {'system': ({'InstallOwnKernelSpec': {'system': True}},
111 "Install to the systemwide kernel registry"),
112 'debug': base_flags['debug'],
113 }
114
115 def start(self):
116 try:
117 self.kernel_spec_manager.install_native_kernel_spec(system=self.system)
118 except OSError as e:
119 if e.errno == errno.EACCES:
120 print("Permission denied")
121 self.exit(1)
122 raise
123
94 124 class KernelSpecApp(Application):
95 125 name = "ipython kernelspec"
96 126 description = """Manage IPython kernel specifications."""
97 127
98 subcommands = Dict(dict(
99 list = (ListKernelSpecs, ListKernelSpecs.description.splitlines()[0]),
100 install = (InstallKernelSpec, InstallKernelSpec.description.splitlines()[0])
101 ))
128 subcommands = Dict({
129 'list': (ListKernelSpecs, ListKernelSpecs.description.splitlines()[0]),
130 'install': (InstallKernelSpec, InstallKernelSpec.description.splitlines()[0]),
131 'install-self': (InstallNativeKernelSpec, InstallNativeKernelSpec.description.splitlines()[0]),
132 })
102 133
103 134 aliases = {}
104 135 flags = {}
105 136
106 137 def start(self):
107 138 if self.subapp is None:
108 139 print("No subcommand specified. Must specify one of: %s"% list(self.subcommands))
109 140 print()
110 141 self.print_description()
111 142 self.print_subcommands()
112 143 self.exit(1)
113 144 else:
114 145 return self.subapp.start()
General Comments 0
You need to be logged in to leave comments. Login now