give file name/uri containing failed key in resolve errors
diff --git a/reclass/core.py b/reclass/core.py
index 3cf3721..f1de527 100644
--- a/reclass/core.py
+++ b/reclass/core.py
@@ -16,7 +16,7 @@
import yaml
from reclass.output.yaml_outputter import ExplicitDumper
from reclass.datatypes import Entity, Classes, Parameters, Exports
-from reclass.errors import MappingFormatError, ClassNotFound
+from reclass.errors import MappingFormatError, ClassNotFound, ResolveError
from reclass.defaults import AUTOMATIC_RECLASS_PARAMETERS
class Core(object):
@@ -127,7 +127,11 @@
inventory = {}
for nodename in self._storage.enumerate_nodes():
node = self._node_entity(nodename)
- node.interpolate_exports()
+ try:
+ node.interpolate_exports()
+ except ResolveError as e:
+ e.export = nodename
+ raise e
inventory[nodename] = node.exports.as_dict()
return inventory
diff --git a/reclass/datatypes/exports.py b/reclass/datatypes/exports.py
index 0d33312..8fbf880 100644
--- a/reclass/datatypes/exports.py
+++ b/reclass/datatypes/exports.py
@@ -4,14 +4,14 @@
# This file is part of reclass (http://github.com/madduck/reclass)
#
from parameters import Parameters
-from reclass.errors import UndefinedVariableError
+from reclass.errors import ResolveError
from reclass.values.value import Value
from reclass.values.valuelist import ValueList
class Exports(Parameters):
- def __init__(self, mapping=None, delimiter=None, options=None):
- super(Exports, self).__init__(mapping, delimiter, options)
+ def __init__(self, mapping=None, uri=None, delimiter=None, options=None):
+ super(Exports, self).__init__(mapping, uri, delimiter, options)
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self._base,
@@ -45,8 +45,9 @@
def _interpolate_render_from_external(self, context, path, value):
try:
new = value.render(context, None, self._options)
- except UndefinedVariableError as e:
- raise UndefinedVariableError(e.var, path)
+ except ResolveError as e:
+ e.context = path
+ raise e
if isinstance(new, dict):
self._render_simple_dict(new, path)
elif isinstance(new, list):
diff --git a/reclass/datatypes/parameters.py b/reclass/datatypes/parameters.py
index f9210c7..411f631 100644
--- a/reclass/datatypes/parameters.py
+++ b/reclass/datatypes/parameters.py
@@ -16,7 +16,7 @@
from reclass.values.mergeoptions import MergeOptions
from reclass.values.value import Value
from reclass.values.valuelist import ValueList
-from reclass.errors import InfiniteRecursionError, UndefinedVariableError, InterpolationError
+from reclass.errors import InfiniteRecursionError, ResolveError, InterpolationError
class Parameters(object):
'''
@@ -44,13 +44,14 @@
DEFAULT_PATH_DELIMITER = PARAMETER_INTERPOLATION_DELIMITER
DICT_KEY_OVERRIDE_PREFIX = PARAMETER_DICT_KEY_OVERRIDE_PREFIX
- def __init__(self, mapping=None, delimiter=None, options=None):
+ def __init__(self, mapping=None, uri=None, delimiter=None, options=None):
if delimiter is None:
delimiter = Parameters.DEFAULT_PATH_DELIMITER
if options is None:
options = MergeOptions()
self._delimiter = delimiter
self._base = {}
+ self._uri = uri
self._unrendered = None
self._escapes_handled = {}
self._has_inv_query = False
@@ -93,7 +94,7 @@
elif isinstance(value, (Value, ValueList)):
return value
else:
- return Value(value, self._delimiter)
+ return Value(value, uri=self._uri, delimiter=self._delimiter)
def _wrap_list(self, source):
return [ self._wrap_value(v) for v in source ]
@@ -107,14 +108,14 @@
elif isinstance(cur, ValueList):
values = cur
else:
- values = ValueList(Value(cur))
+ values = ValueList(Value(cur, uri=self._uri, delimiter=self._delimiter))
if isinstance(new, Value):
values.append(new)
elif isinstance(new, ValueList):
values.extend(new)
else:
- values.append(Value(new))
+ values.append(Value(new, uri=self._uri, delimiter=self._delimiter))
return values
@@ -266,8 +267,9 @@
def _interpolate_render_value(self, path, value, inventory):
try:
new = value.render(self._base, inventory, self._options)
- except UndefinedVariableError as e:
- raise UndefinedVariableError(e.var, path)
+ except ResolveError as e:
+ e.context = path
+ raise e
if isinstance(new, dict):
self._render_simple_dict(new, path)
diff --git a/reclass/datatypes/tests/test_parameters.py b/reclass/datatypes/tests/test_parameters.py
index 0e8de3b..2543ba9 100644
--- a/reclass/datatypes/tests/test_parameters.py
+++ b/reclass/datatypes/tests/test_parameters.py
@@ -21,7 +21,7 @@
class TestParameters(unittest.TestCase):
def _construct_mocked_params(self, iterable=None, delimiter=None):
- p = Parameters(iterable, delimiter)
+ p = Parameters(iterable, delimiter=delimiter)
self._base = base = p._base
p._base = mock.MagicMock(spec_set=dict, wraps=base)
p._base.__repr__ = mock.MagicMock(autospec=dict.__repr__,
diff --git a/reclass/errors.py b/reclass/errors.py
index d38ed84..d753306 100644
--- a/reclass/errors.py
+++ b/reclass/errors.py
@@ -33,9 +33,10 @@
return 'No error message provided.'
def exit_with_message(self, out=sys.stderr):
- print >>out, self.message
if self._traceback:
print >>out, self._traceback
+ print >>out, self.message
+ print >>out
sys.exit(self.rc)
@@ -128,19 +129,24 @@
super(InterpolationError, self).__init__(rc=rc, msg=msg)
-class UndefinedVariableError(InterpolationError):
+class ResolveError(InterpolationError):
- def __init__(self, var, context=None):
- super(UndefinedVariableError, self).__init__(msg=None)
- self._var = var
- self._context = context
- var = property(lambda self: self._var)
- context = property(lambda self: self._context)
+ def __init__(self, var, uri=None, export=None, context=None):
+ super(ResolveError, self).__init__(msg=None)
+ self.var = var
+ self.context = context
+ self.uri = uri
+ self.export = export
def _get_message(self):
- msg = "Cannot resolve " + self._var.join(REFERENCE_SENTINELS)
- if self._context:
- msg += ' in the context of %s' % self._context
+ msg = ''
+ if self.export:
+ msg = '** InvQuery: %s **\n' % self.export
+ msg += "Cannot resolve " + self.var.join(REFERENCE_SENTINELS)
+ if self.context:
+ msg += ', at %s' % self.context
+ if self.uri:
+ msg += ', in %s' % self.uri
return msg
def set_context(self, context):
diff --git a/reclass/storage/yaml_git/__init__.py b/reclass/storage/yaml_git/__init__.py
index 614d481..3482423 100644
--- a/reclass/storage/yaml_git/__init__.py
+++ b/reclass/storage/yaml_git/__init__.py
@@ -224,7 +224,7 @@
def get_node(self, name):
file = self._nodes[name]
blob = self._repos[self._nodes_uri.repo].get(file.id)
- entity = YamlData.from_string(blob.data, 'git_fs://{0}#{1}/{2}'.format(self._nodes_uri.repo, self._nodes_uri.branch, file.path)).get_entity(name)
+ entity = YamlData.from_string(blob.data, 'git_fs://{0} {1} {2}'.format(self._nodes_uri.repo, self._nodes_uri.branch, file.path)).get_entity(name)
return entity
def get_class(self, name, environment):
@@ -239,7 +239,7 @@
raise reclass.errors.NotFoundError("File " + name + " missing from " + uri.repo + " branch " + uri.branch)
file = self._repos[uri.repo].files[uri.branch][name]
blob = self._repos[uri.repo].get(file.id)
- entity = YamlData.from_string(blob.data, 'git_fs://{0}#{1}/{2}'.format(uri.repo, uri.branch, file.path)).get_entity(name)
+ entity = YamlData.from_string(blob.data, 'git_fs://{0} {1} {2}'.format(uri.repo, uri.branch, file.path)).get_entity(name)
return entity
def enumerate_nodes(self):
diff --git a/reclass/storage/yamldata.py b/reclass/storage/yamldata.py
index b28db06..31cc8ff 100644
--- a/reclass/storage/yamldata.py
+++ b/reclass/storage/yamldata.py
@@ -16,12 +16,13 @@
@classmethod
def from_file(cls, path):
''' Initialise yaml data from a local file '''
- if not os.path.isfile(path):
- raise NotFoundError('No such file: %s' % path)
- if not os.access(path, os.R_OK):
- raise NotFoundError('Cannot open: %s' % path)
- y = cls('yaml_fs://{0}'.format(path))
- fp = file(path)
+ abs_path = os.path.abspath(path)
+ if not os.path.isfile(abs_path):
+ raise NotFoundError('No such file: %s' % abs_path)
+ if not os.access(abs_path, os.R_OK):
+ raise NotFoundError('Cannot open: %s' % abs_path)
+ y = cls('yaml_fs://{0}'.format(abs_path))
+ fp = file(abs_path)
data = yaml.safe_load(fp)
if data is not None:
y._data = data
@@ -47,6 +48,9 @@
return self._data
def get_entity(self, name=None):
+ if name is None:
+ name = self._uri
+
classes = self._data.get('classes')
if classes is None:
classes = []
@@ -60,15 +64,12 @@
parameters = self._data.get('parameters')
if parameters is None:
parameters = {}
- parameters = datatypes.Parameters(parameters)
+ parameters = datatypes.Parameters(parameters, uri=self._uri)
exports = self._data.get('exports')
if exports is None:
exports = {}
- exports = datatypes.Exports(exports)
-
- if name is None:
- name = self._uri
+ exports = datatypes.Exports(exports, uri=self._uri)
env = self._data.get('environment', None)
diff --git a/reclass/values/invitem.py b/reclass/values/invitem.py
index c3af2f8..50eb388 100644
--- a/reclass/values/invitem.py
+++ b/reclass/values/invitem.py
@@ -9,7 +9,7 @@
from item import Item
from reclass.utils.dictpath import DictPath
-from reclass.errors import ExpressionError, ParseError, UndefinedVariableError
+from reclass.errors import ExpressionError, ParseError, ResolveError
_OBJ = 'OBJ'
_TEST = 'TEST'
@@ -72,7 +72,7 @@
try:
return path.get_value(dictionary)
except KeyError as e:
- raise UndefinedVariableError(str(path))
+ raise ResolveError(str(path))
def _get_vars(self, var, export, parameter, value):
if isinstance(var, str):
@@ -246,7 +246,7 @@
try:
return path.get_value(dictionary)
except KeyError as e:
- raise UndefinedVariableError(str(path))
+ raise ResolveError(str(path))
def _value_expression(self, inventory):
results = {}
diff --git a/reclass/values/item.py b/reclass/values/item.py
index 1d29ab1..4728142 100644
--- a/reclass/values/item.py
+++ b/reclass/values/item.py
@@ -5,7 +5,6 @@
#
from reclass.utils.dictpath import DictPath
-from reclass.errors import UndefinedVariableError
class Item(object):
diff --git a/reclass/values/refitem.py b/reclass/values/refitem.py
index b97780a..a69bbf5 100644
--- a/reclass/values/refitem.py
+++ b/reclass/values/refitem.py
@@ -6,7 +6,7 @@
from item import Item
from reclass.utils.dictpath import DictPath
-from reclass.errors import UndefinedVariableError
+from reclass.errors import ResolveError
class RefItem(Item):
@@ -31,7 +31,7 @@
strings = [ str(i.render(context, None)) for i in self._items ]
value = "".join(strings)
self._refs.append(value)
- except UndefinedVariableError as e:
+ except ResolveError as e:
self._allRefs = False
def contents(self):
@@ -50,8 +50,8 @@
path = DictPath(self._delimiter, ref)
try:
return path.get_value(context)
- except KeyError as e:
- raise UndefinedVariableError(ref)
+ except (KeyError, TypeError) as e:
+ raise ResolveError(ref)
def render(self, context, inventory):
if len(self._items) == 1:
diff --git a/reclass/values/tests/test_value.py b/reclass/values/tests/test_value.py
index 8211dcd..1b83094 100644
--- a/reclass/values/tests/test_value.py
+++ b/reclass/values/tests/test_value.py
@@ -12,7 +12,7 @@
from reclass.values.value import Value
from reclass.defaults import REFERENCE_SENTINELS, \
PARAMETER_INTERPOLATION_DELIMITER
-from reclass.errors import UndefinedVariableError, \
+from reclass.errors import ResolveError, \
IncompleteInterpolationError, ParseError
import unittest
@@ -117,7 +117,7 @@
def test_undefined_variable(self):
s = _var('no_such_variable')
tv = Value(s)
- with self.assertRaises(UndefinedVariableError):
+ with self.assertRaises(ResolveError):
tv.render(CONTEXT, None)
def test_incomplete_variable(self):
diff --git a/reclass/values/value.py b/reclass/values/value.py
index 355aab2..fcca3ba 100644
--- a/reclass/values/value.py
+++ b/reclass/values/value.py
@@ -9,13 +9,15 @@
from listitem import ListItem
from scaitem import ScaItem
from reclass.defaults import PARAMETER_INTERPOLATION_DELIMITER
+from reclass.errors import ResolveError
class Value(object):
_parser = Parser()
- def __init__(self, value, delimiter=PARAMETER_INTERPOLATION_DELIMITER):
+ def __init__(self, value, uri=None, delimiter=PARAMETER_INTERPOLATION_DELIMITER):
self._delimiter = delimiter
+ self._uri = uri
if isinstance(value, str):
self._item = self._parser.parse(value, delimiter)
elif isinstance(value, list):
@@ -48,7 +50,11 @@
self._item.assembleRefs(context)
def render(self, context, inventory, options=None):
- return self._item.render(context, inventory)
+ try:
+ return self._item.render(context, inventory)
+ except ResolveError as e:
+ e.uri = self._uri
+ raise e
def contents(self):
return self._item.contents()
diff --git a/reclass/values/valuelist.py b/reclass/values/valuelist.py
index 38f782c..3dc9c62 100644
--- a/reclass/values/valuelist.py
+++ b/reclass/values/valuelist.py
@@ -80,8 +80,8 @@
else:
new = value.render(context, inventory)
if isinstance(output, dict) and isinstance(new, dict):
- p1 = Parameters(output, value._delimiter)
- p2 = Parameters(new, value._delimiter)
+ p1 = Parameters(output, delimiter=value._delimiter)
+ p2 = Parameters(new, delimiter=value._delimiter)
p1.merge(p2)
output = p1.as_dict()
continue