Skip to content

Commit

Permalink
Merge pull request #34 from moggers87/23-config-better-errors
Browse files Browse the repository at this point in the history
Better errors from config objects
  • Loading branch information
moggers87 committed Jul 22, 2018
2 parents 2982423 + 9393bf4 commit 310b70c
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 17 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ Unreleased
----------

- Added vesrioneer
- Fix bug where `exhibit serve` was not serving files with extension stripping
enabled
- Fix bug where ``exhibit serve`` was not serving files with extension
stripping enabled
- ``KeyError``s raised by ``Config`` now display the path of the node they are
attached to, making debuging missing keys far easier.

.. _zero-zero-three:

Expand Down
34 changes: 27 additions & 7 deletions exhibition/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,23 @@ class Config:
If a key cannot be found in this instance, the parent :class:`Config` will
be searched (and its parent, etc.)
"""
def __init__(self, data=None, parent=None):
def __init__(self, data=None, parent=None, node=None):
"""
:param data:
Can be one of a string, a file-like object, a dict-like object, or
``None``. The first two will be assumed as YAML
:param parent:
Parent :class:`Config` or ``None`` if this is the root configuration object
Parent :class:`Config` or ``None`` if this is the root
configuration object
:param node:
The node that this object to bound to, or ``None`` if it is the
root configuration object
"""
assert (parent is None) == (node is None), \
"Either both parent and node are defined or they are both None"

self.parent = parent
self.node = node
self._base_config = {}

if data:
Expand Down Expand Up @@ -88,14 +96,24 @@ def from_path(cls, path):

return obj

def get_name(self):
if self.node is None:
return SITE_YAML_PATH
else:
return self.node.full_path

def __getitem__(self, key):
try:
return self._base_config[key]
except KeyError:
except KeyError as exp:
exp_str = "Could not find %s in %s" % (key, self.get_name())
if self.parent is None:
raise
raise KeyError(exp_str) from exp
else:
return self.parent[key]
try:
return self.parent[key]
except KeyError as exp_parent:
raise KeyError(exp_str) from exp

def __setitem__(self, key, value):
self._base_config[key] = value
Expand Down Expand Up @@ -143,7 +161,9 @@ def copy(self):
return klass(self._base_config.copy(), self.parent)

def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self._base_config.keys())
return "<%s: %s: %s>" % (self.__class__.__name__,
self.get_name(),
self._base_config.keys())


class Node:
Expand Down Expand Up @@ -180,7 +200,7 @@ def __init__(self, path, parent, meta=None):

self.is_leaf = self.path_obj.is_file()

self._meta = Config({}, getattr(self.parent, "meta", None))
self._meta = Config({}, parent=getattr(self.parent, "meta", None), node=self.parent)
if meta:
self._meta.update(meta)

Expand Down
55 changes: 47 additions & 8 deletions exhibition/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@

from io import StringIO
from tempfile import NamedTemporaryFile
from unittest import TestCase
from unittest import TestCase, mock

from ruamel.yaml import YAML

from exhibition.main import Config
from exhibition.main import Config, SITE_YAML_PATH


YAML_DATA = """
Expand Down Expand Up @@ -90,7 +90,7 @@ def test_getitem(self):

def test_getitem_with_parent(self):
parent = Config({"test": True})
settings = Config(YAML_DATA, parent=parent)
settings = Config(YAML_DATA, parent=parent, node=mock.Mock())

self.assertEqual(settings["sitename"], "bob")
self.assertEqual(settings["thingy"], ["one", "two", "three"])
Expand All @@ -111,7 +111,7 @@ def test_keys(self):

def test_keys_with_parent(self):
parent = Config({"test": True})
settings = Config(YAML_DATA, parent=parent)
settings = Config(YAML_DATA, parent=parent, node=mock.Mock())

self.assertCountEqual(list(settings.keys()), ["sitename", "thingy", "test"])
self.assertCountEqual(list(parent.keys()), ["test"])
Expand All @@ -123,7 +123,7 @@ def test_values(self):

def test_values_with_parent(self):
parent = Config({"test": True})
settings = Config(YAML_DATA, parent=parent)
settings = Config(YAML_DATA, parent=parent, node=mock.Mock())

self.assertCountEqual(list(settings.values()), ["bob", ["one", "two", "three"], True])
self.assertCountEqual(list(parent.values()), [True])
Expand All @@ -136,7 +136,7 @@ def test_items(self):

def test_items_with_parent(self):
parent = Config({"test": True})
settings = Config(YAML_DATA, parent=parent)
settings = Config(YAML_DATA, parent=parent, node=mock.Mock())

self.assertCountEqual(list(settings.items()),
[("sitename", "bob"), ("thingy", ["one", "two", "three"]),
Expand All @@ -152,7 +152,7 @@ def test_contains(self):

def test_contains_with_parent(self):
parent = Config({"test": True})
settings = Config(YAML_DATA, parent=parent)
settings = Config(YAML_DATA, parent=parent, node=mock.Mock())

self.assertIn("test", settings)
self.assertIn("sitename", settings)
Expand All @@ -169,7 +169,46 @@ def test_len(self):

def test_len_with_parent(self):
parent = Config({"test": True})
settings = Config(YAML_DATA, parent=parent)
settings = Config(YAML_DATA, parent=parent, node=mock.Mock())

self.assertEqual(len(settings), 3)
self.assertEqual(len(parent), 1)

def test_parent_and_node(self):
with self.assertRaises(AssertionError):
Config({}, parent=Config({}))

with self.assertRaises(AssertionError):
Config({}, node=mock.Mock())

# these should be fine
Config({})
Config({}, parent=Config({}), node=mock.Mock())

def test_get_name_for_root_config(self):
config = Config({})
self.assertEqual(config.get_name(), SITE_YAML_PATH)

def test_get_name_for_node_config(self):
node = mock.Mock()
node.full_path = "this-file.html"
config = Config({}, parent=mock.Mock(), node=node)
self.assertEqual(config.get_name(), node.full_path)

def test_KeyError_contains_name_of_child(self):
node = mock.Mock()
node.full_path = "this-file.html"
config = Config({}, parent=Config({}), node=node)

with self.assertRaises(KeyError) as exp:
config["some key"]

self.assertIn("this-file.html", str(exp.exception))

def test_KeyError_contains_name_of_site_yaml(self):
config = Config({})

with self.assertRaises(KeyError) as exp:
config["some key"]

self.assertIn(SITE_YAML_PATH, str(exp.exception))

0 comments on commit 310b70c

Please sign in to comment.