kernelspec.py
222 lines
| 7.7 KiB
| text/x-python
|
PythonLexer
Thomas Kluyver
|
r16260 | import io | ||
import json | ||||
import os | ||||
Thomas Kluyver
|
r16559 | import shutil | ||
Thomas Kluyver
|
r16261 | import sys | ||
Thomas Kluyver
|
r16260 | |||
pjoin = os.path.join | ||||
from IPython.utils.path import get_ipython_dir | ||||
from IPython.utils.py3compat import PY3 | ||||
Jason Grout
|
r17686 | from IPython.utils.traitlets import HasTraits, List, Unicode, Dict, Any | ||
Thomas Kluyver
|
r17748 | from .launcher import make_ipkernel_cmd | ||
Thomas Kluyver
|
r16260 | |||
if os.name == 'nt': | ||||
programdata = os.environ.get('PROGRAMDATA', None) | ||||
if programdata: | ||||
Thomas Kluyver
|
r16825 | SYSTEM_KERNEL_DIRS = [pjoin(programdata, 'ipython', 'kernels')] | ||
Thomas Kluyver
|
r16260 | else: # PROGRAMDATA is not defined by default on XP. | ||
Thomas Kluyver
|
r16825 | SYSTEM_KERNEL_DIRS = [] | ||
Thomas Kluyver
|
r16260 | else: | ||
Thomas Kluyver
|
r19519 | SYSTEM_KERNEL_DIRS = ["/usr/share/jupyter/kernels", | ||
"/usr/local/share/jupyter/kernels", | ||||
Thomas Kluyver
|
r16825 | ] | ||
Thomas Kluyver
|
r16260 | |||
NATIVE_KERNEL_NAME = 'python3' if PY3 else 'python2' | ||||
Thomas Kluyver
|
r17381 | def _pythonfirst(s): | ||
"Sort key function that will put strings starting with 'python' first." | ||||
if s == NATIVE_KERNEL_NAME: | ||||
return ' ' + s # Two spaces to sort this first of all | ||||
elif s.startswith('python'): | ||||
# Space is not valid in kernel names, so this should sort first | ||||
return ' ' + s | ||||
return s | ||||
Thomas Kluyver
|
r16260 | class KernelSpec(HasTraits): | ||
argv = List() | ||||
display_name = Unicode() | ||||
Thomas Kluyver
|
r16349 | env = Dict() | ||
Thomas Kluyver
|
r16260 | resource_dir = Unicode() | ||
@classmethod | ||||
def from_resource_dir(cls, resource_dir): | ||||
Thomas Kluyver
|
r16262 | """Create a KernelSpec object by reading kernel.json | ||
Pass the path to the *directory* containing kernel.json. | ||||
""" | ||||
kernel_file = pjoin(resource_dir, 'kernel.json') | ||||
Thomas Kluyver
|
r16260 | with io.open(kernel_file, 'r', encoding='utf-8') as f: | ||
kernel_dict = json.load(f) | ||||
return cls(resource_dir=resource_dir, **kernel_dict) | ||||
Thomas Kluyver
|
r16714 | |||
def to_dict(self): | ||||
Thomas Kluyver
|
r18380 | d = dict(argv=self.argv, | ||
env=self.env, | ||||
display_name=self.display_name, | ||||
) | ||||
return d | ||||
Thomas Kluyver
|
r16260 | |||
Thomas Kluyver
|
r16684 | def to_json(self): | ||
Thomas Kluyver
|
r16714 | return json.dumps(self.to_dict()) | ||
Thomas Kluyver
|
r16260 | |||
def _is_kernel_dir(path): | ||||
Thomas Kluyver
|
r16262 | """Is ``path`` a kernel directory?""" | ||
Thomas Kluyver
|
r16260 | return os.path.isdir(path) and os.path.isfile(pjoin(path, 'kernel.json')) | ||
def _list_kernels_in(dir): | ||||
Thomas Kluyver
|
r16382 | """Return a mapping of kernel names to resource directories from dir. | ||
If dir is None or does not exist, returns an empty dict. | ||||
Thomas Kluyver
|
r16262 | """ | ||
Thomas Kluyver
|
r16271 | if dir is None or not os.path.isdir(dir): | ||
Thomas Kluyver
|
r16260 | return {} | ||
return {f.lower(): pjoin(dir, f) for f in os.listdir(dir) | ||||
if _is_kernel_dir(pjoin(dir, f))} | ||||
Thomas Kluyver
|
r16383 | class NoSuchKernel(KeyError): | ||
def __init__(self, name): | ||||
self.name = name | ||||
Thomas Kluyver
|
r16382 | class KernelSpecManager(HasTraits): | ||
ipython_dir = Unicode() | ||||
def _ipython_dir_default(self): | ||||
return get_ipython_dir() | ||||
user_kernel_dir = Unicode() | ||||
def _user_kernel_dir_default(self): | ||||
return pjoin(self.ipython_dir, 'kernels') | ||||
Thomas Kluyver
|
r19520 | |||
@property | ||||
def env_kernel_dir(self): | ||||
return pjoin(sys.prefix, 'share', 'jupyter', 'kernels') | ||||
Thomas Kluyver
|
r16262 | |||
Thomas Kluyver
|
r16382 | kernel_dirs = List( | ||
help="List of kernel directories to search. Later ones take priority over earlier." | ||||
) | ||||
def _kernel_dirs_default(self): | ||||
Thomas Kluyver
|
r19520 | dirs = SYSTEM_KERNEL_DIRS[:] | ||
if self.env_kernel_dir not in dirs: | ||||
dirs.append(self.env_kernel_dir) | ||||
dirs.append(self.user_kernel_dir) | ||||
return dirs | ||||
Thomas Kluyver
|
r16382 | |||
Thomas Kluyver
|
r17747 | @property | ||
def _native_kernel_dict(self): | ||||
Thomas Kluyver
|
r16382 | """Makes a kernel directory for the native kernel. | ||
The native kernel is the kernel using the same Python runtime as this | ||||
Min RK
|
r19763 | process. This will put its information in the user kernels directory. | ||
Thomas Kluyver
|
r16382 | """ | ||
MinRK
|
r18434 | return {'argv': make_ipkernel_cmd(), | ||
Min RK
|
r19763 | 'display_name': 'Python %i' % (3 if PY3 else 2), | ||
Thomas Kluyver
|
r18468 | } | ||
Thomas Kluyver
|
r17747 | |||
@property | ||||
def _native_kernel_resource_dir(self): | ||||
Thomas Kluyver
|
r19373 | return pjoin(os.path.dirname(__file__), 'resources') | ||
Thomas Kluyver
|
r17747 | |||
Thomas Kluyver
|
r16382 | def find_kernel_specs(self): | ||
"""Returns a dict mapping kernel names to resource directories.""" | ||||
d = {} | ||||
for kernel_dir in self.kernel_dirs: | ||||
d.update(_list_kernels_in(kernel_dir)) | ||||
Thomas Kluyver
|
r17747 | |||
d[NATIVE_KERNEL_NAME] = self._native_kernel_resource_dir | ||||
Thomas Kluyver
|
r16382 | return d | ||
# TODO: Caching? | ||||
Thomas Kluyver
|
r17747 | |||
Thomas Kluyver
|
r16382 | def get_kernel_spec(self, kernel_name): | ||
"""Returns a :class:`KernelSpec` instance for the given kernel_name. | ||||
Thomas Kluyver
|
r16383 | Raises :exc:`NoSuchKernel` if the given kernel name is not found. | ||
Thomas Kluyver
|
r16382 | """ | ||
Thomas Kluyver
|
r17747 | if kernel_name in {'python', NATIVE_KERNEL_NAME}: | ||
Thomas Kluyver
|
r19373 | return KernelSpec(resource_dir=self._native_kernel_resource_dir, | ||
**self._native_kernel_dict) | ||||
Thomas Kluyver
|
r17747 | |||
Thomas Kluyver
|
r16382 | d = self.find_kernel_specs() | ||
Thomas Kluyver
|
r16383 | try: | ||
resource_dir = d[kernel_name.lower()] | ||||
except KeyError: | ||||
raise NoSuchKernel(kernel_name) | ||||
Thomas Kluyver
|
r16382 | return KernelSpec.from_resource_dir(resource_dir) | ||
Thomas Kluyver
|
r17747 | |||
Thomas Kluyver
|
r19522 | def _get_destination_dir(self, kernel_name, user=False): | ||
if user: | ||||
return os.path.join(self.user_kernel_dir, kernel_name) | ||||
else: | ||||
Thomas Kluyver
|
r17747 | if SYSTEM_KERNEL_DIRS: | ||
return os.path.join(SYSTEM_KERNEL_DIRS[-1], kernel_name) | ||||
else: | ||||
raise EnvironmentError("No system kernel directory is available") | ||||
Thomas Kluyver
|
r16261 | |||
Thomas Kluyver
|
r19522 | |||
def install_kernel_spec(self, source_dir, kernel_name=None, user=False, | ||||
Thomas Kluyver
|
r16559 | replace=False): | ||
"""Install a kernel spec by copying its directory. | ||||
If ``kernel_name`` is not given, the basename of ``source_dir`` will | ||||
be used. | ||||
Doug Blank
|
r19810 | If ``user`` is False, it will attempt to install into the systemwide | ||
Thomas Kluyver
|
r16559 | kernel registry. If the process does not have appropriate permissions, | ||
an :exc:`OSError` will be raised. | ||||
If ``replace`` is True, this will replace an existing kernel of the same | ||||
name. Otherwise, if the destination already exists, an :exc:`OSError` | ||||
will be raised. | ||||
""" | ||||
if not kernel_name: | ||||
kernel_name = os.path.basename(source_dir) | ||||
kernel_name = kernel_name.lower() | ||||
Thomas Kluyver
|
r17747 | |||
Thomas Kluyver
|
r19522 | destination = self._get_destination_dir(kernel_name, user=user) | ||
Thomas Kluyver
|
r16559 | |||
if replace and os.path.isdir(destination): | ||||
shutil.rmtree(destination) | ||||
shutil.copytree(source_dir, destination) | ||||
Thomas Kluyver
|
r19522 | def install_native_kernel_spec(self, user=False): | ||
Thomas Kluyver
|
r17747 | """Install the native kernel spec to the filesystem | ||
This allows a Python 3 frontend to use a Python 2 kernel, or vice versa. | ||||
The kernelspec will be written pointing to the Python executable on | ||||
which this is run. | ||||
Doug Blank
|
r19810 | If ``user`` is False, it will attempt to install into the systemwide | ||
Thomas Kluyver
|
r17747 | kernel registry. If the process does not have appropriate permissions, | ||
an :exc:`OSError` will be raised. | ||||
""" | ||||
Thomas Kluyver
|
r19522 | path = self._get_destination_dir(NATIVE_KERNEL_NAME, user=user) | ||
Thomas Kluyver
|
r17747 | os.makedirs(path, mode=0o755) | ||
with open(pjoin(path, 'kernel.json'), 'w') as f: | ||||
json.dump(self._native_kernel_dict, f, indent=1) | ||||
Thomas Kluyver
|
r19373 | copy_from = self._native_kernel_resource_dir | ||
for file in os.listdir(copy_from): | ||||
shutil.copy(pjoin(copy_from, file), path) | ||||
Thomas Kluyver
|
r17747 | return path | ||
Thomas Kluyver
|
r16276 | def find_kernel_specs(): | ||
Thomas Kluyver
|
r16262 | """Returns a dict mapping kernel names to resource directories.""" | ||
Thomas Kluyver
|
r16382 | return KernelSpecManager().find_kernel_specs() | ||
Thomas Kluyver
|
r16260 | |||
def get_kernel_spec(kernel_name): | ||||
Thomas Kluyver
|
r16262 | """Returns a :class:`KernelSpec` instance for the given kernel_name. | ||
Thomas Kluyver
|
r16260 | |||
Raises KeyError if the given kernel name is not found. | ||||
""" | ||||
Thomas Kluyver
|
r16559 | return KernelSpecManager().get_kernel_spec(kernel_name) | ||
Thomas Kluyver
|
r19522 | def install_kernel_spec(source_dir, kernel_name=None, user=False, replace=False): | ||
Thomas Kluyver
|
r17103 | return KernelSpecManager().install_kernel_spec(source_dir, kernel_name, | ||
Thomas Kluyver
|
r19522 | user, replace) | ||
Thomas Kluyver
|
r16559 | |||
install_kernel_spec.__doc__ = KernelSpecManager.install_kernel_spec.__doc__ | ||||
Thomas Kluyver
|
r17747 | |||
Thomas Kluyver
|
r19522 | def install_native_kernel_spec(user=False): | ||
return KernelSpecManager().install_native_kernel_spec(user=user) | ||||
Thomas Kluyver
|
r17747 | |||
Thomas Kluyver
|
r17748 | install_native_kernel_spec.__doc__ = KernelSpecManager.install_native_kernel_spec.__doc__ | ||