28 __all__ = [
"ConfigChoiceField"]
 
   31 import collections.abc
 
   33 from .config 
import Config, Field, FieldValidationError, _typeStr, _joinNamePath
 
   34 from .comparison 
import getComparisonName, compareScalars, compareConfigs
 
   35 from .callStack 
import getCallStack, getStackFrame
 
   39     """A mutable set class that tracks the selection of multi-select 
   40     `~lsst.pex.config.ConfigChoiceField` objects. 
   44     dict_ : `ConfigInstanceDict` 
   45         The dictionary of instantiated configs. 
   48     at : `lsst.pex.config.callStack.StackFrame`, optional 
   49         The call stack when the selection was made. 
   50     label : `str`, optional 
   51         Label for history tracking. 
   52     setHistory : `bool`, optional 
   53         Add this even to the history, if `True`. 
   57     This class allows a user of a multi-select 
   58     `~lsst.pex.config.ConfigChoiceField` to add or discard items from the set 
   59     of active configs. Each change to the selection is tracked in the field's 
   63     def __init__(self, dict_, value, at=None, label="assignment", setHistory=True):
 
   73                     if v 
not in self.
_dict:
 
   75                         self.
_dict.__getitem__(v, at=at)
 
   77                 msg = 
"Value %s is of incorrect type %s. Sequence type expected" % (value, _typeStr(value))
 
   86     def add(self, value, at=None):
 
   87         """Add a value to the selected set. 
   91                                        "Cannot modify a frozen Config")
 
   96         if value 
not in self.
_dict:
 
   98             self.
_dict.__getitem__(value, at=at)
 
  100         self.
__history.
append((
"added %s to selection" % value, at, 
"selection"))
 
  104         """Discard a value from the selected set. 
  108                                        "Cannot modify a frozen Config")
 
  110         if value 
not in self.
_dict:
 
  116         self.
__history.
append((
"removed %s from selection" % value, at, 
"selection"))
 
  120         return len(self.
_set)
 
  126         return value 
in self.
_set 
  136     """Dictionary of instantiated configs, used to populate a 
  137     `~lsst.pex.config.ConfigChoiceField`. 
  141     config : `lsst.pex.config.Config` 
  142         A configuration instance. 
  143     field : `lsst.pex.config.Field`-type 
  144         A configuration field. Note that the `lsst.pex.config.Field.fieldmap` 
  145         attribute must provide key-based access to configuration classes, 
  146         (that is, ``typemap[name]``). 
  149         collections.abc.Mapping.__init__(self)
 
  154         self.
_history = config._history.setdefault(field.name, [])
 
  163         return k 
in self.
types 
  166         return len(self.
types)
 
  171     def _setSelection(self, value, at=None, label="assignment"):
 
  183             if value 
