LSST Applications  21.0.0-172-gfb10e10a+18fedfabac,22.0.0+297cba6710,22.0.0+80564b0ff1,22.0.0+8d77f4f51a,22.0.0+a28f4c53b1,22.0.0+dcf3732eb2,22.0.1-1-g7d6de66+2a20fdde0d,22.0.1-1-g8e32f31+297cba6710,22.0.1-1-geca5380+7fa3b7d9b6,22.0.1-12-g44dc1dc+2a20fdde0d,22.0.1-15-g6a90155+515f58c32b,22.0.1-16-g9282f48+790f5f2caa,22.0.1-2-g92698f7+dcf3732eb2,22.0.1-2-ga9b0f51+7fa3b7d9b6,22.0.1-2-gd1925c9+bf4f0e694f,22.0.1-24-g1ad7a390+a9625a72a8,22.0.1-25-g5bf6245+3ad8ecd50b,22.0.1-25-gb120d7b+8b5510f75f,22.0.1-27-g97737f7+2a20fdde0d,22.0.1-32-gf62ce7b1+aa4237961e,22.0.1-4-g0b3f228+2a20fdde0d,22.0.1-4-g243d05b+871c1b8305,22.0.1-4-g3a563be+32dcf1063f,22.0.1-4-g44f2e3d+9e4ab0f4fa,22.0.1-42-gca6935d93+ba5e5ca3eb,22.0.1-5-g15c806e+85460ae5f3,22.0.1-5-g58711c4+611d128589,22.0.1-5-g75bb458+99c117b92f,22.0.1-6-g1c63a23+7fa3b7d9b6,22.0.1-6-g50866e6+84ff5a128b,22.0.1-6-g8d3140d+720564cf76,22.0.1-6-gd805d02+cc5644f571,22.0.1-8-ge5750ce+85460ae5f3,master-g6e05de7fdc+babf819c66,master-g99da0e417a+8d77f4f51a,w.2021.48
LSST Data Management Base Package
listField.py
Go to the documentation of this file.
1 # This file is part of pex_config.
2 #
3 # Developed for the LSST Data Management System.
4 # This product includes software developed by the LSST Project
5 # (http://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 software is dual licensed under the GNU General Public License and also
10 # under a 3-clause BSD license. Recipients may choose which of these licenses
11 # to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12 # respectively. If you choose the GPL option then the following text applies
13 # (but note that there is still no warranty even if you opt for BSD instead):
14 #
15 # This program is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation, either version 3 of the License, or
18 # (at your option) any later version.
19 #
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
24 #
25 # You should have received a copy of the GNU General Public License
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 
28 __all__ = ["ListField"]
29 
30 import collections.abc
31 
32 from .config import Field, FieldValidationError, _typeStr, _autocast, _joinNamePath, Config
33 from .comparison import compareScalars, getComparisonName
34 from .callStack import getCallStack, getStackFrame
35 
36 import weakref
37 
38 
39 class List(collections.abc.MutableSequence):
40  """List collection used internally by `ListField`.
41 
42  Parameters
43  ----------
44  config : `lsst.pex.config.Config`
45  Config instance that contains the ``field``.
46  field : `ListField`
47  Instance of the `ListField` using this ``List``.
48  value : sequence
49  Sequence of values that are inserted into this ``List``.
50  at : `list` of `lsst.pex.config.callStack.StackFrame`
51  The call stack (created by `lsst.pex.config.callStack.getCallStack`).
52  label : `str`
53  Event label for the history.
54  setHistory : `bool`, optional
55  Enable setting the field's history, using the value of the ``at``
56  parameter. Default is `True`.
57 
58  Raises
59  ------
60  FieldValidationError
61  Raised if an item in the ``value`` parameter does not have the
62  appropriate type for this field or does not pass the
63  `ListField.itemCheck` method of the ``field`` parameter.
64  """
65 
66  def __init__(self, config, field, value, at, label, setHistory=True):
67  self._field_field = field
68  self._config__config_ = weakref.ref(config)
69  self._history_history = self._config_config._history.setdefault(self._field_field.name, [])
70  self._list_list = []
71  self.__doc____doc__ = field.doc
72  if value is not None:
73  try:
74  for i, x in enumerate(value):
75  self.insertinsert(i, x, setHistory=False)
76  except TypeError:
77  msg = "Value %s is of incorrect type %s. Sequence type expected" % (value, _typeStr(value))
78  raise FieldValidationError(self._field_field, config, msg)
79  if setHistory:
80  self.historyhistory.append((list(self._list_list), at, label))
81 
82  @property
83  def _config(self) -> Config:
84  # Config Fields should never outlive their config class instance
85  # assert that as such here
86  assert(self._config__config_() is not None)
87  return self._config__config_()
88 
89  def validateItem(self, i, x):
90  """Validate an item to determine if it can be included in the list.
91 
92  Parameters
93  ----------
94  i : `int`
95  Index of the item in the `list`.
96  x : object
97  Item in the `list`.
98 
99  Raises
100  ------
101  FieldValidationError
102  Raised if an item in the ``value`` parameter does not have the
103  appropriate type for this field or does not pass the field's
104  `ListField.itemCheck` method.
105  """
106 
107  if not isinstance(x, self._field_field.itemtype) and x is not None:
108  msg = "Item at position %d with value %s is of incorrect type %s. Expected %s" % \
109  (i, x, _typeStr(x), _typeStr(self._field_field.itemtype))
110  raise FieldValidationError(self._field_field, self._config_config, msg)
111 
112  if self._field_field.itemCheck is not None and not self._field_field.itemCheck(x):
113  msg = "Item at position %d is not a valid value: %s" % (i, x)
114  raise FieldValidationError(self._field_field, self._config_config, msg)
115 
116  def list(self):
117  """Sequence of items contained by the `List` (`list`).
118  """
119  return self._list_list
120 
121  history = property(lambda x: x._history)
122  """Read-only history.
123  """
124 
125  def __contains__(self, x):
126  return x in self._list_list
127 
128  def __len__(self):
129  return len(self._list_list)
130 
131  def __setitem__(self, i, x, at=None, label="setitem", setHistory=True):
132  if self._config_config._frozen:
133  raise FieldValidationError(self._field_field, self._config_config,
134  "Cannot modify a frozen Config")
135  if isinstance(i, slice):
136  k, stop, step = i.indices(len(self))
137  for j, xj in enumerate(x):
138  xj = _autocast(xj, self._field_field.itemtype)
139  self.validateItemvalidateItem(k, xj)
140  x[j] = xj
141  k += step
142  else:
143  x = _autocast(x, self._field_field.itemtype)
144  self.validateItemvalidateItem(i, x)
145 
146  self._list_list[i] = x
147  if setHistory:
148  if at is None:
149  at = getCallStack()
150  self.historyhistory.append((list(self._list_list), at, label))
151 
152  def __getitem__(self, i):
153  return self._list_list[i]
154 
155  def __delitem__(self, i, at=None, label="delitem", setHistory=True):
156  if self._config_config._frozen:
157  raise FieldValidationError(self._field_field, self._config_config,
158  "Cannot modify a frozen Config")
159  del self._list_list[i]
160  if setHistory:
161  if at is None:
162  at = getCallStack()
163  self.historyhistory.append((list(self._list_list), at, label))
164 
165  def __iter__(self):
166  return iter(self._list_list)
167 
168  def insert(self, i, x, at=None, label="insert", setHistory=True):
169  """Insert an item into the list at the given index.
170 
171  Parameters
172  ----------
173  i : `int`
174  Index where the item is inserted.
175  x : object
176  Item that is inserted.
177  at : `list` of `lsst.pex.config.callStack.StackFrame`, optional
178  The call stack (created by
179  `lsst.pex.config.callStack.getCallStack`).
180  label : `str`, optional
181  Event label for the history.
182  setHistory : `bool`, optional
183  Enable setting the field's history, using the value of the ``at``
184  parameter. Default is `True`.
185  """
186  if at is None:
187  at = getCallStack()
188  self.__setitem____setitem__(slice(i, i), [x], at=at, label=label, setHistory=setHistory)
189 
190  def __repr__(self):
191  return repr(self._list_list)
192 
193  def __str__(self):
194  return str(self._list_list)
195 
196  def __eq__(self, other):
197  try:
198  if len(self) != len(other):
199  return False
200 
201  for i, j in zip(self, other):
202  if i != j:
203  return False
204  return True
205  except AttributeError:
206  # other is not a sequence type
207  return False
208 
209  def __ne__(self, other):
210  return not self.__eq____eq__(other)
211 
212  def __setattr__(self, attr, value, at=None, label="assignment"):
213  if hasattr(getattr(self.__class__, attr, None), '__set__'):
214  # This allows properties to work.
215  object.__setattr__(self, attr, value)
216  elif attr in self.__dict__ or attr in ["_field", "_config_", "_history", "_list", "__doc__"]:
217  # This allows specific private attributes to work.
218  object.__setattr__(self, attr, value)
219  else:
220  # We throw everything else.
221  msg = "%s has no attribute %s" % (_typeStr(self._field_field), attr)
222  raise FieldValidationError(self._field_field, self._config_config, msg)
223 
224 
226  """A configuration field (`~lsst.pex.config.Field` subclass) that contains
227  a list of values of a specific type.
228 
229  Parameters
230  ----------
231  doc : `str`
232  A description of the field.
233  dtype : class
234  The data type of items in the list.
235  default : sequence, optional
236  The default items for the field.
237  optional : `bool`, optional
238  Set whether the field is *optional*. When `False`,
239  `lsst.pex.config.Config.validate` will fail if the field's value is
240  `None`.
241  listCheck : callable, optional
242  A callable that validates the list as a whole.
243  itemCheck : callable, optional
244  A callable that validates individual items in the list.
245  length : `int`, optional
246  If set, this field must contain exactly ``length`` number of items.
247  minLength : `int`, optional
248  If set, this field must contain *at least* ``minLength`` number of
249  items.
250  maxLength : `int`, optional
251  If set, this field must contain *no more than* ``maxLength`` number of
252  items.
253  deprecated : None or `str`, optional
254  A description of why this Field is deprecated, including removal date.
255  If not None, the string is appended to the docstring for this Field.
256 
257  See also
258  --------
259  ChoiceField
260  ConfigChoiceField
261  ConfigDictField
262  ConfigField
263  ConfigurableField
264  DictField
265  Field
266  RangeField
267  RegistryField
268  """
269  def __init__(self, doc, dtype, default=None, optional=False,
270  listCheck=None, itemCheck=None,
271  length=None, minLength=None, maxLength=None,
272  deprecated=None):
273  if dtype not in Field.supportedTypes:
274  raise ValueError("Unsupported dtype %s" % _typeStr(dtype))
275  if length is not None:
276  if length <= 0:
277  raise ValueError("'length' (%d) must be positive" % length)
278  minLength = None
279  maxLength = None
280  else:
281  if maxLength is not None and maxLength <= 0:
282  raise ValueError("'maxLength' (%d) must be positive" % maxLength)
283  if minLength is not None and maxLength is not None \
284  and minLength > maxLength:
285  raise ValueError("'maxLength' (%d) must be at least"
286  " as large as 'minLength' (%d)" % (maxLength, minLength))
287 
288  if listCheck is not None and not hasattr(listCheck, "__call__"):
289  raise ValueError("'listCheck' must be callable")
290  if itemCheck is not None and not hasattr(itemCheck, "__call__"):
291  raise ValueError("'itemCheck' must be callable")
292 
293  source = getStackFrame()
294  self._setup_setup(doc=doc, dtype=List, default=default, check=None, optional=optional, source=source,
295  deprecated=deprecated)
296 
297  self.listChecklistCheck = listCheck
298  """Callable used to check the list as a whole.
299  """
300 
301  self.itemCheckitemCheck = itemCheck
302  """Callable used to validate individual items as they are inserted
303  into the list.
304  """
305 
306  self.itemtypeitemtype = dtype
307  """Data type of list items.
308  """
309 
310  self.lengthlength = length
311  """Number of items that must be present in the list (or `None` to
312  disable checking the list's length).
313  """
314 
315  self.minLengthminLength = minLength
316  """Minimum number of items that must be present in the list (or `None`
317  to disable checking the list's minimum length).
318  """
319 
320  self.maxLengthmaxLength = maxLength
321  """Maximum number of items that must be present in the list (or `None`
322  to disable checking the list's maximum length).
323  """
324 
325  def validate(self, instance):
326  """Validate the field.
327 
328  Parameters
329  ----------
330  instance : `lsst.pex.config.Config`
331  The config instance that contains this field.
332 
333  Raises
334  ------
335  lsst.pex.config.FieldValidationError
336  Raised if:
337 
338  - The field is not optional, but the value is `None`.
339  - The list itself does not meet the requirements of the `length`,
340  `minLength`, or `maxLength` attributes.
341  - The `listCheck` callable returns `False`.
342 
343  Notes
344  -----
345  Individual item checks (`itemCheck`) are applied when each item is
346  set and are not re-checked by this method.
347  """
348  Field.validate(self, instance)
349  value = self.__get____get__(instance)
350  if value is not None:
351  lenValue = len(value)
352  if self.lengthlength is not None and not lenValue == self.lengthlength:
353  msg = "Required list length=%d, got length=%d" % (self.lengthlength, lenValue)
354  raise FieldValidationError(self, instance, msg)
355  elif self.minLengthminLength is not None and lenValue < self.minLengthminLength:
356  msg = "Minimum allowed list length=%d, got length=%d" % (self.minLengthminLength, lenValue)
357  raise FieldValidationError(self, instance, msg)
358  elif self.maxLengthmaxLength is not None and lenValue > self.maxLengthmaxLength:
359  msg = "Maximum allowed list length=%d, got length=%d" % (self.maxLengthmaxLength, lenValue)
360  raise FieldValidationError(self, instance, msg)
361  elif self.listChecklistCheck is not None and not self.listChecklistCheck(value):
362  msg = "%s is not a valid value" % str(value)
363  raise FieldValidationError(self, instance, msg)
364 
365  def __set__(self, instance, value, at=None, label="assignment"):
366  if instance._frozen:
367  raise FieldValidationError(self, instance, "Cannot modify a frozen Config")
368 
369  if at is None:
370  at = getCallStack()
371 
372  if value is not None:
373  value = List(instance, self, value, at, label)
374  else:
375  history = instance._history.setdefault(self.name, [])
376  history.append((value, at, label))
377 
378  instance._storage[self.name] = value
379 
380  def toDict(self, instance):
381  """Convert the value of this field to a plain `list`.
382 
383  `lsst.pex.config.Config.toDict` is the primary user of this method.
384 
385  Parameters
386  ----------
387  instance : `lsst.pex.config.Config`
388  The config instance that contains this field.
389 
390  Returns
391  -------
392  `list`
393  Plain `list` of items, or `None` if the field is not set.
394  """
395  value = self.__get____get__(instance)
396  return list(value) if value is not None else None
397 
398  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
399  """Compare two config instances for equality with respect to this
400  field.
401 
402  `lsst.pex.config.config.compare` is the primary user of this method.
403 
404  Parameters
405  ----------
406  instance1 : `lsst.pex.config.Config`
407  Left-hand-side `~lsst.pex.config.Config` instance in the
408  comparison.
409  instance2 : `lsst.pex.config.Config`
410  Right-hand-side `~lsst.pex.config.Config` instance in the
411  comparison.
412  shortcut : `bool`
413  If `True`, return as soon as an **inequality** is found.
414  rtol : `float`
415  Relative tolerance for floating point comparisons.
416  atol : `float`
417  Absolute tolerance for floating point comparisons.
418  output : callable
419  If not None, a callable that takes a `str`, used (possibly
420  repeatedly) to report inequalities.
421 
422  Returns
423  -------
424  equal : `bool`
425  `True` if the fields are equal; `False` otherwise.
426 
427  Notes
428  -----
429  Floating point comparisons are performed by `numpy.allclose`.
430  """
431  l1 = getattr(instance1, self.name)
432  l2 = getattr(instance2, self.name)
433  name = getComparisonName(
434  _joinNamePath(instance1._name, self.name),
435  _joinNamePath(instance2._name, self.name)
436  )
437  if not compareScalars("isnone for %s" % name, l1 is None, l2 is None, output=output):
438  return False
439  if l1 is None and l2 is None:
440  return True
441  if not compareScalars("size for %s" % name, len(l1), len(l2), output=output):
442  return False
443  equal = True
444  for n, v1, v2 in zip(range(len(l1)), l1, l2):
445  result = compareScalars("%s[%d]" % (name, n), v1, v2, dtype=self.dtypedtype,
446  rtol=rtol, atol=atol, output=output)
447  if not result and shortcut:
448  return False
449  equal = equal and result
450  return equal
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
def __init__(self, doc, dtype, default=None, optional=False, listCheck=None, itemCheck=None, length=None, minLength=None, maxLength=None, deprecated=None)
Definition: listField.py:272
def __set__(self, instance, value, at=None, label="assignment")
Definition: listField.py:365
def validate(self, instance)
Definition: listField.py:325
def __delitem__(self, i, at=None, label="delitem", setHistory=True)
Definition: listField.py:155
def __setattr__(self, attr, value, at=None, label="assignment")
Definition: listField.py:212
def __init__(self, config, field, value, at, label, setHistory=True)
Definition: listField.py:66
def insert(self, i, x, at=None, label="insert", setHistory=True)
Definition: listField.py:168
def __setitem__(self, i, x, at=None, label="setitem", setHistory=True)
Definition: listField.py:131
def validateItem(self, i, x)
Definition: listField.py:89
daf::base::PropertyList * list
Definition: fits.cc:913
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 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