|
"""Apps for managing kernel specs.""" |
|
|
|
|
|
from __future__ import annotations |
|
|
|
import errno |
|
import json |
|
import os.path |
|
import sys |
|
import typing as t |
|
|
|
from jupyter_core.application import JupyterApp, base_aliases, base_flags |
|
from traitlets import Bool, Dict, Instance, List, Unicode |
|
from traitlets.config.application import Application |
|
|
|
from . import __version__ |
|
from .kernelspec import KernelSpecManager |
|
from .provisioning.factory import KernelProvisionerFactory |
|
|
|
|
|
class ListKernelSpecs(JupyterApp): |
|
"""An app to list kernel specs.""" |
|
|
|
version = __version__ |
|
description = """List installed kernel specifications.""" |
|
kernel_spec_manager = Instance(KernelSpecManager) |
|
json_output = Bool( |
|
False, |
|
help="output spec name and location as machine-readable json.", |
|
config=True, |
|
) |
|
|
|
flags = { |
|
"json": ( |
|
{"ListKernelSpecs": {"json_output": True}}, |
|
"output spec name and location as machine-readable json.", |
|
), |
|
"debug": base_flags["debug"], |
|
} |
|
|
|
def _kernel_spec_manager_default(self) -> KernelSpecManager: |
|
return KernelSpecManager(parent=self, data_dir=self.data_dir) |
|
|
|
def start(self) -> dict[str, t.Any] | None: |
|
"""Start the application.""" |
|
paths = self.kernel_spec_manager.find_kernel_specs() |
|
specs = self.kernel_spec_manager.get_all_specs() |
|
if not self.json_output: |
|
if not specs: |
|
print("No kernels available") |
|
return None |
|
|
|
name_len = len(sorted(paths, key=lambda name: len(name))[-1]) |
|
|
|
def path_key(item: t.Any) -> t.Any: |
|
"""sort key function for Jupyter path priority""" |
|
path = item[1] |
|
for idx, prefix in enumerate(self.jupyter_path): |
|
if path.startswith(prefix): |
|
return (idx, path) |
|
|
|
return (-1, path) |
|
|
|
print("Available kernels:") |
|
for kernelname, path in sorted(paths.items(), key=path_key): |
|
print(f" {kernelname.ljust(name_len)} {path}") |
|
else: |
|
print(json.dumps({"kernelspecs": specs}, indent=2)) |
|
return specs |
|
|
|
|
|
class InstallKernelSpec(JupyterApp): |
|
"""An app to install a kernel spec.""" |
|
|
|
version = __version__ |
|
description = """Install a kernel specification directory. |
|
|
|
Given a SOURCE DIRECTORY containing a kernel spec, |
|
jupyter will copy that directory into one of the Jupyter kernel directories. |
|
The default is to install kernelspecs for all users. |
|
`--user` can be specified to install a kernel only for the current user. |
|
""" |
|
examples = """ |
|
jupyter kernelspec install /path/to/my_kernel --user |
|
""" |
|
usage = "jupyter kernelspec install SOURCE_DIR [--options]" |
|
kernel_spec_manager = Instance(KernelSpecManager) |
|
|
|
def _kernel_spec_manager_default(self) -> KernelSpecManager: |
|
return KernelSpecManager(data_dir=self.data_dir) |
|
|
|
sourcedir = Unicode() |
|
kernel_name = Unicode("", config=True, help="Install the kernel spec with this name") |
|
|
|
def _kernel_name_default(self) -> str: |
|
return os.path.basename(self.sourcedir) |
|
|
|
user = Bool( |
|
False, |
|
config=True, |
|
help=""" |
|
Try to install the kernel spec to the per-user directory instead of |
|
the system or environment directory. |
|
""", |
|
) |
|
prefix = Unicode( |
|
"", |
|
config=True, |
|
help="""Specify a prefix to install to, e.g. an env. |
|
The kernelspec will be installed in PREFIX/share/jupyter/kernels/ |
|
""", |
|
) |
|
replace = Bool(False, config=True, help="Replace any existing kernel spec with this name.") |
|
|
|
aliases = { |
|
"name": "InstallKernelSpec.kernel_name", |
|
"prefix": "InstallKernelSpec.prefix", |
|
} |
|
aliases.update(base_aliases) |
|
|
|
flags = { |
|
"user": ( |
|
{"InstallKernelSpec": {"user": True}}, |
|
"Install to the per-user kernel registry", |
|
), |
|
"replace": ( |
|
{"InstallKernelSpec": {"replace": True}}, |
|
"Replace any existing kernel spec with this name.", |
|
), |
|
"sys-prefix": ( |
|
{"InstallKernelSpec": {"prefix": sys.prefix}}, |
|
"Install to Python's sys.prefix. Useful in conda/virtual environments.", |
|
), |
|
"debug": base_flags["debug"], |
|
} |
|
|
|
def parse_command_line(self, argv: None | list[str]) -> None: |
|
"""Parse the command line args.""" |
|
super().parse_command_line(argv) |
|
|
|
if self.extra_args: |
|
self.sourcedir = self.extra_args[0] |
|
else: |
|
print("No source directory specified.", file=sys.stderr) |
|
self.exit(1) |
|
|
|
def start(self) -> None: |
|
"""Start the application.""" |
|
if self.user and self.prefix: |
|
self.exit("Can't specify both user and prefix. Please choose one or the other.") |
|
try: |
|
self.kernel_spec_manager.install_kernel_spec( |
|
self.sourcedir, |
|
kernel_name=self.kernel_name, |
|
user=self.user, |
|
prefix=self.prefix, |
|
replace=self.replace, |
|
) |
|
except OSError as e: |
|
if e.errno == errno.EACCES: |
|
print(e, file=sys.stderr) |
|
if not self.user: |
|
print("Perhaps you want to install with `sudo` or `--user`?", file=sys.stderr) |
|
self.exit(1) |
|
elif e.errno == errno.EEXIST: |
|
print(f"A kernel spec is already present at {e.filename}", file=sys.stderr) |
|
self.exit(1) |
|
raise |
|
|
|
|
|
class RemoveKernelSpec(JupyterApp): |
|
"""An app to remove a kernel spec.""" |
|
|
|
version = __version__ |
|
description = """Remove one or more Jupyter kernelspecs by name.""" |
|
examples = """jupyter kernelspec remove python2 [my_kernel ...]""" |
|
|
|
force = Bool(False, config=True, help="""Force removal, don't prompt for confirmation.""") |
|
spec_names = List(Unicode()) |
|
|
|
kernel_spec_manager = Instance(KernelSpecManager) |
|
|
|
def _kernel_spec_manager_default(self) -> KernelSpecManager: |
|
return KernelSpecManager(data_dir=self.data_dir, parent=self) |
|
|
|
flags = { |
|
"f": ({"RemoveKernelSpec": {"force": True}}, force.help), |
|
} |
|
flags.update(JupyterApp.flags) |
|
|
|
def parse_command_line(self, argv: list[str] | None) -> None: |
|
"""Parse the command line args.""" |
|
super().parse_command_line(argv) |
|
|
|
if self.extra_args: |
|
self.spec_names = sorted(set(self.extra_args)) |
|
else: |
|
self.exit("No kernelspec specified.") |
|
|
|
def start(self) -> None: |
|
"""Start the application.""" |
|
self.kernel_spec_manager.ensure_native_kernel = False |
|
spec_paths = self.kernel_spec_manager.find_kernel_specs() |
|
missing = set(self.spec_names).difference(set(spec_paths)) |
|
if missing: |
|
self.exit("Couldn't find kernel spec(s): %s" % ", ".join(missing)) |
|
|
|
if not (self.force or self.answer_yes): |
|
print("Kernel specs to remove:") |
|
for name in self.spec_names: |
|
path = spec_paths.get(name, name) |
|
print(f" {name.ljust(20)}\t{path.ljust(20)}") |
|
answer = input("Remove %i kernel specs [y/N]: " % len(self.spec_names)) |
|
if not answer.lower().startswith("y"): |
|
return |
|
|
|
for kernel_name in self.spec_names: |
|
try: |
|
path = self.kernel_spec_manager.remove_kernel_spec(kernel_name) |
|
except OSError as e: |
|
if e.errno == errno.EACCES: |
|
print(e, file=sys.stderr) |
|
print("Perhaps you want sudo?", file=sys.stderr) |
|
self.exit(1) |
|
else: |
|
raise |
|
print(f"Removed {path}") |
|
|
|
|
|
class InstallNativeKernelSpec(JupyterApp): |
|
"""An app to install the native kernel spec.""" |
|
|
|
version = __version__ |
|
description = """[DEPRECATED] Install the IPython kernel spec directory for this Python.""" |
|
kernel_spec_manager = Instance(KernelSpecManager) |
|
|
|
def _kernel_spec_manager_default(self) -> KernelSpecManager: |
|
return KernelSpecManager(data_dir=self.data_dir) |
|
|
|
user = Bool( |
|
False, |
|
config=True, |
|
help=""" |
|
Try to install the kernel spec to the per-user directory instead of |
|
the system or environment directory. |
|
""", |
|
) |
|
|
|
flags = { |
|
"user": ( |
|
{"InstallNativeKernelSpec": {"user": True}}, |
|
"Install to the per-user kernel registry", |
|
), |
|
"debug": base_flags["debug"], |
|
} |
|
|
|
def start(self) -> None: |
|
"""Start the application.""" |
|
self.log.warning( |
|
"`jupyter kernelspec install-self` is DEPRECATED as of 4.0." |
|
" You probably want `ipython kernel install` to install the IPython kernelspec." |
|
) |
|
try: |
|
from ipykernel import kernelspec |
|
except ModuleNotFoundError: |
|
print("ipykernel not available, can't install its spec.", file=sys.stderr) |
|
self.exit(1) |
|
try: |
|
kernelspec.install(self.kernel_spec_manager, user=self.user) |
|
except OSError as e: |
|
if e.errno == errno.EACCES: |
|
print(e, file=sys.stderr) |
|
if not self.user: |
|
print( |
|
"Perhaps you want to install with `sudo` or `--user`?", |
|
file=sys.stderr, |
|
) |
|
self.exit(1) |
|
self.exit(e) |
|
|
|
|
|
class ListProvisioners(JupyterApp): |
|
"""An app to list provisioners.""" |
|
|
|
version = __version__ |
|
description = """List available provisioners for use in kernel specifications.""" |
|
|
|
def start(self) -> None: |
|
"""Start the application.""" |
|
kfp = KernelProvisionerFactory.instance(parent=self) |
|
print("Available kernel provisioners:") |
|
provisioners = kfp.get_provisioner_entries() |
|
|
|
|
|
name_len = len(sorted(provisioners, key=lambda name: len(name))[-1]) |
|
|
|
for name in sorted(provisioners): |
|
print(f" {name.ljust(name_len)} {provisioners[name]}") |
|
|
|
|
|
class KernelSpecApp(Application): |
|
"""An app to manage kernel specs.""" |
|
|
|
version = __version__ |
|
name = "jupyter kernelspec" |
|
description = """Manage Jupyter kernel specifications.""" |
|
|
|
subcommands = Dict( |
|
{ |
|
"list": (ListKernelSpecs, ListKernelSpecs.description.splitlines()[0]), |
|
"install": ( |
|
InstallKernelSpec, |
|
InstallKernelSpec.description.splitlines()[0], |
|
), |
|
"uninstall": (RemoveKernelSpec, "Alias for remove"), |
|
"remove": (RemoveKernelSpec, RemoveKernelSpec.description.splitlines()[0]), |
|
"install-self": ( |
|
InstallNativeKernelSpec, |
|
InstallNativeKernelSpec.description.splitlines()[0], |
|
), |
|
"provisioners": (ListProvisioners, ListProvisioners.description.splitlines()[0]), |
|
} |
|
) |
|
|
|
aliases = {} |
|
flags = {} |
|
|
|
def start(self) -> None: |
|
"""Start the application.""" |
|
if self.subapp is None: |
|
print("No subcommand specified. Must specify one of: %s" % list(self.subcommands)) |
|
print() |
|
self.print_description() |
|
self.print_subcommands() |
|
self.exit(1) |
|
else: |
|
return self.subapp.start() |
|
|
|
|
|
if __name__ == "__main__": |
|
KernelSpecApp.launch_instance() |
|
|