Statistics
| Branch: | Tag: | Revision:

root / lib / ht.py @ 596b2459

History | View | Annotate | Download (9.4 kB)

1
#
2
#
3

    
4
# Copyright (C) 2010, 2011 Google Inc.
5
#
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.
10
#
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.
15
#
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
19
# 02110-1301, USA.
20

    
21

    
22
"""Module implementing the parameter types code."""
23

    
24
import re
25
import operator
26

    
27
from ganeti import compat
28
from ganeti import utils
29
from ganeti import constants
30

    
31

    
32
_PAREN_RE = re.compile("^[a-zA-Z0-9_-]+$")
33

    
34

    
35
def Parens(text):
36
  """Enclose text in parens if necessary.
37

38
  @param text: Text
39

40
  """
41
  text = str(text)
42

    
43
  if _PAREN_RE.match(text):
44
    return text
45
  else:
46
    return "(%s)" % text
47

    
48

    
49
class _WrapperBase(object):
50
  __slots__ = [
51
    "_fn",
52
    "_text",
53
    ]
54

    
55
  def __init__(self, text, fn):
56
    """Initializes this class.
57

58
    @param text: Description
59
    @param fn: Wrapped function
60

61
    """
62
    assert text.strip()
63

    
64
    self._text = text
65
    self._fn = fn
66

    
67
  def __call__(self, *args):
68
    return self._fn(*args)
69

    
70

    
71
class _DescWrapper(_WrapperBase):
72
  """Wrapper class for description text.
73

74
  """
75
  def __str__(self):
76
    return self._text
77

    
78

    
79
class _CommentWrapper(_WrapperBase):
80
  """Wrapper class for comment.
81

82
  """
83
  def __str__(self):
84
    return "%s [%s]" % (self._fn, self._text)
85

    
86

    
87
def WithDesc(text):
88
  """Builds wrapper class with description text.
89

90
  @type text: string
91
  @param text: Description text
92
  @return: Callable class
93

94
  """
95
  assert text[0] == text[0].upper()
96

    
97
  return compat.partial(_DescWrapper, text)
98

    
99

    
100
def Comment(text):
101
  """Builds wrapper for adding comment to description text.
102

103
  @type text: string
104
  @param text: Comment text
105
  @return: Callable class
106

107
  """
108
  assert not frozenset(text).intersection("[]")
109

    
110
  return compat.partial(_CommentWrapper, text)
111

    
112

    
113
def CombinationDesc(op, args, fn):
114
  """Build description for combinating operator.
115

116
  @type op: string
117
  @param op: Operator as text (e.g. "and")
118
  @type args: list
119
  @param args: Operator arguments
120
  @type fn: callable
121
  @param fn: Wrapped function
122

123
  """
124
  if len(args) == 1:
125
    descr = str(args[0])
126
  else:
127
    descr = (" %s " % op).join(Parens(i) for i in args)
128

    
129
  return WithDesc(descr)(fn)
130

    
131

    
132
# Modifiable default values; need to define these here before the
133
# actual LUs
134

    
135
@WithDesc(str([]))
136
def EmptyList():
137
  """Returns an empty list.
138

139
  """
140
  return []
141

    
142

    
143
@WithDesc(str({}))
144
def EmptyDict():
145
  """Returns an empty dict.
146

147
  """
148
  return {}
149

    
150

    
151
#: The without-default default value
152
NoDefault = object()
153

    
154

    
155
#: The no-type (value too complex to check it in the type system)
156
NoType = object()
157

    
158

    
159
# Some basic types
160
@WithDesc("Anything")
161
def TAny(_):
162
  """Accepts any value.
163

164
  """
165
  return True
166

    
167

    
168
@WithDesc("NotNone")
169
def TNotNone(val):
170
  """Checks if the given value is not None.
171

172
  """
173
  return val is not None
174

    
175

    
176
@WithDesc("None")
177
def TNone(val):
178
  """Checks if the given value is None.
179

180
  """
181
  return val is None
182

    
183

    
184
@WithDesc("Boolean")
185
def TBool(val):
186
  """Checks if the given value is a boolean.
187

188
  """
189
  return isinstance(val, bool)
190

    
191

    
192
@WithDesc("Integer")
193
def TInt(val):
194
  """Checks if the given value is an integer.
195

196
  """
197
  # For backwards compatibility with older Python versions, boolean values are
198
  # also integers and should be excluded in this test.
199
  #
200
  # >>> (isinstance(False, int), isinstance(True, int))
201
  # (True, True)
202
  return isinstance(val, (int, long)) and not isinstance(val, bool)
203

    
204

    
205
@WithDesc("Float")
206
def TFloat(val):
207
  """Checks if the given value is a float.
208

209
  """
210
  return isinstance(val, float)
211

    
212

    
213
@WithDesc("String")
214
def TString(val):
215
  """Checks if the given value is a string.
216

217
  """
218
  return isinstance(val, basestring)
219

    
220

    
221
@WithDesc("EvalToTrue")
222
def TTrue(val):
223
  """Checks if a given value evaluates to a boolean True value.
224

225
  """
226
  return bool(val)
227

    
228

    
229
def TElemOf(target_list):
230
  """Builds a function that checks if a given value is a member of a list.
231

232
  """
233
  def fn(val):
234
    return val in target_list
235

    
236
  return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
237

    
238

    
239
# Container types
240
@WithDesc("List")
241
def TList(val):
242
  """Checks if the given value is a list.
243

244
  """
245
  return isinstance(val, list)
246

    
247

    
248
@WithDesc("Dictionary")
249
def TDict(val):
250
  """Checks if the given value is a dictionary.
251

252
  """
253
  return isinstance(val, dict)
