Statistics
| Branch: | Tag: | Revision:

root / lib / ht.py @ 2c0af7da

History | View | Annotate | Download (9.6 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 maybe positive integer (positive integer or None)
326
TMaybePositiveInt = TOr(TPositiveInt, TNone)
327

    
328
#: a strictly positive integer
329
TStrictPositiveInt = \
330
  TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
331

    
332
#: a maybe strictly positive integer (strictly positive integer or None)
333
TMaybeStrictPositiveInt = TOr(TStrictPositiveInt, TNone)
334

    
335
#: a strictly negative integer (0 > value)
336
TStrictNegativeInt = \
337
  TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0)))
338

    
339
#: a positive float
340
TPositiveFloat = \
341
  TAnd(TFloat, WithDesc("EqualGreaterZero")(lambda v: v >= 0.0))
342

    
343
#: Job ID
344
TJobId = WithDesc("JobId")(TOr(TPositiveInt,
345
                               TRegex(re.compile("^%s$" %
346
                                                 constants.JOB_ID_TEMPLATE))))
347

    
348
#: Number
349
TNumber = TOr(TInt, TFloat)
350

    
351
#: Relative job ID
352
TRelativeJobId = WithDesc("RelativeJobId")(TStrictNegativeInt)
353

    
354

    
355
def TListOf(my_type):
356
  """Checks if a given value is a list with all elements of the same type.
357

358
  """
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)))
361

    
362

    
363
def TDictOf(key_type, val_type):
364
  """Checks a dict type for the type of its key/values.
365

366
  """
367
  desc = WithDesc("Dictionary with keys of %s and values of %s" %
368
                  (Parens(key_type), Parens(val_type)))
369

    
370
  def fn(container):
371
    return (compat.all(key_type(v) for v in container.keys()) and
372
            compat.all(val_type(v) for v in container.values()))
373

    
374
  return desc(TAnd(TDict, fn))
375

    
376

    
377
def _TStrictDictCheck(require_all, exclusive, items, val):
378
  """Helper function for L{TStrictDict}.
379

380
  """
381
  notfound_fn = lambda _: not exclusive
382

    
383
  if require_all and not frozenset(val.keys()).issuperset(items.keys()):
384
    # Requires items not found in value
385
    return False
386

    
387
  return compat.all(items.get(key, notfound_fn)(value)
388
                    for (key, value) in val.items())
389

    
390

    
391
def TStrictDict(require_all, exclusive, items):
392
  """Strict dictionary check with specific keys.
393

394
  @type require_all: boolean
395
  @param require_all: Whether all keys in L{items} are required
396
  @type exclusive: boolean
397
  @param exclusive: Whether only keys listed in L{items} should be accepted
398
  @type items: dictionary
399
  @param items: Mapping from key (string) to verification function
400

401
  """
402
  descparts = ["Dictionary containing"]
403

    
404
  if exclusive:
405
    descparts.append(" none but the")
406

    
407
  if require_all:
408
    descparts.append(" required")
409

    
410
  if len(items) == 1:
411
    descparts.append(" key ")
412
  else:
413
    descparts.append(" keys ")
414

    
415
  descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
416
                                   for (key, value) in items.items()))
417

    
418
  desc = WithDesc("".join(descparts))
419

    
420
  return desc(TAnd(TDict,
421
                   compat.partial(_TStrictDictCheck, require_all, exclusive,
422
                                  items)))
423

    
424

    
425
def TItems(items):
426
  """Checks individual items of a container.
427

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

432
  @type items: list
433
  @param items: List of checks
434

435
  """
436
  assert items, "Need items"
437

    
438
  text = ["Item", "item"]
439
  desc = WithDesc(utils.CommaJoin("%s %s is %s" %
440
                                  (text[int(idx > 0)], idx, Parens(check))
441
                                  for (idx, check) in enumerate(items)))
442

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