LSSTApplications  18.0.0+106,18.0.0+50,19.0.0,19.0.0+1,19.0.0+10,19.0.0+11,19.0.0+13,19.0.0+17,19.0.0+2,19.0.0-1-g20d9b18+6,19.0.0-1-g425ff20,19.0.0-1-g5549ca4,19.0.0-1-g580fafe+6,19.0.0-1-g6fe20d0+1,19.0.0-1-g7011481+9,19.0.0-1-g8c57eb9+6,19.0.0-1-gb5175dc+11,19.0.0-1-gdc0e4a7+9,19.0.0-1-ge272bc4+6,19.0.0-1-ge3aa853,19.0.0-10-g448f008b,19.0.0-12-g6990b2c,19.0.0-2-g0d9f9cd+11,19.0.0-2-g3d9e4fb2+11,19.0.0-2-g5037de4,19.0.0-2-gb96a1c4+3,19.0.0-2-gd955cfd+15,19.0.0-3-g2d13df8,19.0.0-3-g6f3c7dc,19.0.0-4-g725f80e+11,19.0.0-4-ga671dab3b+1,19.0.0-4-gad373c5+3,19.0.0-5-ga2acb9c+2,19.0.0-5-gfe96e6c+2,w.2020.01
LSSTDataManagementBasePackage
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
33 from .comparison import compareScalars, getComparisonName
34 from .callStack import getCallStack, getStackFrame
35 
36 
37 class List(collections.abc.MutableSequence):
38  """List collection used internally by `ListField`.
39 
40  Parameters
41  ----------
42  config : `lsst.pex.config.Config`
43  Config instance that contains the ``field``.
44  field : `ListField`
45  Instance of the `ListField` using this ``List``.
46  value : sequence
47  Sequence of values that are inserted into this ``List``.
48  at : `list` of `lsst.pex.config.callStack.StackFrame`
49  The call stack (created by `lsst.pex.config.callStack.getCallStack`).
50  label : `str`
51  Event label for the history.
52  setHistory : `bool`, optional
53  Enable setting the field's history, using the value of the ``at``
54  parameter. Default is `True`.
55 
56  Raises
57  ------
58  FieldValidationError
59  Raised if an item in the ``value`` parameter does not have the
60  appropriate type for this field or does not pass the
61  `ListField.itemCheck` method of the ``field`` parameter.
62  """
63 
64  def __init__(self, config, field, value, at, label, setHistory=True):
65  self._field = field
66  self._config = config
67  self._history = self._config._history.setdefault(self._field.name, [])
68  self._list = []
69  self.__doc__ = field.doc
70  if value is not None:
71  try:
72  for i, x in enumerate(value):
73  self.insert(i, x, setHistory=False)
74  except TypeError:
75  msg = "Value %s is of incorrect type %s. Sequence type expected" % (value, _typeStr(value))
76  raise FieldValidationError(self._field, self._config, msg)
77  if setHistory:
78  self.history.append((list(self._list), at, label))
79 
80  def validateItem(self, i, x):
81  """Validate an item to determine if it can be included in the list.
82 
83  Parameters
84  ----------
85  i : `int`
86  Index of the item in the `list`.
87  x : object
88  Item in the `list`.
89 
90  Raises
91  ------
92  FieldValidationError
93  Raised if an item in the ``value`` parameter does not have the
94  appropriate type for this field or does not pass the field's
95  `ListField.itemCheck` method.
96  """
97 
98  if not isinstance(x, self._field.itemtype) and x is not None:
99  msg = "Item at position %d with value %s is of incorrect type %s. Expected %s" % \
100  (i, x, _typeStr(x), _typeStr(self._field.itemtype))
101  raise FieldValidationError(self._field, self._config, msg)
102 
103  if self._field.itemCheck is not None and not self._field.itemCheck(x):
104  msg = "Item at position %d is not a valid value: %s" % (i, x)
105  raise FieldValidationError(self._field, self._config, msg)
106 
107  def list(self):
108  """Sequence of items contained by the `List` (`list`).
109  """
110  return self._list
111 
112  history = property(lambda x: x._history)
113  """Read-only history.
114  """
115 
116  def __contains__(self, x):
117  return x in self._list
118 
119  def __len__(self):
120  return len(self._list)
121 
122  def __setitem__(self, i, x, at=None, label="setitem", setHistory=True):
123  if self._config._frozen:
124  raise FieldValidationError(self._field, self._config,
125  "Cannot modify a frozen Config")
126  if isinstance(i, slice):
127  k, stop, step = i.indices(len(self))
128  for j, xj in enumerate(x):
129  xj = _autocast(xj, self._field.itemtype)
130  self.validateItem(k, xj)
131  x[j] = xj
132  k += step
133  else:
134  x = _autocast(x, self._field.itemtype)
135  self.validateItem(i, x)
136 
137  self._list[i] = x
138  if setHistory:
139  if at is None:
140  at = getCallStack()
141  self.history.append((list(self._list), at, label))
142 
143  def __getitem__(self, i):
144  return self._list[i]
145 
146  def __delitem__(self, i, at=None, label="delitem", setHistory=True):
147  if self._config._frozen:
148  raise FieldValidationError(self._field, self._config,
149  "Cannot modify a frozen Config")
150  del self._list[i]
151  if setHistory:
152  if at is None:
153  at = getCallStack()
154  self.history.append((list(self._list), at, label))
155 
156  def __iter__(self):
157  return iter(self._list)
158 
159  def insert(self, i, x, at=None, label="insert", setHistory=True):
160  """Insert an item into the list at the given index.
161 
162  Parameters
163  ----------
164  i : `int`
165  Index where the item is inserted.
166  x : object
167  Item that is inserted.
168  at : `list` of `lsst.pex.config.callStack.StackFrame`, optional
169  The call stack (created by
170  `lsst.pex.config.callStack.getCallStack`).
171  label : `str`, optional
172  Event label for the history.
173  setHistory : `bool`, optional
174  Enable setting the field's history, using the value of the ``at``
175  parameter. Default is `True`.
176  """
177  if at is None:
178  at = getCallStack()
179  self.__setitem__(slice(i, i), [x], at=at, label=label, setHistory=setHistory)
180 
181  def __repr__(self):
182  return repr(self._list)
183 
184  def __str__(self):
185  return str(self._list)
186 
187  def __eq__(self, other):
188  try:
189  if len(self) != len(other):
190  return False
191 
192  for i, j in zip(self, other):
193  if i != j:
194  return False
195  return True
196  except AttributeError:
197  # other is not a sequence type
198  return False
199 
200  def __ne__(self, other):
201  return not self.__eq__(other)
202 
203  def __setattr__(self, attr, value, at=None, label="assignment"):
204  if hasattr(getattr(self.__class__, attr, None), '__set__'):
205  # This allows properties to work.
206  object.__setattr__(self, attr, value)
207  elif attr in self.__dict__ or attr in ["_field", "_config", "_history", "_list", "__doc__"]:
208  # This allows specific private attributes to work.
209  object.__setattr__(self, attr, value)
210  else:
211  # We throw everything else.
212  msg = "%s has no attribute %s" % (_typeStr(self._field), attr)
213  raise FieldValidationError(self._field, self._config, msg)
214 
215 
217  """A configuration field (`~lsst.pex.config.Field` subclass) that contains
218  a list of values of a specific type.
219 
220  Parameters
221  ----------
222  doc : `str`
223  A description of the field.
224  dtype : class
225  The data type of items in the list.
226  default : sequence, optional
227  The default items for the field.
228  optional : `bool`, optional
229  Set whether the field is *optional*. When `False`,
230  `lsst.pex.config.Config.validate` will fail if the field's value is
231  `None`.
232  listCheck : callable, optional
233  A callable that validates the list as a whole.
234  itemCheck : callable, optional
235  A callable that validates individual items in the list.
236  length : `int`, optional
237  If set, this field must contain exactly ``length`` number of items.
238  minLength : `int`, optional
239  If set, this field must contain *at least* ``minLength`` number of
240  items.
241  maxLength : `int`, optional
242  If set, this field must contain *no more than* ``maxLength`` number of
243  items.
244  deprecated : None or `str`, optional
245  A description of why this Field is deprecated, including removal date.
246  If not None, the string is appended to the docstring for this Field.
247 
248  See also
249  --------
250  ChoiceField
251  ConfigChoiceField
252  ConfigDictField
253  ConfigField
254  ConfigurableField
255  DictField
256  Field
257  RangeField
258  RegistryField
259  """
260  def __init__(self, doc, dtype, default=None, optional=False,
261  listCheck=None, itemCheck=None,
262  length=None, minLength=None, maxLength=None,
263  deprecated=None):
264  if dtype not in Field.supportedTypes:
265  raise ValueError("Unsupported dtype %s" % _typeStr(dtype))
266  if length is not None:
267  if length <= 0:
268  raise ValueError("'length' (%d) must be positive" % length)
269  minLength = None
270  maxLength = None
271  else:
272  if maxLength is not None and maxLength <= 0:
273  raise ValueError("'maxLength' (%d) must be positive" % maxLength)
274  if minLength is not None and maxLength is not None \
275  and minLength > maxLength:
276  raise ValueError("'maxLength' (%d) must be at least"
277  " as large as 'minLength' (%d)" % (maxLength, minLength))
278 
279  if listCheck is not None and not hasattr(listCheck, "__call__"):
280  raise ValueError("'listCheck' must be callable")
281  if itemCheck is not None and not hasattr(itemCheck, "__call__"):
282  raise ValueError("'itemCheck' must be callable")
283 
284  source = getStackFrame()
285  self._setup(doc=doc, dtype=List, default=default, check=None, optional=optional, source=source,
286  deprecated=deprecated)
287 
288  self.listCheck = listCheck
289  """Callable used to check the list as a whole.
290  """
291 
292  self.itemCheck = itemCheck
293  """Callable used to validate individual items as they are inserted
294  into the list.
295  """
296 
297  self.itemtype = dtype
298  """Data type of list items.
299  """
300 
301  self.length = length
302  """Number of items that must be present in the list (or `None` to
303  disable checking the list's length).
304  """
305 
306  self.minLength = minLength
307  """Minimum number of items that must be present in the list (or `None`
308  to disable checking the list's minimum length).
309  """
310 
311  self.maxLength = maxLength
312  """Maximum number of items that must be present in the list (or `None`
313  to disable checking the list's maximum length).
314  """
315 
316  def validate(self, instance):
317  """Validate the field.
318 
319  Parameters
320  ----------
321  instance : `lsst.pex.config.Config`
322  The config instance that contains this field.
323 
324  Raises
325  ------
326  lsst.pex.config.FieldValidationError
327  Raised if:
328 
329  - The field is not optional, but the value is `None`.
330  - The list itself does not meet the requirements of the `length`,
331  `minLength`, or `maxLength` attributes.
332  - The `listCheck` callable returns `False`.
333 
334  Notes
335  -----
336  Individual item checks (`itemCheck`) are applied when each item is
337  set and are not re-checked by this method.
338  """
339  Field.validate(self, instance)
340  value = self.__get__(instance)
341  if value is not None:
342  lenValue = len(value)
343  if self.length is not None and not lenValue == self.length:
344  msg = "Required list length=%d, got length=%d" % (self.length, lenValue)
345  raise FieldValidationError(self, instance, msg)
346  elif self.minLength is not None and lenValue < self.minLength:
347  msg = "Minimum allowed list length=%d, got length=%d" % (self.minLength, lenValue)
348  raise FieldValidationError(self, instance, msg)
349  elif self.maxLength is not None and lenValue > self.maxLength:
350  msg = "Maximum allowed list length=%d, got length=%d" % (self.maxLength, lenValue)
351  raise FieldValidationError(self, instance, msg)
352  elif self.listCheck is not None and not self.listCheck(value):
353  msg = "%s is not a valid value" % str(value)
354  raise FieldValidationError(self, instance, msg)
355 
356  def __set__(self, instance, value, at=None, label="assignment"):
357  if instance._frozen:
358  raise FieldValidationError(self, instance, "Cannot modify a frozen Config")
359 
360  if at is None:
361  at = getCallStack()
362 
363  if value is not None:
364  value = List(instance, self, value, at, label)
365  else:
366  history = instance._history.setdefault(self.name, [])
367  history.append((value, at, label))
368 
369  instance._storage[self.name] = value
370 
371  def toDict(self, instance):
372  """Convert the value of this field to a plain `list`.
373 
374  `lsst.pex.config.Config.toDict` is the primary user of this method.
375 
376  Parameters
377  ----------
378  instance : `lsst.pex.config.Config`
379  The config instance that contains this field.
380 
381  Returns
382  -------
383  `list`
384  Plain `list` of items, or `None` if the field is not set.
385  """
386  value = self.__get__(instance)
387  return list(value) if value is not None else None
388 
389  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
390  """Compare two config instances for equality with respect to this
391  field.
392 
393  `lsst.pex.config.config.compare` is the primary user of this method.
394 
395  Parameters
396  ----------
397  instance1 : `lsst.pex.config.Config`
398  Left-hand-side `~lsst.pex.config.Config` instance in the
399  comparison.
400  instance2 : `lsst.pex.config.Config`
401  Right-hand-side `~lsst.pex.config.Config` instance in the
402  comparison.
403  shortcut : `bool`
404  If `True`, return as soon as an **inequality** is found.
405  rtol : `float`
406  Relative tolerance for floating point comparisons.
407  atol : `float`
408  Absolute tolerance for floating point comparisons.
409  output : callable
410  If not None, a callable that takes a `str`, used (possibly
411  repeatedly) to report inequalities.
412 
413  Returns
414  -------
415  equal : `bool`
416  `True` if the fields are equal; `False` otherwise.
417 
418  Notes
419  -----
420  Floating point comparisons are performed by `numpy.allclose`.
421  """
422  l1 = getattr(instance1, self.name)
423  l2 = getattr(instance2, self.name)
424  name = getComparisonName(
425  _joinNamePath(instance1._name, self.name),
426  _joinNamePath(instance2._name, self.name)
427  )
428  if not compareScalars("isnone for %s" % name, l1 is None, l2 is None, output=output):
429  return False
430  if l1 is None and l2 is None:
431  return True
432  if not compareScalars("size for %s" % name, len(l1), len(l2), output=output):
433  return False
434  equal = True
435  for n, v1, v2 in zip(range(len(l1)), l1, l2):
436  result = compareScalars("%s[%d]" % (name, n), v1, v2, dtype=self.dtype,
437  rtol=rtol, atol=atol, output=output)
438  if not result and shortcut:
439  return False
440  equal = equal and result
441  return equal
def getCallStack(skip=0)
Definition: callStack.py:175
def __set__(self, instance, value, at=None, label="assignment")
Definition: listField.py:356
def validate(self, instance)
Definition: listField.py:316
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 __init__(self, config, field, value, at, label, setHistory=True)
Definition: listField.py:64
def insert(self, i, x, at=None, label="insert", setHistory=True)
Definition: listField.py:159
def __contains__(self, x)
Definition: listField.py:116
def getStackFrame(relative=0)
Definition: callStack.py:58
def toDict(self, instance)
Definition: listField.py:371
def __setattr__(self, attr, value, at=None, label="assignment")
Definition: listField.py:203
def __get__(self, instance, owner=None, at=None, label="default")
Definition: config.py:498
def __ne__(self, other)
Definition: listField.py:200
def validateItem(self, i, x)
Definition: listField.py:80
def __setitem__(self, i, x, at=None, label="setitem", setHistory=True)
Definition: listField.py:122
def getComparisonName(name1, name2)
Definition: comparison.py:40
def __init__(self, doc, dtype, default=None, optional=False, listCheck=None, itemCheck=None, length=None, minLength=None, maxLength=None, deprecated=None)
Definition: listField.py:263
def compareScalars(name, v1, v2, output, rtol=1E-8, atol=1E-8, dtype=None)
Definition: comparison.py:62
def __eq__(self, other)
Definition: listField.py:187
daf::base::PropertyList * list
Definition: fits.cc:903
def __getitem__(self, i)
Definition: listField.py:143
def __delitem__(self, i, at=None, label="delitem", setHistory=True)
Definition: listField.py:146
def _setup(self, doc, dtype, default, check, optional, source, deprecated)
Definition: config.py:284