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)
70 history = property(
lambda x: x._history)
73 return x
in self.
_list
76 return len(self.
_list)
78 def __setitem__(self, i, x, at=None, label="setitem", setHistory=True):
79 if self._config._frozen:
81 "Cannot modify a frozen Config")
82 if isinstance(i, slice):
83 k, stop, step = i.indices(len(self))
84 for j, xj
in enumerate(x):
85 xj = _autocast(xj, self._field.itemtype)
90 x = _autocast(x, self._field.itemtype)
97 self.history.append((
list(self.
_list), at, label))
102 def __delitem__(self, i, at=None, label="delitem", setHistory=True):
103 if self._config._frozen:
105 "Cannot modify a frozen Config")
110 self.history.append((
list(self.
_list), at, label))
113 return iter(self.
_list)
115 def insert(self, i, x, at=None, label="insert", setHistory=True):
118 self.
__setitem__(slice(i, i), [x], at=at, label=label, setHistory=setHistory)
121 return repr(self.
_list)
124 return str(self.
_list)
128 if len(self) != len(other):
131 for i, j
in zip(self, other):
135 except AttributeError:
140 return not self.
__eq__(other)
143 if hasattr(getattr(self.__class__, attr,
None),
'__set__'):
145 object.__setattr__(self, attr, value)
146 elif attr
in self.__dict__
or attr
in [
"_field",
"_config",
"_history",
"_list",
"__doc__"]:
148 object.__setattr__(self, attr, value)
151 msg =
"%s has no attribute %s" % (_typeStr(self.
_field), attr)
157 Defines a field which is a container of values of type dtype
159 If length is not None, then instances of this field must match this length
161 If minLength is not None, then instances of the field must be no shorter
163 If maxLength is not None, then instances of the field must be no longer
166 Additionally users can provide two check functions:
167 listCheck - used to validate the list as a whole, and
168 itemCheck - used to validate each item individually
170 def __init__(self, doc, dtype, default=None, optional=False,
171 listCheck=
None, itemCheck=
None,
172 length=
None, minLength=
None, maxLength=
None):
173 if dtype
not in Field.supportedTypes:
174 raise ValueError(
"Unsupported dtype %s" % _typeStr(dtype))
175 if length
is not None:
177 raise ValueError(
"'length' (%d) must be positive" % length)
181 if maxLength
is not None and maxLength <= 0:
182 raise ValueError(
"'maxLength' (%d) must be positive" % maxLength)
183 if minLength
is not None and maxLength
is not None \
184 and minLength > maxLength:
185 raise ValueError(
"'maxLength' (%d) must be at least"
186 " as large as 'minLength' (%d)" % (maxLength, minLength))
188 if listCheck
is not None and not hasattr(listCheck,
"__call__"):
189 raise ValueError(
"'listCheck' must be callable")
190 if itemCheck
is not None and not hasattr(itemCheck,
"__call__"):
191 raise ValueError(
"'itemCheck' must be callable")
194 self._setup(doc=doc, dtype=List, default=default, check=
None, optional=optional, source=source)
204 ListField validation ensures that non-optional fields are not None,
205 and that non-None values comply with length requirements and
206 that the list passes listCheck if supplied by the user.
207 Individual Item checks are applied at set time and are not re-checked.
209 Field.validate(self, instance)
210 value = self.__get__(instance)
211 if value
is not None:
212 lenValue = len(value)
213 if self.
length is not None and not lenValue == self.
length:
214 msg =
"Required list length=%d, got length=%d" % (self.
length, lenValue)
215 raise FieldValidationError(self, instance, msg)
217 msg =
"Minimum allowed list length=%d, got length=%d" % (self.
minLength, lenValue)
218 raise FieldValidationError(self, instance, msg)
220 msg =
"Maximum allowed list length=%d, got length=%d" % (self.
maxLength, lenValue)
221 raise FieldValidationError(self, instance, msg)
223 msg =
"%s is not a valid value" % str(value)
224 raise FieldValidationError(self, instance, msg)
226 def __set__(self, instance, value, at=None, label="assignment"):
228 raise FieldValidationError(self, instance,
"Cannot modify a frozen Config")
233 if value
is not None:
234 value =
List(instance, self, value, at, label)
236 history = instance._history.setdefault(self.name, [])
237 history.append((value, at, label))
239 instance._storage[self.name] = value
242 value = self.__get__(instance)
243 return list(value)
if value
is not None else None
245 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
246 """Helper function for Config.compare; used to compare two fields for equality.
248 @param[in] instance1 LHS Config instance to compare.
249 @param[in] instance2 RHS Config instance to compare.
250 @param[in] shortcut If True, return as soon as an inequality is found.
251 @param[in] rtol Relative tolerance for floating point comparisons.
252 @param[in] atol Absolute tolerance for floating point comparisons.
253 @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
254 to report inequalities.
256 Floating point comparisons are performed by numpy.allclose; refer to that for details.
258 l1 = getattr(instance1, self.name)
259 l2 = getattr(instance2, self.name)
261 _joinNamePath(instance1._name, self.name),
262 _joinNamePath(instance2._name, self.name)
264 if not compareScalars(
"isnone for %s" % name, l1
is None, l2
is None, output=output):
266 if l1
is None and l2
is None:
268 if not compareScalars(
"size for %s" % name, len(l1), len(l2), output=output):
271 for n, v1, v2
in zip(range(len(l1)), l1, l2):
272 result =
compareScalars(
"%s[%d]" % (name, n), v1, v2, dtype=self.dtype,
273 rtol=rtol, atol=atol, output=output)
274 if not result
and shortcut:
276 equal = equal
and result