1
2# Copyright (c) Jupyter Development Team.
3# Distributed under the terms of the Modified BSD License.
4
5import errno
6import os.path
7import sys
8import json
9
10from traitlets.config.application import Application
11from jupyter_core.application import (
12    JupyterApp, base_flags, base_aliases
13)
14from traitlets import Instance, Dict, Unicode, Bool, List
15
16from . import __version__
17from .kernelspec import KernelSpecManager
18
19
20class ListKernelSpecs(JupyterApp):
21    version = __version__
22    description = """List installed kernel specifications."""
23    kernel_spec_manager = Instance(KernelSpecManager)
24    json_output = Bool(False, help='output spec name and location as machine-readable json.',
25            config=True)
26
27    flags = {'json': ({'ListKernelSpecs': {'json_output': True}},
28                "output spec name and location as machine-readable json."),
29             'debug': base_flags['debug'],
30            }
31
32    def _kernel_spec_manager_default(self):
33        return KernelSpecManager(parent=self, data_dir=self.data_dir)
34
35    def start(self):
36        paths = self.kernel_spec_manager.find_kernel_specs()
37        specs = self.kernel_spec_manager.get_all_specs()
38        if not self.json_output:
39            if not specs:
40                print("No kernels available")
41                return
42            # pad to width of longest kernel name
43            name_len = len(sorted(paths, key=lambda name: len(name))[-1])
44
45            def path_key(item):
46                """sort key function for Jupyter path priority"""
47                path = item[1]
48                for idx, prefix in enumerate(self.jupyter_path):
49                    if path.startswith(prefix):
50                        return (idx, path)
51                # not in jupyter path, artificially added to the front
52                return (-1, path)
53
54            print("Available kernels:")
55            for kernelname, path in sorted(paths.items(), key=path_key):
56                print("  %s    %s" % (kernelname.ljust(name_len), path))
57        else:
58            print(json.dumps({
59                'kernelspecs': specs
60            }, indent=2))
61
62
63
64class InstallKernelSpec(JupyterApp):
65    version = __version__
66    description = """Install a kernel specification directory.
67
68    Given a SOURCE DIRECTORY containing a kernel spec,
69    jupyter will copy that directory into one of the Jupyter kernel directories.
70    The default is to install kernelspecs for all users.
71    `--user` can be specified to install a kernel only for the current user.
72    """
73    examples = """
74    jupyter kernelspec install /path/to/my_kernel --user
75    """
76    usage = "jupyter kernelspec install SOURCE_DIR [--options]"
77    kernel_spec_manager = Instance(KernelSpecManager)
78
79    def _kernel_spec_manager_default(self):
80        return KernelSpecManager(data_dir=self.data_dir)
81
82    sourcedir = Unicode()
83    kernel_name = Unicode("", config=True,
84        help="Install the kernel spec with this name"
85    )
86    def _kernel_name_default(self):
87        return os.path.basename(self.sourcedir)
88
89    user = Bool(False, config=True,
90        help="""
91        Try to install the kernel spec to the per-user directory instead of
92        the system or environment directory.
93        """
94    )
95    prefix = Unicode('', config=True,
96        help="""Specify a prefix to install to, e.g. an env.
97        The kernelspec will be installed in PREFIX/share/jupyter/kernels/
98        """
99    )
100    replace = Bool(False, config=True,
101        help="Replace any existing kernel spec with this name."
102    )
103
104    aliases = {
105        'name': 'InstallKernelSpec.kernel_name',
106        'prefix': 'InstallKernelSpec.prefix',
107    }
108    aliases.update(base_aliases)
109
110    flags = {'user': ({'InstallKernelSpec': {'user': True}},
111                "Install to the per-user kernel registry"),
112             'replace': ({'InstallKernelSpec': {'replace': True}},
113                "Replace any existing kernel spec with this name."),
114             'sys-prefix': ({'InstallKernelSpec': {'prefix': sys.prefix}},
115                "Install to Python's sys.prefix. Useful in conda/virtual environments."),
116             'debug': base_flags['debug'],
117            }
118
119    def parse_command_line(self, argv):
120        super().parse_command_line(argv)
121        # accept positional arg as profile name
122        if self.extra_args:
123            self.sourcedir = self.extra_args[0]
124        else:
125            print("No source directory specified.")
126            self.exit(1)
127
128    def start(self):
129        if self.user and self.prefix:
130            self.exit("Can't specify both user and prefix. Please choose one or the other.")
131        try:
132            self.kernel_spec_manager.install_kernel_spec(self.sourcedir,
133                                                 kernel_name=self.kernel_name,
134                                                 user=self.user,
135                                                 prefix=self.prefix,
136                                                 replace=self.replace,
137                                                )
138        except OSError as e:
139            if e.errno == errno.EACCES:
140                print(e, file=sys.stderr)
141                if not self.user:
142                    print("Perhaps you want to install with `sudo` or `--user`?", file=sys.stderr)
143                self.exit(1)
144            elif e.errno == errno.EEXIST:
145                print("A kernel spec is already present at %s" % e.filename, file=sys.stderr)
146                self.exit(1)
147            raise
148
149class RemoveKernelSpec(JupyterApp):
150    version = __version__
151    description = """Remove one or more Jupyter kernelspecs by name."""
152    examples = """jupyter kernelspec remove python2 [my_kernel ...]"""
153
154    force = Bool(False, config=True,
155        help="""Force removal, don't prompt for confirmation."""
156    )
157    spec_names = List(Unicode())
158
159    kernel_spec_manager = Instance(KernelSpecManager)
160    def _kernel_spec_manager_default(self):
161        return KernelSpecManager(data_dir=self.data_dir, parent=self)
162
163    flags = {
164        'f': ({'RemoveKernelSpec': {'force': True}}, force.get_metadata('help')),
165    }
166    flags.update(JupyterApp.flags)
167
168    def parse_command_line(self, argv):
169        super().parse_command_line(argv)
170        # accept positional arg as profile name
171        if self.extra_args:
172            self.spec_names = sorted(set(self.extra_args)) # remove duplicates
173        else:
174            self.exit("No kernelspec specified.")
175
176    def start(self):
177        self.kernel_spec_manager.ensure_native_kernel = False
178        spec_paths = self.kernel_spec_manager.find_kernel_specs()
179        missing = set(self.spec_names).difference(set(spec_paths))
180        if missing:
181            self.exit("Couldn't find kernel spec(s): %s" % ', '.join(missing))
182
183        if not self.force:
184            print("Kernel specs to remove:")
185            for name in self.spec_names:
186                print("  %s\t%s" % (name.ljust(20), spec_paths[name]))
187            answer = input("Remove %i kernel specs [y/N]: " % len(self.spec_names))
188            if not answer.lower().startswith('y'):
189                return
190
191        for kernel_name in self.spec_names:
192            try:
193                path = self.kernel_spec_manager.remove_kernel_spec(kernel_name)
194            except OSError as e:
195                if e.errno == errno.EACCES:
196                    print(e, file=sys.stderr)
197                    print("Perhaps you want sudo?", file=sys.stderr)
198                    self.exit(1)
199                else:
200                    raise
201            self.log.info("Removed %s", path)
202
203
204class InstallNativeKernelSpec(JupyterApp):
205    version = __version__
206    description = """[DEPRECATED] Install the IPython kernel spec directory for this Python."""
207    kernel_spec_manager = Instance(KernelSpecManager)
208
209    def _kernel_spec_manager_default(self):
210        return KernelSpecManager(data_dir=self.data_dir)
211
212    user = Bool(False, config=True,
213        help="""
214        Try to install the kernel spec to the per-user directory instead of
215        the system or environment directory.
216        """
217    )
218
219    flags = {'user': ({'InstallNativeKernelSpec': {'user': True}},
220                "Install to the per-user kernel registry"),
221             'debug': base_flags['debug'],
222            }
223
224    def start(self):
225        self.log.warning("`jupyter kernelspec install-self` is DEPRECATED as of 4.0."
226            " You probably want `ipython kernel install` to install the IPython kernelspec.")
227        try:
228            from ipykernel import kernelspec
229        except ImportError:
230            print("ipykernel not available, can't install its spec.", file=sys.stderr)
231            self.exit(1)
232        try:
233            kernelspec.install(self.kernel_spec_manager, user=self.user)
234        except OSError as e:
235            if e.errno == errno.EACCES:
236                print(e, file=sys.stderr)
237                if not self.user:
238                    print("Perhaps you want to install with `sudo` or `--user`?", file=sys.stderr)
239                self.exit(1)
240            self.exit(e)
241
242class KernelSpecApp(Application):
243    version = __version__
244    name = "jupyter kernelspec"
245    description = """Manage Jupyter kernel specifications."""
246
247    subcommands = Dict({
248        'list': (ListKernelSpecs, ListKernelSpecs.description.splitlines()[0]),
249        'install': (InstallKernelSpec, InstallKernelSpec.description.splitlines()[0]),
250        'uninstall': (RemoveKernelSpec, "Alias for remove"),
251        'remove': (RemoveKernelSpec, RemoveKernelSpec.description.splitlines()[0]),
252        'install-self': (InstallNativeKernelSpec, InstallNativeKernelSpec.description.splitlines()[0]),
253    })
254
255    aliases = {}
256    flags = {}
257
258    def start(self):
259        if self.subapp is None:
260            print("No subcommand specified. Must specify one of: %s"% list(self.subcommands))
261            print()
262            self.print_description()
263            self.print_subcommands()
264            self.exit(1)
265        else:
266            return self.subapp.start()
267
268
269if __name__ == '__main__':
270    KernelSpecApp.launch_instance()
271