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.
history.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:
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 name = _joinNamePath(self.
_config._name, self._field.name, attr)
169 if isinstance(value, ConfigurableAction):
170 valueInst = type(value)(__name=name, __at=at, __label=label, **value._storage)
172 valueInst = value(__name=name, __at=at, __label=label)
173 self._attrs[attr] = valueInst
178 if attr
in object.__getattribute__(self,
'_attrs'):
179 return self._attrs[attr]
181 super().__getattribute__(attr)
184 if name
in self._attrs:
185 del self._attrs[name]
189 def __iter__(self) -> Iterable[ConfigurableAction]:
190 return iter(self._attrs.values())
192 def items(self) -> Iterable[Tuple[str, ConfigurableAction]]:
193 return iter(self._attrs.items())
196T = TypeVar(
"T", bound=
"ConfigurableActionStructField")
200 r"""`ConfigurableActionStructField` is a `~lsst.pex.config.Field` subclass
201 that allows `ConfigurableAction`\ s to be organized in a
202 `~lsst.pex.config.Config`
class in a manor similar to how a
203 `~lsst.pipe.base.Struct` works.
205 This
class implements a `ConfigurableActionStruct`
as an intermediary
206 object to organize the `ConfigurableActions`. See it
's documentation for
211 StructClass = ConfigurableActionStruct
216 default: Optional[Mapping[str, ConfigurableAction]]
218 def __init__(self, doc: str, default: Optional[Mapping[str, ConfigurableAction]] =
None,
219 optional: bool =
False,
221 source = getStackFrame()
222 self._setup(doc=doc, dtype=self.__class__, default=default, check=
None,
223 optional=optional, source=source, deprecated=deprecated)
226 value: Union[
None, Mapping[str, ConfigurableAction], ConfigurableActionStruct],
227 at: Iterable[StackFrame] =
None, label: str =
'assigment'):
229 msg =
"Cannot modify a frozen Config. "\
230 "Attempting to set field to value %s" % value
236 if value
is None or value == self.default:
237 value = self.
StructClass(instance, self, value, at=at, label=label)
239 history = instance._history.setdefault(self.name, [])
240 history.append((value, at, label))
242 if not isinstance(value, ConfigurableActionStruct):
244 "Can only assign things that are subclasses of Configurable Action")
245 instance._storage[self.name] = value
247 def __get__(self: T, instance: Config, owner:
None =
None, at: Iterable[StackFrame] =
None,
248 label: str =
"default"
249 ) -> Union[
None, T, ConfigurableActionStruct]:
250 if instance
is None or not isinstance(instance, Config):
253 field: Optional[ConfigurableActionStruct] = instance._storage[self.name]
257 actionStruct: ConfigurableActionStruct = self.
__get____get__(instance)
258 if actionStruct
is not None:
259 for k, v
in actionStruct.items():
260 fullname = _joinNamePath(instance._name, self.name, k)
265 if value
is not None:
271 if actionStruct
is None:
274 dict_ = {k: v.toDict()
for k, v
in actionStruct.items()}
278 def save(self, outfile, instance):
280 fullname = _joinNamePath(instance._name, self.name)
281 if actionStruct
is None:
282 outfile.write(
u"{}={!r}\n".format(fullname, actionStruct))
285 outfile.write(
u"{}={!r}\n".format(fullname, {}))
286 for v
in actionStruct:
287 outfile.write(
u"{}={}()\n".format(v._name, _typeStr(v)))
292 if actionStruct
is not None:
293 for v
in actionStruct:
296 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
297 """Compare two fields for equality.
301 instance1 : `lsst.pex.config.Config`
302 Left-hand side config instance to compare.
303 instance2 : `lsst.pex.config.Config`
304 Right-hand side config instance to compare.
306 If `True`, this function returns
as soon
as an inequality
if found.
308 Relative tolerance
for floating point comparisons.
310 Absolute tolerance
for floating point comparisons.
312 A callable that takes a string, used (possibly repeatedly) to
318 `
True`
if the fields are equal, `
False` otherwise.
322 Floating point comparisons are performed by `numpy.allclose`.
324 d1: ConfigurableActionStruct = getattr(instance1, self.name)
325 d2: ConfigurableActionStruct = getattr(instance2, self.name)
326 name = getComparisonName(
327 _joinNamePath(instance1._name, self.name),
328 _joinNamePath(instance2._name, self.name)
330 if not compareScalars(f
"keys for {name}", set(d1.fieldNames), set(d2.fieldNames), output=output):
333 for k, v1
in d1.items():
335 result = compareConfigs(f
"{name}.{k}", v1, v2, shortcut=shortcut,
336 rtol=rtol, atol=atol, output=output)
337 if not result
and shortcut:
339 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)
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)