From 129c0eee4d1c2c77ead871c70411f35ff9c2cf52 Mon Sep 17 00:00:00 2001 From: Zach Sailer Date: Thu, 11 Aug 2022 14:50:10 -0700 Subject: [PATCH 1/3] add a simple CLI to validate a schema --- jupyter_events/cli.py | 48 ++++++++++++++++++++++++++++++++++++ jupyter_events/schema.py | 4 +-- jupyter_events/validators.py | 5 ++-- pyproject.toml | 7 ++++++ 4 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 jupyter_events/cli.py diff --git a/jupyter_events/cli.py b/jupyter_events/cli.py new file mode 100644 index 0000000..7875da0 --- /dev/null +++ b/jupyter_events/cli.py @@ -0,0 +1,48 @@ +import json + +import click +from jsonschema import ValidationError +from rich.console import Console +from rich.json import JSON +from rich.markup import escape +from rich.padding import Padding + +from jupyter_events.schema import EventSchema + +console = Console() + + +@click.group() +def main(): + pass + + +@click.command() +@click.argument("schema") +def validate(schema): + """Validate a SCHEMA against Jupyter Event's meta schema. + + SCHEMA can be a JSON/YAML string or filepath to a schema. + """ + console.rule("Validating the following schema") + # Soft load the schema without validating. + _schema = EventSchema._load_schema(schema) + # Print what was found. + schema_json = JSON(json.dumps(_schema)) + console.print(Padding(schema_json, (1, 0, 1, 4))) + # Now validate this schema against the meta-schema. + console.rule("Results") + try: + EventSchema(_schema) + out = Padding( + "[green]\u2714[white] Nice work! This schema is valid.", (1, 0, 1, 0) + ) + console.print(out) + except ValidationError as err: + console.print("[red]\u274c [white]The schema failed to validate.\n") + console.print("We found the following error with your schema:") + out = escape(str(err)) + console.print(Padding(out, (1, 0, 1, 4))) + + +main.add_command(validate) diff --git a/jupyter_events/schema.py b/jupyter_events/schema.py index a0a5f57..c1859c3 100644 --- a/jupyter_events/schema.py +++ b/jupyter_events/schema.py @@ -49,9 +49,7 @@ def __init__( self._schema = _schema def __repr__(self): - out = f"Validator class: {self._validator.__class__.__name__}\n" - out += f"Schema: {json.dumps(self._schema, indent=2)}" - return out + return json.dumps(self._schema, indent=2) @staticmethod def _load_schema(schema: Union[dict, str, PurePath]) -> dict: diff --git a/jupyter_events/validators.py b/jupyter_events/validators.py index e64d671..df230ef 100644 --- a/jupyter_events/validators.py +++ b/jupyter_events/validators.py @@ -27,10 +27,11 @@ def validate_schema(schema: dict): except ValidationError as err: reserved_property_msg = " does not match '^(?!__.*)'" if reserved_property_msg in str(err): - bad_property = str(err)[: -(len(reserved_property_msg))] + idx = str(err).find(reserved_property_msg) + bad_property = str(err)[:idx].strip() raise ValidationError( f"{bad_property} is an invalid property name because it " "starts with `__`. Properties starting with 'dunder' " - "are reserved for Jupyter Events." + "are reserved as special meta-fields for Jupyter Events to use." ) raise err diff --git a/pyproject.toml b/pyproject.toml index d4a24b4..5e43b6f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,9 @@ file = 'COPYING.md' [project.urls] Homepage = "http://jupyter.org" +[project.scripts] +jupyter-events = "jupyter_events.cli:main" + [project.optional-dependencies] test = [ "coverage", @@ -47,6 +50,10 @@ test = [ "pytest-cov", "pytest>=6.0", ] +cli = [ + "click", + "rich" +] [tool.hatch.version] path = "jupyter_events/_version.py" From 7a4145957259f8a6f570ef26358639c469284305 Mon Sep 17 00:00:00 2001 From: Zach Sailer Date: Thu, 11 Aug 2022 14:58:31 -0700 Subject: [PATCH 2/3] add documentation about CLI --- docs/user_guide/defining-schema.md | 70 ++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/docs/user_guide/defining-schema.md b/docs/user_guide/defining-schema.md index 29a4d98..14203a7 100644 --- a/docs/user_guide/defining-schema.md +++ b/docs/user_guide/defining-schema.md @@ -39,3 +39,73 @@ required: - thing - user ``` + +## Checking if a schema is valid + +When authoring a schema, how do you check if you schema is following the expected form? Jupyter Events offers a simple command line tool to validate your schema against its Jupyter Events metaschema. + +First, install the CLI: + +``` +pip install jupyter_events[cli] +``` + +Then, run the CLI against your schema: + +``` +jupyter events validate path/to/my_schema.json +``` + +The output will look like this, if it passes: + +``` +──────────────────────────────────── Validating the following schema ──────────────────────────────────── + + { + "$id": "http://event.jupyter.org/test", + "version": 1, + "title": "Simple Test Schema", + "description": "A simple schema for testing\n", + "type": "object", + "properties": { + "prop": { + "title": "Test Property", + "description": "Test property.", + "type": "string" + } + } + } + +──────────────────────────────────────────────── Results ──────────────────────────────────────────────── + +✔ Nice work! This schema is valid. +``` + +or this if fails: + +``` +──────────────────────────────────── Validating the following schema ──────────────────────────────────── + + { + "$id": "http://event.jupyter.org/test", + "version": 1, + "title": "Simple Test Schema", + "description": "A simple schema for testing\n", + "type": "object", + "properties": { + "__badName": { + "title": "Test Property", + "description": "Test property.", + "type": "string" + } + } + } + +──────────────────────────────────────────────── Results ──────────────────────────────────────────────── +❌ The schema failed to validate. + +We found the following error with your schema: + + '__badName' is an invalid property name because it starts with `__`. Properties starting with + 'dunder' are reserved as special meta-fields for Jupyter Events to use. +``` From 0101fcb9c93a311a1d18bfbf04e43ee667bda8e7 Mon Sep 17 00:00:00 2001 From: Zach Sailer Date: Mon, 29 Aug 2022 08:37:46 -0700 Subject: [PATCH 3/3] update after latest changes --- jupyter_events/cli.py | 22 ++++++++++++++++++---- jupyter_events/logger.py | 5 +++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/jupyter_events/cli.py b/jupyter_events/cli.py index 7875da0..a0d5091 100644 --- a/jupyter_events/cli.py +++ b/jupyter_events/cli.py @@ -1,4 +1,5 @@ import json +import pathlib import click from jsonschema import ValidationError @@ -6,14 +7,22 @@ from rich.json import JSON from rich.markup import escape from rich.padding import Padding +from rich.style import Style -from jupyter_events.schema import EventSchema +from jupyter_events.schema import EventSchema, EventSchemaLoadingError console = Console() @click.group() def main(): + """A simple CLI tool to quickly validate JSON schemas against + Jupyter Event's custom validator. + + You can see Jupyter Event's meta-schema here: + + https://raw.githubusercontent.com/jupyter/jupyter_events/main/jupyter_events/schemas/event-metaschema.yml + """ pass @@ -24,21 +33,26 @@ def validate(schema): SCHEMA can be a JSON/YAML string or filepath to a schema. """ - console.rule("Validating the following schema") + console.rule("Validating the following schema", style=Style(color="blue")) # Soft load the schema without validating. - _schema = EventSchema._load_schema(schema) + try: + _schema = EventSchema._load_schema(schema) + except EventSchemaLoadingError: + schema_path = pathlib.Path(schema) + _schema = EventSchema._load_schema(schema_path) # Print what was found. schema_json = JSON(json.dumps(_schema)) console.print(Padding(schema_json, (1, 0, 1, 4))) # Now validate this schema against the meta-schema. - console.rule("Results") try: EventSchema(_schema) + console.rule("Results", style=Style(color="green")) out = Padding( "[green]\u2714[white] Nice work! This schema is valid.", (1, 0, 1, 0) ) console.print(out) except ValidationError as err: + console.rule("Results", style=Style(color="red")) console.print("[red]\u274c [white]The schema failed to validate.\n") console.print("We found the following error with your schema:") out = escape(str(err)) diff --git a/jupyter_events/logger.py b/jupyter_events/logger.py index ff0f55d..6e72414 100644 --- a/jupyter_events/logger.py +++ b/jupyter_events/logger.py @@ -245,10 +245,11 @@ def add_listener( Parameters ---------- + modified: bool + If True (default), listens to the data after it has been mutated/modified + by the list of modifiers. schema_id: str $id of the schema - version: str - The schema version listener: Callable A callable function/method that executes when the named event occurs. """