Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion papermill/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def print_papermill_version(ctx, param, value):
@click.option(
'--kernel',
'-k',
help='Name of kernel to run. Ignores kernel name in the notebook document metadata.',
help='Name of kernel to run. Overrides kernel name in the notebook document metadata.',
)
@click.option(
'--language',
Expand Down
19 changes: 17 additions & 2 deletions papermill/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@
from .utils import chdir


def _override_kernel_name_in_metadata(nb, kernel_name):
if not kernel_name:
return nb

kernelspec = nb.metadata.get('kernelspec', {})
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_override_kernel_name_in_metadata assumes nb.metadata['kernelspec'] is a mapping; if a notebook has kernelspec=None (or any non-dict), this will raise when assigning ['name']/['display_name']. Consider normalizing kernelspec to an empty dict when it's missing or not a dict-like object before writing into it.

Suggested change
kernelspec = nb.metadata.get('kernelspec', {})
kernelspec = nb.metadata.get('kernelspec')
if not kernelspec:
kernelspec = {}
elif not isinstance(kernelspec, dict):
# Normalize non-dict, mapping-like objects to a plain dict; fall back to empty dict otherwise.
try:
kernelspec = dict(kernelspec)
except TypeError:
kernelspec = {}

Copilot uses AI. Check for mistakes.
kernelspec['name'] = kernel_name
kernelspec['display_name'] = kernel_name
nb.metadata['kernelspec'] = kernelspec
return nb


def execute_notebook(
input_path,
output_path,
Expand Down Expand Up @@ -104,7 +115,7 @@ def execute_notebook(
engine_name=engine_name,
)

nb = prepare_notebook_metadata(nb, input_path, output_path, report_mode)
nb = prepare_notebook_metadata(nb, input_path, output_path, report_mode, kernel_name=kernel_name)
# clear out any existing error markers from previous papermill runs
nb = remove_error_markers(nb)

Expand Down Expand Up @@ -136,7 +147,7 @@ def execute_notebook(
return nb


def prepare_notebook_metadata(nb, input_path, output_path, report_mode=False):
def prepare_notebook_metadata(nb, input_path, output_path, report_mode=False, kernel_name=None):
"""Prepare metadata associated with a notebook and its cells

Parameters
Expand All @@ -149,6 +160,8 @@ def prepare_notebook_metadata(nb, input_path, output_path, report_mode=False):
Path to write executed notebook
report_mode : bool, optional
Flag to set report mode
kernel_name : str, optional
Name of kernel to persist in notebook metadata
"""
# Hide input if report-mode is set to True.
if report_mode:
Expand All @@ -157,6 +170,8 @@ def prepare_notebook_metadata(nb, input_path, output_path, report_mode=False):
cell.metadata['jupyter'] = cell.get('jupyter', {})
cell.metadata['jupyter']['source_hidden'] = True

nb = _override_kernel_name_in_metadata(nb, kernel_name)

# Record specified environment variable values.
nb.metadata.papermill['input_path'] = input_path
nb.metadata.papermill['output_path'] = output_path
Expand Down
1 change: 1 addition & 0 deletions papermill/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os

kernel_name = 'python3'
override_kernel_name = 'papermill-kernel-override'


def get_notebook_path(*args):
Expand Down
16 changes: 15 additions & 1 deletion papermill/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from .. import cli
from ..cli import _is_float, _is_int, _resolve_type, papermill
from . import get_notebook_path, kernel_name
from . import get_notebook_path, kernel_name, override_kernel_name


@pytest.mark.parametrize(
Expand Down Expand Up @@ -72,6 +72,20 @@ def test_is_int(value, expected):
assert (_is_int(value)) == expected


def test_kernel_override_updates_output_metadata():
runner = CliRunner()
with tempfile.TemporaryDirectory() as tmp_dir:
output_path = os.path.join(tmp_dir, f"{uuid.uuid4().hex}.ipynb")
result = runner.invoke(
papermill,
[get_notebook_path('blank-vscode.ipynb'), output_path, '--prepare-only', '-k', override_kernel_name],
)
assert result.exit_code == 0
output_nb = nbformat.read(output_path, as_version=4)
assert output_nb.metadata.kernelspec.name == override_kernel_name
assert output_nb.metadata.kernelspec.display_name == override_kernel_name


class TestCLI(unittest.TestCase):
default_execute_kwargs = dict(
input_path='input.ipynb',
Expand Down
Loading