21 from __future__
import annotations
23 __all__ = (
"ConfigurableActionStructField",
"ConfigurableActionStruct")
25 from typing
import Iterable, Mapping, Optional, TypeVar, Union, Type, Tuple, List, Any, Dict
28 from lsst.pex.config.comparison
import compareConfigs, compareScalars, getComparisonName
31 from .
import ConfigurableAction
35 """This descriptor exists to abstract the logic of using a dictionary to
36 update a ConfigurableActionStruct through attribute assignment. This is
37 useful in the context of setting configuration through pipelines or on
40 def __set__(self, instance: ConfigurableActionStruct,
41 value: Union[Mapping[str, ConfigurableAction], ConfigurableActionStruct]) ->
None:
42 if isinstance(value, Mapping):
44 elif isinstance(value, ConfigurableActionStruct):
49 raise ValueError(
"Can only update a ConfigurableActionStruct with an instance of such, or a "
51 for name, action
in value.items():
52 setattr(instance, name, action)
54 def __get__(self, instance, objtype=None) -> None:
60 """This descriptor exists to abstract the logic of removing an interable
61 of action names from a ConfigurableActionStruct at one time using
62 attribute assignment. This is useful in the context of setting
63 configuration through pipelines or on the command line.
68 Raised if an attribute specified for removal does not exist in the
69 ConfigurableActionStruct
71 def __set__(self, instance: ConfigurableActionStruct,
72 value: Union[str, Iterable[str]]) ->
None:
76 if isinstance(value, str):
79 delattr(instance, name)
81 def __get__(self, instance, objtype=None) -> None:
87 """A ConfigurableActionStruct is the storage backend class that supports
88 the ConfigurableActionStructField. This class should not be created
91 This class allows managing a collection of `ConfigurableActions` with a
92 struct like interface, that is to say in an attribute like notation.
94 Attributes can be dynamically added or removed as such:
96 ConfigurableActionStructInstance.variable1 = a_configurable_action
97 del ConfigurableActionStructInstance.variable1
99 Each action is then available to be individually configured as a normal
100 `lsst.pex.config.Config` object.
102 ConfigurableActionStruct supports two special convenance attributes.
104 The first is `update`. You may assign a dict of `ConfigurableActions` or
105 a `ConfigurableActionStruct` to this attribute which will update the
106 `ConfigurableActionStruct` on which the attribute is invoked such that it
107 will be updated to contain the entries specified by the structure on the
108 right hand side of the equals sign.
110 The second convenience attribute is named remove. You may assign an
111 iterable of strings which correspond to attribute names on the
112 `ConfigurableActionStruct`. All of the corresponding attributes will then
113 be removed. If any attribute does not exist, an `AttributeError` will be
114 raised. Any attributes in the Iterable prior to the name which raises will
115 have been removed from the `ConfigurableActionStruct`
119 _attrs: Dict[str, ConfigurableAction]
120 _field: ConfigurableActionStructField
121 _history: List[tuple]
127 def __init__(self, config: Config, field: ConfigurableActionStructField,
128 value: Mapping[str, ConfigurableAction], at: Any, label: str):
129 object.__setattr__(self,
'_config', config)
130 object.__setattr__(self,
'_attrs', {})
131 object.__setattr__(self,
'_field', field)
132 object.__setattr__(self,
'_history', [])
134 self.
historyhistory.append((
"Struct initialized", at, label))
136 if value
is not None:
137 for k, v
in value.items():
146 return self._attrs.keys()
148 def __setattr__(self, attr: str, value: Union[ConfigurableAction, Type[ConfigurableAction]],
149 at=
None, label=
'setattr', setHistory=
False) ->
None:
151 if hasattr(self._config,
'_frozen')
and self._config._frozen:
152 msg =
"Cannot modify a frozen Config. "\
153 f
"Attempting to set item {attr} to value {value}"
156 if attr
not in (self.__dict__.keys() | type(self).__dict__.keys()):
157 name = _joinNamePath(self._config._name, self._field.name, attr)
160 if isinstance(value, ConfigurableAction):
161 valueInst = type(value)(__name=name, __at=at, __label=label, **value._storage)
163 valueInst = value(__name=name, __at=at, __label=label)
164 self._attrs[attr] = valueInst
169 if attr
in object.__getattribute__(self,
'_attrs'):
170 return self._attrs[attr]
172 super().__getattribute__(attr)
175 if name
in self._attrs:
176 del self._attrs[name]
180 def __iter__(self) -> Iterable[ConfigurableAction]:
181 return iter(self._attrs.values())
183 def items(self) -> Iterable[Tuple[str, ConfigurableAction]]:
184 return iter(self._attrs.
items())
187 T = TypeVar(
"T", bound=
"ConfigurableActionStructField")
191 r"""`ConfigurableActionStructField` is a `~lsst.pex.config.Field` subclass
192 that allows `ConfigurableAction`\ s to be organized in a
193 `~lsst.pex.config.Config` class in a manor similar to how a
194 `~lsst.pipe.base.Struct` works.
196 This class implements a `ConfigurableActionStruct` as an intermediary
197 object to organize the `ConfigurableActions`. See it's documentation for
202 StructClass = ConfigurableActionStruct
207 default: Optional[Mapping[str, ConfigurableAction]]
209 def __init__(self, doc: str, default: Optional[Mapping[str, ConfigurableAction]] =
None,
210 optional: bool =
False,
212 source = getStackFrame()
213 self._setup(doc=doc, dtype=self.__class__, default=default, check=
None,
214 optional=optional, source=source, deprecated=deprecated)
217 value: Union[
None, Mapping[str, ConfigurableAction], ConfigurableActionStruct],
218 at: Iterable[StackFrame] =
None, label: str =
'assigment'):
220 msg =
"Cannot modify a frozen Config. "\
221 "Attempting to set field to value %s" % value
227 if value
is None or value == self.default:
228 value = self.
StructClassStructClass(instance, self, value, at=at, label=label)
230 history = instance._history.setdefault(self.name, [])
231 history.append((value, at, label))
233 if not isinstance(value, ConfigurableActionStruct):
235 "Can only assign things that are subclasses of Configurable Action")
236 instance._storage[self.name] = value
238 def __get__(self: T, instance: Config, owner:
None =
None, at: Iterable[StackFrame] =
None,
239 label: str =
"default"
240 ) -> Union[
None, T, ConfigurableActionStruct]:
241 if instance
is None or not isinstance(instance, Config):
244 field: Optional[ConfigurableActionStruct] = instance._storage[self.name]
248 actionStruct: ConfigurableActionStruct = self.
__get____get____get__(instance)
249 if actionStruct
is not None:
250 for k, v
in actionStruct.items():
251 fullname = _joinNamePath(instance._name, self.name, k)
256 if value
is not None:
262 if actionStruct
is None:
265 dict_ = {k: v.toDict()
for k, v
in actionStruct.items()}
269 def save(self, outfile, instance):
271 fullname = _joinNamePath(instance._name, self.name)
272 if actionStruct
is None:
273 outfile.write(
u"{}={!r}\n".format(fullname, actionStruct))
276 outfile.write(
u"{}={!r}\n".format(fullname, {}))
277 for v
in actionStruct:
278 outfile.write(
u"{}={}()\n".format(v._name, _typeStr(v)))
283 if actionStruct
is not None:
284 for v
in actionStruct:
287 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
288 """Compare two fields for equality.
292 instance1 : `lsst.pex.config.Config`
293 Left-hand side config instance to compare.
294 instance2 : `lsst.pex.config.Config`
295 Right-hand side config instance to compare.
297 If `True`, this function returns as soon as an inequality if found.
299 Relative tolerance for floating point comparisons.
301 Absolute tolerance for floating point comparisons.
303 A callable that takes a string, used (possibly repeatedly) to
309 `True` if the fields are equal, `False` otherwise.
313 Floating point comparisons are performed by `numpy.allclose`.
315 d1: ConfigurableActionStruct = getattr(instance1, self.name)
316 d2: ConfigurableActionStruct = getattr(instance2, self.name)
317 name = getComparisonName(
318 _joinNamePath(instance1._name, self.name),
319 _joinNamePath(instance2._name, self.name)
321 if not compareScalars(f
"keys for {name}", set(d1.fieldNames), set(d2.fieldNames), output=output):
324 for k, v1
in d1.items():
326 result = compareConfigs(f
"{name}.{k}", v1, v2, shortcut=shortcut,
327 rtol=rtol, atol=atol, output=output)
328 if not result
and shortcut:
330 equal = equal
and result
def __get__(self, instance, owner=None, at=None, label="default")
def toDict(self, instance)
Union[None, T, ConfigurableActionStruct] __get__(T self, Config instance, None owner=None, Iterable[StackFrame] at=None, str label="default")
def freeze(self, instance)
def save(self, outfile, instance)
def validate(self, instance)
def __init__(self, str doc, Optional[Mapping[str, ConfigurableAction]] default=None, bool optional=False, deprecated=None)
def rename(self, Config instance)
def __set__(self, Config instance, Union[None, Mapping[str, ConfigurableAction], ConfigurableActionStruct] value, Iterable[StackFrame] at=None, str label='assigment')
def __init__(self, Config config, ConfigurableActionStructField field, Mapping[str, ConfigurableAction] value, Any at, str label)
Iterable[str] fieldNames(self)
def __getattr__(self, attr)
None __setattr__(self, str attr, Union[ConfigurableAction, Type[ConfigurableAction]] value, at=None, label='setattr', setHistory=False)
Iterable[ConfigurableAction] __iter__(self)
def __delattr__(self, name)
Iterable[Tuple[str, ConfigurableAction]] items(self)
List[tuple] history(self)
None __get__(self, instance, objtype=None)
None __set__(self, ConfigurableActionStruct instance, Union[str, Iterable[str]] value)
None __get__(self, instance, objtype=None)
None __set__(self, ConfigurableActionStruct instance, Union[Mapping[str, ConfigurableAction], ConfigurableActionStruct] value)