not in self.
_dict:
 
  191                                        "Single-selection field has no attribute 'names'")
 
  194     def _setNames(self, value):
 
  197                                        "Single-selection field has no attribute 'names'")
 
  203                                        "Single-selection field has no attribute 'names'")
 
  209                                        "Multi-selection field has no attribute 'name'")
 
  212     def _setName(self, value):
 
  215                                        "Multi-selection field has no attribute 'name'")
 
  221                                        "Multi-selection field has no attribute 'name'")
 
  224     names = property(_getNames, _setNames, _delNames)
 
  225     """List of names of active items in a multi-selection 
  226     ``ConfigInstanceDict``. Disabled in a single-selection ``_Registry``; use 
  227     the `name` attribute instead. 
  230     name = property(_getName, _setName, _delName)
 
  231     """Name of the active item in a single-selection ``ConfigInstanceDict``. 
  232     Disabled in a multi-selection ``_Registry``; use the ``names`` attribute 
  236     def _getActive(self):
 
  245     active = property(_getActive)
 
  246     """The selected items. 
  248     For multi-selection, this is equivalent to: ``[self[name] for name in 
  249     self.names]``. For single-selection, this is equivalent to: ``self[name]``. 
  254             value = self.
_dict[k]
 
  257                 dtype = self.
types[k]
 
  260                                            "Unknown key %r in Registry/ConfigChoiceField" % k)
 
  261             name = _joinNamePath(self.
_config._name, self.
_field.name, k)
 
  264                 at.insert(0, dtype._source)
 
  265             value = self.
_dict.setdefault(k, dtype(__name=name, __at=at, __label=label))
 
  273             dtype = self.
types[k]
 
  277         if value != dtype 
and type(value) != dtype:
 
  278             msg = 
"Value %s at key %k is of incorrect type %s. Expected type %s" % \
 
  279                 (value, k, _typeStr(value), _typeStr(dtype))
 
  284         name = _joinNamePath(self.
_config._name, self.
_field.name, k)
 
  285         oldValue = self.
_dict.get(k, 
None)
 
  288                 self.
_dict[k] = value(__name=name, __at=at, __label=label)
 
  290                 self.
_dict[k] = dtype(__name=name, __at=at, __label=label, **value._storage)
 
  294             oldValue.update(__at=at, __label=label, **value._storage)
 
  296     def _rename(self, fullname):
 
  298             v._rename(_joinNamePath(name=fullname, index=k))
 
  301         if hasattr(getattr(self.__class__, attr, 
None), 
'__set__'):
 
  303             object.__setattr__(self, attr, value)
 
  304         elif attr 
in self.__dict__ 
or attr 
in [
"_history", 
"_field", 
"_config", 
"_dict",
 
  305                                                "_selection", 
"__doc__", 
"_typemap"]:
 
  307             object.__setattr__(self, attr, value)
 
  310             msg = 
"%s has no attribute %s" % (_typeStr(self.
_field), attr)
 
  314         """Invoking this freeze method will create a local copy of the field 
  315         attribute's typemap. This decouples this instance dict from the 
  316         underlying objects type map ensuring that and subsequent changes to the 
  317         typemap will not be reflected in this instance (i.e imports adding 
  318         additional registry entries). 
  325     """A configuration field (`~lsst.pex.config.Field` subclass) that allows a 
  326     user to choose from a set of `~lsst.pex.config.Config` types. 
  331         Documentation string for the field. 
  332     typemap : `dict`-like 
  333         A mapping between keys and `~lsst.pex.config.Config`-types as values. 
  334         See *Examples* for details. 
  335     default : `str`, optional 
  336         The default configuration name. 
  337     optional : `bool`, optional 
  338         When `False`, `lsst.pex.config.Config.validate` will fail if the 
  339         field's value is `None`. 
  340     multi : `bool`, optional 
  341         If `True`, the field allows multiple selections. In this case, set the 
  342         selections by assigning a sequence to the ``names`` attribute of the 
  345         If `False`, the field allows only a single selection. In this case, 
  346         set the active config by assigning the config's key from the 
  347         ``typemap`` to the field's ``name`` attribute (see *Examples*). 
  348     deprecated : None or `str`, optional 
  349         A description of why this Field is deprecated, including removal date. 
  350         If not None, the string is appended to the docstring for this Field. 
  366     ``ConfigChoiceField`` instances can allow either single selections or 
  367     multiple selections, depending on the ``multi`` parameter. For 
  368     single-selection fields, set the selection with the ``name`` attribute. 
  369     For multi-selection fields, set the selection though the ``names`` 
  372     This field is validated only against the active selection. If the 
  373     ``active`` attribute is `None` and the field is not optional, validation 
  376     When saving a configuration with a ``ConfigChoiceField``, the entire set is 
  377     saved, as well as the active selection. 
  381     While the ``typemap`` is shared by all instances of the field, each 
  382     instance of the field has its own instance of a particular sub-config type. 
  384     For example, ``AaaConfig`` is a config object 
  386     >>> from lsst.pex.config import Config, ConfigChoiceField, Field 
  387     >>> class AaaConfig(Config): 
  388     ...     somefield = Field("doc", int) 
  391     The ``MyConfig`` config has a ``ConfigChoiceField`` field called ``choice`` 
  392     that maps the ``AaaConfig`` type to the ``"AAA"`` key: 
  394     >>> TYPEMAP = {"AAA", AaaConfig} 
  395     >>> class MyConfig(Config): 
  396     ...     choice = ConfigChoiceField("doc for choice", TYPEMAP) 
  399     Creating an instance of ``MyConfig``: 
  401     >>> instance = MyConfig() 
  403     Setting value of the field ``somefield`` on the "AAA" key of the ``choice`` 
  406     >>> instance.choice['AAA'].somefield = 5 
  408     **Selecting the active configuration** 
  410     Make the ``"AAA"`` key the active configuration value for the ``choice`` 
  413     >>> instance.choice = "AAA" 
  415     Alternatively, the last line can be written: 
  417     >>> instance.choice.name = "AAA" 
  419     (If the config instance allows multiple selections, you'd assign a sequence 
  420     to the ``names`` attribute instead.) 
  422     ``ConfigChoiceField`` instances also allow multiple values of the same 
  425     >>> TYPEMAP["CCC"] = AaaConfig 
  426     >>> TYPEMAP["BBB"] = AaaConfig 
  429     instanceDictClass = ConfigInstanceDict
 
  431     def __init__(self, doc, typemap, default=None, optional=False, multi=False, deprecated=None):
 
  434                     source=source, deprecated=deprecated)
 
  438     def _getOrMake(self, instance, label="default"):
 
  439         instanceDict = instance._storage.get(self.name)
 
  440         if instanceDict 
is None:
 
  442             instanceDict = self.
dtype(instance, self)
 
  443             instanceDict.__doc__ = self.
doc 
  444             instance._storage[self.name] = instanceDict
 
  445             history = instance._history.setdefault(self.name, [])
 
  446             history.append((
"Initialized from defaults", at, label))
 
  451         if instance 
is None or not isinstance(instance, Config):
 
  456     def __set__(self, instance, value, at=None, label="assignment"):
 
  463             for k, v 
in value.items():
 
  464                 instanceDict.__setitem__(k, v, at=at, label=label)
 
  465             instanceDict._setSelection(value._selection, at=at, label=label)
 
  468             instanceDict._setSelection(value, at=at, label=label)
 
  471         instanceDict = self.
__get__(instance)
 
  472         fullname = _joinNamePath(instance._name, self.name)
 
  473         instanceDict._rename(fullname)
 
  476         instanceDict = self.
__get__(instance)
 
  477         if instanceDict.active 
is None and not self.
optional:
 
  478             msg = 
"Required field cannot be None" 
  480         elif instanceDict.active 
is not None:
 
  482                 for a 
in instanceDict.active:
 
  485                 instanceDict.active.validate()
 
  488         instanceDict = self.
__get__(instance)
 
  492             dict_[
"names"] = instanceDict.names
 
  494             dict_[
"name"] = instanceDict.name
 
  497         for k, v 
in instanceDict.items():
 
  498             values[k] = v.toDict()
 
  499         dict_[
"values"] = values
 
  504         instanceDict = self.
__get__(instance)
 
  505         instanceDict.freeze()
 
  506         for v 
in instanceDict.values():
 
  509     def _collectImports(self, instance, imports):
 
  510         instanceDict = self.
__get__(instance)
 
  511         for config 
in instanceDict.values():
 
  512             config._collectImports()
 
  513             imports |= config._imports
 
  515     def save(self, outfile, instance):
 
  516         instanceDict = self.
__get__(instance)
 
  517         fullname = _joinNamePath(instance._name, self.name)
 
  518         for v 
in instanceDict.values():
 
  521             outfile.write(
u"{}.names={!r}\n".
format(fullname, instanceDict.names))
 
  523             outfile.write(
u"{}.name={!r}\n".
format(fullname, instanceDict.name))
 
  526         """Customize deep-copying, because we always want a reference to the 
  529         WARNING: this must be overridden by subclasses if they change the 
  530         constructor signature! 
  534         other.source = self.
