25 from .config
import Field, FieldValidationError, _typeStr, _autocast, _joinNamePath
26 from .comparison
import getComparisonName, compareScalars
30 class Dict(collections.MutableMapping):
32 Config-Internal mapping container
33 Emulates a dict, but adds validation and provenance.
36 def __init__(self, config, field, value, at, label, setHistory=True):
40 self.
_history = self._config._history.setdefault(self._field.name, [])
46 self.
__setitem__(k, value[k], at=at, label=label, setHistory=
False)
48 msg =
"Value %s is of incorrect type %s. Mapping type expected."%\
52 self._history.append((dict(self.
_dict), at, label))
58 history = property(
lambda x: x._history)
68 def __setitem__(self, k, x, at=None, label="setitem", setHistory=True):
69 if self._config._frozen:
70 msg =
"Cannot modify a frozen Config. "\
71 "Attempting to set item at key %r to value %s"%(k, x)
76 if type(k) != self._field.keytype:
77 msg =
"Key %r is of type %s, expected type %s"%\
83 if self._field.itemtype
is None:
84 if type(x)
not in self._field.supportedTypes
and x
is not None:
85 msg =
"Value %s at key %r is of invalid type %s" % (x, k,
_typeStr(x))
88 if type(x) != self._field.itemtype
and x
is not None:
89 msg =
"Value %s at key %r is of incorrect type %s. Expected type %s"%\
94 if self._field.itemCheck
is not None and not self._field.itemCheck(x):
95 msg=
"Item at key %r is not a valid value: %s"%(k, x)
99 at = traceback.extract_stack()[:-1]
103 self._history.append((dict(self.
_dict), at, label))
105 def __delitem__(self, k, at=None, label="delitem", setHistory=True):
106 if self._config._frozen:
108 "Cannot modify a frozen Config")
113 at = traceback.extract_stack()[:-1]
114 self._history.append((dict(self.
_dict), at, label))
121 if hasattr(getattr(self.__class__, attr,
None),
'__set__'):
123 object.__setattr__(self, attr, value)
124 elif attr
in self.__dict__
or attr
in [
"_field",
"_config",
"_history",
"_dict",
"__doc__"]:
126 object.__setattr__(self, attr, value)
135 Defines a field which is a mapping of values
137 Both key and item types are restricted to builtin POD types:
138 (int, float, complex, bool, str)
140 Users can provide two check functions:
141 dictCheck: used to validate the dict as a whole, and
142 itemCheck: used to validate each item individually
144 For example to define a field which is a mapping from names to int values:
146 class MyConfig(Config):
148 doc="example string-to-int mapping field",
149 keytype=str, itemtype=int,
154 def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None):
155 source = traceback.extract_stack(limit=2)[0]
156 self._setup( doc=doc, dtype=Dict, default=default, check=
None,
157 optional=optional, source=source)
158 if keytype
not in self.supportedTypes:
159 raise ValueError(
"'keytype' %s is not a supported type"%\
161 elif itemtype
is not None and itemtype
not in self.supportedTypes:
162 raise ValueError(
"'itemtype' %s is not a supported type"%\
164 if dictCheck
is not None and not hasattr(dictCheck,
"__call__"):
165 raise ValueError(
"'dictCheck' must be callable")
166 if itemCheck
is not None and not hasattr(itemCheck,
"__call__"):
167 raise ValueError(
"'itemCheck' must be callable")
176 DictField validation ensures that non-optional fields are not None,
177 and that non-None values comply with dictCheck.
178 Individual Item checks are applied at set time and are not re-checked.
180 Field.validate(self, instance)
181 value = self.__get__(instance)
182 if value
is not None and self.
dictCheck is not None \
184 msg =
"%s is not a valid value"%str(value)
185 raise FieldValidationError(self, instance, msg)
188 def __set__(self, instance, value, at=None, label="assignment"):
190 msg =
"Cannot modify a frozen Config. "\
191 "Attempting to set field to value %s"%value
192 raise FieldValidationError(self, instance, msg)
195 at = traceback.extract_stack()[:-1]
196 if value
is not None:
197 value = self.
DictClass(instance, self, value, at=at, label=label)
199 history = instance._history.setdefault(self.name, [])
200 history.append((value, at, label))
202 instance._storage[self.name] = value
205 value = self.__get__(instance)
206 return dict(value)
if value
is not None else None
208 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
209 """Helper function for Config.compare; used to compare two fields for equality.
211 @param[in] instance1 LHS Config instance to compare.
212 @param[in] instance2 RHS Config instance to compare.
213 @param[in] shortcut If True, return as soon as an inequality is found.
214 @param[in] rtol Relative tolerance for floating point comparisons.
215 @param[in] atol Absolute tolerance for floating point comparisons.
216 @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
217 to report inequalities.
219 Floating point comparisons are performed by numpy.allclose; refer to that for details.
221 d1 = getattr(instance1, self.name)
222 d2 = getattr(instance2, self.name)
227 if not compareScalars(
"isnone for %s" % name, d1
is None, d2
is None, output=output):
229 if d1
is None and d2
is None:
231 if not compareScalars(
"keys for %s" % name, d1.keys(), d2.keys(), output=output):
234 for k, v1
in d1.iteritems():
237 rtol=rtol, atol=atol, output=output)
238 if not result
and shortcut:
240 equal = equal
and result