21from __future__
import annotations
23__all__ = (
"ConfigurableActionStructField",
"ConfigurableActionStruct")
25from types
import SimpleNamespace
26from typing
import Iterable, Mapping, Optional, TypeVar, Union, Type, Tuple, List, Any, Dict
29from lsst.pex.config.comparison
import compareConfigs, compareScalars, getComparisonName
33from .
import ConfigurableAction
39 """This descriptor exists to abstract the logic of using a dictionary to
40 update a ConfigurableActionStruct through attribute assignment. This is
41 useful
in the context of setting configuration through pipelines
or on
44 def __set__(self, instance: ConfigurableActionStruct,
45 value: Union[Mapping[str, ConfigurableAction], ConfigurableActionStruct]) ->
None:
46 if isinstance(value, Mapping):
48 elif isinstance(value, ConfigurableActionStruct):
53 raise ValueError(
"Can only update a ConfigurableActionStruct with an instance of such, or a "
55 for name, action
in value.items():
56 setattr(instance, name, action)
58 def __get__(self, instance, objtype=None) -> None:
64 """This descriptor exists to abstract the logic of removing an interable
65 of action names from a ConfigurableActionStruct at one time using
66 attribute assignment. This
is useful
in the context of setting
67 configuration through pipelines
or on the command line.
72 Raised
if an attribute specified
for removal does
not exist
in the
73 ConfigurableActionStruct
75 def __set__(self, instance: ConfigurableActionStruct,
76 value: Union[str, Iterable[str]]) ->
None:
80 if isinstance(value, str):
83 delattr(instance, name)
85 def __get__(self, instance, objtype=None) -> None:
91 """A ConfigurableActionStruct is the storage backend class that supports
92 the ConfigurableActionStructField. This class should not be created
95 This
class allows managing a collection of `ConfigurableActions`
with a
96 struct like interface, that
is to say
in an attribute like notation.
98 Attributes can be dynamically added
or removed
as such:
100 ConfigurableActionStructInstance.variable1 = a_configurable_action
101 del ConfigurableActionStructInstance.variable1
103 Each action
is then available to be individually configured
as a normal
104 `lsst.pex.config.Config` object.
106 ConfigurableActionStruct supports two special convenance attributes.
108 The first
is `update`. You may assign a dict of `ConfigurableActions`
or
109 a `ConfigurableActionStruct` to this attribute which will update the
110 `ConfigurableActionStruct` on which the attribute
is invoked such that it
111 will be updated to contain the entries specified by the structure on the
112 right hand side of the equals sign.
114 The second convenience attribute
is named remove. You may assign an
115 iterable of strings which correspond to attribute names on the
116 `ConfigurableActionStruct`. All of the corresponding attributes will then
117 be removed. If any attribute does
not exist, an `AttributeError` will be
118 raised. Any attributes
in the Iterable prior to the name which raises will
119 have been removed
from the `ConfigurableActionStruct`
123 _attrs: Dict[str, ConfigurableAction]
124 _field: ConfigurableActionStructField
125 _history: List[tuple]
131 def __init__(self, config: Config, field: ConfigurableActionStructField,
132 value: Mapping[str, ConfigurableAction], at: Any, label: str):
133 object.__setattr__(self,
'_config_', weakref.ref(config))
134 object.__setattr__(self,
'_attrs', {})
135 object.__setattr__(self,
'_field', field)
136 object.__setattr__(self,
'_history', [])
138 self.
history.append((
"Struct initialized", at, label))
140 if value
is not None:
141 for k, v
in value.items():
145 def _config(self) -> Config:
148 assert(self._config_()
is not None)
149 return self._config_()
157 return self._attrs.keys()
159 def __setattr__(self, attr: str, value: Union[ConfigurableAction, Type[ConfigurableAction]],
160 at=
None, label=
'setattr', setHistory=
False) ->
None:
163 msg =
"Cannot modify a frozen Config. "\
164 f
"Attempting to set item {attr} to value {value}"
169 if not attr.isidentifier():
170 raise ValueError(
"Names used in ConfigurableStructs must be valid as python variable names")
172 if attr
not in (self.__dict__.keys() | type(self).__dict__.keys()):
173 base_name = _joinNamePath(self.
_config._name, self._field.name)
174 name = _joinNamePath(base_name, attr)
177 if isinstance(value, ConfigurableAction):
178 valueInst = type(value)(__name=name, __at=at, __label=label, **value._storage)
180 valueInst = value(__name=name, __at=at, __label=label)
181 self._attrs[attr] = valueInst
186 if attr
in object.__getattribute__(self,
'_attrs'):
187 return self._attrs[attr]
189 super().__getattribute__(attr)
192 if name
in self._attrs:
193 del self._attrs[name]
197 def __iter__(self) -> Iterable[ConfigurableAction]:
198 return iter(self._attrs.values())
200 def items(self) -> Iterable[Tuple[str, ConfigurableAction]]:
201 return iter(self._attrs.
items())
204T = TypeVar(
"T", bound=
"ConfigurableActionStructField")
208 r"""`ConfigurableActionStructField` is a `~lsst.pex.config.Field` subclass
209 that allows `ConfigurableAction`\ s to be organized in a
210 `~lsst.pex.config.Config`
class in a manner similar to how a
211 `~lsst.pipe.base.Struct` works.
213 This
class implements a `ConfigurableActionStruct`
as an intermediary
214 object to organize the `ConfigurableActions`. See it
's documentation for
219 StructClass = ConfigurableActionStruct
224 default: Optional[Mapping[str, ConfigurableAction]]
226 def __init__(self, doc: str, default: Optional[Mapping[str, ConfigurableAction]] =
None,
227 optional: bool =
False,
229 source = getStackFrame()
230 self._setup(doc=doc, dtype=self.__class__, default=default, check=
None,
231 optional=optional, source=source, deprecated=deprecated)
234 value: Union[
None, Mapping[str, ConfigurableAction],
235 ConfigurableActionStruct,
236 ConfigurableActionStructField,
237 Type[ConfigurableActionStructField]],
238 at: Iterable[StackFrame] =
None, label: str =
'assigment'):
240 msg =
"Cannot modify a frozen Config. "\
241 "Attempting to set field to value %s" % value
248 value = self.
StructClass(instance, self, value, at=at, label=label)
254 value = self.
StructClass(instance, self, value._attrs, at=at, label=label)
255 elif isinstance(value, (SimpleNamespace, Struct)):
258 value = self.
StructClass(instance, self, vars(value), at=at, label=label)
260 elif type(value) == ConfigurableActionStructField:
261 raise ValueError(
"ConfigurableActionStructFields can only be used in a class body declaration"
262 f
"Use a {self.StructClass}, SimpleNamespace or Struct")
264 raise ValueError(f
"Unrecognized value {value}, cannot be assigned to this field")
266 history = instance._history.setdefault(self.name, [])
267 history.append((value, at, label))
269 if not isinstance(value, ConfigurableActionStruct):
271 "Can only assign things that are subclasses of Configurable Action")
272 instance._storage[self.name] = value
274 def __get__(self: T, instance: Config, owner:
None =
None, at: Iterable[StackFrame] =
None,
275 label: str =
"default"
276 ) -> Union[
None, T, ConfigurableActionStruct]:
277 if instance
is None or not isinstance(instance, Config):
280 field: Optional[ConfigurableActionStruct] = instance._storage[self.name]
284 actionStruct: ConfigurableActionStruct = self.
__get____get__(instance)
285 if actionStruct
is not None:
286 for k, v
in actionStruct.items():
287 base_name = _joinNamePath(instance._name, self.name)
288 fullname = _joinNamePath(base_name, k)
293 if value
is not None:
299 if actionStruct
is None:
302 dict_ = {k: v.toDict()
for k, v
in actionStruct.items()}
306 def save(self, outfile, instance):
308 fullname = _joinNamePath(instance._name, self.name)
309 if actionStruct
is None:
310 outfile.write(
u"{}={!r}\n".format(fullname, actionStruct))
313 for v
in actionStruct:
314 outfile.write(
u"{}={}()\n".format(v._name, _typeStr(v)))
319 if actionStruct
is not None:
320 for v
in actionStruct:
323 def _collectImports(self, instance, imports):
326 for v
in actionStruct:
328 imports |= v._imports
330 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
331 """Compare two fields for equality.
335 instance1 : `lsst.pex.config.Config`
336 Left-hand side config instance to compare.
337 instance2 : `lsst.pex.config.Config`
338 Right-hand side config instance to compare.
340 If `True`, this function returns
as soon
as an inequality
if found.
342 Relative tolerance
for floating point comparisons.
344 Absolute tolerance
for floating point comparisons.
346 A callable that takes a string, used (possibly repeatedly) to
352 `
True`
if the fields are equal, `
False` otherwise.
356 Floating point comparisons are performed by `numpy.allclose`.
358 d1: ConfigurableActionStruct = getattr(instance1, self.name)
359 d2: ConfigurableActionStruct = getattr(instance2, self.name)
360 name = getComparisonName(
361 _joinNamePath(instance1._name, self.name),
362 _joinNamePath(instance2._name, self.name)
364 if not compareScalars(f
"keys for {name}", set(d1.fieldNames), set(d2.fieldNames), output=output):
367 for k, v1
in d1.items():
369 result = compareConfigs(f
"{name}.{k}", v1, v2, shortcut=shortcut,
370 rtol=rtol, atol=atol, output=output)
371 if not result
and shortcut:
373 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 __set__(self, Config instance, Union[None, Mapping[str, ConfigurableAction], ConfigurableActionStruct, ConfigurableActionStructField, Type[ConfigurableActionStructField]] value, Iterable[StackFrame] at=None, str label='assigment')
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 __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)