From ba5144f7a172d75a9a6e3a569fdb8a0b161bbe63 2014-08-30 09:25:21 From: Thomas Kluyver Date: 2014-08-30 09:25:21 Subject: [PATCH] Make installation of native kernelspec explicit Instead of creating a kernelspec directory matching the running frontend, return its spec without writing to disk. Adds a command 'ipython kernelspec install-self' to write a kernelspec for the current version of Python. --- diff --git a/IPython/kernel/kernelspec.py b/IPython/kernel/kernelspec.py index f129886..46edf99 100644 --- a/IPython/kernel/kernelspec.py +++ b/IPython/kernel/kernelspec.py @@ -100,51 +100,60 @@ class KernelSpecManager(HasTraits): self.user_kernel_dir, ] - def _make_native_kernel_dir(self): + @property + def _native_kernel_dict(self): """Makes a kernel directory for the native kernel. The native kernel is the kernel using the same Python runtime as this process. This will put its informatino in the user kernels directory. """ - path = pjoin(self.user_kernel_dir, NATIVE_KERNEL_NAME) - os.makedirs(path, mode=0o755) - with open(pjoin(path, 'kernel.json'), 'w') as f: - json.dump({'argv':[NATIVE_KERNEL_NAME, '-c', - 'from IPython.kernel.zmq.kernelapp import main; main()', - '-f', '{connection_file}'], - 'display_name': 'IPython (Python %d)' % (3 if PY3 else 2), - 'language': 'python', - 'codemirror_mode': {'name': 'ipython', - 'version': sys.version_info[0]}, - }, - f, indent=1) - # TODO: Copy icons into directory - return path - + return {'argv':[sys.executable, '-c', + 'from IPython.kernel.zmq.kernelapp import main; main()', + '-f', '{connection_file}'], + 'display_name': 'IPython (Python %d)' % (3 if PY3 else 2), + 'language': 'python', + 'codemirror_mode': {'name': 'ipython', + 'version': sys.version_info[0]}, + } + + @property + def _native_kernel_resource_dir(self): + # TODO: This may be different when we actually have any resources + return os.path.dirname(__file__) + 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)) - - if NATIVE_KERNEL_NAME not in d: - d[NATIVE_KERNEL_NAME] = self._make_native_kernel_dir() + + d[NATIVE_KERNEL_NAME] = self._native_kernel_resource_dir return d # TODO: Caching? - + def get_kernel_spec(self, kernel_name): """Returns a :class:`KernelSpec` instance for the given kernel_name. Raises :exc:`NoSuchKernel` if the given kernel name is not found. """ - if kernel_name == 'python': - kernel_name = NATIVE_KERNEL_NAME + if kernel_name in {'python', NATIVE_KERNEL_NAME}: + return KernelSpec(self._native_kernel_resource_dir, **self._native_kernel_dict) + d = self.find_kernel_specs() try: resource_dir = d[kernel_name.lower()] except KeyError: raise NoSuchKernel(kernel_name) return KernelSpec.from_resource_dir(resource_dir) + + def _get_destination_dir(self, kernel_name, system=False): + if system: + if SYSTEM_KERNEL_DIRS: + return os.path.join(SYSTEM_KERNEL_DIRS[-1], kernel_name) + else: + raise EnvironmentError("No system kernel directory is available") + else: + return os.path.join(self.user_kernel_dir, kernel_name) def install_kernel_spec(self, source_dir, kernel_name=None, system=False, replace=False): @@ -164,20 +173,32 @@ class KernelSpecManager(HasTraits): if not kernel_name: kernel_name = os.path.basename(source_dir) kernel_name = kernel_name.lower() - - if system: - if SYSTEM_KERNEL_DIRS: - destination = os.path.join(SYSTEM_KERNEL_DIRS[-1], kernel_name) - else: - raise EnvironmentError("No system kernel directory is available") - else: - destination = os.path.join(self.user_kernel_dir, kernel_name) + + destination = self._get_destination_dir(kernel_name, system=system) if replace and os.path.isdir(destination): shutil.rmtree(destination) shutil.copytree(source_dir, destination) + def install_native_kernel_spec(self, system=False): + """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. + + If ``system`` is True, it will attempt to install into the systemwide + kernel registry. If the process does not have appropriate permissions, + an :exc:`OSError` will be raised. + """ + path = self._get_destination_dir(NATIVE_KERNEL_NAME, system=system) + os.makedirs(path, mode=0o755) + with open(pjoin(path, 'kernel.json'), 'w') as f: + json.dump(self._native_kernel_dict, f, indent=1) + # TODO: Copy icons into directory + return path + def find_kernel_specs(): """Returns a dict mapping kernel names to resource directories.""" return KernelSpecManager().find_kernel_specs() @@ -194,3 +215,8 @@ def install_kernel_spec(source_dir, kernel_name=None, system=False, replace=Fals system, replace) install_kernel_spec.__doc__ = KernelSpecManager.install_kernel_spec.__doc__ + +def install_native_kernel_spec(self, system=False): + return KernelSpecManager().install_native_kernel_spec(system=system) + +install_native_kernel_spec.__doc__ = KernelSpecManager.install_native_kernel_spec.__doc__ \ No newline at end of file diff --git a/IPython/kernel/kernelspecapp.py b/IPython/kernel/kernelspecapp.py index 0de87eb..be94f3f 100644 --- a/IPython/kernel/kernelspecapp.py +++ b/IPython/kernel/kernelspecapp.py @@ -91,15 +91,46 @@ class InstallKernelSpec(BaseIPythonApplication): self.exit(1) raise +class InstallNativeKernelSpec(BaseIPythonApplication): + description = """Install the native kernel spec directory for this Python.""" + kernel_spec_manager = Instance(KernelSpecManager) + + def _kernel_spec_manager_default(self): + return KernelSpecManager(ipython_dir=self.ipython_dir) + + system = Bool(False, config=True, + help=""" + Try to install the kernel spec to the systemwide directory instead of + the per-user directory. + """ + ) + + # Not all of the base aliases are meaningful (e.g. profile) + aliases = {k: base_aliases[k] for k in ['ipython-dir', 'log-level']} + flags = {'system': ({'InstallOwnKernelSpec': {'system': True}}, + "Install to the systemwide kernel registry"), + 'debug': base_flags['debug'], + } + + def start(self): + try: + self.kernel_spec_manager.install_native_kernel_spec(system=self.system) + except OSError as e: + if e.errno == errno.EACCES: + print("Permission denied") + self.exit(1) + raise + class KernelSpecApp(Application): name = "ipython kernelspec" description = """Manage IPython kernel specifications.""" - subcommands = Dict(dict( - list = (ListKernelSpecs, ListKernelSpecs.description.splitlines()[0]), - install = (InstallKernelSpec, InstallKernelSpec.description.splitlines()[0]) - )) - + subcommands = Dict({ + 'list': (ListKernelSpecs, ListKernelSpecs.description.splitlines()[0]), + 'install': (InstallKernelSpec, InstallKernelSpec.description.splitlines()[0]), + 'install-self': (InstallNativeKernelSpec, InstallNativeKernelSpec.description.splitlines()[0]), + }) + aliases = {} flags = {}