254

    
255

    
256
def TIsLength(size):
257
  """Check is the given container is of the given size.
258

259
  """
260
  def fn(container):
261
    return len(container) == size
262

    
263
  return WithDesc("Length %s" % (size, ))(fn)
264

    
265

    
266
# Combinator types
267
def TAnd(*args):
268
  """Combine multiple functions using an AND operation.
269

270
  """
271
  def fn(val):
272
    return compat.all(t(val) for t in args)
273

    
274
  return CombinationDesc("and", args, fn)
275

    
276

    
277
def TOr(*args):
278
  """Combine multiple functions using an AND operation.
279

280
  """
281
  def fn(val):
282
    return compat.any(t(val) for t in args)
283

    
284
  return CombinationDesc("or", args, fn)
285

    
286

    
287
def TMap(fn, test):
288
  """Checks that a modified version of the argument passes the given test.
289

290
  """
291
  return WithDesc("Result of %s must be %s" %
292
                  (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
293

    
294

    
295
def TRegex(pobj):
296
  """Checks whether a string matches a specific regular expression.
297

298
  @param pobj: Compiled regular expression as returned by C{re.compile}
299

300
  """
301
  desc = WithDesc("String matching regex \"%s\"" %
302
                  pobj.pattern.encode("string_escape"))
303

    
304
  return desc(TAnd(TString, pobj.match))
305

    
306

    
307
# Type aliases
308

    
309
#: a non-empty string
310
TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
311

    
312
#: a maybe non-empty string
313
TMaybeString = TOr(TNonEmptyString, TNone)
314

    
315
#: a maybe boolean (bool or none)
316
TMaybeBool = TOr(TBool, TNone)
317

    
318
#: Maybe a dictionary (dict or None)
319
TMaybeDict = TOr(TDict, TNone)
320

    
321
#: a positive integer
322
TPositiveInt = \
323
  TAnd(TInt, WithDesc("EqualGreaterZero")(lambda v: v >= 0))
324

    
325
#: a strictly positive integer
326
TStrictPositiveInt = \
327
  TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
328

    
329
#: a strictly negative integer (0 > value)
330
TStrictNegativeInt = \
331
  TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0)))
332

    
333
#: a positive float
334
TPositiveFloat = \
335
  TAnd(TFloat, WithDesc("EqualGreaterZero")(lambda v: v >= 0.0))
336

    
337
#: Job ID
338
TJobId = WithDesc("JobId")(TOr(TPositiveInt,
339
                               TRegex(re.compile("^%s$" %
340
                                                 constants.JOB_ID_TEMPLATE))))
341

    
342
#: Number
343
TNumber = TOr(TInt, TFloat)
344

    
345
#: Relative job ID
346
TRelativeJobId = WithDesc("RelativeJobId")(TStrictNegativeInt)
347

    
348

    
349
def TListOf(my_type):
350
  """Checks if a given value is a list with all elements of the same type.
351

352
  """
353
  desc = WithDesc("List of %s" % (Parens(my_type), ))
354
  return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
355

    
356

    
357
def TDictOf(key_type, val_type):
358
  """Checks a dict type for the type of its key/values.
359

360
  """
361
  desc = WithDesc("Dictionary with keys of %s and values of %s" %
362
                  (Parens(key_type), Parens(val_type)))
363

    
364
  def fn(container):
365
    return (compat.all(key_type(v) for v in container.keys()) and
366
            compat.all(val_type(v) for v in container.values()))
367

    
368
  return desc(TAnd(TDict, fn))
369

    
370

    
371
def _TStrictDictCheck(require_all, exclusive, items, val):
372
  """Helper function for L{TStrictDict}.
373

374
  """
375
  notfound_fn = lambda _: not exclusive
376

    
377
  if require_all and not frozenset(val.keys()).issuperset(items.keys()):
378
    # Requires items not found in value
379
    return False
380

    
381
  return compat.all(items.get(key, notfound_fn)(value)
382
                    for (key, value) in val.items())
383

    
384

    
385
def TStrictDict(require_all, exclusive, items):
386
  """Strict dictionary check with specific keys.
387

388
  @type require_all: boolean
389
  @param require_all: Whether all keys in L{items} are required
390
  @type exclusive: boolean
391
  @param exclusive: Whether only keys listed in L{items} should be accepted
392
  @type items: dictionary
393
  @param items: Mapping from key (string) to verification function
394

395
  """
396
  descparts = ["Dictionary containing"]
397

    
398
  if exclusive:
399
    descparts.append(" none but the")
400

    
401
  if require_all:
402
    descparts.append(" required")
403

    
404
  if len(items) == 1:
405
    descparts.append(" key ")
406
  else:
407
    descparts.append(" keys ")
408

    
409
  descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
410
                                   for (key, value) in items.items()))
411

    
412
  desc = WithDesc("".join(descparts))
413

    
414
  return desc(TAnd(TDict,
415
                   compat.partial(_TStrictDictCheck, require_all, exclusive,
416
                                  items)))
417

    
418

    
419
def TItems(items):
420
  """Checks individual items of a container.
421

422
  If the verified value and the list of expected items differ in length, this
423
  check considers only as many items as are contained in the shorter list. Use
424
  L{TIsLength} to enforce a certain length.
425

426
  @type items: list
427
  @param items: List of checks
428

429
  """
430
  assert items, "Need items"
431

    
432
  text = ["Item", "item"]
433
  desc = WithDesc(utils.CommaJoin("%s %s is %s" %
434
                                  (text[int(idx > 0)], idx, Parens(check))
435
                                  for (idx, check) in enumerate(items)))
436

    
437
  return desc(lambda value: compat.all(check(i)
438
                                       for (check, i) in zip(items, value)))