diff --git a/nbformat/v4/nbformat.v4.schema.json b/nbformat/v4/nbformat.v4.schema.json index 670bbd35..68383b0f 100644 --- a/nbformat/v4/nbformat.v4.schema.json +++ b/nbformat/v4/nbformat.v4.schema.json @@ -141,6 +141,15 @@ "type": "boolean" } }, + "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" } } @@ -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", @@ -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" } } @@ -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 } @@ -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", diff --git a/tests/test4_with_cell_metadata.ipynb b/tests/test4_with_cell_metadata.ipynb new file mode 100644 index 00000000..eadb5350 --- /dev/null +++ b/tests/test4_with_cell_metadata.ipynb @@ -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 +} diff --git a/tests/test_validator.py b/tests/test_validator.py index ba7fd5ef..2ae0b526 100644 --- a/tests/test_validator.py +++ b/tests/test_validator.py @@ -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""" diff --git a/tests/v4_5_invalid_metadata.ipynb b/tests/v4_5_invalid_metadata.ipynb index d96de051..9a4796d4 100644 --- a/tests/v4_5_invalid_metadata.ipynb +++ b/tests/v4_5_invalid_metadata.ipynb @@ -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": [ {