23 from .config
import Config, FieldValidationError, _autocast, _typeStr, _joinNamePath
24 from .dictField
import Dict, DictField
25 from .comparison
import compareConfigs, compareScalars, getComparisonName
26 from .callStack
import getCallStack, getStackFrame
28 __all__ = [
"ConfigDictField"]
32 """Internal representation of a dictionary of configuration classes. 34 Much like `Dict`, `ConfigDict` is a custom `MutableMapper` which tracks 35 the history of changes to any of its items. 38 def __init__(self, config, field, value, at, label):
39 Dict.__init__(self, config, field, value, at, label, setHistory=
False)
40 self.
history.append((
"Dict initialized", at, label))
42 def __setitem__(self, k, x, at=None, label="setitem", setHistory=True):
44 msg =
"Cannot modify a frozen Config. "\
45 "Attempting to set item at key %r to value %s" % (k, x)
49 k = _autocast(k, self.
_field.keytype)
50 if type(k) != self.
_field.keytype:
51 msg =
"Key %r is of type %s, expected type %s" % \
52 (k, _typeStr(k), _typeStr(self.
_field.keytype))
56 dtype = self.
_field.itemtype
57 if type(x) != self.
_field.itemtype
and x != self.
_field.itemtype:
58 msg =
"Value %s at key %r is of incorrect type %s. Expected type %s" % \
59 (x, k, _typeStr(x), _typeStr(self.
_field.itemtype))
65 oldValue = self.
_dict.get(k,
None)
68 self.
_dict[k] = dtype(__name=name, __at=at, __label=label)
70 self.
_dict[k] = dtype(__name=name, __at=at, __label=label, **x._storage)
72 self.
history.append((
"Added item at key %s" % k, at, label))
76 oldValue.update(__at=at, __label=label, **x._storage)
78 self.
history.append((
"Modified item at key %s" % k, at, label))
83 Dict.__delitem__(self, k, at, label,
False)
84 self.
history.append((
"Removed item at key %s" % k, at, label))
88 """A configuration field (`~lsst.pex.config.Field` subclass) that is a 89 mapping of keys to `~lsst.pex.config.Config` instances. 91 ``ConfigDictField`` behaves like `DictField` except that the 92 ``itemtype`` must be a `~lsst.pex.config.Config` subclass. 97 A description of the configuration field. 98 keytype : {`int`, `float`, `complex`, `bool`, `str`} 99 The type of the mapping keys. All keys must have this type. 100 itemtype : `lsst.pex.config.Config`-type 101 The type of the values in the mapping. This must be 102 `~lsst.pex.config.Config` or a subclass. 105 default : ``itemtype``-dtype, optional 106 Default value of this field. 107 optional : `bool`, optional 108 If `True`, this configuration `~lsst.pex.config.Field` is *optional*. 110 deprecated : None or `str`, optional 111 A description of why this Field is deprecated, including removal date. 112 If not None, the string is appended to the docstring for this Field. 117 Raised if the inputs are invalid: 119 - ``keytype`` or ``itemtype`` arguments are not supported types 120 (members of `ConfigDictField.supportedTypes`. 121 - ``dictCheck`` or ``itemCheck`` is not a callable function. 137 You can use ``ConfigDictField`` to create name-to-config mappings. One use 138 case is for configuring mappings for dataset types in a Butler. In this 139 case, the dataset type names are arbitrary and user-selected while the 140 mapping configurations are known and fixed. 143 DictClass = ConfigDict
145 def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None,
148 self.
_setup(doc=doc, dtype=ConfigDict, default=default, check=
None,
149 optional=optional, source=source, deprecated=deprecated)
151 raise ValueError(
"'keytype' %s is not a supported type" %
153 elif not issubclass(itemtype, Config):
154 raise ValueError(
"'itemtype' %s is not a supported type" %
156 if dictCheck
is not None and not hasattr(dictCheck,
"__call__"):
157 raise ValueError(
"'dictCheck' must be callable")
158 if itemCheck
is not None and not hasattr(itemCheck,
"__call__"):
159 raise ValueError(
"'itemCheck' must be callable")
167 configDict = self.
__get__(instance)
168 if configDict
is not None:
170 fullname = _joinNamePath(instance._name, self.name, k)
171 configDict[k]._rename(fullname)
175 if value
is not None:
180 msg =
"Item at key %r is not a valid value: %s" % (k, item)
182 DictField.validate(self, instance)
185 configDict = self.
__get__(instance)
186 if configDict
is None:
191 dict_[k] = configDict[k].
toDict()
195 def save(self, outfile, instance):
196 configDict = self.
__get__(instance)
197 fullname = _joinNamePath(instance._name, self.name)
198 if configDict
is None:
199 outfile.write(
u"{}={!r}\n".
format(fullname, configDict))
202 outfile.write(
u"{}={!r}\n".
format(fullname, {}))
203 for v
in configDict.values():
204 outfile.write(
u"{}={}()\n".
format(v._name, _typeStr(v)))
208 configDict = self.
__get__(instance)
209 if configDict
is not None:
213 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
214 """Compare two fields for equality. 216 Used by `lsst.pex.ConfigDictField.compare`. 220 instance1 : `lsst.pex.config.Config` 221 Left-hand side config instance to compare. 222 instance2 : `lsst.pex.config.Config` 223 Right-hand side config instance to compare. 225 If `True`, this function returns as soon as an inequality if found. 227 Relative tolerance for floating point comparisons. 229 Absolute tolerance for floating point comparisons. 231 A callable that takes a string, used (possibly repeatedly) to report inequalities. 236 `True` if the fields are equal, `False` otherwise. 240 Floating point comparisons are performed by `numpy.allclose`. 242 d1 = getattr(instance1, self.name)
243 d2 = getattr(instance2, self.name)
245 _joinNamePath(instance1._name, self.name),
246 _joinNamePath(instance2._name, self.name)
248 if not compareScalars(
"keys for %s" % name, set(d1.keys()), set(d2.keys()), output=output):
251 for k, v1
in d1.items():
253 result =
compareConfigs(
"%s[%r]" % (name, k), v1, v2, shortcut=shortcut,
254 rtol=rtol, atol=atol, output=output)
255 if not result
and shortcut:
257 equal = equal
and result
def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None, deprecated=None)
def __setitem__(self, k, x, at=None, label="setitem", setHistory=True)
def toDict(self, instance)
def save(self, outfile, instance)
def compareConfigs(name, c1, c2, shortcut=True, rtol=1E-8, atol=1E-8, output=None)
def validate(self, instance)
def freeze(self, instance)
def __get__(self, instance, owner=None, at=None, label="default")
def rename(self, instance)
def getStackFrame(relative=0)
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
def __delitem__(self, k, at=None, label="delitem")
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 getComparisonName(name1, name2)
def __init__(self, config, field, value, at, label)