Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Use fastjsonschema if installed and add tests #191

Merged
merged 7 commits into from
Oct 6, 2020
Merged
Show file tree
Hide file tree
Changes from 6 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
3 changes: 2 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ jobs:
- name: Install test dependencies
run: |
pip install --upgrade pip setuptools
pip install nbformat[test]
pip install .[test]
pip install .[fast]
pip install codecov
- name: Install nbformat
run: |
Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

[![codecov.io](https://codecov.io/github/jupyter/nbformat/coverage.svg?branch=master)](https://codecov.io/github/jupyter/nbformat?branch=master)
[![Code Health](https://landscape.io/github/jupyter/nbformat/master/landscape.svg?style=flat)](https://landscape.io/github/jupyter/nbformat/master)


![CI Tests](https://github.com/jupyter/nbformat/workflows/Run%20tests/badge.svg)

`nbformat` contains the reference implementation of the [Jupyter Notebook format][],
and Python APIs for working with notebooks.
Expand All @@ -20,6 +19,16 @@ From the command line:
pip install nbformat
```

## Using a different json schema validator

You can install and use [fastjsonschema](https://horejsek.github.io/python-fastjsonschema/) by running:

``` {.sourceCode .bash}
pip install nbformat[fast]
```

To enable fast validation with `fastjsonschema`, set the environment variable `NBFORMAT_VALIDATOR` to the value `fastjsonschema`.

## Python Version Support

This library supported python 2.7 and python 3.5+ for `4.x.x` releases. With python 2's end-of-life nbformat `5.x.x` is now python 3.5+ only. Support for 3.5 will be dropped when it's officially sunset by the python organization.
Expand Down
12 changes: 12 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,15 @@ bindings are available, and an in-memory store otherwise.
.. autoclass:: SQLiteSignatureStore

.. autoclass:: MemorySignatureStore

Optional fast validation
------------------------

You can install and use `fastjsonschema <https://horejsek.github.io/python-fastjsonschema/>`_ by running::

pip install nbformat[fast]


To enable fast validation with `fastjsonschema`, set the environment variable::

NBFORMAT_VALIDATOR="fastjsonschema"
4 changes: 4 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Changes in nbformat
In Development
==============

- Add optional support for using `fastjsonschema` as the JSON validation library.
To enable fast validation, install `fastjsonschema` and set the environment
variable `NBFORMAT_VALIDATOR` to the value `fastjsonschema`.

5.0.7
=====

Expand Down
82 changes: 82 additions & 0 deletions nbformat/json_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
"""
Common validator wrapper to provide a uniform usage of other schema validation
libraries.
"""

import os

import jsonschema
from jsonschema import Draft4Validator as _JsonSchemaValidator
from jsonschema import ValidationError

try:
import fastjsonschema
from fastjsonschema import JsonSchemaException as _JsonSchemaException
except ImportError:
fastjsonschema = None
_JsonSchemaException = ValidationError


class JsonSchemaValidator:
name = "jsonschema"

def __init__(self, schema):
self._schema = schema
self._default_validator = _JsonSchemaValidator(schema) # Default
self._validator = self._default_validator

def validate(self, data):
self._default_validator.validate(data)

def iter_errors(self, data, schema=None):
return self._default_validator.iter_errors(data, schema)


class FastJsonSchemaValidator(JsonSchemaValidator):
name = "fastjsonschema"

def __init__(self, schema):
self._validator = fastjsonschema.compile(schema)

def validate(self, data):
try:
self._validator(data)
except _JsonSchemaException as error:
raise ValidationError(error.message, schema_path=error.path)

def iter_errors(self, data, schema=None):
errors = []
validate_func = self._validator if schema is None else fastjsonschema.compile(schema)
try:
validate_func(data)
except _JsonSchemaException as error:
errors = [ValidationError(error.message, schema_path=error.path)]

return errors


_VALIDATOR_MAP = [
("fastjsonschema", fastjsonschema, FastJsonSchemaValidator),
("jsonschema", jsonschema, JsonSchemaValidator),
]
VALIDATORS = [item[0] for item in _VALIDATOR_MAP]


def _validator_for_name(validator_name):
if validator_name not in VALIDATORS:
raise ValueError("Invalid validator '{0}' value!\nValid values are: {1}".format(
validator_name, VALIDATORS))

for (name, module, validator_cls) in _VALIDATOR_MAP:
if module and validator_name == name:
return validator_cls


def get_current_validator():
MSeal marked this conversation as resolved.
Show resolved Hide resolved
"""
Return the default validator based on the value of an environment variable.
"""
validator_name = os.environ.get("NBFORMAT_VALIDATOR", "jsonschema")
return _validator_for_name(validator_name)
9 changes: 5 additions & 4 deletions nbformat/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
class TestsBase(unittest.TestCase):
"""Base tests class."""

def fopen(self, f, mode=u'r',encoding='utf-8'):
return io.open(os.path.join(self._get_files_path(), f), mode, encoding=encoding)
@classmethod
def fopen(cls, f, mode=u'r',encoding='utf-8'):
return io.open(os.path.join(cls._get_files_path(), f), mode, encoding=encoding)


def _get_files_path(self):
@classmethod
def _get_files_path(cls):
return os.path.dirname(__file__)
46 changes: 46 additions & 0 deletions nbformat/tests/many_tracebacks.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"ename": "NameError",
"evalue": "name 'iAmNotDefined' is not defined",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-22-56e1109ae320>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0miAmNotDefined\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mNameError\u001b[0m: name 'iAmNotDefined' is not defined"
]
}
],
"source": [
"# Imagine this cell called a function which runs things on a cluster and you have an error"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.5"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Loading