28 __all__ = [
"ConfigDictField"]
 
   30 from .config 
import Config, FieldValidationError, _autocast, _typeStr, _joinNamePath
 
   31 from .dictField 
import Dict, DictField
 
   32 from .comparison 
import compareConfigs, compareScalars, getComparisonName
 
   33 from .callStack 
import getCallStack, getStackFrame
 
   37     """Internal representation of a dictionary of configuration classes. 
   39     Much like `Dict`, `ConfigDict` is a custom `MutableMapper` which tracks 
   40     the history of changes to any of its items. 
   43     def __init__(self, config, field, value, at, label):
 
   44         Dict.__init__(self, config, field, value, at, label, setHistory=
False)
 
   47     def __setitem__(self, k, x, at=None, label="setitem", setHistory=True):
 
   49             msg = 
"Cannot modify a frozen Config. "\
 
   50                   "Attempting to set item at key %r to value %s" % (k, x)
 
   54         k = _autocast(k, self.
_field.keytype)
 
   56             msg = 
"Key %r is of type %s, expected type %s" % \
 
   57                 (k, _typeStr(k), _typeStr(self.
_field.keytype))
 
   61         dtype = self.
_field.itemtype
 
   63             msg = 
"Value %s at key %r is of incorrect type %s. Expected type %s" % \
 
   64                 (x, k, _typeStr(x), _typeStr(self.
_field.itemtype))
 
   70         oldValue = self.
_dict.get(k, 
None)
 
   73                 self.
_dict[k] = dtype(__name=name, __at=at, __label=label)
 
   75                 self.
_dict[k] = dtype(__name=name, __at=at, __label=label, **x._storage)
 
   81             oldValue.update(__at=at, __label=label, **x._storage)
 
   83                 self.
history.
append((
"Modified item at key %s" % k, at, label))
 
   88         Dict.__delitem__(self, k, at, label, 
False)
 
   89         self.
history.
append((
"Removed item at key %s" % k, at, label))
 
   93     """A configuration field (`~lsst.pex.config.Field` subclass) that is a 
   94     mapping of keys to `~lsst.pex.config.Config` instances. 
   96     ``ConfigDictField`` behaves like `DictField` except that the 
   97     ``itemtype`` must be a `~lsst.pex.config.Config` subclass. 
  102         A description of the configuration field. 
  103     keytype : {`int`, `float`, `complex`, `bool`, `str`} 
  104         The type of the mapping keys. All keys must have this type. 
  105     itemtype : `lsst.pex.config.Config`-type 
  106         The type of the values in the mapping. This must be 
  107         `~lsst.pex.config.Config` or a subclass. 
  110     default : ``itemtype``-dtype, optional 
  111         Default value of this field. 
  112     optional : `bool`, optional 
  113         If `True`, this configuration `~lsst.pex.config.Field` is *optional*. 
  115     deprecated : None or `str`, optional 
  116         A description of why this Field is deprecated, including removal date. 
  117         If not None, the string is appended to the docstring for this Field. 
  122         Raised if the inputs are invalid: 
  124         - ``keytype`` or ``itemtype`` arguments are not supported types 
  125           (members of `ConfigDictField.supportedTypes`. 
  126         - ``dictCheck`` or ``itemCheck`` is not a callable function. 
  142     You can use ``ConfigDictField`` to create name-to-config mappings. One use 
  143     case is for configuring mappings for dataset types in a Butler. In this 
  144     case, the dataset type names are arbitrary and user-selected while the 
  145     mapping configurations are known and fixed. 
  148     DictClass = ConfigDict
 
  150     def __init__(self, doc, keytype, itemtype, default=None, optional=False, dictCheck=None, itemCheck=None,
 
  153         self.
_setup(doc=doc, dtype=ConfigDict, default=default, check=
None,
 
  154                     optional=optional, source=source, deprecated=deprecated)
 
  156             raise ValueError(
"'keytype' %s is not a supported type" %
 
  158         elif not issubclass(itemtype, Config):
 
  159             raise ValueError(
"'itemtype' %s is not a supported type" %
 
  161         if dictCheck 
is not None and not hasattr(dictCheck, 
"__call__"):
 
  162             raise ValueError(
"'dictCheck' must be callable")
 
  163         if itemCheck 
is not None and not hasattr(itemCheck, 
"__call__"):
 
  164             raise ValueError(
"'itemCheck' must be callable")
 
  172         configDict = self.
__get__(instance)
 
  173         if configDict 
is not None:
 
  175                 fullname = _joinNamePath(instance._name, self.name, k)
 
  176                 configDict[k]._rename(fullname)
 
  180         if value 
is not None:
 
  185                     msg = 
"Item at key %r is not a valid value: %s" % (k, item)
 
  187         DictField.validate(self, instance)
 
  190         configDict = self.
__get__(instance)
 
  191         if configDict 
is None:
 
  196             dict_[k] = configDict[k].
toDict()
 
  200     def save(self, outfile, instance):
 
  201         configDict = self.
__get__(instance)
 
  202         fullname = _joinNamePath(instance._name, self.name)
 
  203         if configDict 
is None:
 
  204             outfile.write(
u"{}={!r}\n".
format(fullname, configDict))
 
  207         outfile.write(
u"{}={!r}\n".
format(fullname, {}))
 
  208         for v 
in configDict.values():
 
  209             outfile.write(
u"{}={}()\n".
format(v._name, _typeStr(v)))
 
  213         configDict = self.
__get__(instance)
 
  214         if configDict 
is not None:
 
  218     def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
 
  219         """Compare two fields for equality. 
  221         Used by `lsst.pex.ConfigDictField.compare`. 
  225         instance1 : `lsst.pex.config.Config` 
  226             Left-hand side config instance to compare. 
  227         instance2 : `lsst.pex.config.Config` 
  228             Right-hand side config instance to compare. 
  230             If `True`, this function returns as soon as an inequality if found. 
  232             Relative tolerance for floating point comparisons. 
  234             Absolute tolerance for floating point comparisons. 
  236             A callable that takes a string, used (possibly repeatedly) to 
  242             `True` if the fields are equal, `False` otherwise. 
  246         Floating point comparisons are performed by `numpy.allclose`. 
  248         d1 = getattr(instance1, self.name)
 
  249         d2 = getattr(instance2, self.name)
 
  251             _joinNamePath(instance1._name, self.name),
 
  252             _joinNamePath(instance2._name, self.name)
 
  257         for k, v1 
in d1.items():
 
  259             result = 
compareConfigs(
"%s[%r]" % (name, k), v1, v2, shortcut=shortcut,
 
  260                                     rtol=rtol, atol=atol, output=output)
 
  261             if not result 
and shortcut:
 
  263             equal = equal 
and result