22 from __future__
import print_function
26 from .config
import Config, FieldValidationError, _autocast, _typeStr, _joinNamePath
27 from .dictField
import Dict, DictField
28 from .comparison
import compareConfigs, compareScalars, getComparisonName
30 __all__ = [
"ConfigDictField"]
35 Config-Insternal representation of a dict of config classes
37 Much like Dict, ConfigDict is a custom MutableMapper which tracks the
38 history of changes to any of its items.
40 def __init__(self, config, field, value, at, label):
41 Dict.__init__(self, config, field, value, at, label, setHistory=
False)
42 self.history.append((
"Dict initialized", at, label))
44 def __setitem__(self, k, x, at=None, label="setitem", setHistory=True):
45 if self._config._frozen:
46 msg =
"Cannot modify a frozen Config. "\
47 "Attempting to set item at key %r to value %s" % (k, x)
48 raise FieldValidationError(self._field, self._config, msg)
51 k = _autocast(k, self._field.keytype)
52 if type(k) != self._field.keytype:
53 msg =
"Key %r is of type %s, expected type %s" % \
54 (k, _typeStr(k), _typeStr(self._field.keytype))
55 raise FieldValidationError(self._field, self._config, msg)
58 dtype = self._field.itemtype
59 if type(x) != self._field.itemtype
and x != self._field.itemtype:
60 msg =
"Value %s at key %r is of incorrect type %s. Expected type %s" % \
61 (x, k, _typeStr(x), _typeStr(self._field.itemtype))
62 raise FieldValidationError(self._field, self._config, msg)
65 at = traceback.extract_stack()[:-1]
66 name = _joinNamePath(self._config._name, self._field.name, k)
67 oldValue = self._dict.get(k,
None)
70 self._dict[k] = dtype(__name=name, __at=at, __label=label)
72 self._dict[k] = dtype(__name=name, __at=at, __label=label, **x._storage)
74 self.history.append((
"Added item at key %s" % k, at, label))
78 oldValue.update(__at=at, __label=label, **x._storage)
80 self.history.append((
"Modified item at key %s" % k, at, label))
84 at = traceback.extract_stack()[:-1]
85 Dict.__delitem__(self, k, at, label,
False)
86 self.history.append((
"Removed item at key %s" % k, at, label))
91 Defines a field which is a mapping between a POD and a config class.
93 This behaves exactly like a DictField with the slight difference that
94 itemtype must be an subclass of Config.
96 This allows config writters to create name-to-config mappings. One use case
97 is for configuring mappings for dataset types in a butler. In this case,
98 the dataset type names are arbitrary and user-selected; the mapping
99 configurations are known and fixed.
102 DictClass = ConfigDict
104 def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None):
105 source = traceback.extract_stack(limit=2)[0]
106 self._setup(doc=doc, dtype=ConfigDict, default=default, check=
None,
107 optional=optional, source=source)
108 if keytype
not in self.supportedTypes:
109 raise ValueError(
"'keytype' %s is not a supported type" %
111 elif not issubclass(itemtype, Config):
112 raise ValueError(
"'itemtype' %s is not a supported type" %
114 if dictCheck
is not None and not hasattr(dictCheck,
"__call__"):
115 raise ValueError(
"'dictCheck' must be callable")
116 if itemCheck
is not None and not hasattr(itemCheck,
"__call__"):
117 raise ValueError(
"'itemCheck' must be callable")
125 configDict = self.__get__(instance)
126 if configDict
is not None:
128 fullname = _joinNamePath(instance._name, self.name, k)
129 configDict[k]._rename(fullname)
132 value = self.__get__(instance)
133 if value
is not None:
138 msg =
"Item at key %r is not a valid value: %s" % (k, item)
139 raise FieldValidationError(self, instance, msg)
140 DictField.validate(self, instance)
143 configDict = self.__get__(instance)
144 if configDict
is None:
149 dict_[k] = configDict[k].
toDict()
153 def save(self, outfile, instance):
154 configDict = self.__get__(instance)
155 fullname = _joinNamePath(instance._name, self.name)
156 if configDict
is None:
157 outfile.write(
u"{}={!r}\n".
format(fullname, configDict))
160 outfile.write(
u"{}={!r}\n".
format(fullname, {}))
161 for v
in configDict.values():
162 outfile.write(
u"{}={}()\n".
format(v._name, _typeStr(v)))
166 configDict = self.__get__(instance)
167 if configDict
is not None:
171 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
172 """Helper function for Config.compare; used to compare two fields for equality.
174 @param[in] instance1 LHS Config instance to compare.
175 @param[in] instance2 RHS Config instance to compare.
176 @param[in] shortcut If True, return as soon as an inequality is found.
177 @param[in] rtol Relative tolerance for floating point comparisons.
178 @param[in] atol Absolute tolerance for floating point comparisons.
179 @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
180 to report inequalities.
182 Floating point comparisons are performed by numpy.allclose; refer to that for details.
184 d1 = getattr(instance1, self.name)
185 d2 = getattr(instance2, self.name)
187 _joinNamePath(instance1._name, self.name),
188 _joinNamePath(instance2._name, self.name)
190 if not compareScalars(
"keys for %s" % name, set(d1.keys()), set(d2.keys()), output=output):
193 for k, v1
in d1.items():
195 result =
compareConfigs(
"%s[%r]" % (name, k), v1, v2, shortcut=shortcut,
196 rtol=rtol, atol=atol, output=output)
197 if not result
and shortcut:
199 equal = equal
and result