Statistics
| Branch: | Tag: | Revision:

root / lib / ht.py @ 7fc548e9

History | View | Annotate | Download (8.6 kB)

1 62e0e880 Iustin Pop
#
2 62e0e880 Iustin Pop
#
3 62e0e880 Iustin Pop
4 8c9ee749 Michael Hanselmann
# Copyright (C) 2010, 2011 Google Inc.
5 62e0e880 Iustin Pop
#
6 62e0e880 Iustin Pop
# This program is free software; you can redistribute it and/or modify
7 62e0e880 Iustin Pop
# it under the terms of the GNU General Public License as published by
8 62e0e880 Iustin Pop
# the Free Software Foundation; either version 2 of the License, or
9 62e0e880 Iustin Pop
# (at your option) any later version.
10 62e0e880 Iustin Pop
#
11 62e0e880 Iustin Pop
# This program is distributed in the hope that it will be useful, but
12 62e0e880 Iustin Pop
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 62e0e880 Iustin Pop
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 62e0e880 Iustin Pop
# General Public License for more details.
15 62e0e880 Iustin Pop
#
16 62e0e880 Iustin Pop
# You should have received a copy of the GNU General Public License
17 62e0e880 Iustin Pop
# along with this program; if not, write to the Free Software
18 62e0e880 Iustin Pop
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 62e0e880 Iustin Pop
# 02110-1301, USA.
20 62e0e880 Iustin Pop
21 62e0e880 Iustin Pop
22 62e0e880 Iustin Pop
"""Module implementing the parameter types code."""
23 62e0e880 Iustin Pop
24 8c9ee749 Michael Hanselmann
import re
25 8c9ee749 Michael Hanselmann
26 62e0e880 Iustin Pop
from ganeti import compat
27 8c9ee749 Michael Hanselmann
from ganeti import utils
28 8620f50e Michael Hanselmann
from ganeti import constants
29 8c9ee749 Michael Hanselmann
30 8c9ee749 Michael Hanselmann
31 8c9ee749 Michael Hanselmann
_PAREN_RE = re.compile("^[a-zA-Z0-9_-]+$")
32 8c9ee749 Michael Hanselmann
33 8c9ee749 Michael Hanselmann
34 8c9ee749 Michael Hanselmann
def Parens(text):
35 8c9ee749 Michael Hanselmann
  """Enclose text in parens if necessary.
36 8c9ee749 Michael Hanselmann

37 8c9ee749 Michael Hanselmann
  @param text: Text
38 8c9ee749 Michael Hanselmann

39 8c9ee749 Michael Hanselmann
  """
40 8c9ee749 Michael Hanselmann
  text = str(text)
41 8c9ee749 Michael Hanselmann
42 8c9ee749 Michael Hanselmann
  if _PAREN_RE.match(text):
43 8c9ee749 Michael Hanselmann
    return text
44 8c9ee749 Michael Hanselmann
  else:
45 8c9ee749 Michael Hanselmann
    return "(%s)" % text
46 8c9ee749 Michael Hanselmann
47 8c9ee749 Michael Hanselmann
48 7fc548e9 Michael Hanselmann
class _DescWrapper(object):
49 7fc548e9 Michael Hanselmann
  __slots__ = [
50 7fc548e9 Michael Hanselmann
    "_fn",
51 7fc548e9 Michael Hanselmann
    "_text",
52 7fc548e9 Michael Hanselmann
    ]
53 7fc548e9 Michael Hanselmann
54 7fc548e9 Michael Hanselmann
  def __init__(self, text, fn):
55 7fc548e9 Michael Hanselmann
    """Initializes this class.
56 7fc548e9 Michael Hanselmann

57 7fc548e9 Michael Hanselmann
    @param text: Description
58 7fc548e9 Michael Hanselmann
    @param fn: Wrapped function
59 7fc548e9 Michael Hanselmann

60 7fc548e9 Michael Hanselmann
    """
61 7fc548e9 Michael Hanselmann
    self._text = text
62 7fc548e9 Michael Hanselmann
    self._fn = fn
63 7fc548e9 Michael Hanselmann
64 7fc548e9 Michael Hanselmann
  def __call__(self, *args):
65 7fc548e9 Michael Hanselmann
    return self._fn(*args)
