23 __all__ = [
"ConfigChoiceField"]
26 import collections.abc
28 from .config
import Config, Field, FieldValidationError, _typeStr, _joinNamePath
29 from .comparison
import getComparisonName, compareScalars, compareConfigs
30 from .callStack
import getCallStack, getStackFrame
34 """A mutable set class that tracks the selection of multi-select 35 `~lsst.pex.config.ConfigChoiceField` objects. 39 dict_ : `ConfigInstanceDict` 40 The dictionary of instantiated configs. 43 at : `lsst.pex.config.callStack.StackFrame`, optional 44 The call stack when the selection was made. 45 label : `str`, optional 46 Label for history tracking. 47 setHistory : `bool`, optional 48 Add this even to the history, if `True`. 52 This class allows a user of a multi-select 53 `~lsst.pex.config.ConfigChoiceField` to add or discard items from the set 54 of active configs. Each change to the selection is tracked in the field's 58 def __init__(self, dict_, value, at=None, label="assignment", setHistory=True):
68 if v
not in self.
_dict:
70 self.
_dict.__getitem__(v, at=at)
72 msg =
"Value %s is of incorrect type %s. Sequence type expected"(value, _typeStr(value))
74 self.
_set = set(value)
79 self.
__history.append((
"Set selection to %s" % self, at, label))
81 def add(self, value, at=None):
82 """Add a value to the selected set. 86 "Cannot modify a frozen Config")
91 if value
not in self.
_dict:
93 self.
_dict.__getitem__(value, at=at)
95 self.
__history.append((
"added %s to selection" % value, at,
"selection"))
99 """Discard a value from the selected set. 103 "Cannot modify a frozen Config")
105 if value
not in self.
_dict:
111 self.
__history.append((
"removed %s from selection" % value, at,
"selection"))
115 return len(self.
_set)
118 return iter(self.
_set)
121 return value
in self.
_set 124 return repr(list(self.
_set))
127 return str(list(self.
_set))
131 """Dictionary of instantiated configs, used to populate a 132 `~lsst.pex.config.ConfigChoiceField`. 136 config : `lsst.pex.config.Config` 137 A configuration instance. 138 field : `lsst.pex.config.Field`-type 139 A configuration field. Note that the `lsst.pex.config.Field.fieldmap` 140 attribute must provide key-based access to configuration classes, 141 (that is, ``typemap[name]``). 144 collections.abc.Mapping.__init__(self)
149 self.
_history = config._history.setdefault(field.name, [])
152 types = property(
lambda x: x._field.typemap)
155 return k
in self.
_field.typemap
158 return len(self.
_field.typemap)
161 return iter(self.
_field.typemap)
163 def _setSelection(self, value, at=None, label="assignment"):
175 if value
not in self.
_dict:
178 self.
_history.append((value, at, label))
183 "Single-selection field has no attribute 'names'")
186 def _setNames(self, value):
189 "Single-selection field has no attribute 'names'")
195 "Single-selection field has no attribute 'names'")
201 "Multi-selection field has no attribute 'name'")
204 def _setName(self, value):
207 "Multi-selection field has no attribute 'name'")
213 "Multi-selection field has no attribute 'name'")
216 names = property(_getNames, _setNames, _delNames)
217 """List of names of active items in a multi-selection 218 ``ConfigInstanceDict``. Disabled in a single-selection ``_Registry``; use 219 the `name` attribute instead. 222 name = property(_getName, _setName, _delName)
223 """Name of the active item in a single-selection ``ConfigInstanceDict``. 224 Disabled in a multi-selection ``_Registry``; use the ``names`` attribute 228 def _getActive(self):
237 active = property(_getActive)
238 """The selected items. 240 For multi-selection, this is equivalent to: ``[self[name] for name in 241 self.names]``. For single-selection, this is equivalent to: ``self[name]``. 246 value = self.
_dict[k]
249 dtype = self.
_field.typemap[k]
252 "Unknown key %r in Registry/ConfigChoiceField" % k)
253 name = _joinNamePath(self.
_config._name, self.
_field.name, k)
256 at.insert(0, dtype._source)
257 value = self.
_dict.setdefault(k, dtype(__name=name, __at=at, __label=label))
265 dtype = self.
_field.typemap[k]
269 if value != dtype
and type(value) != dtype:
270 msg =
"Value %s at key %k is of incorrect type %s. Expected type %s" % \
271 (value, k, _typeStr(value), _typeStr(dtype))
276 name = _joinNamePath(self.
_config._name, self.
_field.name, k)
277 oldValue = self.
_dict.get(k,
None)
280 self.
_dict[k] = value(__name=name, __at=at, __label=label)
282 self.
_dict[k] = dtype(__name=name, __at=at, __label=label, **value._storage)
286 oldValue.update(__at=at, __label=label, **value._storage)
288 def _rename(self, fullname):
289 for k, v
in self.
_dict.items():
290 v._rename(_joinNamePath(name=fullname, index=k))
293 if hasattr(getattr(self.__class__, attr,
None),
'__set__'):
295 object.__setattr__(self, attr, value)
296 elif attr
in self.__dict__
or attr
in [
"_history",
"_field",
"_config",
"_dict",
297 "_selection",
"__doc__"]:
299 object.__setattr__(self, attr, value)
302 msg =
"%s has no attribute %s" % (_typeStr(self.
_field), attr)
307 """A configuration field (`~lsst.pex.config.Field` subclass) that allows a 308 user to choose from a set of `~lsst.pex.config.Config` types. 313 Documentation string for the field. 314 typemap : `dict`-like 315 A mapping between keys and `~lsst.pex.config.Config`-types as values. 316 See *Examples* for details. 317 default : `str`, optional 318 The default configuration name. 319 optional : `bool`, optional 320 When `False`, `lsst.pex.config.Config.validate` will fail if the 321 field's value is `None`. 322 multi : `bool`, optional 323 If `True`, the field allows multiple selections. In this case, set the 324 selections by assigning a sequence to the ``names`` attribute of the 327 If `False`, the field allows only a single selection. In this case, 328 set the active config by assigning the config's key from the 329 ``typemap`` to the field's ``name`` attribute (see *Examples*). 330 deprecated : None or `str`, optional 331 A description of why this Field is deprecated, including removal date. 332 If not None, the string is appended to the docstring for this Field. 348 ``ConfigChoiceField`` instances can allow either single selections or 349 multiple selections, depending on the ``multi`` parameter. For 350 single-selection fields, set the selection with the ``name`` attribute. 351 For multi-selection fields, set the selection though the ``names`` 354 This field is validated only against the active selection. If the 355 ``active`` attribute is `None` and the field is not optional, validation 358 When saving a configuration with a ``ConfigChoiceField``, the entire set is 359 saved, as well as the active selection. 363 While the ``typemap`` is shared by all instances of the field, each 364 instance of the field has its own instance of a particular sub-config type. 366 For example, ``AaaConfig`` is a config object 368 >>> from lsst.pex.config import Config, ConfigChoiceField, Field 369 >>> class AaaConfig(Config): 370 ... somefield = Field("doc", int) 373 The ``MyConfig`` config has a ``ConfigChoiceField`` field called ``choice`` 374 that maps the ``AaaConfig`` type to the ``"AAA"`` key: 376 >>> TYPEMAP = {"AAA", AaaConfig} 377 >>> class MyConfig(Config): 378 ... choice = ConfigChoiceField("doc for choice", TYPEMAP) 381 Creating an instance of ``MyConfig``: 383 >>> instance = MyConfig() 385 Setting value of the field ``somefield`` on the "AAA" key of the ``choice`` 388 >>> instance.choice['AAA'].somefield = 5 390 **Selecting the active configuration** 392 Make the ``"AAA"`` key the active configuration value for the ``choice`` 395 >>> instance.choice = "AAA" 397 Alternatively, the last line can be written: 399 >>> instance.choice.name = "AAA" 401 (If the config instance allows multiple selections, you'd assign a sequence 402 to the ``names`` attribute instead.) 404 ``ConfigChoiceField`` instances also allow multiple values of the same type: 406 >>> TYPEMAP["CCC"] = AaaConfig 407 >>> TYPEMAP["BBB"] = AaaConfig 410 instanceDictClass = ConfigInstanceDict
412 def __init__(self, doc, typemap, default=None, optional=False, multi=False, deprecated=None):
415 source=source, deprecated=deprecated)
419 def _getOrMake(self, instance, label="default"):
420 instanceDict = instance._storage.get(self.name)
421 if instanceDict
is None:
423 instanceDict = self.
dtype(instance, self)
424 instanceDict.__doc__ = self.
doc 425 instance._storage[self.name] = instanceDict
426 history = instance._history.setdefault(self.name, [])
427 history.append((
"Initialized from defaults", at, label))
432 if instance
is None or not isinstance(instance, Config):
437 def __set__(self, instance, value, at=None, label="assignment"):
444 for k, v
in value.items():
445 instanceDict.__setitem__(k, v, at=at, label=label)
446 instanceDict._setSelection(value._selection, at=at, label=label)
449 instanceDict._setSelection(value, at=at, label=label)
452 instanceDict = self.
__get__(instance)
453 fullname = _joinNamePath(instance._name, self.name)
454 instanceDict._rename(fullname)
457 instanceDict = self.
__get__(instance)
458 if instanceDict.active
is None and not self.
optional:
459 msg =
"Required field cannot be None" 461 elif instanceDict.active
is not None:
463 for a
in instanceDict.active:
466 instanceDict.active.validate()
469 instanceDict = self.
__get__(instance)
473 dict_[
"names"] = instanceDict.names
475 dict_[
"name"] = instanceDict.name
478 for k, v
in instanceDict.items():
479 values[k] = v.toDict()
480 dict_[
"values"] = values
489 instanceDict = self.
__get__(instance)
490 for v
in instanceDict.values():
493 def _collectImports(self, instance, imports):
494 instanceDict = self.
__get__(instance)
495 for config
in instanceDict.values():
496 config._collectImports()
497 imports |= config._imports
499 def save(self, outfile, instance):
500 instanceDict = self.
__get__(instance)
501 fullname = _joinNamePath(instance._name, self.name)
502 for v
in instanceDict.values():
505 outfile.write(
u"{}.names={!r}\n".
format(fullname, instanceDict.names))
507 outfile.write(
u"{}.name={!r}\n".
format(fullname, instanceDict.name))
510 """Customize deep-copying, because we always want a reference to the 513 WARNING: this must be overridden by subclasses if they change the 514 constructor signature! 516 other = type(self)(doc=self.
doc, typemap=self.
typemap, default=copy.deepcopy(self.
default),
518 other.source = self.
source 521 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
522 """Compare two fields for equality. 524 Used by `lsst.pex.ConfigChoiceField.compare`. 528 instance1 : `lsst.pex.config.Config` 529 Left-hand side config instance to compare. 530 instance2 : `lsst.pex.config.Config` 531 Right-hand side config instance to compare. 533 If `True`, this function returns as soon as an inequality if found. 535 Relative tolerance for floating point comparisons. 537 Absolute tolerance for floating point comparisons. 539 A callable that takes a string, used (possibly repeatedly) to 545 `True` if the fields are equal, `False` otherwise. 549 Only the selected configurations are compared, as the parameters of any 550 others do not matter. 552 Floating point comparisons are performed by `numpy.allclose`. 554 d1 = getattr(instance1, self.name)
555 d2 = getattr(instance2, self.name)
557 _joinNamePath(instance1._name, self.name),
558 _joinNamePath(instance2._name, self.name)
560 if not compareScalars(
"selection for %s" % name, d1._selection, d2._selection, output=output):
562 if d1._selection
is None:
565 nested = [(k, d1[k], d2[k])
for k
in d1._selection]
567 nested = [(d1._selection, d1[d1._selection], d2[d1._selection])]
569 for k, c1, c2
in nested:
570 result =
compareConfigs(
"%s[%r]" % (name, k), c1, c2, shortcut=shortcut,
571 rtol=rtol, atol=atol, output=output)
572 if not result
and shortcut:
574 equal = equal
and result
def compareConfigs(name, c1, c2, shortcut=True, rtol=1E-8, atol=1E-8, output=None)
def __set__(self, instance, value, at=None, label="assignment")
def __contains__(self, k)
def save(self, outfile, instance)
def __get__(self, instance, owner=None, at=None, label="default")
def __init__(self, doc, typemap, default=None, optional=False, multi=False, deprecated=None)
def __setitem__(self, k, value, at=None, label="assignment")
def add(self, value, at=None)
def toDict(self, instance)
def getStackFrame(relative=0)
def _getOrMake(self, instance, label="default")
def freeze(self, instance)
def __getitem__(self, k, at=None, label="default")
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
def validate(self, instance)
def __get__(self, instance, owner=None)
def discard(self, value, at=None)
def compareScalars(name, v1, v2, output, rtol=1E-8, atol=1E-8, dtype=None)
def __deepcopy__(self, memo)
def __init__(self, config, field)
def __contains__(self, value)
def _setup(self, doc, dtype, default, check, optional, source, deprecated)
def _setSelection(self, value, at=None, label="assignment")
def __init__(self, dict_, value, at=None, label="assignment", setHistory=True)
def rename(self, instance)
def getComparisonName(name1, name2)
def __setattr__(self, attr, value, at=None, label="assignment")