4 # Copyright (C) 2010, 2011 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Module implementing the parameter types code."""
27 from ganeti import compat
28 from ganeti import utils
29 from ganeti import constants
32 _PAREN_RE = re.compile("^[a-zA-Z0-9_-]+$")
36 """Enclose text in parens if necessary.
43 if _PAREN_RE.match(text):
49 class _WrapperBase(object):
55 def __init__(self, text, fn):
56 """Initializes this class.
58 @param text: Description
59 @param fn: Wrapped function
67 def __call__(self, *args):
68 return self._fn(*args)
71 class _DescWrapper(_WrapperBase):
72 """Wrapper class for description text.
79 class _CommentWrapper(_WrapperBase):
80 """Wrapper class for comment.
84 return "%s [%s]" % (self._fn, self._text)
88 """Builds wrapper class with description text.
91 @param text: Description text
92 @return: Callable class
95 assert text[0] == text[0].upper()
97 return compat.partial(_DescWrapper, text)
101 """Builds wrapper for adding comment to description text.
104 @param text: Comment text
105 @return: Callable class
108 assert not frozenset(text).intersection("[]")
110 return compat.partial(_CommentWrapper, text)
113 def CombinationDesc(op, args, fn):
114 """Build description for combinating operator.
117 @param op: Operator as text (e.g. "and")
119 @param args: Operator arguments
121 @param fn: Wrapped function
127 descr = (" %s " % op).join(Parens(i) for i in args)
129 return WithDesc(descr)(fn)
132 # Modifiable default values; need to define these here before the
137 """Returns an empty list.
145 """Returns an empty dict.
151 #: The without-default default value
155 #: The no-type (value too complex to check it in the type system)
160 @WithDesc("Anything")
162 """Accepts any value.
170 """Checks if the given value is not None.
173 return val is not None
178 """Checks if the given value is None.
186 """Checks if the given value is a boolean.
189 return isinstance(val, bool)
194 """Checks if the given value is an integer.
197 # For backwards compatibility with older Python versions, boolean values are
198 # also integers and should be excluded in this test.
200 # >>> (isinstance(False, int), isinstance(True, int))
202 return isinstance(val, (int, long)) and not isinstance(val, bool)
207 """Checks if the given value is a float.
210 return isinstance(val, float)
215 """Checks if the given value is a string.
218 return isinstance(val, basestring)
221 @WithDesc("EvalToTrue")
223 """Checks if a given value evaluates to a boolean True value.
229 def TElemOf(target_list):
230 """Builds a function that checks if a given value is a member of a list.
234 return val in target_list
236 return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
242 """Checks if the given value is a list.
245 return isinstance(val, list)
248 @WithDesc("Dictionary")
250 """Checks if the given value is a dictionary.
253 return isinstance(val, dict)
257 """Check is the given container is of the given size.
261 return len(container) == size
263 return WithDesc("Length %s" % (size, ))(fn)
268 """Combine multiple functions using an AND operation.
272 return compat.all(t(val) for t in args)
274 return CombinationDesc("and", args, fn)
278 """Combine multiple functions using an AND operation.
282 return compat.any(t(val) for t in args)
284 return CombinationDesc("or", args, fn)
288 """Checks that a modified version of the argument passes the given test.
291 return WithDesc("Result of %s must be %s" %
292 (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
296 """Checks whether a string matches a specific regular expression.
298 @param pobj: Compiled regular expression as returned by C{re.compile}
301 desc = WithDesc("String matching regex \"%s\"" %
302 pobj.pattern.encode("string_escape"))
304 return desc(TAnd(TString, pobj.match))
309 #: a non-empty string
310 TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
312 #: a maybe non-empty string
313 TMaybeString = TOr(TNonEmptyString, TNone)
315 #: a maybe boolean (bool or none)
316 TMaybeBool = TOr(TBool, TNone)
318 #: Maybe a dictionary (dict or None)
319 TMaybeDict = TOr(TDict, TNone)
321 #: a positive integer
323 TAnd(TInt, WithDesc("EqualGreaterZero")(lambda v: v >= 0))
325 #: a maybe positive integer (positive integer or None)
326 TMaybePositiveInt = TOr(TPositiveInt, TNone)
328 #: a strictly positive integer
329 TStrictPositiveInt = \
330 TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
332 #: a maybe strictly positive integer (strictly positive integer or None)
333 TMaybeStrictPositiveInt = TOr(TStrictPositiveInt, TNone)
335 #: a strictly negative integer (0 > value)
336 TStrictNegativeInt = \
337 TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0)))
341 TAnd(TFloat, WithDesc("EqualGreaterZero")(lambda v: v >= 0.0))
344 TJobId = WithDesc("JobId")(TOr(TPositiveInt,
345 TRegex(re.compile("^%s$" %
346 constants.JOB_ID_TEMPLATE))))
349 TNumber = TOr(TInt, TFloat)
352 TRelativeJobId = WithDesc("RelativeJobId")(TStrictNegativeInt)
355 def TListOf(my_type):
356 """Checks if a given value is a list with all elements of the same type.
359 desc = WithDesc("List of %s" % (Parens(my_type), ))
360 return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
363 TMaybeListOf = lambda item_type: TOr(TNone, TListOf(item_type))
366 def TDictOf(key_type, val_type):
367 """Checks a dict type for the type of its key/values.
370 desc = WithDesc("Dictionary with keys of %s and values of %s" %
371 (Parens(key_type), Parens(val_type)))
374 return (compat.all(key_type(v) for v in container.keys()) and
375 compat.all(val_type(v) for v in container.values()))
377 return desc(TAnd(TDict, fn))
380 def _TStrictDictCheck(require_all, exclusive, items, val):
381 """Helper function for L{TStrictDict}.
384 notfound_fn = lambda _: not exclusive
386 if require_all and not frozenset(val.keys()).issuperset(items.keys()):
387 # Requires items not found in value
390 return compat.all(items.get(key, notfound_fn)(value)
391 for (key, value) in val.items())
394 def TStrictDict(require_all, exclusive, items):
395 """Strict dictionary check with specific keys.
397 @type require_all: boolean
398 @param require_all: Whether all keys in L{items} are required
399 @type exclusive: boolean
400 @param exclusive: Whether only keys listed in L{items} should be accepted
401 @type items: dictionary
402 @param items: Mapping from key (string) to verification function
405 descparts = ["Dictionary containing"]
408 descparts.append(" none but the")
411 descparts.append(" required")
414 descparts.append(" key ")
416 descparts.append(" keys ")
418 descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
419 for (key, value) in items.items()))
421 desc = WithDesc("".join(descparts))
423 return desc(TAnd(TDict,
424 compat.partial(_TStrictDictCheck, require_all, exclusive,
429 """Checks individual items of a container.
431 If the verified value and the list of expected items differ in length, this
432 check considers only as many items as are contained in the shorter list. Use
433 L{TIsLength} to enforce a certain length.
436 @param items: List of checks
439 assert items, "Need items"
441 text = ["Item", "item"]
442 desc = WithDesc(utils.CommaJoin("%s %s is %s" %
443 (text[int(idx > 0)], idx, Parens(check))
444 for (idx, check) in enumerate(items)))
446 return desc(lambda value: compat.all(check(i)
447 for (check, i) in zip(items, value)))