27from __future__
import annotations
29__all__ = [
"ConfigChoiceField"]
35from typing
import Any, ForwardRef, Optional, Union, overload
37from .callStack
import getCallStack, getStackFrame
38from .comparison
import compareConfigs, compareScalars, getComparisonName
39from .config
import Config, Field, FieldValidationError, UnexpectedProxyUsageError, _joinNamePath, _typeStr
43 """A mutable set class that tracks the selection of multi-select
48 dict_ : `ConfigInstanceDict`
49 The dictionary of instantiated configs.
53 The call stack when the selection was made.
54 label : `str`, optional
55 Label for history tracking.
56 setHistory : `bool`, optional
57 Add this even to the history,
if `
True`.
61 This
class allows
a user of
a multi-select
63 of active configs. Each change to the selection
is tracked
in the field
's
67 def __init__(self, dict_, value, at=None, label="assignment", setHistory=True):
77 if v
not in self.
_dict:
79 self.
_dict.__getitem__(v, at=at)
81 msg =
"Value %s is of incorrect type %s. Sequence type expected" % (value, _typeStr(value))
88 self.
__history.append((
"Set selection to %s" % self, at, label))
91 def _config(self) -> Config:
97 def add(self, value, at=None):
98 """Add a value to the selected set."""
105 if value
not in self.
_dict:
107 self.
_dict.__getitem__(value, at=at)
109 self.
__history.append((
"added %s to selection" % value, at,
"selection"))
113 """Discard a value from the selected set."""
117 if value
not in self.
_dict:
123 self.
__history.append((
"removed %s from selection" % value, at,
"selection"))
127 return len(self.
_set)
130 return iter(self.
_set)
133 return value
in self.
_set
143 f
"Proxy container for config field {self._field.name} cannot "
144 "be pickled; it should be converted to a built-in container before "
145 "being assigned to other objects or variables."
149if int(sys.version_info.minor) < 9:
150 _bases = (collections.abc.Mapping,)
152 _bases = (collections.abc.Mapping[str, Config],)
156 """Dictionary of instantiated configs, used to populate a
162 A configuration instance.
164 A configuration field. Note that the `lsst.pex.config.Field.fieldmap`
165 attribute must provide key-based access to configuration classes,
166 (that is, ``typemap[name]``).
170 collections.abc.Mapping.__init__(self)
175 self.
_history = config._history.setdefault(field.name, [])
184 return k
in self.
types
187 return len(self.
types)
190 return iter(self.
types)
192 def _setSelection(self, value, at=None, label="assignment"):
204 if value
not in self.
_dict:
207 self.
_history.append((value, at, label))
212 self.
_field, self.
_config,
"Single-selection field has no attribute 'names'"
216 def _setNames(self, value):
219 self.
_field, self.
_config,
"Single-selection field has no attribute 'names'"
226 self.
_field, self.
_config,
"Single-selection field has no attribute 'names'"
233 self.
_field, self.
_config,
"Multi-selection field has no attribute 'name'"
237 def _setName(self, value):
240 self.
_field, self.
_config,
"Multi-selection field has no attribute 'name'"
247 self.
_field, self.
_config,
"Multi-selection field has no attribute 'name'"
251 names = property(_getNames, _setNames, _delNames)
252 """List of names of active items in a multi-selection
253 ``ConfigInstanceDict``. Disabled in a single-selection ``_Registry``; use
254 the `name` attribute instead.
257 name = property(_getName, _setName, _delName)
258 """Name of the active item in a single-selection ``ConfigInstanceDict``.
259 Disabled in a multi-selection ``_Registry``; use the ``names`` attribute
263 def _getActive(self):
272 active = property(_getActive)
273 """The selected items.
275 For multi-selection, this is equivalent to: ``[self[name]
for name
in
276 self.
names]``. For single-selection, this
is equivalent to: ``self[name]``.
281 value = self.
_dict[k]
284 dtype = self.
types[k]
287 self.
_field, self.
_config,
"Unknown key %r in Registry/ConfigChoiceField" % k
289 name = _joinNamePath(self.
_config._name, self.
_field.name, k)
292 at.insert(0, dtype._source)
293 value = self.
_dict.setdefault(k, dtype(__name=name, __at=at, __label=label))
301 dtype = self.
types[k]
305 if value != dtype
and type(value) != dtype:
306 msg =
"Value %s at key %s is of incorrect type %s. Expected type %s" % (
316 name = _joinNamePath(self.
_config._name, self.
_field.name, k)
317 oldValue = self.
_dict.get(k,
None)
320 self.
_dict[k] = value(__name=name, __at=at, __label=label)
322 self.
_dict[k] = dtype(__name=name, __at=at, __label=label, **value._storage)
326 oldValue.update(__at=at, __label=label, **value._storage)
328 def _rename(self, fullname):
330 v._rename(_joinNamePath(name=fullname, index=k))
333 if hasattr(getattr(self.__class__, attr,
None),
"__set__"):
335 object.__setattr__(self, attr, value)
336 elif attr
in self.__dict__
or attr
in [
346 object.__setattr__(self, attr, value)
349 msg =
"%s has no attribute %s" % (_typeStr(self.
_field), attr)
353 """Invoking this freeze method will create a local copy of the field
354 attribute's typemap. This decouples this instance dict from the
355 underlying objects type map ensuring that and subsequent changes to the
356 typemap will
not be reflected
in this instance (i.e imports adding
357 additional registry entries).
364 f
"Proxy container for config field {self._field.name} cannot "
365 "be pickled; it should be converted to a built-in container before "
366 "being assigned to other objects or variables."
371 """A configuration field (`~lsst.pex.config.Field` subclass) that allows a
377 Documentation string
for the field.
378 typemap : `dict`-like
380 See *Examples*
for details.
381 default : `str`, optional
382 The default configuration name.
383 optional : `bool`, optional
384 When `
False`, `lsst.pex.config.Config.validate` will fail
if the
385 field
's value is `None`.
386 multi : `bool`, optional
387 If `True`, the field allows multiple selections. In this case, set the
388 selections by assigning a sequence to the ``names`` attribute of the
391 If `
False`, the field allows only a single selection. In this case,
392 set the active config by assigning the config
's key from the
393 ``typemap`` to the field's ``name`` attribute (see *Examples*).
394 deprecated : None or `str`, optional
395 A description of why this Field
is deprecated, including removal date.
396 If
not None, the string
is appended to the docstring
for this Field.
412 ``ConfigChoiceField`` instances can allow either single selections
or
413 multiple selections, depending on the ``multi`` parameter. For
414 single-selection fields, set the selection
with the ``name`` attribute.
415 For multi-selection fields, set the selection though the ``names``
418 This field
is validated only against the active selection. If the
419 ``active`` attribute
is `
None`
and the field
is not optional, validation
422 When saving a configuration
with a ``ConfigChoiceField``, the entire set
is
423 saved,
as well
as the active selection.
427 While the ``typemap``
is shared by all instances of the field, each
428 instance of the field has its own instance of a particular sub-config type.
430 For example, ``AaaConfig``
is a config object
433 >>>
class AaaConfig(
Config):
434 ... somefield =
Field(
"doc", int)
437 The ``MyConfig`` config has a ``ConfigChoiceField`` field called ``choice``
438 that maps the ``AaaConfig`` type to the ``
"AAA"`` key:
440 >>> TYPEMAP = {
"AAA", AaaConfig}
441 >>>
class MyConfig(
Config):
445 Creating an instance of ``MyConfig``:
447 >>> instance = MyConfig()
449 Setting value of the field ``somefield`` on the
"AAA" key of the ``choice``
452 >>> instance.choice[
'AAA'].somefield = 5
454 **Selecting the active configuration**
456 Make the ``
"AAA"`` key the active configuration value
for the ``choice``
459 >>> instance.choice =
"AAA"
461 Alternatively, the last line can be written:
463 >>> instance.choice.name =
"AAA"
465 (If the config instance allows multiple selections, you
'd assign a sequence
466 to the ``names`` attribute instead.)
468 ``ConfigChoiceField`` instances also allow multiple values of the same
471 >>> TYPEMAP["CCC"] = AaaConfig
472 >>> TYPEMAP[
"BBB"] = AaaConfig
475 instanceDictClass = ConfigInstanceDict
477 def __init__(self, doc, typemap, default=None, optional=False, multi=False, deprecated=None):
478 source = getStackFrame()
486 deprecated=deprecated,
492 raise ValueError(
"ConfigChoiceField does not support typing argument")
494 def _getOrMake(self, instance, label="default"):
495 instanceDict = instance._storage.get(self.
name)
496 if instanceDict
is None:
498 instanceDict = self.
dtype(instance, self)
499 instanceDict.__doc__ = self.
doc
500 instance._storage[self.
name] = instanceDict
501 history = instance._history.setdefault(self.
name, [])
502 history.append((
"Initialized from defaults", at, label))
508 self, instance: None, owner: Any =
None, at: Any =
None, label: str =
"default"
509 ) ->
"ConfigChoiceField":
514 self, instance: Config, owner: Any =
None, at: Any =
None, label: str =
"default"
515 ) -> ConfigInstanceDict:
518 def __get__(self, instance, owner=None, at=None, label="default"):
519 if instance
is None or not isinstance(instance, Config):
525 self, instance: Config, value: Optional[ConfigInstanceDict], at: Any =
None, label: str =
"assignment"
533 for k, v
in value.items():
534 instanceDict.__setitem__(k, v, at=at, label=label)
535 instanceDict._setSelection(value._selection, at=at, label=label)
538 instanceDict._setSelection(value, at=at, label=label)
542 fullname = _joinNamePath(instance._name, self.
name)
543 instanceDict._rename(fullname)
547 if instanceDict.active
is None and not self.
optional:
548 msg =
"Required field cannot be None"
550 elif instanceDict.active
is not None:
552 for a
in instanceDict.active:
555 instanceDict.active.validate()
562 dict_[
"names"] = instanceDict.names
564 dict_[
"name"] = instanceDict.name
567 for k, v
in instanceDict.items():
568 values[k] = v.toDict()
569 dict_[
"values"] = values
575 instanceDict.freeze()
576 for v
in instanceDict.values():
579 def _collectImports(self, instance, imports):
581 for config
in instanceDict.values():
582 config._collectImports()
583 imports |= config._imports
585 def save(self, outfile, instance):
587 fullname = _joinNamePath(instance._name, self.
name)
588 for v
in instanceDict.values():
591 outfile.write(
"{}.names={!r}\n".format(fullname, sorted(instanceDict.names)))
593 outfile.write(
"{}.name={!r}\n".format(fullname, instanceDict.name))
596 """Customize deep-copying, because we always want a reference to the
599 WARNING: this must be overridden by subclasses if they change the
600 constructor signature!
605 default=copy.deepcopy(self.default),
609 other.source = self.source
612 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
613 """Compare two fields for equality.
615 Used by `lsst.pex.ConfigChoiceField.compare`.
620 Left-hand side config instance to compare.
622 Right-hand side config instance to compare.
624 If `True`, this function returns
as soon
as an inequality
if found.
626 Relative tolerance
for floating point comparisons.
628 Absolute tolerance
for floating point comparisons.
630 A callable that takes a string, used (possibly repeatedly) to
636 `
True`
if the fields are equal, `
False` otherwise.
640 Only the selected configurations are compared,
as the parameters of any
641 others do
not matter.
643 Floating point comparisons are performed by `numpy.allclose`.
645 d1 = getattr(instance1, self.name)
646 d2 = getattr(instance2, self.name)
647 name = getComparisonName(
648 _joinNamePath(instance1._name, self.name), _joinNamePath(instance2._name, self.name)
650 if not compareScalars(
"selection for %s" % name, d1._selection, d2._selection, output=output):
652 if d1._selection
is None:
655 nested = [(k, d1[k], d2[k])
for k
in d1._selection]
657 nested = [(d1._selection, d1[d1._selection], d2[d1._selection])]
659 for k, c1, c2
in nested:
660 result = compareConfigs(
661 "%s[%r]" % (name, k), c1, c2, shortcut=shortcut, rtol=rtol, atol=atol, output=output
663 if not result
and shortcut:
665 equal = equal
and result
std::vector< SchemaItem< Flag > > * items
"Field[FieldTypeVar]" __get__(self, None instance, Any owner=None, Any at=None, str label="default")
def __get__(self, instance, owner=None, at=None, label="default")
FieldTypeVar __get__(self, "Config" instance, Any owner=None, Any at=None, str label="default")
def _setup(self, doc, dtype, default, check, optional, source, deprecated)
ConfigInstanceDict __get__(self, Config instance, Any owner=None, Any at=None, str label="default")
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, at=None, label="default")
def save(self, outfile, instance)
def __deepcopy__(self, memo)
def __class_getitem__(cls, Union[tuple[type,...], type, ForwardRef] params)
None __set__(self, Config instance, Optional[ConfigInstanceDict] value, Any at=None, str label="assignment")
"ConfigChoiceField" __get__(self, None instance, Any owner=None, Any at=None, str label="default")
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