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."""
26 from ganeti import compat
27 from ganeti import utils
28 from ganeti import constants
31 _PAREN_RE = re.compile("^[a-zA-Z0-9_-]+$")
35 """Enclose text in parens if necessary.
42 if _PAREN_RE.match(text):
49 """Builds wrapper class with description text.
52 @param text: Description text
53 @return: Callable class
56 assert text[0] == text[0].upper()
58 class wrapper(object): # pylint: disable-msg=C0103
59 __slots__ = ["__call__"]
61 def __init__(self, fn):
62 """Initializes this class.
64 @param fn: Wrapped function
75 def CombinationDesc(op, args, fn):
76 """Build description for combinating operator.
79 @param op: Operator as text (e.g. "and")
81 @param args: Operator arguments
83 @param fn: Wrapped function
89 descr = (" %s " % op).join(Parens(i) for i in args)
91 return WithDesc(descr)(fn)
94 # Modifiable default values; need to define these here before the
99 """Returns an empty list.
107 """Returns an empty dict.
113 #: The without-default default value
117 #: The no-type (value too complex to check it in the type system)
122 @WithDesc("Anything")
124 """Accepts any value.
132 """Checks if the given value is not None.
135 return val is not None
140 """Checks if the given value is None.
148 """Checks if the given value is a boolean.
151 return isinstance(val, bool)
156 """Checks if the given value is an integer.
159 # For backwards compatibility with older Python versions, boolean values are
160 # also integers and should be excluded in this test.
162 # >>> (isinstance(False, int), isinstance(True, int))
164 return isinstance(val, (int, long)) and not isinstance(val, bool)
169 """Checks if the given value is a float.
172 return isinstance(val, float)
177 """Checks if the given value is a string.
180 return isinstance(val, basestring)
183 @WithDesc("EvalToTrue")
185 """Checks if a given value evaluates to a boolean True value.
191 def TElemOf(target_list):
192 """Builds a function that checks if a given value is a member of a list.
196 return val in target_list
198 return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
204 """Checks if the given value is a list.
207 return isinstance(val, list)
210 @WithDesc("Dictionary")
212 """Checks if the given value is a dictionary.
215 return isinstance(val, dict)
219 """Check is the given container is of the given size.
223 return len(container) == size
225 return WithDesc("Length %s" % (size, ))(fn)
230 """Combine multiple functions using an AND operation.
234 return compat.all(t(val) for t in args)
236 return CombinationDesc("and", args, fn)
240 """Combine multiple functions using an AND operation.
244 return compat.any(t(val) for t in args)
246 return CombinationDesc("or", args, fn)
250 """Checks that a modified version of the argument passes the given test.
253 return WithDesc("Result of %s must be %s" %
254 (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
258 """Checks whether a string matches a specific regular expression.
260 @param pobj: Compiled regular expression as returned by C{re.compile}
263 desc = WithDesc("String matching regex \"%s\"" %
264 pobj.pattern.encode("string_escape"))
266 return desc(TAnd(TString, pobj.match))
271 #: a non-empty string
272 TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
274 #: a maybe non-empty string
275 TMaybeString = TOr(TNonEmptyString, TNone)
277 #: a maybe boolean (bool or none)
278 TMaybeBool = TOr(TBool, TNone)
280 #: Maybe a dictionary (dict or None)
281 TMaybeDict = TOr(TDict, TNone)
283 #: a positive integer
285 TAnd(TInt, WithDesc("EqualGreaterZero")(lambda v: v >= 0))
287 #: a strictly positive integer
288 TStrictPositiveInt = \
289 TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
293 TAnd(TFloat, WithDesc("EqualGreaterZero")(lambda v: v >= 0.0))
296 TJobId = TOr(TPositiveInt,
297 TRegex(re.compile("^%s$" % constants.JOB_ID_TEMPLATE)))
300 def TListOf(my_type):
301 """Checks if a given value is a list with all elements of the same type.
304 desc = WithDesc("List of %s" % (Parens(my_type), ))
305 return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
308 def TDictOf(key_type, val_type):
309 """Checks a dict type for the type of its key/values.
312 desc = WithDesc("Dictionary with keys of %s and values of %s" %
313 (Parens(key_type), Parens(val_type)))
316 return (compat.all(key_type(v) for v in container.keys()) and
317 compat.all(val_type(v) for v in container.values()))
319 return desc(TAnd(TDict, fn))
322 def _TStrictDictCheck(require_all, exclusive, items, val):
323 """Helper function for L{TStrictDict}.
326 notfound_fn = lambda _: not exclusive
328 if require_all and not frozenset(val.keys()).issuperset(items.keys()):
329 # Requires items not found in value
332 return compat.all(items.get(key, notfound_fn)(value)
333 for (key, value) in val.items())
336 def TStrictDict(require_all, exclusive, items):
337 """Strict dictionary check with specific keys.
339 @type require_all: boolean
340 @param require_all: Whether all keys in L{items} are required
341 @type exclusive: boolean
342 @param exclusive: Whether only keys listed in L{items} should be accepted
343 @type items: dictionary
344 @param items: Mapping from key (string) to verification function
347 descparts = ["Dictionary containing"]
350 descparts.append(" none but the")
353 descparts.append(" required")
356 descparts.append(" key ")
358 descparts.append(" keys ")
360 descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
361 for (key, value) in items.items()))
363 desc = WithDesc("".join(descparts))
365 return desc(TAnd(TDict,
366 compat.partial(_TStrictDictCheck, require_all, exclusive,
371 """Checks individual items of a container.
373 If the verified value and the list of expected items differ in length, this
374 check considers only as many items as are contained in the shorter list. Use
375 L{TIsLength} to enforce a certain length.
378 @param items: List of checks
381 assert items, "Need items"
383 text = ["Item", "item"]
384 desc = WithDesc(utils.CommaJoin("%s %s is %s" %
385 (text[int(idx > 0)], idx, Parens(check))
386 for (idx, check) in enumerate(items)))
388 return desc(lambda value: compat.all(check(i)
389 for (check, i) in zip(items, value)))