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
30 _PAREN_RE = re.compile("^[a-zA-Z0-9_-]+$")
34 """Enclose text in parens if necessary.
41 if _PAREN_RE.match(text):
48 """Builds wrapper class with description text.
51 @param text: Description text
52 @return: Callable class
55 assert text[0] == text[0].upper()
57 class wrapper(object): # pylint: disable-msg=C0103
58 __slots__ = ["__call__"]
60 def __init__(self, fn):
61 """Initializes this class.
63 @param fn: Wrapped function
74 def CombinationDesc(op, args, fn):
75 """Build description for combinating operator.
78 @param op: Operator as text (e.g. "and")
80 @param args: Operator arguments
82 @param fn: Wrapped function
88 descr = (" %s " % op).join(Parens(i) for i in args)
90 return WithDesc(descr)(fn)
93 # Modifiable default values; need to define these here before the
98 """Returns an empty list.
106 """Returns an empty dict.
112 #: The without-default default value
116 #: The no-type (value too complex to check it in the type system)
123 """Checks if the given value is not None.
126 return val is not None
131 """Checks if the given value is None.
139 """Checks if the given value is a boolean.
142 return isinstance(val, bool)
147 """Checks if the given value is an integer.
150 # For backwards compatibility with older Python versions, boolean values are
151 # also integers and should be excluded in this test.
153 # >>> (isinstance(False, int), isinstance(True, int))
155 return isinstance(val, int) and not isinstance(val, bool)
160 """Checks if the given value is a float.
163 return isinstance(val, float)
168 """Checks if the given value is a string.
171 return isinstance(val, basestring)
174 @WithDesc("EvalToTrue")
176 """Checks if a given value evaluates to a boolean True value.
182 def TElemOf(target_list):
183 """Builds a function that checks if a given value is a member of a list.
187 return val in target_list
189 return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
195 """Checks if the given value is a list.
198 return isinstance(val, list)
201 @WithDesc("Dictionary")
203 """Checks if the given value is a dictionary.
206 return isinstance(val, dict)
210 """Check is the given container is of the given size.
214 return len(container) == size
216 return WithDesc("Length %s" % (size, ))(fn)
221 """Combine multiple functions using an AND operation.
225 return compat.all(t(val) for t in args)
227 return CombinationDesc("and", args, fn)
231 """Combine multiple functions using an AND operation.
235 return compat.any(t(val) for t in args)
237 return CombinationDesc("or", args, fn)
241 """Checks that a modified version of the argument passes the given test.
244 return WithDesc("Result of %s must be %s" %
245 (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
250 #: a non-empty string
251 TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
253 #: a maybe non-empty string
254 TMaybeString = TOr(TNonEmptyString, TNone)
256 #: a maybe boolean (bool or none)
257 TMaybeBool = TOr(TBool, TNone)
259 #: Maybe a dictionary (dict or None)
260 TMaybeDict = TOr(TDict, TNone)
262 #: a positive integer
264 TAnd(TInt, WithDesc("EqualGreaterZero")(lambda v: v >= 0))
266 #: a strictly positive integer
267 TStrictPositiveInt = \
268 TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
272 TAnd(TFloat, WithDesc("EqualGreaterZero")(lambda v: v >= 0.0))
275 def TListOf(my_type):
276 """Checks if a given value is a list with all elements of the same type.
279 desc = WithDesc("List of %s" % (Parens(my_type), ))
280 return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
283 def TDictOf(key_type, val_type):
284 """Checks a dict type for the type of its key/values.
287 desc = WithDesc("Dictionary with keys of %s and values of %s" %
288 (Parens(key_type), Parens(val_type)))
291 return (compat.all(key_type(v) for v in container.keys()) and
292 compat.all(val_type(v) for v in container.values()))
294 return desc(TAnd(TDict, fn))
297 def _TStrictDictCheck(require_all, exclusive, items, val):
298 """Helper function for L{TStrictDict}.
301 notfound_fn = lambda _: not exclusive
303 if require_all and not frozenset(val.keys()).issuperset(items.keys()):
304 # Requires items not found in value
307 return compat.all(items.get(key, notfound_fn)(value)
308 for (key, value) in val.items())
311 def TStrictDict(require_all, exclusive, items):
312 """Strict dictionary check with specific keys.
314 @type require_all: boolean
315 @param require_all: Whether all keys in L{items} are required
316 @type exclusive: boolean
317 @param exclusive: Whether only keys listed in L{items} should be accepted
318 @type items: dictionary
319 @param items: Mapping from key (string) to verification function
322 descparts = ["Dictionary containing"]
325 descparts.append(" none but the")
328 descparts.append(" required")
331 descparts.append(" key ")
333 descparts.append(" keys ")
335 descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
336 for (key, value) in items.items()))
338 desc = WithDesc("".join(descparts))
340 return desc(TAnd(TDict,
341 compat.partial(_TStrictDictCheck, require_all, exclusive,