source 
  537     def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
 
  538         """Compare two fields for equality. 
  540         Used by `lsst.pex.ConfigChoiceField.compare`. 
  544         instance1 : `lsst.pex.config.Config` 
  545             Left-hand side config instance to compare. 
  546         instance2 : `lsst.pex.config.Config` 
  547             Right-hand side config instance to compare. 
  549             If `True`, this function returns as soon as an inequality if found. 
  551             Relative tolerance for floating point comparisons. 
  553             Absolute tolerance for floating point comparisons. 
  555             A callable that takes a string, used (possibly repeatedly) to 
  561             `True` if the fields are equal, `False` otherwise. 
  565         Only the selected configurations are compared, as the parameters of any 
  566         others do not matter. 
  568         Floating point comparisons are performed by `numpy.allclose`. 
  570         d1 = getattr(instance1, self.name)
 
  571         d2 = getattr(instance2, self.name)
 
  573             _joinNamePath(instance1._name, self.name),
 
  574             _joinNamePath(instance2._name, self.name)
 
  576         if not compareScalars(
"selection for %s" % name, d1._selection, d2._selection, output=output):
 
  578         if d1._selection 
is None:
 
  581             nested = [(k, d1[k], d2[k]) 
for k 
in d1._selection]
 
  583             nested = [(d1._selection, d1[d1._selection], d2[d1._selection])]
 
  585         for k, c1, c2 
in nested:
 
  586             result = 
compareConfigs(
"%s[%r]" % (name, k), c1, c2, shortcut=shortcut,
 
  587                                     rtol=rtol, atol=atol, output=output)
 
  588             if not result 
and shortcut:
 
  590             equal = equal 
and result