LSST Applications  21.0.0-147-g0e635eb1+1acddb5be5,22.0.0+052faf71bd,22.0.0+1ea9a8b2b2,22.0.0+6312710a6c,22.0.0+729191ecac,22.0.0+7589c3a021,22.0.0+9f079a9461,22.0.1-1-g7d6de66+b8044ec9de,22.0.1-1-g87000a6+536b1ee016,22.0.1-1-g8e32f31+6312710a6c,22.0.1-10-gd060f87+016f7cdc03,22.0.1-12-g9c3108e+df145f6f68,22.0.1-16-g314fa6d+c825727ab8,22.0.1-19-g93a5c75+d23f2fb6d8,22.0.1-19-gb93eaa13+aab3ef7709,22.0.1-2-g8ef0a89+b8044ec9de,22.0.1-2-g92698f7+9f079a9461,22.0.1-2-ga9b0f51+052faf71bd,22.0.1-2-gac51dbf+052faf71bd,22.0.1-2-gb66926d+6312710a6c,22.0.1-2-gcb770ba+09e3807989,22.0.1-20-g32debb5+b8044ec9de,22.0.1-23-gc2439a9a+fb0756638e,22.0.1-3-g496fd5d+09117f784f,22.0.1-3-g59f966b+1e6ba2c031,22.0.1-3-g849a1b8+f8b568069f,22.0.1-3-gaaec9c0+c5c846a8b1,22.0.1-32-g5ddfab5d3+60ce4897b0,22.0.1-4-g037fbe1+64e601228d,22.0.1-4-g8623105+b8044ec9de,22.0.1-5-g096abc9+d18c45d440,22.0.1-5-g15c806e+57f5c03693,22.0.1-7-gba73697+57f5c03693,master-g6e05de7fdc+c1283a92b8,master-g72cdda8301+729191ecac,w.2021.39
LSST Data Management Base Package
_configurableActionStructField.py
Go to the documentation of this file.
1 # This file is part of pipe_tasks.
2 #
3 # Developed for the LSST Data Management System.
4 # This product includes software developed by the LSST Project
5 # (https://www.lsst.org).
6 # See the COPYRIGHT file at the top-level directory of this distribution
7 # for details of code ownership.
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <https://www.gnu.org/licenses/>.
21 from __future__ import annotations
22 
23 __all__ = ("ConfigurableActionStructField", "ConfigurableActionStruct")
24 
25 from typing import Iterable, Mapping, Optional, TypeVar, Union, Type, Tuple, List, Any, Dict
26 
27 from lsst.pex.config.config import Config, Field, FieldValidationError, _typeStr, _joinNamePath
28 from lsst.pex.config.comparison import compareConfigs, compareScalars, getComparisonName
29 from lsst.pex.config.callStack import StackFrame, getCallStack, getStackFrame
30 
31 from . import ConfigurableAction
32 
33 import weakref
34 
35 
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
40  the command line.
41  """
42  def __set__(self, instance: ConfigurableActionStruct,
43  value: Union[Mapping[str, ConfigurableAction], ConfigurableActionStruct]) -> None:
44  if isinstance(value, Mapping):
45  pass
46  elif isinstance(value, ConfigurableActionStruct):
47  # If the update target is a ConfigurableActionStruct, get the
48  # internal dictionary
49  value = value._attrs
50  else:
51  raise ValueError("Can only update a ConfigurableActionStruct with an instance of such, or a "
52  "mapping")
53  for name, action in value.items():
54  setattr(instance, name, action)
55 
56  def __get__(self, instance, objtype=None) -> None:
57  # This descriptor does not support fetching any value
58  return None
59 
60 
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.
66 
67  Raises
68  ------
69  AttributeError
70  Raised if an attribute specified for removal does not exist in the
71  ConfigurableActionStruct
72  """
73  def __set__(self, instance: ConfigurableActionStruct,
74  value: Union[str, Iterable[str]]) -> None:
75  # strings are iterable, but not in the way that is intended. If a
76  # single name is specified, turn it into a tuple before attempting
77  # to remove the attribute
78  if isinstance(value, str):
79  value = (value, )
80  for name in value:
81  delattr(instance, name)
82 
83  def __get__(self, instance, objtype=None) -> None:
84  # This descriptor does not support fetching any value
85  return None
86 
87 
89  """A ConfigurableActionStruct is the storage backend class that supports
90  the ConfigurableActionStructField. This class should not be created
91  directly.
92 
93  This class allows managing a collection of `ConfigurableActions` with a
94  struct like interface, that is to say in an attribute like notation.
95 
96  Attributes can be dynamically added or removed as such:
97 
98  ConfigurableActionStructInstance.variable1 = a_configurable_action
99  del ConfigurableActionStructInstance.variable1
100 
101  Each action is then available to be individually configured as a normal
102  `lsst.pex.config.Config` object.
103 
104  ConfigurableActionStruct supports two special convenance attributes.
105 
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.
111 
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`
118  """
119  # declare attributes that are set with __setattr__
120  _config: Config
121  _attrs: Dict[str, ConfigurableAction]
122  _field: ConfigurableActionStructField
123  _history: List[tuple]
124 
125  # create descriptors to handle special update and remove behavior
128 
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', [])
135 
136  self.historyhistory.append(("Struct initialized", at, label))
137 
138  if value is not None:
139  for k, v in value.items():
140  setattr(self, k, v)
141 
142  @property
143  def _config(self) -> Config:
144  # Config Fields should never outlive their config class instance
145  # assert that as such here
146  assert(self._config_() is not None)
147  return self._config_()
148 
149  @property
150  def history(self) -> List[tuple]:
151  return self._history
152 
153  @property
154  def fieldNames(self) -> Iterable[str]:
155  return self._attrs.keys()
156 
157  def __setattr__(self, attr: str, value: Union[ConfigurableAction, Type[ConfigurableAction]],
158  at=None, label='setattr', setHistory=False) -> None:
159 
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}"
163  raise FieldValidationError(self._field, self._config_config, msg)
164 
165  if attr not in (self.__dict__.keys() | type(self).__dict__.keys()):
166  name = _joinNamePath(self._config_config._name, self._field.name, attr)
167  if at is None:
168  at = getCallStack()
169  if isinstance(value, ConfigurableAction):
170  valueInst = type(value)(__name=name, __at=at, __label=label, **value._storage)
171  else:
172  valueInst = value(__name=name, __at=at, __label=label)
173  self._attrs[attr] = valueInst
174  else:
175  super().__setattr__(attr, value)
176 
177  def __getattr__(self, attr):
178  if attr in object.__getattribute__(self, '_attrs'):
179  return self._attrs[attr]
180  else:
181  super().__getattribute__(attr)
182 
183  def __delattr__(self, name):
184  if name in self._attrs:
185  del self._attrs[name]
186  else:
187  super().__delattr__(name)
188 
189  def __iter__(self) -> Iterable[ConfigurableAction]:
190  return iter(self._attrs.values())
191 
192  def items(self) -> Iterable[Tuple[str, ConfigurableAction]]:
193  return iter(self._attrs.items())
194 
195 
196 T = TypeVar("T", bound="ConfigurableActionStructField")
197 
198 
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.
204 
205  This class implements a `ConfigurableActionStruct` as an intermediary
206  object to organize the `ConfigurableActions`. See it's documentation for
207  futher information.
208  """
209  # specify StructClass to make this more generic for potential future
210  # inheritance
211  StructClass = ConfigurableActionStruct
212 
213  # Explicitly annotate these on the class, they are present in the base
214  # class through injection, so type systems have trouble seeing them.
215  name: str
216  default: Optional[Mapping[str, ConfigurableAction]]
217 
218  def __init__(self, doc: str, default: Optional[Mapping[str, ConfigurableAction]] = None,
219  optional: bool = False,
220  deprecated=None):
221  source = getStackFrame()
222  self._setup_setup(doc=doc, dtype=self.__class__, default=default, check=None,
223  optional=optional, source=source, deprecated=deprecated)
224 
225  def __set__(self, instance: Config,
226  value: Union[None, Mapping[str, ConfigurableAction], ConfigurableActionStruct],
227  at: Iterable[StackFrame] = None, label: str = 'assigment'):
228  if instance._frozen:
229  msg = "Cannot modify a frozen Config. "\
230  "Attempting to set field to value %s" % value
231  raise FieldValidationError(self, instance, msg)
232 
233  if at is None:
234  at = getCallStack()
235 
236  if value is None or value == self.defaultdefault:
237  value = self.StructClassStructClass(instance, self, value, at=at, label=label)
238  else:
239  history = instance._history.setdefault(self.name, [])
240  history.append((value, at, label))
241 
242  if not isinstance(value, ConfigurableActionStruct):
243  raise FieldValidationError(self, instance,
244  "Can only assign things that are subclasses of Configurable Action")
245  instance._storage[self.name] = value
246 
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):
251  return self
252  else:
253  field: Optional[ConfigurableActionStruct] = instance._storage[self.name]
254  return field
255 
256  def rename(self, instance: Config):
257  actionStruct: ConfigurableActionStruct = self.__get____get____get__(instance)
258  if actionStruct is not None:
259  for k, v in actionStruct.items():
260  fullname = _joinNamePath(instance._name, self.name, k)
261  v._rename(fullname)
262 
263  def validate(self, instance):
264  value = self.__get____get____get__(instance)
265  if value is not None:
266  for item in value:
267  item.validate()
268 
269  def toDict(self, instance):
270  actionStruct = self.__get____get____get__(instance)
271  if actionStruct is None:
272  return None
273 
274  dict_ = {k: v.toDict() for k, v in actionStruct.items()}
275 
276  return dict_
277 
278  def save(self, outfile, instance):
279  actionStruct = self.__get____get____get__(instance)
280  fullname = _joinNamePath(instance._name, self.name)
281  if actionStruct is None:
282  outfile.write(u"{}={!r}\n".format(fullname, actionStruct))
283  return
284 
285  outfile.write(u"{}={!r}\n".format(fullname, {}))
286  for v in actionStruct:
287  outfile.write(u"{}={}()\n".format(v._name, _typeStr(v)))
288  v._save(outfile)
289 
290  def freeze(self, instance):
291  actionStruct = self.__get____get____get__(instance)
292  if actionStruct is not None:
293  for v in actionStruct:
294  v.freeze()
295 
296  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
297  """Compare two fields for equality.
298 
299  Parameters
300  ----------
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.
305  shortcut : `bool`
306  If `True`, this function returns as soon as an inequality if found.
307  rtol : `float`
308  Relative tolerance for floating point comparisons.
309  atol : `float`
310  Absolute tolerance for floating point comparisons.
311  output : callable
312  A callable that takes a string, used (possibly repeatedly) to
313  report inequalities.
314 
315  Returns
316  -------
317  isEqual : bool
318  `True` if the fields are equal, `False` otherwise.
319 
320  Notes
321  -----
322  Floating point comparisons are performed by `numpy.allclose`.
323  """
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)
329  )
330  if not compareScalars(f"keys for {name}", set(d1.fieldNames), set(d2.fieldNames), output=output):
331  return False
332  equal = True
333  for k, v1 in d1.items():
334  v2 = getattr(d2, k)
335  result = compareConfigs(f"{name}.{k}", v1, v2, shortcut=shortcut,
336  rtol=rtol, atol=atol, output=output)
337  if not result and shortcut:
338  return False
339  equal = equal and result
340  return equal
table::Key< int > type
Definition: Detector.cc:163
def __get__(self, instance, owner=None, at=None, label="default")
Definition: config.py:550
def _setup(self, doc, dtype, default, check, optional, source, deprecated)
Definition: config.py:336
Union[None, T, ConfigurableActionStruct] __get__(T self, Config instance, None owner=None, Iterable[StackFrame] at=None, str label="default")
def __init__(self, str doc, Optional[Mapping[str, ConfigurableAction]] default=None, bool optional=False, deprecated=None)
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)
None __setattr__(self, str attr, Union[ConfigurableAction, Type[ConfigurableAction]] value, at=None, label='setattr', setHistory=False)
None __set__(self, ConfigurableActionStruct instance, Union[str, Iterable[str]] value)
None __set__(self, ConfigurableActionStruct instance, Union[Mapping[str, ConfigurableAction], ConfigurableActionStruct] value)
daf::base::PropertySet * set
Definition: fits.cc:912
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Definition: functional.cc:33
def getCallStack(skip=0)
Definition: callStack.py:175
def getStackFrame(relative=0)
Definition: callStack.py:58
def compareConfigs(name, c1, c2, shortcut=True, rtol=1E-8, atol=1E-8, output=None)
Definition: comparison.py:111
def compareScalars(name, v1, v2, output, rtol=1E-8, atol=1E-8, dtype=None)
Definition: comparison.py:62
def getComparisonName(name1, name2)
Definition: comparison.py:40
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174