27from __future__
import annotations
29__all__ = [
"ConfigChoiceField"]
34from typing
import Any, ForwardRef, overload
36from .callStack
import getCallStack, getStackFrame
37from .comparison
import compareConfigs, compareScalars, getComparisonName
38from .config
import Config, Field, FieldValidationError, UnexpectedProxyUsageError, _joinNamePath, _typeStr
42 """A mutable set class that tracks the selection of multi-select
47 dict_ : `ConfigInstanceDict`
48 The dictionary of instantiated configs.
52 The call stack when the selection was made.
53 label : `str`, optional
54 Label for history tracking.
55 setHistory : `bool`, optional
56 Add this even to the history,
if `
True`.
60 This
class allows
a user of
a multi-select
62 of active configs. Each change to the selection
is tracked
in the field
's
66 def __init__(self, dict_, value, at=None, label="assignment", setHistory=True):
76 if v
not in self.
_dict:
78 self.
_dict.__getitem__(v, at=at)
80 msg = f
"Value {value} is of incorrect type {_typeStr(value)}. Sequence type expected"
87 self.
__history.append((
"Set selection to %s" % self, at, label))
96 def add(self, value, at=None):
97 """Add a value to the selected set."""
104 if value
not in self.
_dict:
106 self.
_dict.__getitem__(value, at=at)
108 self.
__history.append((
"added %s to selection" % value, at,
"selection"))
112 """Discard a value from the selected set."""
116 if value
not in self.
_dict:
122 self.
__history.append((
"removed %s from selection" % value, at,
"selection"))
126 return len(self.
_set)
129 return iter(self.
_set)
132 return value
in self.
_set
142 f
"Proxy container for config field {self._field.name} cannot "
143 "be pickled; it should be converted to a built-in container before "
144 "being assigned to other objects or variables."
149 """Dictionary of instantiated configs, used to populate a
155 A configuration instance.
157 A configuration field. Note that the `lsst.pex.config.Field.fieldmap`
158 attribute must provide key-based access to configuration classes,
159 (that is, ``typemap[name]``).
163 collections.abc.Mapping.__init__(self)
197 if value
not in self.
_dict:
205 self.
_field, self.
_config,
"Single-selection field has no attribute 'names'"
212 self.
_field, self.
_config,
"Single-selection field has no attribute 'names'"
219 self.
_field, self.
_config,
"Single-selection field has no attribute 'names'"
226 self.
_field, self.
_config,
"Multi-selection field has no attribute 'name'"
233 self.
_field, self.
_config,
"Multi-selection field has no attribute 'name'"
240 self.
_field, self.
_config,
"Multi-selection field has no attribute 'name'"
244 names = property(_getNames, _setNames, _delNames)
245 """List of names of active items in a multi-selection
246 ``ConfigInstanceDict``. Disabled in a single-selection ``_Registry``; use
247 the `name` attribute instead.
250 name = property(_getName, _setName, _delName)
251 """Name of the active item in a single-selection ``ConfigInstanceDict``.
252 Disabled in a multi-selection ``_Registry``; use the ``names`` attribute
265 active = property(_getActive)
266 """The selected items.
268 For multi-selection, this is equivalent to: ``[self[name]
for name
in
269 self.
namesnames]``. For single-selection, this
is equivalent to: ``self[name]``.
274 value = self.
_dict[k]
280 self.
_field, self.
_config,
"Unknown key %r in Registry/ConfigChoiceField" % k
282 name = _joinNamePath(self.
_config._name, self.
_field.name, k)
285 at.insert(0, dtype._source)
286 value = self.
_dict.setdefault(k, dtype(__name=name, __at=at, __label=label))
298 if value != dtype
and type(value) != dtype:
299 msg =
"Value {} at key {} is of incorrect type {}. Expected type {}".format(
309 name = _joinNamePath(self.
_config._name, self.
_field.name, k)
310 oldValue = self.
_dict.get(k,
None)
313 self.
_dict[k] = value(__name=name, __at=at, __label=label)
315 self.
_dict[k] = dtype(__name=name, __at=at, __label=label, **value._storage)
319 oldValue.update(__at=at, __label=label, **value._storage)
323 v._rename(_joinNamePath(name=fullname, index=k))
328 object.__setattr__(self, attr, value)
329 elif attr
in self.__dict__
or attr
in [
339 object.__setattr__(self, attr, value)
342 msg = f
"{_typeStr(self._field)} has no attribute {attr}"
346 """Freeze the config.
348 Invoking this freeze method will create a local copy of the field
349 attribute's typemap. This decouples this instance dict from the
350 underlying objects type map ensuring that and subsequent changes to the
351 typemap will
not be reflected
in this instance (i.e imports adding
352 additional registry entries).
359 f
"Proxy container for config field {self._field.name} cannot "
360 "be pickled; it should be converted to a built-in container before "
361 "being assigned to other objects or variables."
366 """A configuration field (`~lsst.pex.config.Field` subclass) that allows a
372 Documentation string
for the field.
373 typemap : `dict`-like
375 See *Examples*
for details.
376 default : `str`, optional
377 The default configuration name.
378 optional : `bool`, optional
379 When `
False`, `lsst.pex.config.Config.validate` will fail
if the
380 field
's value is `None`.
381 multi : `bool`, optional
382 If `True`, the field allows multiple selections. In this case, set the
383 selections by assigning a sequence to the ``names`` attribute of the
386 If `
False`, the field allows only a single selection. In this case,
387 set the active config by assigning the config
's key from the
388 ``typemap`` to the field's ``name`` attribute (see *Examples*).
389 deprecated : None or `str`, optional
390 A description of why this Field
is deprecated, including removal date.
391 If
not None, the string
is appended to the docstring
for this Field.
407 ``ConfigChoiceField`` instances can allow either single selections
or
408 multiple selections, depending on the ``multi`` parameter. For
409 single-selection fields, set the selection
with the ``name`` attribute.
410 For multi-selection fields, set the selection though the ``names``
413 This field
is validated only against the active selection. If the
414 ``active`` attribute
is `
None`
and the field
is not optional, validation
417 When saving a configuration
with a ``ConfigChoiceField``, the entire set
is
418 saved,
as well
as the active selection.
422 While the ``typemap``
is shared by all instances of the field, each
423 instance of the field has its own instance of a particular sub-config type.
425 For example, ``AaaConfig``
is a config object
428 >>>
class AaaConfig(
Config):
429 ... somefield =
Field(
"doc", int)
432 The ``MyConfig`` config has a ``ConfigChoiceField`` field called ``choice``
433 that maps the ``AaaConfig`` type to the ``
"AAA"`` key:
435 >>> TYPEMAP = {
"AAA", AaaConfig}
436 >>>
class MyConfig(
Config):
440 Creating an instance of ``MyConfig``:
442 >>> instance = MyConfig()
444 Setting value of the field ``somefield`` on the
"AAA" key of the ``choice``
447 >>> instance.choice[
'AAA'].somefield = 5
449 **Selecting the active configuration**
451 Make the ``
"AAA"`` key the active configuration value
for the ``choice``
454 >>> instance.choice =
"AAA"
456 Alternatively, the last line can be written:
458 >>> instance.choice.name =
"AAA"
460 (If the config instance allows multiple selections, you
'd assign a sequence
461 to the ``names`` attribute instead.)
463 ``ConfigChoiceField`` instances also allow multiple values of the same
466 >>> TYPEMAP["CCC"] = AaaConfig
467 >>> TYPEMAP[
"BBB"] = AaaConfig
470 instanceDictClass = ConfigInstanceDict
472 def __init__(self, doc, typemap, default=None, optional=False, multi=False, deprecated=None):
473 source = getStackFrame()
481 deprecated=deprecated,
487 raise ValueError(
"ConfigChoiceField does not support typing argument")
491 if instanceDict
is None:
493 instanceDict = self.
dtype(instance, self)
494 instanceDict.__doc__ = self.
doc
497 history.append((
"Initialized from defaults", at, label))
503 self, instance: None, owner: Any =
None, at: Any =
None, label: str =
"default"
504 ) -> ConfigChoiceField:
509 self, instance: Config, owner: Any =
None, at: Any =
None, label: str =
"default"
510 ) -> ConfigInstanceDict:
513 def __get__(self, instance, owner=None, at=None, label="default"):
514 if instance
is None or not isinstance(instance, Config):
520 self, instance: Config, value: ConfigInstanceDict |
None, at: Any =
None, label: str =
"assignment"
528 for k, v
in value.items():
529 instanceDict.__setitem__(k, v, at=at, label=label)
530 instanceDict._setSelection(value._selection, at=at, label=label)
533 instanceDict._setSelection(value, at=at, label=label)
538 instanceDict._rename(fullname)
542 if instanceDict.active
is None and not self.
optional:
543 msg =
"Required field cannot be None"
545 elif instanceDict.active
is not None:
547 for a
in instanceDict.active:
550 instanceDict.active.validate()
557 dict_[
"names"] = instanceDict.names
559 dict_[
"name"] = instanceDict.name
562 for k, v
in instanceDict.items():
563 values[k] = v.toDict()
564 dict_[
"values"] = values
570 instanceDict.freeze()
571 for v
in instanceDict.values():
576 for config
in instanceDict.values():
577 config._collectImports()
578 imports |= config._imports
580 def save(self, outfile, instance):
583 for v
in instanceDict.values():
586 outfile.write(f
"{fullname}.names={sorted(instanceDict.names)!r}\n")
588 outfile.write(f
"{fullname}.name={instanceDict.name!r}\n")
591 """Customize deep-copying, because we always want a reference to the
594 WARNING: this must be overridden by subclasses if they change the
595 constructor signature!
600 default=copy.deepcopy(self.default),
604 other.source = self.source
607 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
608 """Compare two fields for equality.
610 Used by `lsst.pex.ConfigChoiceField.compare`.
615 Left-hand side config instance to compare.
617 Right-hand side config instance to compare.
619 If `True`, this function returns
as soon
as an inequality
if found.
621 Relative tolerance
for floating point comparisons.
623 Absolute tolerance
for floating point comparisons.
625 A callable that takes a string, used (possibly repeatedly) to
631 `
True`
if the fields are equal, `
False` otherwise.
635 Only the selected configurations are compared,
as the parameters of any
636 others do
not matter.
638 Floating point comparisons are performed by `numpy.allclose`.
642 name = getComparisonName(
645 if not compareScalars(
"selection for %s" % name, d1._selection, d2._selection, output=output):
647 if d1._selection
is None:
650 nested = [(k, d1[k], d2[k])
for k
in d1._selection]
652 nested = [(d1._selection, d1[d1._selection], d2[d1._selection])]
654 for k, c1, c2
in nested:
655 result = compareConfigs(
656 f
"{name}[{k!r}]", c1, c2, shortcut=shortcut, rtol=rtol, atol=atol, output=output
658 if not result
and shortcut:
660 equal = equal
and result
std::vector< SchemaItem< Flag > > * items
__get__(self, instance, owner=None, at=None, label="default")
FieldTypeVar __get__(self, Config instance, Any owner=None, Any at=None, str label="default")
Field[FieldTypeVar] __get__(self, None instance, Any owner=None, Any at=None, str label="default")
_setup(self, doc, dtype, default, check, optional, source, deprecated)
__init__(self, doc, typemap, default=None, optional=False, multi=False, deprecated=None)
_getOrMake(self, instance, label="default")
ConfigInstanceDict __get__(self, Config instance, Any owner=None, Any at=None, str label="default")
__get__(self, instance, owner=None, at=None, label="default")
_collectImports(self, instance, imports)
__class_getitem__(cls, tuple[type,...]|type|ForwardRef params)
_compare(self, instance1, instance2, shortcut, rtol, atol, output)
None __set__(self, Config instance, ConfigInstanceDict|None value, Any at=None, str label="assignment")
save(self, outfile, instance)
ConfigChoiceField __get__(self, None instance, Any owner=None, Any at=None, str label="default")
__setattr__(self, attr, value, at=None, label="assignment")
__setitem__(self, k, value, at=None, label="assignment")
__init__(self, config, field)
__getitem__(self, k, at=None, label="default")
_setSelection(self, value, at=None, label="assignment")
__contains__(self, value)
discard(self, value, at=None)
__init__(self, dict_, value, at=None, label="assignment", setHistory=True)
add(self, value, at=None)
daf::base::PropertyList * list
daf::base::PropertySet * set