Skip to content

Commit

Permalink
Configuration errors are reported in the console and/or in Jupyter
Browse files Browse the repository at this point in the history
  • Loading branch information
mwouts committed Sep 11, 2020
1 parent b72d03f commit b387a3b
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 20 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
1.6.1-dev (2020-09-??)
----------------------

**Added**
- Configuration errors are reported in the console and/or in Jupyter (#613)

**Fixed**
- Freeze optional dependency on `sphinx-gallery` to version `~=0.7.0` (#614)

Expand Down
59 changes: 43 additions & 16 deletions jupytext/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@
from traitlets import Unicode, Float, Bool, Enum
from traitlets.config import Configurable
from traitlets.config.loader import JSONFileConfigLoader, PyFileConfigLoader
from traitlets.traitlets import TraitError
from .formats import (
NOTEBOOK_EXTENSIONS,
long_form_one_format,
long_form_multiple_formats,
rearrange_jupytext_metadata,
)


class JupytextConfigurationError(ValueError):
"""Error in the specification of the format for the text notebook"""


JUPYTEXT_CONFIG_FILES = [
"jupytext",
"jupytext.toml",
Expand Down Expand Up @@ -287,25 +293,28 @@ def find_jupytext_configuration_file(path, search_parent_dirs=True):
def load_jupytext_configuration_file(jupytext_config_file):
"""Read a Jupytext config file, and return a JupytextConfig object"""

if jupytext_config_file.endswith((".toml", "jupytext")):
import toml
try:
if jupytext_config_file.endswith((".toml", "jupytext")):
import toml

config = toml.load(jupytext_config_file)
return JupytextConfiguration(**config)
config = toml.load(jupytext_config_file)
return config

if jupytext_config_file.endswith((".yml", ".yaml")):
with open(jupytext_config_file) as stream:
config = yaml.safe_load(stream)
return JupytextConfiguration(**config)
if jupytext_config_file.endswith((".yml", ".yaml")):
with open(jupytext_config_file) as stream:
config = yaml.safe_load(stream)
return config

if jupytext_config_file.endswith(".py"):
return JupytextConfiguration(
**PyFileConfigLoader(jupytext_config_file).load_config()
)
if jupytext_config_file.endswith(".py"):
return PyFileConfigLoader(jupytext_config_file).load_config()

return JupytextConfiguration(
**JSONFileConfigLoader(jupytext_config_file).load_config()
)
return JSONFileConfigLoader(jupytext_config_file).load_config()
except (ValueError, NameError) as err:
raise JupytextConfigurationError(
"The Jupytext configuration file {} is incorrect: {}".format(
jupytext_config_file, err
)
)


def load_jupytext_config(nb_file):
Expand All @@ -315,7 +324,25 @@ def load_jupytext_config(nb_file):
return None
if os.path.isfile(nb_file) and os.path.samefile(config_file, nb_file):
return None
return load_jupytext_configuration_file(config_file)
config_values = load_jupytext_configuration_file(config_file)
try:
config = JupytextConfiguration(**config_values)
except TraitError as err:
raise JupytextConfigurationError(
"The Jupytext configuration file {} is incorrect: {}".format(
config_file, err
)
)
invalid_options = set(config_values).difference(dir(JupytextConfiguration()))
if any(invalid_options):
raise JupytextConfigurationError(
"The Jupytext configuration file {} is incorrect: options {} are not supported".format(
config_file, ",".join(invalid_options)
)
)
return config

return


def prepare_notebook_for_save(nbk, config, path):
Expand Down
10 changes: 7 additions & 3 deletions jupytext/contentsmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from .kernels import set_kernelspec_from_language
from .config import (
JupytextConfiguration,
JupytextConfigurationError,
preferred_format,
load_jupytext_config,
prepare_notebook_for_save,
Expand Down Expand Up @@ -435,9 +436,12 @@ def get_config(self, path, use_cache=False):
self.cached_config.timestamp + timedelta(seconds=1) < datetime.now()
)
):
self.cached_config.path = parent_dir
self.cached_config.timestamp = datetime.now()
self.cached_config.config = load_jupytext_config(parent_dir)
try:
self.cached_config.path = parent_dir
self.cached_config.timestamp = datetime.now()
self.cached_config.config = load_jupytext_config(parent_dir)
except JupytextConfigurationError as err:
raise HTTPError(400, "{}".format(err))

return self.cached_config.config or self

Expand Down
39 changes: 38 additions & 1 deletion tests/test_cm_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import mock
from nbformat.v4.nbbase import new_notebook, new_markdown_cell, new_code_cell
from nbformat import read
import pytest
from tornado.web import HTTPError
import jupytext

SAMPLE_NOTEBOOK = new_notebook(
Expand Down Expand Up @@ -64,4 +66,39 @@ def test_pairing_through_config_leaves_ipynb_unmodified(tmpdir):
assert py_file.isfile()

nb = read(nb_file, as_version=4)
assert 'jupytext' not in nb.metadata
assert "jupytext" not in nb.metadata


@pytest.mark.parametrize(
"cfg_file,cfg_text",
[
# Should be false, not False
("jupytext.toml", "hide_notebook_metadata = False"),
("jupytext.toml", 'hide_notebook_metadata = "False"'),
("jupytext.toml", "not_a_jupytext_option = true"),
("jupytext.json", '{"notebook_metadata_filter":"-all",}'),
(".jupytext.py", "c.not_a_jupytext_option = True"),
(".jupytext.py", "c.hide_notebook_metadata = true"),
],
)
def test_incorrect_config_message(tmpdir, cfg_file, cfg_text):
cm = jupytext.TextFileContentsManager()
cm.root_dir = str(tmpdir)

cfg_file = tmpdir.join(cfg_file)
cfg_file.write(cfg_text)

tmpdir.join("empty.ipynb").write("{}")

expected_message = "The Jupytext configuration file {} is incorrect".format(
cfg_file
)

# This is a regexp so we have to escape the path separator on Windows
expected_message = expected_message.replace("\\", "\\\\")

with pytest.raises(HTTPError, match=expected_message):
cm.get("empty.ipynb", type="notebook", content=False)

with pytest.raises(HTTPError, match=expected_message):
cm.save(dict(type="notebook", content=SAMPLE_NOTEBOOK), "notebook.ipynb")
2 changes: 2 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from jupytext.config import (
find_jupytext_configuration_file,
load_jupytext_configuration_file,
JupytextConfiguration,
)


Expand Down Expand Up @@ -83,6 +84,7 @@ def test_load_jupytext_configuration_file(tmpdir, config_file):
)

config = load_jupytext_configuration_file(str(full_config_path))
config = JupytextConfiguration(**config)
assert config.default_jupytext_formats == "ipynb,py:percent"
assert config.default_notebook_metadata_filter == "all"
assert config.default_cell_metadata_filter == "all"

0 comments on commit b387a3b

Please sign in to comment.