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

Add ability to capture validation errors #1

Merged
merged 1 commit into from
Mar 2, 2022
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ __pycache__
.coverage
.cache
.python-version
.idea
34 changes: 27 additions & 7 deletions nbformat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class NBFormatError(ValueError):
""")


def reads(s, as_version, **kwargs):
def reads(s, as_version, capture_validation_error=None, **kwargs):
"""Read a notebook from a string and return the NotebookNode object as the given version.

The string can contain a notebook of any version.
Expand All @@ -64,6 +64,10 @@ def reads(s, as_version, **kwargs):
The version of the notebook format to return.
The notebook will be converted, if necessary.
Pass nbformat.NO_CONVERT to prevent conversion.
capture_validation_error : dict, optional
If provided, a key of "ValidationError" with a
value of the ValidationError instance will be added
to the dictionary.

Returns
-------
Expand All @@ -77,10 +81,12 @@ def reads(s, as_version, **kwargs):
validate(nb)
except ValidationError as e:
get_logger().error("Notebook JSON is invalid: %s", e)
if isinstance(capture_validation_error, dict):
capture_validation_error['ValidationError'] = e
return nb


def writes(nb, version=NO_CONVERT, **kwargs):
def writes(nb, version=NO_CONVERT, capture_validation_error=None, **kwargs):
"""Write a notebook to a string in a given format in the given nbformat version.

Any notebook format errors will be logged.
Expand All @@ -93,6 +99,10 @@ def writes(nb, version=NO_CONVERT, **kwargs):
The nbformat version to write.
If unspecified, or specified as nbformat.NO_CONVERT,
the notebook's own version will be used and no conversion performed.
capture_validation_error : dict, optional
If provided, a key of "ValidationError" with a
value of the ValidationError instance will be added
to the dictionary.

Returns
-------
Expand All @@ -107,10 +117,12 @@ def writes(nb, version=NO_CONVERT, **kwargs):
validate(nb)
except ValidationError as e:
get_logger().error("Notebook JSON is invalid: %s", e)
if isinstance(capture_validation_error, dict):
capture_validation_error['ValidationError'] = e
return versions[version].writes_json(nb, **kwargs)


def read(fp, as_version, **kwargs):
def read(fp, as_version, capture_validation_error=None, **kwargs):
"""Read a notebook from a file as a NotebookNode of the given version.

The string can contain a notebook of any version.
Expand All @@ -127,6 +139,10 @@ def read(fp, as_version, **kwargs):
The version of the notebook format to return.
The notebook will be converted, if necessary.
Pass nbformat.NO_CONVERT to prevent conversion.
capture_validation_error : dict, optional
If provided, a key of "ValidationError" with a
value of the ValidationError instance will be added
to the dictionary.

Returns
-------
Expand All @@ -138,12 +154,12 @@ def read(fp, as_version, **kwargs):
buf = fp.read()
except AttributeError:
with io.open(fp, encoding='utf-8') as f:
return reads(f.read(), as_version, **kwargs)
return reads(f.read(), as_version, capture_validation_error, **kwargs)

return reads(buf, as_version, **kwargs)
return reads(buf, as_version, capture_validation_error, **kwargs)


def write(nb, fp, version=NO_CONVERT, **kwargs):
def write(nb, fp, version=NO_CONVERT, capture_validation_error=None, **kwargs):
"""Write a notebook to a file in a given nbformat version.

The file-like object must accept unicode input.
Expand All @@ -160,8 +176,12 @@ def write(nb, fp, version=NO_CONVERT, **kwargs):
If nb is not this version, it will be converted.
If unspecified, or specified as nbformat.NO_CONVERT,
the notebook's own version will be used and no conversion performed.
capture_validation_error : dict, optional
If provided, a key of "ValidationError" with a
value of the ValidationError instance will be added
to the dictionary.
"""
s = writes(nb, version, **kwargs)
s = writes(nb, version, capture_validation_error, **kwargs)
if isinstance(s, bytes):
s = s.decode('utf8')

Expand Down
33 changes: 33 additions & 0 deletions nbformat/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
import unittest

from .base import TestsBase
from jsonschema import ValidationError

from tempfile import TemporaryDirectory
from ..reader import get_version
from ..validator import isvalid
from nbformat import read, current_nbformat, writes, write


Expand Down Expand Up @@ -64,3 +66,34 @@ def test_read_write_pathlib_object(self):
dest = pathlib.Path(td) / 'echidna.ipynb'
write(nb, dest)
assert os.path.isfile(dest)

def test_capture_validation_error(self):
"""Test that validation error can be captured on read() and write()"""
validation_error = {}
path = os.path.join(self._get_files_path(), u'invalid.ipynb')
nb = read(path, as_version=4, capture_validation_error=validation_error)
assert not isvalid(nb)
assert 'ValidationError' in validation_error
assert isinstance(validation_error['ValidationError'], ValidationError)

validation_error = {}
with TemporaryDirectory() as td:
dest = os.path.join(td, 'invalid.ipynb')
write(nb, dest, capture_validation_error=validation_error)
assert os.path.isfile(dest)
assert 'ValidationError' in validation_error
assert isinstance(validation_error['ValidationError'], ValidationError)

# Repeat with a valid notebook file
validation_error = {}
path = os.path.join(self._get_files_path(), u'test4.ipynb')
nb = read(path, as_version=4, capture_validation_error=validation_error)
assert isvalid(nb)
assert 'ValidationError' not in validation_error

validation_error = {}
with TemporaryDirectory() as td:
dest = os.path.join(td, 'test4.ipynb')
write(nb, dest, capture_validation_error=validation_error)
assert os.path.isfile(dest)
assert 'ValidationError' not in validation_error