21from __future__
import annotations
23__all__ = (
"ConfigurableActionStructField",
"ConfigurableActionStruct")
25from typing
import Iterable, Mapping, Optional, TypeVar, Union, Type, Tuple, List, Any, Dict
28from lsst.pex.config.comparison
import compareConfigs, compareScalars, getComparisonName
31from .
import ConfigurableAction
37 """This descriptor exists to abstract the logic of using a dictionary to
38 update a ConfigurableActionStruct through attribute assignment. This is
39 useful
in the context of setting configuration through pipelines
or on
42 def __set__(self, instance: ConfigurableActionStruct,
43 value: Union[Mapping[str, ConfigurableAction], ConfigurableActionStruct]) ->
None:
44 if isinstance(value, Mapping):
46 elif isinstance(value, ConfigurableActionStruct):
51 raise ValueError(
"Can only update a ConfigurableActionStruct with an instance of such, or a "
53 for name, action
in value.items():
54 setattr(instance, name, action)
56 def __get__(self, instance, objtype=None) -> None:
62 """This descriptor exists to abstract the logic of removing an interable
63 of action names from a ConfigurableActionStruct at one time using
64 attribute assignment. This
is useful
in the context of setting
65 configuration through pipelines
or on the command line.
70 Raised
if an attribute specified
for removal does
not exist
in the
71 ConfigurableActionStruct
73 def __set__(self, instance: ConfigurableActionStruct,
74 value: Union[str, Iterable[str]]) ->
None:
78 if isinstance(value, str):
81 delattr(instance, name)
83 def __get__(self, instance, objtype=None) -> None:
89 """A ConfigurableActionStruct is the storage backend class that supports
90 the ConfigurableActionStructField. This class should not be created
93 This
class allows managing a collection of `ConfigurableActions`
with a
94 struct like interface, that
is to say
in an attribute like notation.
96 Attributes can be dynamically added
or removed
as such:
98 ConfigurableActionStructInstance.variable1 = a_configurable_action
99 del ConfigurableActionStructInstance.variable1
101 Each action
is then available to be individually configured
as a normal
102 `lsst.pex.config.Config` object.
104 ConfigurableActionStruct supports two special convenance attributes.
106 The first
is `update`. You may assign a dict of `ConfigurableActions`
or
107 a `ConfigurableActionStruct` to this attribute which will update the
108 `ConfigurableActionStruct` on which the attribute
is invoked such that it
109 will be updated to contain the entries specified by the structure on the
110 right hand side of the equals sign.
112 The second convenience attribute
is named remove. You may assign an
113 iterable of strings which correspond to attribute names on the
114 `ConfigurableActionStruct`. All of the corresponding attributes will then
115 be removed. If any attribute does
not exist, an `AttributeError` will be
116 raised. Any attributes
in the Iterable prior to the name which raises will
117 have been removed
from the `ConfigurableActionStruct`
121 _attrs: Dict[str, ConfigurableAction]
122 _field: ConfigurableActionStructField
123 _history: List[tuple]
129 def __init__(self, config: Config, field: ConfigurableActionStructField,
130 value: Mapping[str, ConfigurableAction], at: Any, label: str):
131 object.__setattr__(self,
'_config_', weakref.ref(config))
132 object.__setattr__(self,
'_attrs', {})
133 object.__setattr__(self,
'_field', field)
134 object.__setattr__(self,
'_history', [])
136 self.
historyhistory.append((
"Struct initialized", at, label))
138 if value
is not None:
139 for k, v
in value.items():
143 def _config(self) -> Config:
146 assert(self._config_()
is not None)
147 return self._config_()
155 return self._attrs.keys()
157 def __setattr__(self, attr: str, value: Union[ConfigurableAction, Type[ConfigurableAction]],
158 at=
None, label=
'setattr', setHistory=
False) ->
None:
160 if hasattr(self.
_config_config,
'_frozen')
and self.
_config_config._frozen:
161 msg =
"Cannot modify a frozen Config. "\
162 f
"Attempting to set item {attr} to value {value}"
165 if attr
not in (self.__dict__.keys() | type(self).__dict__.keys()):
166 base_name = _joinNamePath(self.
_config_config._name, self._field.name)
167 name = _joinNamePath(base_name, attr)
170 if isinstance(value, ConfigurableAction):
171 valueInst = type(value)(__name=name, __at=at, __label=label, **value._storage)
173 valueInst = value(__name=name, __at=at, __label=label)
174 self._attrs[attr] = valueInst
179 if attr
in object.__getattribute__(self,
'_attrs'):
180 return self._attrs[attr]
182 super().__getattribute__(attr)
185 if name
in self._attrs:
186 del self._attrs[name]
190 def __iter__(self) -> Iterable[ConfigurableAction]:
191 return iter(self._attrs.values())
193 def items(self) -> Iterable[Tuple[str, ConfigurableAction]]:
194 return iter(self._attrs.
items())
197T = TypeVar(
"T", bound=
"ConfigurableActionStructField")
201 r"""`ConfigurableActionStructField` is a `~lsst.pex.config.Field` subclass
202 that allows `ConfigurableAction`\ s to be organized in a
203 `~lsst.pex.config.Config`
class in a manner similar to how a
204 `~lsst.pipe.base.Struct` works.
206 This
class implements a `ConfigurableActionStruct`
as an intermediary
207 object to organize the `ConfigurableActions`. See it
's documentation for
212 StructClass = ConfigurableActionStruct
217 default: Optional[Mapping[str, ConfigurableAction]]
219 def __init__(self, doc: str, default: Optional[Mapping[str, ConfigurableAction]] =
None,
220 optional: bool =
False,
222 source = getStackFrame()
223 self._setup(doc=doc, dtype=self.__class__, default=default, check=
None,
224 optional=optional, source=source, deprecated=deprecated)
227 value: Union[
None, Mapping[str, ConfigurableAction], ConfigurableActionStruct],
228 at: Iterable[StackFrame] =
None, label: str =
'assigment'):
230 msg =
"Cannot modify a frozen Config. "\
231 "Attempting to set field to value %s" % value
237 if value
is None or value == self.default:
238 value = self.
StructClassStructClass(instance, self, value, at=at, label=label)
240 history = instance._history.setdefault(self.name, [])
241 history.append((value, at, label))
243 if not isinstance(value, ConfigurableActionStruct):
245 "Can only assign things that are subclasses of Configurable Action")
246 instance._storage[self.name] = value
248 def __get__(self: T, instance: Config, owner:
None =
None, at: Iterable[StackFrame] =
None,
249 label: str =
"default"
250 ) -> Union[
None, T, ConfigurableActionStruct]:
251 if instance
is None or not isinstance(instance, Config):
254 field: Optional[ConfigurableActionStruct] = instance._storage[self.name]
258 actionStruct: ConfigurableActionStruct = self.
__get____get____get__(instance)
259 if actionStruct
is not None:
260 for k, v
in actionStruct.items():
261 base_name = _joinNamePath(instance._name, self.name)
262 fullname = _joinNamePath(base_name, k)
267 if value
is not None:
273 if actionStruct
is None:
276 dict_ = {k: v.toDict()
for k, v
in actionStruct.items()}
280 def save(self, outfile, instance):
282 fullname = _joinNamePath(instance._name, self.name)
283 if actionStruct
is None:
284 outfile.write(
u"{}={!r}\n".format(fullname, actionStruct))
287 for v
in actionStruct:
288 outfile.write(
u"{}={}()\n".format(v._name, _typeStr(v)))
293 if actionStruct
is not None:
294 for v
in actionStruct:
297 def _collectImports(self, instance, imports):
300 for v
in actionStruct:
302 imports |= v._imports
304 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
305 """Compare two fields for equality.
309 instance1 : `lsst.pex.config.Config`
310 Left-hand side config instance to compare.
311 instance2 : `lsst.pex.config.Config`
312 Right-hand side config instance to compare.
314 If `True`, this function returns
as soon
as an inequality
if found.
316 Relative tolerance
for floating point comparisons.
318 Absolute tolerance
for floating point comparisons.
320 A callable that takes a string, used (possibly repeatedly) to
326 `
True`
if the fields are equal, `
False` otherwise.
330 Floating point comparisons are performed by `numpy.allclose`.
332 d1: ConfigurableActionStruct = getattr(instance1, self.name)
333 d2: ConfigurableActionStruct = getattr(instance2, self.name)
334 name = getComparisonName(
335 _joinNamePath(instance1._name, self.name),
336 _joinNamePath(instance2._name, self.name)
338 if not compareScalars(f
"keys for {name}", set(d1.fieldNames), set(d2.fieldNames), output=output):
341 for k, v1
in d1.items():
343 result = compareConfigs(f
"{name}.{k}", v1, v2, shortcut=shortcut,
344 rtol=rtol, atol=atol, output=output)
345 if not result
and shortcut:
347 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)