66 7fc548e9 Michael Hanselmann
67 7fc548e9 Michael Hanselmann
  def __str__(self):
68 7fc548e9 Michael Hanselmann
    return self._text
69 7fc548e9 Michael Hanselmann
70 7fc548e9 Michael Hanselmann
71 8c9ee749 Michael Hanselmann
def WithDesc(text):
72 8c9ee749 Michael Hanselmann
  """Builds wrapper class with description text.
73 8c9ee749 Michael Hanselmann

74 8c9ee749 Michael Hanselmann
  @type text: string
75 8c9ee749 Michael Hanselmann
  @param text: Description text
76 8c9ee749 Michael Hanselmann
  @return: Callable class
77 8c9ee749 Michael Hanselmann

78 8c9ee749 Michael Hanselmann
  """
79 8c9ee749 Michael Hanselmann
  assert text[0] == text[0].upper()
80 8c9ee749 Michael Hanselmann
81 7fc548e9 Michael Hanselmann
  return compat.partial(_DescWrapper, text)
82 8c9ee749 Michael Hanselmann
83 8c9ee749 Michael Hanselmann
84 8c9ee749 Michael Hanselmann
def CombinationDesc(op, args, fn):
85 8c9ee749 Michael Hanselmann
  """Build description for combinating operator.
86 8c9ee749 Michael Hanselmann

87 8c9ee749 Michael Hanselmann
  @type op: string
88 8c9ee749 Michael Hanselmann
  @param op: Operator as text (e.g. "and")
89 8c9ee749 Michael Hanselmann
  @type args: list
90 8c9ee749 Michael Hanselmann
  @param args: Operator arguments
91 8c9ee749 Michael Hanselmann
  @type fn: callable
92 8c9ee749 Michael Hanselmann
  @param fn: Wrapped function
93 8c9ee749 Michael Hanselmann

94 8c9ee749 Michael Hanselmann
  """
95 8c9ee749 Michael Hanselmann
  if len(args) == 1:
96 8c9ee749 Michael Hanselmann
    descr = str(args[0])
97 8c9ee749 Michael Hanselmann
  else:
98 8c9ee749 Michael Hanselmann
    descr = (" %s " % op).join(Parens(i) for i in args)
99 8c9ee749 Michael Hanselmann
100 8c9ee749 Michael Hanselmann
  return WithDesc(descr)(fn)
101 8c9ee749 Michael Hanselmann
102 62e0e880 Iustin Pop
103 62e0e880 Iustin Pop
# Modifiable default values; need to define these here before the
104 62e0e880 Iustin Pop
# actual LUs
105 62e0e880 Iustin Pop
106 8c9ee749 Michael Hanselmann
@WithDesc(str([]))
107 62e0e880 Iustin Pop
def EmptyList():
108 62e0e880 Iustin Pop
  """Returns an empty list.
109 62e0e880 Iustin Pop

110 62e0e880 Iustin Pop
  """
111 62e0e880 Iustin Pop
  return []
112 62e0e880 Iustin Pop
113 62e0e880 Iustin Pop
114 8c9ee749 Michael Hanselmann
@WithDesc(str({}))
115 62e0e880 Iustin Pop
def EmptyDict():
116 62e0e880 Iustin Pop
  """Returns an empty dict.
117 62e0e880 Iustin Pop

118 62e0e880 Iustin Pop
  """
119 62e0e880 Iustin Pop
  return {}
120 62e0e880 Iustin Pop
121 62e0e880 Iustin Pop
122 62e0e880 Iustin Pop
#: The without-default default value
123 62e0e880 Iustin Pop
NoDefault = object()
124 62e0e880 Iustin Pop
125 62e0e880 Iustin Pop
126 8c9ee749 Michael Hanselmann
#: The no-type (value too complex to check it in the type system)
127 62e0e880 Iustin Pop
NoType = object()
128 62e0e880 Iustin Pop
129 62e0e880 Iustin Pop
130 62e0e880 Iustin Pop
# Some basic types
131 8620f50e Michael Hanselmann
@WithDesc("Anything")
132 8620f50e Michael Hanselmann
def TAny(_):
133 8620f50e Michael Hanselmann
  """Accepts any value.
134 8620f50e Michael Hanselmann

135 8620f50e Michael Hanselmann
  """
136 8620f50e Michael Hanselmann
  return True
