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