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