137 8620f50e Michael Hanselmann
138 8620f50e Michael Hanselmann
139 8c9ee749 Michael Hanselmann
@WithDesc("NotNone")
140 62e0e880 Iustin Pop
def TNotNone(val):
141 62e0e880 Iustin Pop
  """Checks if the given value is not None.
142 62e0e880 Iustin Pop

143 62e0e880 Iustin Pop
  """
144 62e0e880 Iustin Pop
  return val is not None
145 62e0e880 Iustin Pop
146 62e0e880 Iustin Pop
147 8c9ee749 Michael Hanselmann
@WithDesc("None")
148 62e0e880 Iustin Pop
def TNone(val):
149 62e0e880 Iustin Pop
  """Checks if the given value is None.
150 62e0e880 Iustin Pop

151 62e0e880 Iustin Pop
  """
152 62e0e880 Iustin Pop
  return val is None
153 62e0e880 Iustin Pop
154 62e0e880 Iustin Pop
155 8c9ee749 Michael Hanselmann
@WithDesc("Boolean")
156 62e0e880 Iustin Pop
def TBool(val):
157 62e0e880 Iustin Pop
  """Checks if the given value is a boolean.
158 62e0e880 Iustin Pop

159 62e0e880 Iustin Pop
  """
160 62e0e880 Iustin Pop
  return isinstance(val, bool)
161 62e0e880 Iustin Pop
162 62e0e880 Iustin Pop
163 8c9ee749 Michael Hanselmann
@WithDesc("Integer")
164 62e0e880 Iustin Pop
def TInt(val):
165 62e0e880 Iustin Pop
  """Checks if the given value is an integer.
166 62e0e880 Iustin Pop

167 62e0e880 Iustin Pop
  """
168 8568de9e Michael Hanselmann
  # For backwards compatibility with older Python versions, boolean values are
169 8568de9e Michael Hanselmann
  # also integers and should be excluded in this test.
170 8568de9e Michael Hanselmann
  #
171 8568de9e Michael Hanselmann
  # >>> (isinstance(False, int), isinstance(True, int))
172 8568de9e Michael Hanselmann
  # (True, True)
173 b99b607f Michael Hanselmann
  return isinstance(val, (int, long)) and not isinstance(val, bool)
174 62e0e880 Iustin Pop
175 62e0e880 Iustin Pop
176 8c9ee749 Michael Hanselmann
@WithDesc("Float")
177 62e0e880 Iustin Pop
def TFloat(val):
178 62e0e880 Iustin Pop
  """Checks if the given value is a float.
179 62e0e880 Iustin Pop

180 62e0e880 Iustin Pop
  """
181 62e0e880 Iustin Pop
  return isinstance(val, float)
182 62e0e880 Iustin Pop
183 62e0e880 Iustin Pop
184 8c9ee749 Michael Hanselmann
@WithDesc("String")
185 62e0e880 Iustin Pop
def TString(val):
186 62e0e880 Iustin Pop
  """Checks if the given value is a string.
187 62e0e880 Iustin Pop

188 62e0e880 Iustin Pop
  """
189 62e0e880 Iustin Pop
  return isinstance(val, basestring)
190 62e0e880 Iustin Pop
191 62e0e880 Iustin Pop
192 8c9ee749 Michael Hanselmann
@WithDesc("EvalToTrue")
193 62e0e880 Iustin Pop
def TTrue(val):
194 62e0e880 Iustin Pop
  """Checks if a given value evaluates to a boolean True value.
195 62e0e880 Iustin Pop

196 62e0e880 Iustin Pop
  """
197 62e0e880 Iustin Pop
  return bool(val)
198 62e0e880 Iustin Pop
199 62e0e880 Iustin Pop
200 62e0e880 Iustin Pop
def TElemOf(target_list):
201 62e0e880 Iustin Pop
  """Builds a function that checks if a given value is a member of a list.
202 62e0e880 Iustin Pop

203 62e0e880 Iustin Pop
  """
204 8c9ee749 Michael Hanselmann
  def fn(val):
205 8c9ee749 Michael Hanselmann
    return val in target_list
206 8c9ee749 Michael Hanselmann
207 8c9ee749 Michael Hanselmann
  return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
