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
41 """A mutable set class that tracks the selection of multi-select
42 `~lsst.pex.config.ConfigChoiceField` objects.
46 dict_ : `ConfigInstanceDict`
47 The dictionary of instantiated configs.
50 at : `lsst.pex.config.callStack.StackFrame`, optional
51 The call stack when the selection was made.
52 label : `str`, optional
53 Label for history tracking.
54 setHistory : `bool`, optional
55 Add this even to the history, if `True`.
59 This class allows a user of a multi-select
60 `~lsst.pex.config.ConfigChoiceField` to add or discard items from the set
61 of active configs. Each change to the selection is tracked in the field's
65 def __init__(self, dict_, value, at=None, label="assignment", setHistory=True):
68 self.
_dict_dict = dict_
75 if v
not in self.
_dict_dict:
77 self.
_dict_dict.__getitem__(v, at=at)
79 msg =
"Value %s is of incorrect type %s. Sequence type expected" % (value, _typeStr(value))
86 self.
__history__history.
append((
"Set selection to %s" % self, at, label))
89 def _config(self) -> Config:
92 assert(self.
_config__config_()
is not None)
95 def add(self, value, at=None):
96 """Add a value to the selected set.
100 "Cannot modify a frozen Config")
105 if value
not in self.
_dict_dict:
107 self.
_dict_dict.__getitem__(value, at=at)
109 self.
__history__history.
append((
"added %s to selection" % value, at,
"selection"))
113 """Discard a value from the selected set.
115 if self.
_config_config._frozen:
117 "Cannot modify a frozen Config")
119 if value
not in self.
_dict_dict:
125 self.
__history__history.
append((
"removed %s from selection" % value, at,
"selection"))
129 return len(self.
_set_set)
135 return value
in self.
_set_set
145 """Dictionary of instantiated configs, used to populate a
146 `~lsst.pex.config.ConfigChoiceField`.
150 config : `lsst.pex.config.Config`
151 A configuration instance.
152 field : `lsst.pex.config.Field`-type
153 A configuration field. Note that the `lsst.pex.config.Field.fieldmap`
154 attribute must provide key-based access to configuration classes,
155 (that is, ``typemap[name]``).
158 collections.abc.Mapping.__init__(self)
159 self.
_dict_dict = dict()
163 self.
_history_history = config._history.setdefault(field.name, [])
164 self.
__doc____doc__ = field.doc
172 return k
in self.
typestypes
175 return len(self.
typestypes)
180 def _setSelection(self, value, at=None, label="assignment"):
181 if self.
_config_config._frozen:
189 elif self.
_field_field.multi:
192 if value
not in self.
_dict_dict:
198 if not self.
_field_field.multi:
200 "Single-selection field has no attribute 'names'")
203 def _setNames(self, value):
204 if not self.
_field_field.multi:
206 "Single-selection field has no attribute 'names'")
210 if not self.
_field_field.multi:
212 "Single-selection field has no attribute 'names'")
216 if self.
_field_field.multi:
218 "Multi-selection field has no attribute 'name'")
221 def _setName(self, value):
222 if self.
_field_field.multi:
224 "Multi-selection field has no attribute 'name'")
228 if self.
_field_field.multi:
230 "Multi-selection field has no attribute 'name'")
233 names = property(_getNames, _setNames, _delNames)
234 """List of names of active items in a multi-selection
235 ``ConfigInstanceDict``. Disabled in a single-selection ``_Registry``; use
236 the `name` attribute instead.
239 name = property(_getName, _setName, _delName)
240 """Name of the active item in a single-selection ``ConfigInstanceDict``.
241 Disabled in a multi-selection ``_Registry``; use the ``names`` attribute
245 def _getActive(self):
249 if self.
_field_field.multi:
250 return [self[c]
for c
in self.
_selection_selection]
254 active = property(_getActive)
255 """The selected items.
257 For multi-selection, this is equivalent to: ``[self[name] for name in
258 self.names]``. For single-selection, this is equivalent to: ``self[name]``.
263 value = self.
_dict_dict[k]
266 dtype = self.
typestypes[k]
269 "Unknown key %r in Registry/ConfigChoiceField" % k)
270 name = _joinNamePath(self.
_config_config._name, self.
_field_field.name, k)
273 at.insert(0, dtype._source)
274 value = self.
_dict_dict.setdefault(k,
dtype(__name=name, __at=at, __label=label))
278 if self.
_config_config._frozen:
282 dtype = self.
typestypes[k]
286 if value != dtype
and type(value) != dtype:
287 msg =
"Value %s at key %s is of incorrect type %s. Expected type %s" % \
288 (value, k, _typeStr(value), _typeStr(dtype))
293 name = _joinNamePath(self.
_config_config._name, self.
_field_field.name, k)
294 oldValue = self.
_dict_dict.get(k,
None)
297 self.
_dict_dict[k] = value(__name=name, __at=at, __label=label)
299 self.
_dict_dict[k] =
dtype(__name=name, __at=at, __label=label, **value._storage)
303 oldValue.update(__at=at, __label=label, **value._storage)
305 def _rename(self, fullname):
307 v._rename(_joinNamePath(name=fullname, index=k))
310 if hasattr(getattr(self.__class__, attr,
None),
'__set__'):
312 object.__setattr__(self, attr, value)
313 elif attr
in self.__dict__
or attr
in [
"_history",
"_field",
"_config",
"_dict",
314 "_selection",
"__doc__",
"_typemap"]:
316 object.__setattr__(self, attr, value)
319 msg =
"%s has no attribute %s" % (_typeStr(self.
_field_field), attr)
323 """Invoking this freeze method will create a local copy of the field
324 attribute's typemap. This decouples this instance dict from the
325 underlying objects type map ensuring that and subsequent changes to the
326 typemap will not be reflected in this instance (i.e imports adding
327 additional registry entries).
334 """A configuration field (`~lsst.pex.config.Field` subclass) that allows a
335 user to choose from a set of `~lsst.pex.config.Config` types.
340 Documentation string for the field.
341 typemap : `dict`-like
342 A mapping between keys and `~lsst.pex.config.Config`-types as values.
343 See *Examples* for details.
344 default : `str`, optional
345 The default configuration name.
346 optional : `bool`, optional
347 When `False`, `lsst.pex.config.Config.validate` will fail if the
348 field's value is `None`.
349 multi : `bool`, optional
350 If `True`, the field allows multiple selections. In this case, set the
351 selections by assigning a sequence to the ``names`` attribute of the
354 If `False`, the field allows only a single selection. In this case,
355 set the active config by assigning the config's key from the
356 ``typemap`` to the field's ``name`` attribute (see *Examples*).
357 deprecated : None or `str`, optional
358 A description of why this Field is deprecated, including removal date.
359 If not None, the string is appended to the docstring for this Field.
375 ``ConfigChoiceField`` instances can allow either single selections or
376 multiple selections, depending on the ``multi`` parameter. For
377 single-selection fields, set the selection with the ``name`` attribute.
378 For multi-selection fields, set the selection though the ``names``
381 This field is validated only against the active selection. If the
382 ``active`` attribute is `None` and the field is not optional, validation
385 When saving a configuration with a ``ConfigChoiceField``, the entire set is
386 saved, as well as the active selection.
390 While the ``typemap`` is shared by all instances of the field, each
391 instance of the field has its own instance of a particular sub-config type.
393 For example, ``AaaConfig`` is a config object
395 >>> from lsst.pex.config import Config, ConfigChoiceField, Field
396 >>> class AaaConfig(Config):
397 ... somefield = Field("doc", int)
400 The ``MyConfig`` config has a ``ConfigChoiceField`` field called ``choice``
401 that maps the ``AaaConfig`` type to the ``"AAA"`` key:
403 >>> TYPEMAP = {"AAA", AaaConfig}
404 >>> class MyConfig(Config):
405 ... choice = ConfigChoiceField("doc for choice", TYPEMAP)
408 Creating an instance of ``MyConfig``:
410 >>> instance = MyConfig()
412 Setting value of the field ``somefield`` on the "AAA" key of the ``choice``
415 >>> instance.choice['AAA'].somefield = 5
417 **Selecting the active configuration**
419 Make the ``"AAA"`` key the active configuration value for the ``choice``
422 >>> instance.choice = "AAA"
424 Alternatively, the last line can be written:
426 >>> instance.choice.name = "AAA"
428 (If the config instance allows multiple selections, you'd assign a sequence
429 to the ``names`` attribute instead.)
431 ``ConfigChoiceField`` instances also allow multiple values of the same
434 >>> TYPEMAP["CCC"] = AaaConfig
435 >>> TYPEMAP["BBB"] = AaaConfig
438 instanceDictClass = ConfigInstanceDict
440 def __init__(self, doc, typemap, default=None, optional=False, multi=False, deprecated=None):
442 self.
_setup_setup(doc=doc, dtype=self.
instanceDictClassinstanceDictClass, default=default, check=
None, optional=optional,
443 source=source, deprecated=deprecated)
447 def _getOrMake(self, instance, label="default"):
448 instanceDict = instance._storage.get(self.name)
449 if instanceDict
is None:
451 instanceDict = self.
dtypedtype(instance, self)
452 instanceDict.__doc__ = self.
docdoc
453 instance._storage[self.name] = instanceDict
454 history = instance._history.setdefault(self.name, [])
455 history.append((
"Initialized from defaults", at, label))
460 if instance
is None or not isinstance(instance, Config):
465 def __set__(self, instance, value, at=None, label="assignment"):
470 instanceDict = self.
_getOrMake_getOrMake(instance)
472 for k, v
in value.items():
473 instanceDict.__setitem__(k, v, at=at, label=label)
474 instanceDict._setSelection(value._selection, at=at, label=label)
477 instanceDict._setSelection(value, at=at, label=label)
481 fullname = _joinNamePath(instance._name, self.name)
482 instanceDict._rename(fullname)
486 if instanceDict.active
is None and not self.
optionaloptional:
487 msg =
"Required field cannot be None"
489 elif instanceDict.active
is not None:
491 for a
in instanceDict.active:
494 instanceDict.active.validate()
501 dict_[
"names"] = instanceDict.names
503 dict_[
"name"] = instanceDict.name
506 for k, v
in instanceDict.items():
507 values[k] = v.toDict()
508 dict_[
"values"] = values
514 instanceDict.freeze()
515 for v
in instanceDict.values():
518 def _collectImports(self, instance, imports):
520 for config
in instanceDict.values():
521 config._collectImports()
522 imports |= config._imports
524 def save(self, outfile, instance):
526 fullname = _joinNamePath(instance._name, self.name)
527 for v
in instanceDict.values():
530 outfile.write(
u"{}.names={!r}\n".
format(fullname, instanceDict.names))
532 outfile.write(
u"{}.name={!r}\n".
format(fullname, instanceDict.name))
535 """Customize deep-copying, because we always want a reference to the
538 WARNING: this must be overridden by subclasses if they change the
539 constructor signature!
541 other =
type(self)(doc=self.
docdoc, typemap=self.
typemaptypemap, default=copy.deepcopy(self.
defaultdefault),
543 other.source = self.
sourcesource
546 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
547 """Compare two fields for equality.
549 Used by `lsst.pex.ConfigChoiceField.compare`.
553 instance1 : `lsst.pex.config.Config`
554 Left-hand side config instance to compare.
555 instance2 : `lsst.pex.config.Config`
556 Right-hand side config instance to compare.
558 If `True`, this function returns as soon as an inequality if found.
560 Relative tolerance for floating point comparisons.
562 Absolute tolerance for floating point comparisons.
564 A callable that takes a string, used (possibly repeatedly) to
570 `True` if the fields are equal, `False` otherwise.
574 Only the selected configurations are compared, as the parameters of any
575 others do not matter.
577 Floating point comparisons are performed by `numpy.allclose`.
579 d1 = getattr(instance1, self.name)
580 d2 = getattr(instance2, self.name)
582 _joinNamePath(instance1._name, self.name),
583 _joinNamePath(instance2._name, self.name)
585 if not compareScalars(
"selection for %s" % name, d1._selection, d2._selection, output=output):
587 if d1._selection
is None:
590 nested = [(k, d1[k], d2[k])
for k
in d1._selection]
592 nested = [(d1._selection, d1[d1._selection], d2[d1._selection])]
594 for k, c1, c2
in nested:
595 result =
compareConfigs(
"%s[%r]" % (name, k), c1, c2, shortcut=shortcut,
596 rtol=rtol, atol=atol, output=output)
597 if not result
and shortcut:
599 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)