28 __all__ = [
"ConfigChoiceField"]
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):
66 self.
_dict_dict = dict_
73 if v
not in self.
_dict_dict:
75 self.
_dict_dict.__getitem__(v, at=at)
77 msg =
"Value %s is of incorrect type %s. Sequence type expected" % (value, _typeStr(value))
84 self.
__history__history.
append((
"Set selection to %s" % self, at, label))
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_dict:
98 self.
_dict_dict.__getitem__(value, at=at)
100 self.
__history__history.
append((
"added %s to selection" % value, at,
"selection"))
104 """Discard a value from the selected set.
106 if self.
_config_config._frozen:
108 "Cannot modify a frozen Config")
110 if value
not in self.
_dict_dict:
116 self.
__history__history.
append((
"removed %s from selection" % value, at,
"selection"))
120 return len(self.
_set_set)
126 return value
in self.
_set_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)
150 self.
_dict_dict = dict()
154 self.
_history_history = config._history.setdefault(field.name, [])
155 self.
__doc____doc__ = field.doc
163 return k
in self.
typestypes
166 return len(self.
typestypes)
171 def _setSelection(self, value, at=None, label="assignment"):
172 if self.
_config_config._frozen:
180 elif self.
_field_field.multi:
183 if value
not in self.
_dict_dict:
189 if not self.
_field_field.multi:
191 "Single-selection field has no attribute 'names'")
194 def _setNames(self, value):
195 if not self.
_field_field.multi:
197 "Single-selection field has no attribute 'names'")
201 if not self.
_field_field.multi:
203 "Single-selection field has no attribute 'names'")
207 if self.
_field_field.multi:
209 "Multi-selection field has no attribute 'name'")
212 def _setName(self, value):
213 if self.
_field_field.multi:
215 "Multi-selection field has no attribute 'name'")
219 if self.
_field_field.multi:
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):
240 if self.
_field_field.multi:
241 return [self[c]
for c
in self.
_selection_selection]
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_dict[k]
257 dtype = self.
typestypes[k]
260 "Unknown key %r in Registry/ConfigChoiceField" % k)
261 name = _joinNamePath(self.
_config_config._name, self.
_field_field.name, k)
264 at.insert(0, dtype._source)
265 value = self.
_dict_dict.setdefault(k,
dtype(__name=name, __at=at, __label=label))
269 if self.
_config_config._frozen:
273 dtype = self.
typestypes[k]
277 if value != dtype
and type(value) != dtype:
278 msg =
"Value %s at key %s is of incorrect type %s. Expected type %s" % \
279 (value, k, _typeStr(value), _typeStr(dtype))
284 name = _joinNamePath(self.
_config_config._name, self.
_field_field.name, k)
285 oldValue = self.
_dict_dict.get(k,
None)
288 self.
_dict_dict[k] = value(__name=name, __at=at, __label=label)
290 self.
_dict_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_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):
433 self.
_setup_setup(doc=doc, dtype=self.
instanceDictClassinstanceDictClass, default=default, check=
None, optional=optional,
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.
dtypedtype(instance, self)
443 instanceDict.__doc__ = self.
docdoc
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"):
461 instanceDict = self.
_getOrMake_getOrMake(instance)
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)
472 fullname = _joinNamePath(instance._name, self.name)
473 instanceDict._rename(fullname)
477 if instanceDict.active
is None and not self.
optionaloptional:
478 msg =
"Required field cannot be None"
480 elif instanceDict.active
is not None:
482 for a
in instanceDict.active:
485 instanceDict.active.validate()
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
505 instanceDict.freeze()
506 for v
in instanceDict.values():
509 def _collectImports(self, instance, imports):
511 for config
in instanceDict.values():
512 config._collectImports()
513 imports |= config._imports
515 def save(self, outfile, 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!
532 other =
type(self)(doc=self.
docdoc, typemap=self.
typemaptypemap, default=copy.deepcopy(self.
defaultdefault),
534 other.source = self.
sourcesource
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
std::vector< SchemaItem< Flag > > * items
def __get__(self, instance, owner=None, at=None, label="default")
def _setup(self, doc, dtype, default, check, optional, source, deprecated)
def __set__(self, instance, value, at=None, label="assignment")
def toDict(self, instance)
def freeze(self, instance)
def __init__(self, doc, typemap, default=None, optional=False, multi=False, deprecated=None)
def _getOrMake(self, instance, label="default")
def rename(self, instance)
def __get__(self, instance, owner=None)
def validate(self, instance)
def save(self, outfile, instance)
def __deepcopy__(self, memo)
def _setSelection(self, value, at=None, label="assignment")
def __getitem__(self, k, at=None, label="default")
def __contains__(self, k)
def __init__(self, config, field)
def __setattr__(self, attr, value, at=None, label="assignment")
def __setitem__(self, k, value, at=None, label="assignment")
def __contains__(self, value)
def discard(self, value, at=None)
def __init__(self, dict_, value, at=None, label="assignment", setHistory=True)
def add(self, value, at=None)
daf::base::PropertyList * list
daf::base::PropertySet * set
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
def getStackFrame(relative=0)
def compareConfigs(name, c1, c2, shortcut=True, rtol=1E-8, atol=1E-8, output=None)
def compareScalars(name, v1, v2, output, rtol=1E-8, atol=1E-8, dtype=None)
def getComparisonName(name1, name2)
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)