208 62e0e880 Iustin Pop
209 62e0e880 Iustin Pop
210 62e0e880 Iustin Pop
# Container types
211 8c9ee749 Michael Hanselmann
@WithDesc("List")
212 62e0e880 Iustin Pop
def TList(val):
213 62e0e880 Iustin Pop
  """Checks if the given value is a list.
214 62e0e880 Iustin Pop

215 62e0e880 Iustin Pop
  """
216 62e0e880 Iustin Pop
  return isinstance(val, list)
217 62e0e880 Iustin Pop
218 62e0e880 Iustin Pop
219 8c9ee749 Michael Hanselmann
@WithDesc("Dictionary")
220 62e0e880 Iustin Pop
def TDict(val):
221 62e0e880 Iustin Pop
  """Checks if the given value is a dictionary.
222 62e0e880 Iustin Pop

223 62e0e880 Iustin Pop
  """
224 62e0e880 Iustin Pop
  return isinstance(val, dict)
225 62e0e880 Iustin Pop
226 62e0e880 Iustin Pop
227 62e0e880 Iustin Pop
def TIsLength(size):
228 62e0e880 Iustin Pop
  """Check is the given container is of the given size.
229 62e0e880 Iustin Pop

230 62e0e880 Iustin Pop
  """
231 8c9ee749 Michael Hanselmann
  def fn(container):
232 8c9ee749 Michael Hanselmann
    return len(container) == size
233 8c9ee749 Michael Hanselmann
234 8c9ee749 Michael Hanselmann
  return WithDesc("Length %s" % (size, ))(fn)
235 62e0e880 Iustin Pop
236 62e0e880 Iustin Pop
237 62e0e880 Iustin Pop
# Combinator types
238 62e0e880 Iustin Pop
def TAnd(*args):
239 62e0e880 Iustin Pop
  """Combine multiple functions using an AND operation.
240 62e0e880 Iustin Pop

241 62e0e880 Iustin Pop
  """
242 62e0e880 Iustin Pop
  def fn(val):
243 62e0e880 Iustin Pop
    return compat.all(t(val) for t in args)
244 8c9ee749 Michael Hanselmann
245 8c9ee749 Michael Hanselmann
  return CombinationDesc("and", args, fn)
246 62e0e880 Iustin Pop
247 62e0e880 Iustin Pop
248 62e0e880 Iustin Pop
def TOr(*args):
249 62e0e880 Iustin Pop
  """Combine multiple functions using an AND operation.
250 62e0e880 Iustin Pop

251 62e0e880 Iustin Pop
  """
252 62e0e880 Iustin Pop
  def fn(val):
253 62e0e880 Iustin Pop
    return compat.any(t(val) for t in args)
254 8c9ee749 Michael Hanselmann
255 8c9ee749 Michael Hanselmann
  return CombinationDesc("or", args, fn)
256 62e0e880 Iustin Pop
257 62e0e880 Iustin Pop
258 62e0e880 Iustin Pop
def TMap(fn, test):
259 62e0e880 Iustin Pop
  """Checks that a modified version of the argument passes the given test.
260 62e0e880 Iustin Pop

261 62e0e880 Iustin Pop
  """
