Source code for kedro.framework.cli.jupyter

"""A collection of helper functions to integrate with Jupyter/IPython
and CLI commands for working with Kedro catalog.
"""
from __future__ import annotations

import json
import os
import shutil
from pathlib import Path
from typing import Any

import click

from kedro.framework.cli.utils import (
    KedroCliError,
    _check_module_importable,
    env_option,
    forward_command,
    python_call,
)
from kedro.framework.project import validate_settings
from kedro.framework.startup import ProjectMetadata

CONVERT_ALL_HELP = """Extract the nodes from all notebooks in the Kedro project directory,
including sub-folders."""

OVERWRITE_HELP = """If Python file already exists for the equivalent notebook,
overwrite its contents."""


[docs] class JupyterCommandGroup(click.Group): """A custom class for ordering the `kedro jupyter` command groups"""
[docs] def list_commands(self, ctx: click.Context) -> list[str]: """List commands according to a custom order""" return ["setup", "notebook", "lab", "convert"]
@click.group(name="Kedro") def jupyter_cli() -> None: # pragma: no cover pass @jupyter_cli.group(cls=JupyterCommandGroup) def jupyter() -> None: """Open Jupyter Notebook / Lab with project specific variables loaded, or convert notebooks into Kedro code. """ @forward_command(jupyter, "setup", forward_help=True) @click.pass_obj # this will pass the metadata as first argument def setup(metadata: ProjectMetadata, /, args: Any, **kwargs: Any) -> None: # noqa: unused-argument """Initialise the Jupyter Kernel for a kedro project.""" _check_module_importable("ipykernel") validate_settings() kernel_name = f"kedro_{metadata.package_name}" kernel_path = _create_kernel(kernel_name, f"Kedro ({metadata.package_name})") click.secho(f"\nThe kernel has been created successfully at {kernel_path}") @forward_command(jupyter, "notebook", forward_help=True) @env_option @click.pass_obj # this will pass the metadata as first argument def jupyter_notebook( metadata: ProjectMetadata, /, env: str, args: Any, **kwargs: Any, ) -> None: # noqa: unused-argument """Open Jupyter Notebook with project specific variables loaded.""" _check_module_importable("notebook") validate_settings() kernel_name = f"kedro_{metadata.package_name}" _create_kernel(kernel_name, f"Kedro ({metadata.package_name})") if env: os.environ["KEDRO_ENV"] = env python_call( "jupyter", ["notebook", f"--MultiKernelManager.default_kernel_name={kernel_name}"] + list(args), ) @forward_command(jupyter, "lab", forward_help=True) @env_option @click.pass_obj # this will pass the metadata as first argument def jupyter_lab( metadata: ProjectMetadata, /, env: str, args: Any, **kwargs: Any, ) -> None: # noqa: unused-argument """Open Jupyter Lab with project specific variables loaded.""" _check_module_importable("jupyterlab") validate_settings() kernel_name = f"kedro_{metadata.package_name}" _create_kernel(kernel_name, f"Kedro ({metadata.package_name})") if env: os.environ["KEDRO_ENV"] = env python_call( "jupyter", ["lab", f"--MultiKernelManager.default_kernel_name={kernel_name}"] + list(args), ) def _create_kernel(kernel_name: str, display_name: str) -> str: """Creates an IPython kernel for the kedro project. If one with the same kernel_name exists already it will be replaced. Installs the default IPython kernel (which points towards `sys.executable`) and customises it to make the launch command load the kedro extension. This is equivalent to the method recommended for creating a custom IPython kernel on the CLI: https://ipython.readthedocs.io/en/stable/install/kernel_install.html. On linux this creates a directory ~/.local/share/jupyter/kernels/{kernel_name} containing kernel.json, logo-32x32.png, logo-64x64.png and logo-svg.svg. An example kernel.json looks as follows: { "argv": [ "/Users/antony_milne/miniconda3/envs/spaceflights/bin/python", "-m", "ipykernel_launcher", "-f", "{connection_file}", "--ext", "kedro.ipython" ], "display_name": "Kedro (spaceflights)", "language": "python", "metadata": { "debugger": false } } Args: kernel_name: Name of the kernel to create. display_name: Kernel name as it is displayed in the UI. Returns: String of the path of the created kernel. Raises: KedroCliError: When kernel cannot be setup. """ # These packages are required by jupyter lab and notebook, which we have already # checked are importable, so we don't run _check_module_importable on them. from ipykernel.kernelspec import install try: # Install with user=True rather than system-wide to minimise footprint and # ensure that we have permissions to write there. Under the hood this calls # jupyter_client.KernelSpecManager.install_kernel_spec, which automatically # removes an old kernel spec if it already exists. kernel_path = install( user=True, kernel_name=kernel_name, display_name=display_name, ) kernel_json = Path(kernel_path) / "kernel.json" kernel_spec = json.loads(kernel_json.read_text(encoding="utf-8")) kernel_spec["argv"].extend(["--ext", "kedro.ipython"]) # indent=1 is to match the default ipykernel style (see # ipykernel.write_kernel_spec). kernel_json.write_text(json.dumps(kernel_spec, indent=1), encoding="utf-8") kedro_ipython_dir = Path(__file__).parents[2] / "ipython" shutil.copy(kedro_ipython_dir / "logo-32x32.png", kernel_path) shutil.copy(kedro_ipython_dir / "logo-64x64.png", kernel_path) shutil.copy(kedro_ipython_dir / "logo-svg.svg", kernel_path) except Exception as exc: raise KedroCliError( f"Cannot setup kedro kernel for Jupyter.\nError: {exc}" ) from exc return kernel_path