21from __future__
import annotations
23__all__ = (
"ConfigurableActionStructField",
"ConfigurableActionStruct")
25from types
import SimpleNamespace
41from types
import GenericAlias
44from lsst.pex.config.comparison
import compareConfigs, compareScalars, getComparisonName
48from .
import ConfigurableAction, ActionTypeVar
54 """This descriptor exists to abstract the logic of using a dictionary to
55 update a ConfigurableActionStruct through attribute assignment. This is
56 useful
in the context of setting configuration through pipelines
or on
59 def __set__(self, instance: ConfigurableActionStruct,
60 value: Union[Mapping[str, ConfigurableAction], ConfigurableActionStruct]) ->
None:
61 if isinstance(value, Mapping):
63 elif isinstance(value, ConfigurableActionStruct):
68 raise ValueError(
"Can only update a ConfigurableActionStruct with an instance of such, or a "
70 for name, action
in value.items():
71 setattr(instance, name, action)
73 def __get__(self, instance, objtype=None) -> None:
79 """This descriptor exists to abstract the logic of removing an interable
80 of action names from a ConfigurableActionStruct at one time using
81 attribute assignment. This
is useful
in the context of setting
82 configuration through pipelines
or on the command line.
87 Raised
if an attribute specified
for removal does
not exist
in the
88 ConfigurableActionStruct
90 def __set__(self, instance: ConfigurableActionStruct,
91 value: Union[str, Iterable[str]]) ->
None:
95 if isinstance(value, str):
98 delattr(instance, name)
100 def __get__(self, instance, objtype=None) -> None:
106 """A ConfigurableActionStruct is the storage backend class that supports
107 the ConfigurableActionStructField. This class should not be created
110 This
class allows managing a collection of `ConfigurableActions`
with a
111 struct like interface, that
is to say
in an attribute like notation.
113 Attributes can be dynamically added
or removed
as such:
115 ConfigurableActionStructInstance.variable1 = a_configurable_action
116 del ConfigurableActionStructInstance.variable1
118 Each action
is then available to be individually configured
as a normal
119 `lsst.pex.config.Config` object.
121 ConfigurableActionStruct supports two special convenance attributes.
123 The first
is `update`. You may assign a dict of `ConfigurableActions`
or
124 a `ConfigurableActionStruct` to this attribute which will update the
125 `ConfigurableActionStruct` on which the attribute
is invoked such that it
126 will be updated to contain the entries specified by the structure on the
127 right hand side of the equals sign.
129 The second convenience attribute
is named remove. You may assign an
130 iterable of strings which correspond to attribute names on the
131 `ConfigurableActionStruct`. All of the corresponding attributes will then
132 be removed. If any attribute does
not exist, an `AttributeError` will be
133 raised. Any attributes
in the Iterable prior to the name which raises will
134 have been removed
from the `ConfigurableActionStruct`
137 _config_: weakref.ref
138 _attrs: Dict[str, ActionTypeVar]
139 _field: ConfigurableActionStructField
140 _history: List[tuple]
146 def __init__(self, config: Config, field: ConfigurableActionStructField,
147 value: Mapping[str, ConfigurableAction], at: Any, label: str):
148 object.__setattr__(self,
'_config_', weakref.ref(config))
149 object.__setattr__(self,
'_attrs', {})
150 object.__setattr__(self,
'_field', field)
151 object.__setattr__(self,
'_history', [])
153 self.
history.append((
"Struct initialized", at, label))
155 if value
is not None:
156 for k, v
in value.items():
160 def _config(self) -> Config:
163 value = self._config_()
164 assert value
is not None
173 return self._attrs.keys()
175 def __setattr__(self, attr: str, value: Union[ActionTypeVar, Type[ActionTypeVar]],
176 at=
None, label=
'setattr', setHistory=
False) ->
None:
179 msg =
"Cannot modify a frozen Config. "\
180 f
"Attempting to set item {attr} to value {value}"
185 if not attr.isidentifier():
186 raise ValueError(
"Names used in ConfigurableStructs must be valid as python variable names")
188 if attr
not in (self.__dict__.keys() | type(self).__dict__.keys()):
189 base_name = _joinNamePath(self.
_config._name, self._field.name)
190 name = _joinNamePath(base_name, attr)
193 if isinstance(value, ConfigurableAction):
194 valueInst = type(value)(__name=name, __at=at, __label=label, **value._storage)
196 valueInst = value(__name=name, __at=at, __label=label)
197 self._attrs[attr] = valueInst
202 if attr
in object.__getattribute__(self,
'_attrs'):
203 result = self._attrs[attr]
204 result.identity = attr
207 super().__getattribute__(attr)
210 if name
in self._attrs:
211 del self._attrs[name]
217 yield getattr(self, name)
219 def items(self) -> Iterable[Tuple[str, ActionTypeVar]]:
221 yield name, getattr(self, name)
224 return bool(self._attrs)
227T = TypeVar(
"T", bound=
"ConfigurableActionStructField")
231 r"""`ConfigurableActionStructField` is a `~lsst.pex.config.Field` subclass
232 that allows `ConfigurableAction`\ s to be organized in a
233 `~lsst.pex.config.Config`
class in a manner similar to how a
234 `~lsst.pipe.base.Struct` works.
236 This
class implements a `ConfigurableActionStruct`
as an intermediary
237 object to organize the `ConfigurableActions`. See it
's documentation for
242 StructClass = ConfigurableActionStruct
247 default: Optional[Mapping[str, ConfigurableAction]]
249 def __init__(self, doc: str, default: Optional[Mapping[str, ConfigurableAction]] =
None,
250 optional: bool =
False,
252 source = getStackFrame()
253 self._setup(doc=doc, dtype=self.__class__, default=default, check=
None,
254 optional=optional, source=source, deprecated=deprecated)
257 return GenericAlias(cls, params)
260 value: Union[
None, Mapping[str, ConfigurableAction],
263 ConfigurableActionStruct,
264 ConfigurableActionStructField,
265 Type[ConfigurableActionStructField]],
266 at: Iterable[StackFrame] =
None, label: str =
'assigment'):
268 msg =
"Cannot modify a frozen Config. "\
269 "Attempting to set field to value %s" % value
276 value = self.
StructClass(instance, self, value, at=at, label=label)
282 value = self.
StructClass(instance, self, value._attrs, at=at, label=label)
283 elif isinstance(value, (SimpleNamespace, Struct)):
286 value = self.
StructClass(instance, self, vars(value), at=at, label=label)
288 elif type(value) == ConfigurableActionStructField:
289 raise ValueError(
"ConfigurableActionStructFields can only be used in a class body declaration"
290 f
"Use a {self.StructClass}, SimpleNamespace or Struct")
292 raise ValueError(f
"Unrecognized value {value}, cannot be assigned to this field")
294 history = instance._history.setdefault(self.name, [])
295 history.append((value, at, label))
297 if not isinstance(value, ConfigurableActionStruct):
299 "Can only assign things that are subclasses of Configurable Action")
300 instance._storage[self.name] = value
308 label: str =
'default'
309 ) -> ConfigurableActionStruct[ActionTypeVar]:
318 label: str =
'default'
319 ) -> ConfigurableActionStruct[ActionTypeVar]:
329 if instance
is None or not isinstance(instance, Config):
332 field: Optional[ConfigurableActionStruct] = instance._storage[self.name]
337 if actionStruct
is not None:
338 for k, v
in actionStruct.items():
339 base_name = _joinNamePath(instance._name, self.name)
340 fullname = _joinNamePath(base_name, k)
345 if value
is not None:
351 if actionStruct
is None:
354 dict_ = {k: v.toDict()
for k, v
in actionStruct.items()}
358 def save(self, outfile, instance):
360 fullname = _joinNamePath(instance._name, self.name)
363 outfile.write(f
"{fullname}=None\n")
365 if actionStruct
is None:
368 for _, v
in sorted(actionStruct.items()):
369 outfile.write(
u"{}={}()\n".format(v._name, _typeStr(v)))
374 if actionStruct
is not None:
375 for v
in actionStruct:
378 def _collectImports(self, instance, imports):
381 for v
in actionStruct:
383 imports |= v._imports
385 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
386 """Compare two fields for equality.
390 instance1 : `lsst.pex.config.Config`
391 Left-hand side config instance to compare.
392 instance2 : `lsst.pex.config.Config`
393 Right-hand side config instance to compare.
395 If `True`, this function returns
as soon
as an inequality
if found.
397 Relative tolerance
for floating point comparisons.
399 Absolute tolerance
for floating point comparisons.
401 A callable that takes a string, used (possibly repeatedly) to
407 `
True`
if the fields are equal, `
False` otherwise.
411 Floating point comparisons are performed by `numpy.allclose`.
413 d1: ConfigurableActionStruct = getattr(instance1, self.name)
414 d2: ConfigurableActionStruct = getattr(instance2, self.name)
415 name = getComparisonName(
416 _joinNamePath(instance1._name, self.name),
417 _joinNamePath(instance2._name, self.name)
419 if not compareScalars(f
"keys for {name}", set(d1.fieldNames), set(d2.fieldNames), output=output):
422 for k, v1
in d1.items():
424 result = compareConfigs(f
"{name}.{k}", v1, v2, shortcut=shortcut,
425 rtol=rtol, atol=atol, output=output)
426 if not result
and shortcut:
428 equal = equal
and result
def __get__(self, instance, owner=None, at=None, label="default")
def toDict(self, instance)
def freeze(self, instance)
def save(self, outfile, instance)
def __set__(self, Config instance, Union[None, Mapping[str, ConfigurableAction], SimpleNamespace, Struct, ConfigurableActionStruct, ConfigurableActionStructField, Type[ConfigurableActionStructField]] value, Iterable[StackFrame] at=None, str label='assigment')
def __get__(self, instance, owner=None, at=None, label='default')
def validate(self, Config instance)
def __class_getitem__(cls, params)
def __init__(self, str doc, Optional[Mapping[str, ConfigurableAction]] default=None, bool optional=False, deprecated=None)
def rename(self, Config instance)
ConfigurableActionStruct[ActionTypeVar] __get__(self, Config instance, Any owner=None, Any at=None, str label='default')
ConfigurableActionStruct[ActionTypeVar] __get__(self, None instance, Any owner=None, Any at=None, str label='default')
def __init__(self, Config config, ConfigurableActionStructField field, Mapping[str, ConfigurableAction] value, Any at, str label)
Any __getattr__(self, attr)
Iterable[str] fieldNames(self)
None __setattr__(self, str attr, Union[ActionTypeVar, Type[ActionTypeVar]] value, at=None, label='setattr', setHistory=False)
def __delattr__(self, name)
Iterator[ActionTypeVar] __iter__(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)