262 8c9ee749 Michael Hanselmann
  return WithDesc("Result of %s must be %s" %
263 8c9ee749 Michael Hanselmann
                  (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
264 62e0e880 Iustin Pop
265 62e0e880 Iustin Pop
266 8620f50e Michael Hanselmann
def TRegex(pobj):
267 8620f50e Michael Hanselmann
  """Checks whether a string matches a specific regular expression.
268 8620f50e Michael Hanselmann

269 8620f50e Michael Hanselmann
  @param pobj: Compiled regular expression as returned by C{re.compile}
270 8620f50e Michael Hanselmann

271 8620f50e Michael Hanselmann
  """
272 8620f50e Michael Hanselmann
  desc = WithDesc("String matching regex \"%s\"" %
273 8620f50e Michael Hanselmann
                  pobj.pattern.encode("string_escape"))
274 8620f50e Michael Hanselmann
275 8620f50e Michael Hanselmann
  return desc(TAnd(TString, pobj.match))
276 8620f50e Michael Hanselmann
277 8620f50e Michael Hanselmann
278 62e0e880 Iustin Pop
# Type aliases
279 62e0e880 Iustin Pop
280 62e0e880 Iustin Pop
#: a non-empty string
281 8c9ee749 Michael Hanselmann
TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
282 62e0e880 Iustin Pop
283 62e0e880 Iustin Pop
#: a maybe non-empty string
284 62e0e880 Iustin Pop
TMaybeString = TOr(TNonEmptyString, TNone)
285 62e0e880 Iustin Pop
286 62e0e880 Iustin Pop
#: a maybe boolean (bool or none)
287 62e0e880 Iustin Pop
TMaybeBool = TOr(TBool, TNone)
288 62e0e880 Iustin Pop
289 5f074973 Michael Hanselmann
#: Maybe a dictionary (dict or None)
290 5f074973 Michael Hanselmann
TMaybeDict = TOr(TDict, TNone)
291 62e0e880 Iustin Pop
292 62e0e880 Iustin Pop
#: a positive integer
293 8c9ee749 Michael Hanselmann
TPositiveInt = \
294 8c9ee749 Michael Hanselmann
  TAnd(TInt, WithDesc("EqualGreaterZero")(lambda v: v >= 0))
295 62e0e880 Iustin Pop
296 62e0e880 Iustin Pop
#: a strictly positive integer
297 8c9ee749 Michael Hanselmann
TStrictPositiveInt = \
298 8c9ee749 Michael Hanselmann
  TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
299 62e0e880 Iustin Pop
300 beff3779 René Nussbaumer
#: a positive float
301 beff3779 René Nussbaumer
TPositiveFloat = \
302 beff3779 René Nussbaumer
  TAnd(TFloat, WithDesc("EqualGreaterZero")(lambda v: v >= 0.0))
303 beff3779 René Nussbaumer
304 8620f50e Michael Hanselmann
#: Job ID
305 8620f50e Michael Hanselmann
TJobId = TOr(TPositiveInt,
306 8620f50e Michael Hanselmann
             TRegex(re.compile("^%s$" % constants.JOB_ID_TEMPLATE)))
307 8620f50e Michael Hanselmann
308 697f49d5 Michael Hanselmann
#: Number
309 697f49d5 Michael Hanselmann
TNumber = TOr(TInt, TFloat)
310 697f49d5 Michael Hanselmann
311 62e0e880 Iustin Pop
312 62e0e880 Iustin Pop
def TListOf(my_type):
313 62e0e880 Iustin Pop
  """Checks if a given value is a list with all elements of the same type.
314 62e0e880 Iustin Pop

315 62e0e880 Iustin Pop
  """
316 8c9ee749 Michael Hanselmann
  desc = WithDesc("List of %s" % (Parens(my_type), ))
317 8c9ee749 Michael Hanselmann
  return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
318 62e0e880 Iustin Pop
319 62e0e880 Iustin Pop
320 62e0e880 Iustin Pop
def TDictOf(key_type, val_type):
321 62e0e880 Iustin Pop
  """Checks a dict type for the type of its key/values.
322 62e0e880 Iustin Pop

323 62e0e880 Iustin Pop
  """
324 8c9ee749 Michael Hanselmann
  desc = WithDesc("Dictionary with keys of %s and values of %s" %
325 8c9ee749 Michael Hanselmann
                  (Parens(key_type), Parens(val_type)))
326 8c9ee749 Michael Hanselmann
327 8c9ee749 Michael Hanselmann
  def fn(container):
328 8c9ee749 Michael Hanselmann
    return (compat.all(key_type(v) for v in container.keys()) and
329 8c9ee749 Michael Hanselmann
            compat.all(val_type(v) for v in container.values()))
330 8c9ee749 Michael Hanselmann
331 8c9ee749 Michael Hanselmann
  return desc(TAnd(TDict, fn))
332 a464ce71 Michael Hanselmann
333 a464ce71 Michael Hanselmann
334 a464ce71 Michael Hanselmann
def _TStrictDictCheck(require_all, exclusive, items, val):
335 a464ce71 Michael Hanselmann
  """Helper function for L{TStrictDict}.
336 a464ce71 Michael Hanselmann

337 a464ce71 Michael Hanselmann
  """
338 a464ce71 Michael Hanselmann
  notfound_fn = lambda _: not exclusive
339 a464ce71 Michael Hanselmann
340 a464ce71 Michael Hanselmann
  if require_all and not frozenset(val.keys()).issuperset(items.keys()):
341 a464ce71 Michael Hanselmann
    # Requires items not found in value
342 a464ce71 Michael Hanselmann
    return False
343 a464ce71 Michael Hanselmann
344 a464ce71 Michael Hanselmann
  return compat.all(items.get(key, notfound_fn)(value)
345 a464ce71 Michael Hanselmann
                    for (key, value) in val.items())
346 a464ce71 Michael Hanselmann
347 a464ce71 Michael Hanselmann
348 a464ce71 Michael Hanselmann
def TStrictDict(require_all, exclusive, items):
349 a464ce71 Michael Hanselmann
  """Strict dictionary check with specific keys.
350 a464ce71 Michael Hanselmann

351 a464ce71 Michael Hanselmann
  @type require_all: boolean
352 a464ce71 Michael Hanselmann
  @param require_all: Whether all keys in L{items} are required
353 a464ce71 Michael Hanselmann
  @type exclusive: boolean
354 a464ce71 Michael Hanselmann
  @param exclusive: Whether only keys listed in L{items} should be accepted
355 a464ce71 Michael Hanselmann
  @type items: dictionary
356 a464ce71 Michael Hanselmann
  @param items: Mapping from key (string) to verification function
357 a464ce71 Michael Hanselmann

358 a464ce71 Michael Hanselmann
  """
359 a464ce71 Michael Hanselmann
  descparts = ["Dictionary containing"]
360 a464ce71 Michael Hanselmann
361 a464ce71 Michael Hanselmann
  if exclusive:
362 a464ce71 Michael Hanselmann
    descparts.append(" none but the")
363 a464ce71 Michael Hanselmann
364 a464ce71 Michael Hanselmann
  if require_all:
365 a464ce71 Michael Hanselmann
    descparts.append(" required")
366 a464ce71 Michael Hanselmann
367 a464ce71 Michael Hanselmann
  if len(items) == 1:
368 a464ce71 Michael Hanselmann
    descparts.append(" key ")
369 a464ce71 Michael Hanselmann
  else:
370 a464ce71 Michael Hanselmann
    descparts.append(" keys ")
371 a464ce71 Michael Hanselmann
372 a464ce71 Michael Hanselmann
  descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
373 a464ce71 Michael Hanselmann
                                   for (key, value) in items.items()))
