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
50 changes: 49 additions & 1 deletion nbformat/v4/nbformat.v4.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,15 @@
"type": "boolean"
}
},
"editable": {
"description": "Whether the cell is editable.",
"type": "boolean"
},
"deletable": {
"description": "Whether the cell is deletable.",
"type": "boolean"
},

@rgbkrk rgbkrk Nov 7, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Each of these new optional boolean fields need to have "default": true since the absence of these fields would likely be interpreted as false. That or it ends up in ambiguous territory even though we'd probably want to assume these are true in notebooks.

Something like:

"editable": {
  "description": "Whether the cell is editable.",
  "type": "boolean",
  "default": true
},

for each

"slideshow": { "$ref": "#/definitions/misc/metadata_slideshow" },
"name": { "$ref": "#/definitions/misc/metadata_name" },
"tags": { "$ref": "#/definitions/misc/metadata_tags" }
}
Expand All @@ -167,6 +176,15 @@
"properties": {
"name": { "$ref": "#/definitions/misc/metadata_name" },
"tags": { "$ref": "#/definitions/misc/metadata_tags" },
"editable": {
"description": "Whether the cell is editable.",
"type": "boolean"
},
"deletable": {
"description": "Whether the cell is deletable.",
"type": "boolean"
},
"slideshow": { "$ref": "#/definitions/misc/metadata_slideshow" },
"jupyter": {
"description": "Official Jupyter Metadata for Markdown Cells",
"type": "object",
Expand Down Expand Up @@ -256,6 +274,15 @@
"description": "Whether the cell's output is scrolled, unscrolled, or autoscrolled.",
"enum": [true, false, "auto"]
},
"editable": {
"description": "Whether the cell is editable.",
"type": "boolean"
},
"deletable": {
"description": "Whether the cell is deletable.",
"type": "boolean"
},
"slideshow": { "$ref": "#/definitions/misc/metadata_slideshow" },
"name": { "$ref": "#/definitions/misc/metadata_name" },
"tags": { "$ref": "#/definitions/misc/metadata_tags" }
}
Expand Down Expand Up @@ -291,7 +318,16 @@
"type": "object",
"properties": {
"name": { "$ref": "#/definitions/misc/metadata_name" },
"tags": { "$ref": "#/definitions/misc/metadata_tags" }
"tags": { "$ref": "#/definitions/misc/metadata_tags" },
"editable": {
"description": "Whether the cell is editable.",
"type": "boolean"
},
"deletable": {
"description": "Whether the cell is deletable.",
"type": "boolean"
},
"slideshow": { "$ref": "#/definitions/misc/metadata_slideshow" }
},
"additionalProperties": true
}
Expand Down Expand Up @@ -420,6 +456,18 @@
"pattern": "^[^,]+$"
}
},
"metadata_slideshow": {
"description": "Slideshow-specific cell metadata.",
"type": "object",
"additionalProperties": true,
"properties": {
"slide_type": {
"description": "Type of slide.",
"type": "string",
"enum": ["slide", "subslide", "fragment", "skip", "notes", "-", ""]
}
}
},
"attachments": {
"description": "Media attachments (e.g. inline images), stored as mimebundle keyed by filename.",
"type": "object",
Expand Down
72 changes: 72 additions & 0 deletions tests/test4_with_cell_metadata.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"cells": [
{
"id": "cell1",
"cell_type": "code",
"execution_count": 1,
"metadata": {
"editable": true,
"deletable": false,
"slideshow": {
"slide_type": "slide"
},
"tags": [
"solution"
]
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Hello, World!\n"
]
}
],
"source": [
"print('Hello, World!')"
]
},
{
"id": "cell2",
"cell_type": "markdown",
"metadata": {
"editable": false,
"deletable": true,
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"# Test Markdown Cell"
]
},
{
"id": "cell3",
"cell_type": "raw",
"metadata": {
"editable": true,
"slideshow": {
"slide_type": "fragment"
},
"tags": [
"raw-data"
]
},
"source": [
"Raw text content"
]
}
],
"metadata": {
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
10 changes: 10 additions & 0 deletions tests/test_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,16 @@ def test_nb4jupyter_metadata_timings(validator_name):
assert isvalid(nb)


@pytest.mark.parametrize("validator_name", VALIDATORS)
def test_nb4_with_cell_metadata(validator_name):
"""Test that a notebook with editable, deletable, and slideshow metadata passes validation"""
set_validator(validator_name)
with TestsBase.fopen("test4_with_cell_metadata.ipynb", "r") as f:
nb = read(f, as_version=4)
validate(nb)
assert isvalid(nb)


@pytest.mark.parametrize("validator_name", VALIDATORS)
def test_invalid(validator_name):
"""Test than an invalid notebook does not pass validation"""
Expand Down
3 changes: 2 additions & 1 deletion tests/v4_5_invalid_metadata.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"name": 12345,
"tags": "BAD: should be list of str",
"jupyter": "BAD: should be dict",
"execution": "BAD: should be dict"
"execution": "BAD: should be dict",
"slideshow": "BAD: should be dict"
},
"outputs": [
{
Expand Down
Loading