lsst.pex.config  14.0-2-g319577b+2
listField.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 from builtins import zip
23 from builtins import str
24 from builtins import range
25 
26 import collections
27 
28 from .config import Field, FieldValidationError, _typeStr, _autocast, _joinNamePath
29 from .comparison import compareScalars, getComparisonName
30 from .callStack import getCallStack, getStackFrame
31 
32 __all__ = ["ListField"]
33 
34 
35 class List(collections.MutableSequence):
36  def __init__(self, config, field, value, at, label, setHistory=True):
37  self._field = field
38  self._config = config
39  self._history = self._config._history.setdefault(self._field.name, [])
40  self._list = []
41  self.__doc__ = field.doc
42  if value is not None:
43  try:
44  for i, x in enumerate(value):
45  self.insert(i, x, setHistory=False)
46  except TypeError:
47  msg = "Value %s is of incorrect type %s. Sequence type expected" % (value, _typeStr(value))
48  raise FieldValidationError(self._field, self._config, msg)
49  if setHistory:
50  self.history.append((list(self._list), at, label))
51 
52  def validateItem(self, i, x):
53 
54  if not isinstance(x, self._field.itemtype) and x is not None:
55  msg = "Item at position %d with value %s is of incorrect type %s. Expected %s" % \
56  (i, x, _typeStr(x), _typeStr(self._field.itemtype))
57  raise FieldValidationError(self._field, self._config, msg)
58 
59  if self._field.itemCheck is not None and not self._field.itemCheck(x):
60  msg = "Item at position %d is not a valid value: %s" % (i, x)
61  raise FieldValidationError(self._field, self._config, msg)
62 
63  def list(self):
64  return self._list
65 
66  """
67  Read-only history
68  """
69  history = property(lambda x: x._history)
70 
71  def __contains__(self, x):
72  return x in self._list
73 
74  def __len__(self):
75  return len(self._list)
76 
77  def __setitem__(self, i, x, at=None, label="setitem", setHistory=True):
78  if self._config._frozen:
79  raise FieldValidationError(self._field, self._config,
80  "Cannot modify a frozen Config")
81  if isinstance(i, slice):
82  k, stop, step = i.indices(len(self))
83  for j, xj in enumerate(x):
84  xj = _autocast(xj, self._field.itemtype)
85  self.validateItem(k, xj)
86  x[j] = xj
87  k += step
88  else:
89  x = _autocast(x, self._field.itemtype)
90  self.validateItem(i, x)
91 
92  self._list[i] = x
93  if setHistory:
94  if at is None:
95  at = getCallStack()
96  self.history.append((list(self._list), at, label))
97 
98  def __getitem__(self, i):
99  return self._list[i]
100 
101  def __delitem__(self, i, at=None, label="delitem", setHistory=True):
102  if self._config._frozen:
103  raise FieldValidationError(self._field, self._config,
104  "Cannot modify a frozen Config")
105  del self._list[i]
106  if setHistory:
107  if at is None:
108  at = getCallStack()
109  self.history.append((list(self._list), at, label))
110 
111  def __iter__(self):
112  return iter(self._list)
113 
114  def insert(self, i, x, at=None, label="insert", setHistory=True):
115  if at is None:
116  at = getCallStack()
117  self.__setitem__(slice(i, i), [x], at=at, label=label, setHistory=setHistory)
118 
119  def __repr__(self):
120  return repr(self._list)
121 
122  def __str__(self):
123  return str(self._list)
124 
125  def __eq__(self, other):
126  try:
127  if len(self) != len(other):
128  return False
129 
130  for i, j in zip(self, other):
131  if i != j:
132  return False
133  return True
134  except AttributeError:
135  # other is not a sequence type
136  return False
137 
138  def __ne__(self, other):
139  return not self.__eq__(other)
140 
141  def __setattr__(self, attr, value, at=None, label="assignment"):
142  if hasattr(getattr(self.__class__, attr, None), '__set__'):
143  # This allows properties to work.
144  object.__setattr__(self, attr, value)
145  elif attr in self.__dict__ or attr in ["_field", "_config", "_history", "_list", "__doc__"]:
146  # This allows specific private attributes to work.
147  object.__setattr__(self, attr, value)
148  else:
149  # We throw everything else.
150  msg = "%s has no attribute %s" % (_typeStr(self._field), attr)
151  raise FieldValidationError(self._field, self._config, msg)
152 
153 
155  """
156  Defines a field which is a container of values of type dtype
157 
158  If length is not None, then instances of this field must match this length
159  exactly.
160  If minLength is not None, then instances of the field must be no shorter
161  then minLength
162  If maxLength is not None, then instances of the field must be no longer
163  than maxLength
164 
165  Additionally users can provide two check functions:
166  listCheck - used to validate the list as a whole, and
167  itemCheck - used to validate each item individually
168  """
169  def __init__(self, doc, dtype, default=None, optional=False,
170  listCheck=None, itemCheck=None,
171  length=None, minLength=None, maxLength=None):
172  if dtype not in Field.supportedTypes:
173  raise ValueError("Unsupported dtype %s" % _typeStr(dtype))
174  if length is not None:
175  if length <= 0:
176  raise ValueError("'length' (%d) must be positive" % length)
177  minLength = None
178  maxLength = None
179  else:
180  if maxLength is not None and maxLength <= 0:
181  raise ValueError("'maxLength' (%d) must be positive" % maxLength)
182  if minLength is not None and maxLength is not None \
183  and minLength > maxLength:
184  raise ValueError("'maxLength' (%d) must be at least"
185  " as large as 'minLength' (%d)" % (maxLength, minLength))
186 
187  if listCheck is not None and not hasattr(listCheck, "__call__"):
188  raise ValueError("'listCheck' must be callable")
189  if itemCheck is not None and not hasattr(itemCheck, "__call__"):
190  raise ValueError("'itemCheck' must be callable")
191 
192  source = getStackFrame()
193  self._setup(doc=doc, dtype=List, default=default, check=None, optional=optional, source=source)
194  self.listCheck = listCheck
195  self.itemCheck = itemCheck
196  self.itemtype = dtype
197  self.length = length
198  self.minLength = minLength
199  self.maxLength = maxLength
200 
201  def validate(self, instance):
202  """
203  ListField validation ensures that non-optional fields are not None,
204  and that non-None values comply with length requirements and
205  that the list passes listCheck if supplied by the user.
206  Individual Item checks are applied at set time and are not re-checked.
207  """
208  Field.validate(self, instance)
209  value = self.__get__(instance)
210  if value is not None:
211  lenValue = len(value)
212  if self.length is not None and not lenValue == self.length:
213  msg = "Required list length=%d, got length=%d" % (self.length, lenValue)
214  raise FieldValidationError(self, instance, msg)
215  elif self.minLength is not None and lenValue < self.minLength:
216  msg = "Minimum allowed list length=%d, got length=%d" % (self.minLength, lenValue)
217  raise FieldValidationError(self, instance, msg)
218  elif self.maxLength is not None and lenValue > self.maxLength:
219  msg = "Maximum allowed list length=%d, got length=%d" % (self.maxLength, lenValue)
220  raise FieldValidationError(self, instance, msg)
221  elif self.listCheck is not None and not self.listCheck(value):
222  msg = "%s is not a valid value" % str(value)
223  raise FieldValidationError(self, instance, msg)
224 
225  def __set__(self, instance, value, at=None, label="assignment"):
226  if instance._frozen:
227  raise FieldValidationError(self, instance, "Cannot modify a frozen Config")
228 
229  if at is None:
230  at = getCallStack()
231 
232  if value is not None:
233  value = List(instance, self, value, at, label)
234  else:
235  history = instance._history.setdefault(self.name, [])
236  history.append((value, at, label))
237 
238  instance._storage[self.name] = value
239 
240  def toDict(self, instance):
241  value = self.__get__(instance)
242  return list(value) if value is not None else None
243 
244  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
245  """Helper function for Config.compare; used to compare two fields for equality.
246 
247  @param[in] instance1 LHS Config instance to compare.
248  @param[in] instance2 RHS Config instance to compare.
249  @param[in] shortcut If True, return as soon as an inequality is found.
250  @param[in] rtol Relative tolerance for floating point comparisons.
251  @param[in] atol Absolute tolerance for floating point comparisons.
252  @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
253  to report inequalities.
254 
255  Floating point comparisons are performed by numpy.allclose; refer to that for details.
256  """
257  l1 = getattr(instance1, self.name)
258  l2 = getattr(instance2, self.name)
259  name = getComparisonName(
260  _joinNamePath(instance1._name, self.name),
261  _joinNamePath(instance2._name, self.name)
262  )
263  if not compareScalars("isnone for %s" % name, l1 is None, l2 is None, output=output):
264  return False
265  if l1 is None and l2 is None:
266  return True
267  if not compareScalars("size for %s" % name, len(l1), len(l2), output=output):
268  return False
269  equal = True
270  for n, v1, v2 in zip(range(len(l1)), l1, l2):
271  result = compareScalars("%s[%d]" % (name, n), v1, v2, dtype=self.dtype,
272  rtol=rtol, atol=atol, output=output)
273  if not result and shortcut:
274  return False
275  equal = equal and result
276  return equal
def __init__(self, doc, dtype, default=None, optional=False, listCheck=None, itemCheck=None, length=None, minLength=None, maxLength=None)
Definition: listField.py:171
def validate(self, instance)
Definition: listField.py:201
def validateItem(self, i, x)
Definition: listField.py:52
def __setitem__(self, i, x, at=None, label="setitem", setHistory=True)
Definition: listField.py:77
def getCallStack(skip=0)
Definition: callStack.py:157
def __get__(self, instance, owner=None, at=None, label="default")
Definition: config.py:287
def insert(self, i, x, at=None, label="insert", setHistory=True)
Definition: listField.py:114
def __init__(self, config, field, value, at, label, setHistory=True)
Definition: listField.py:36
def _setup(self, doc, dtype, default, check, optional, source)
Definition: config.py:188
def __setattr__(self, attr, value, at=None, label="assignment")
Definition: listField.py:141
def getStackFrame(relative=0)
Definition: callStack.py:54
def __delitem__(self, i, at=None, label="delitem", setHistory=True)
Definition: listField.py:101
def compareScalars(name, v1, v2, output, rtol=1E-8, atol=1E-8, dtype=None)
Definition: comparison.py:41
def __set__(self, instance, value, at=None, label="assignment")
Definition: listField.py:225
def getComparisonName(name1, name2)
Definition: comparison.py:35