374 a464ce71 Michael Hanselmann
375 a464ce71 Michael Hanselmann
  desc = WithDesc("".join(descparts))
376 a464ce71 Michael Hanselmann
377 a464ce71 Michael Hanselmann
  return desc(TAnd(TDict,
378 a464ce71 Michael Hanselmann
                   compat.partial(_TStrictDictCheck, require_all, exclusive,
379 a464ce71 Michael Hanselmann
                                  items)))
380 8620f50e Michael Hanselmann
381 8620f50e Michael Hanselmann
382 8620f50e Michael Hanselmann
def TItems(items):
383 8620f50e Michael Hanselmann
  """Checks individual items of a container.
384 8620f50e Michael Hanselmann

385 8620f50e Michael Hanselmann
  If the verified value and the list of expected items differ in length, this
386 8620f50e Michael Hanselmann
  check considers only as many items as are contained in the shorter list. Use
387 8620f50e Michael Hanselmann
  L{TIsLength} to enforce a certain length.
388 8620f50e Michael Hanselmann

389 8620f50e Michael Hanselmann
  @type items: list
390 8620f50e Michael Hanselmann
  @param items: List of checks
391 8620f50e Michael Hanselmann

392 8620f50e Michael Hanselmann
  """
393 8620f50e Michael Hanselmann
  assert items, "Need items"
394 8620f50e Michael Hanselmann
395 8620f50e Michael Hanselmann
  text = ["Item", "item"]
396 8620f50e Michael Hanselmann
  desc = WithDesc(utils.CommaJoin("%s %s is %s" %
397 8620f50e Michael Hanselmann
                                  (text[int(idx > 0)], idx, Parens(check))
398 8620f50e Michael Hanselmann
                                  for (idx, check) in enumerate(items)))
399 8620f50e Michael Hanselmann
400 8620f50e Michael Hanselmann
  return desc(lambda value: compat.all(check(i)
401 8620f50e Michael Hanselmann
                                       for (check, i) in zip(items, value)))