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