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