22 __all__ = [
"ConfigDictField"]
24 from .config
import Config, FieldValidationError, _autocast, _typeStr, _joinNamePath
25 from .dictField
import Dict, DictField
26 from .comparison
import compareConfigs, compareScalars, getComparisonName
27 from .callStack
import getCallStack, getStackFrame
31 """Internal representation of a dictionary of configuration classes. 33 Much like `Dict`, `ConfigDict` is a custom `MutableMapper` which tracks 34 the history of changes to any of its items. 37 def __init__(self, config, field, value, at, label):
38 Dict.__init__(self, config, field, value, at, label, setHistory=
False)
39 self.
history.append((
"Dict initialized", at, label))
41 def __setitem__(self, k, x, at=None, label="setitem", setHistory=True):
43 msg =
"Cannot modify a frozen Config. "\
44 "Attempting to set item at key %r to value %s" % (k, x)
48 k = _autocast(k, self.
_field.keytype)
49 if type(k) != self.
_field.keytype:
50 msg =
"Key %r is of type %s, expected type %s" % \
51 (k, _typeStr(k), _typeStr(self.
_field.keytype))
55 dtype = self.
_field.itemtype
56 if type(x) != self.
_field.itemtype
and x != self.
_field.itemtype:
57 msg =
"Value %s at key %r is of incorrect type %s. Expected type %s" % \
58 (x, k, _typeStr(x), _typeStr(self.
_field.itemtype))
64 oldValue = self.
_dict.get(k,
None)
67 self.
_dict[k] = dtype(__name=name, __at=at, __label=label)
69 self.
_dict[k] = dtype(__name=name, __at=at, __label=label, **x._storage)
71 self.
history.append((
"Added item at key %s" % k, at, label))
75 oldValue.update(__at=at, __label=label, **x._storage)
77 self.
history.append((
"Modified item at key %s" % k, at, label))
82 Dict.__delitem__(self, k, at, label,
False)
83 self.
history.append((
"Removed item at key %s" % k, at, label))
87 """A configuration field (`~lsst.pex.config.Field` subclass) that is a 88 mapping of keys to `~lsst.pex.config.Config` instances. 90 ``ConfigDictField`` behaves like `DictField` except that the 91 ``itemtype`` must be a `~lsst.pex.config.Config` subclass. 96 A description of the configuration field. 97 keytype : {`int`, `float`, `complex`, `bool`, `str`} 98 The type of the mapping keys. All keys must have this type. 99 itemtype : `lsst.pex.config.Config`-type 100 The type of the values in the mapping. This must be 101 `~lsst.pex.config.Config` or a subclass. 104 default : ``itemtype``-dtype, optional 105 Default value of this field. 106 optional : `bool`, optional 107 If `True`, this configuration `~lsst.pex.config.Field` is *optional*. 109 deprecated : None or `str`, optional 110 A description of why this Field is deprecated, including removal date. 111 If not None, the string is appended to the docstring for this Field. 116 Raised if the inputs are invalid: 118 - ``keytype`` or ``itemtype`` arguments are not supported types 119 (members of `ConfigDictField.supportedTypes`. 120 - ``dictCheck`` or ``itemCheck`` is not a callable function. 136 You can use ``ConfigDictField`` to create name-to-config mappings. One use 137 case is for configuring mappings for dataset types in a Butler. In this 138 case, the dataset type names are arbitrary and user-selected while the 139 mapping configurations are known and fixed. 142 DictClass = ConfigDict
144 def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None,
147 self.
_setup(doc=doc, dtype=ConfigDict, default=default, check=
None,
148 optional=optional, source=source, deprecated=deprecated)
150 raise ValueError(
"'keytype' %s is not a supported type" %
152 elif not issubclass(itemtype, Config):
153 raise ValueError(
"'itemtype' %s is not a supported type" %
155 if dictCheck
is not None and not hasattr(dictCheck,
"__call__"):
156 raise ValueError(
"'dictCheck' must be callable")
157 if itemCheck
is not None and not hasattr(itemCheck,
"__call__"):
158 raise ValueError(
"'itemCheck' must be callable")
166 configDict = self.
__get__(instance)
167 if configDict
is not None:
169 fullname = _joinNamePath(instance._name, self.name, k)
170 configDict[k]._rename(fullname)
174 if value
is not None:
179 msg =
"Item at key %r is not a valid value: %s" % (k, item)
181 DictField.validate(self, instance)
184 configDict = self.
__get__(instance)
185 if configDict
is None:
190 dict_[k] = configDict[k].
toDict()
194 def save(self, outfile, instance):
195 configDict = self.
__get__(instance)
196 fullname = _joinNamePath(instance._name, self.name)
197 if configDict
is None:
198 outfile.write(
u"{}={!r}\n".
format(fullname, configDict))
201 outfile.write(
u"{}={!r}\n".
format(fullname, {}))
202 for v
in configDict.values():
203 outfile.write(
u"{}={}()\n".
format(v._name, _typeStr(v)))
207 configDict = self.
__get__(instance)
208 if configDict
is not None:
212 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
213 """Compare two fields for equality. 215 Used by `lsst.pex.ConfigDictField.compare`. 219 instance1 : `lsst.pex.config.Config` 220 Left-hand side config instance to compare. 221 instance2 : `lsst.pex.config.Config` 222 Right-hand side config instance to compare. 224 If `True`, this function returns as soon as an inequality if found. 226 Relative tolerance for floating point comparisons. 228 Absolute tolerance for floating point comparisons. 230 A callable that takes a string, used (possibly repeatedly) to report inequalities. 235 `True` if the fields are equal, `False` otherwise. 239 Floating point comparisons are performed by `numpy.allclose`. 241 d1 = getattr(instance1, self.name)
242 d2 = getattr(instance2, self.name)
244 _joinNamePath(instance1._name, self.name),
245 _joinNamePath(instance2._name, self.name)
247 if not compareScalars(
"keys for %s" % name, set(d1.keys()), set(d2.keys()), output=output):
250 for k, v1
in d1.items():
252 result =
compareConfigs(
"%s[%r]" % (name, k), v1, v2, shortcut=shortcut,
253 rtol=rtol, atol=atol, output=output)
254 if not result
and shortcut:
256 equal = equal
and result
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
def compareConfigs(name, c1, c2, shortcut=True, rtol=1E-8, atol=1E-8, output=None)
def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None, deprecated=None)
def validate(self, instance)
def rename(self, instance)
def getStackFrame(relative=0)
def save(self, outfile, instance)
def freeze(self, instance)
def __get__(self, instance, owner=None, at=None, label="default")
def toDict(self, instance)
def __delitem__(self, k, at=None, label="delitem")
def __init__(self, config, field, value, at, label)
def getComparisonName(name1, name2)
def compareScalars(name, v1, v2, output, rtol=1E-8, atol=1E-8, dtype=None)
def _setup(self, doc, dtype, default, check, optional, source, deprecated)
def __setitem__(self, k, x, at=None, label="setitem", setHistory=True)