22 from builtins
import zip
23 from builtins
import str
24 from builtins
import range
28 from .config
import Field, FieldValidationError, _typeStr, _autocast, _joinNamePath
29 from .comparison
import compareScalars, getComparisonName
30 from .callStack
import getCallStack, getStackFrame
32 __all__ = [
"ListField"]
35 class List(collections.MutableSequence):
36 def __init__(self, config, field, value, at, label, setHistory=True):
39 self.
_history = self._config._history.setdefault(self._field.name, [])
44 for i, x
in enumerate(value):
45 self.
insert(i, x, setHistory=
False)
47 msg =
"Value %s is of incorrect type %s. Sequence type expected" % (value, _typeStr(value))
50 self.history.append((
list(self.
_list), at, label))
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))
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)
69 history = property(
lambda x: x._history)
72 return x
in self.
_list
75 return len(self.
_list)
77 def __setitem__(self, i, x, at=None, label="setitem", setHistory=True):
78 if self._config._frozen:
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)
89 x = _autocast(x, self._field.itemtype)
96 self.history.append((
list(self.
_list), at, label))
101 def __delitem__(self, i, at=None, label="delitem", setHistory=True):
102 if self._config._frozen:
104 "Cannot modify a frozen Config")
109 self.history.append((
list(self.
_list), at, label))
112 return iter(self.
_list)
114 def insert(self, i, x, at=None, label="insert", setHistory=True):
117 self.
__setitem__(slice(i, i), [x], at=at, label=label, setHistory=setHistory)
120 return repr(self.
_list)
123 return str(self.
_list)
127 if len(self) != len(other):
130 for i, j
in zip(self, other):
134 except AttributeError:
139 return not self.
__eq__(other)
142 if hasattr(getattr(self.__class__, attr,
None),
'__set__'):
144 object.__setattr__(self, attr, value)
145 elif attr
in self.__dict__
or attr
in [
"_field",
"_config",
"_history",
"_list",
"__doc__"]:
147 object.__setattr__(self, attr, value)
150 msg =
"%s has no attribute %s" % (_typeStr(self.
_field), attr)
156 Defines a field which is a container of values of type dtype
158 If length is not None, then instances of this field must match this length
160 If minLength is not None, then instances of the field must be no shorter
162 If maxLength is not None, then instances of the field must be no longer
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
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:
176 raise ValueError(
"'length' (%d) must be positive" % length)
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))
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")
193 self._setup(doc=doc, dtype=List, default=default, check=
None, optional=optional, source=source)
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.
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)
216 msg =
"Minimum allowed list length=%d, got length=%d" % (self.
minLength, lenValue)
217 raise FieldValidationError(self, instance, msg)
219 msg =
"Maximum allowed list length=%d, got length=%d" % (self.
maxLength, lenValue)
220 raise FieldValidationError(self, instance, msg)
222 msg =
"%s is not a valid value" % str(value)
223 raise FieldValidationError(self, instance, msg)
225 def __set__(self, instance, value, at=None, label="assignment"):
227 raise FieldValidationError(self, instance,
"Cannot modify a frozen Config")
232 if value
is not None:
233 value =
List(instance, self, value, at, label)
235 history = instance._history.setdefault(self.name, [])
236 history.append((value, at, label))
238 instance._storage[self.name] = value
241 value = self.__get__(instance)
242 return list(value)
if value
is not None else None
244 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
245 """Helper function for Config.compare; used to compare two fields for equality.
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.
255 Floating point comparisons are performed by numpy.allclose; refer to that for details.
257 l1 = getattr(instance1, self.name)
258 l2 = getattr(instance2, self.name)
260 _joinNamePath(instance1._name, self.name),
261 _joinNamePath(instance2._name, self.name)
263 if not compareScalars(
"isnone for %s" % name, l1
is None, l2
is None, output=output):
265 if l1
is None and l2
is None:
267 if not compareScalars(
"size for %s" % name, len(l1), len(l2), output=output):
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:
275 equal = equal
and result