Statistics
| Branch: | Tag: | Revision:

root / lib / ht.py @ 896a03f6

History | View | Annotate | Download (8.8 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 _DescWrapper(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
    self._text = text
63
    self._fn = fn
64

    
65
  def __call__(self, *args):
66
    return self._fn(*args)
67

    
68
  def __str__(self):
69
    return self._text
70

    
71

    
72
def WithDesc(text):
73
  """Builds wrapper class with description text.
74

75
  @type text: string
76
  @param text: Description text
77
  @return: Callable class
78

79
  """
80
  assert text[0] == text[0].upper()
81

    
82
  return compat.partial(_DescWrapper, text)
83

    
84

    
85
def CombinationDesc(op, args, fn):
86
  """Build description for combinating operator.
87

88
  @type op: string
89
  @param op: Operator as text (e.g. "and")
90
  @type args: list
91
  @param args: Operator arguments
92
  @type fn: callable
93
  @param fn: Wrapped function
94

95
  """
96
  if len(args) == 1:
97
    descr = str(args[0])
98
  else:
99
    descr = (" %s " % op).join(Parens(i) for i in args)
100

    
101
  return WithDesc(descr)(fn)
102

    
103

    
104
# Modifiable default values; need to define these here before the
105
# actual LUs
106

    
107
@WithDesc(str([]))
108
def EmptyList():
109
  """Returns an empty list.
110

111
  """
112
  return []
113

    
114

    
115
@WithDesc(str({}))
116
def EmptyDict():
117
  """Returns an empty dict.
118

119
  """
120
  return {}
121

    
122

    
123
#: The without-default default value
124
NoDefault = object()
125

    
126

    
127
#: The no-type (value too complex to check it in the type system)
128
NoType = object()
129

    
130

    
131
# Some basic types
132
@WithDesc("Anything")
133
def TAny(_):
134
  """Accepts any value.
135

136
  """
137
  return True
138

    
139

    
140
@WithDesc("NotNone")
141
def TNotNone(val):
142
  """Checks if the given value is not None.
143

144
  """
145
  return val is not None
146

    
147

    
148
@WithDesc("None")
149
def TNone(val):
150
  """Checks if the given value is None.
151

152
  """
153
  return val is None
154

    
155

    
156
@WithDesc("Boolean")
157
def TBool(val):
158
  """Checks if the given value is a boolean.
159

160
  """
161
  return isinstance(val, bool)
162

    
163

    
164
@WithDesc("Integer")
165
def TInt(val):
166
  """Checks if the given value is an integer.
167

168
  """
169
  # For backwards compatibility with older Python versions, boolean values are
170
  # also integers and should be excluded in this test.
171
  #
172
  # >>> (isinstance(False, int), isinstance(True, int))
173
  # (True, True)
174
  return isinstance(val, (int, long)) and not isinstance(val, bool)
175

    
176

    
177
@WithDesc("Float")
178
def TFloat(val):
179
  """Checks if the given value is a float.
180

181
  """
182
  return isinstance(val, float)
183

    
184

    
185
@WithDesc("String")
186
def TString(val):
187
  """Checks if the given value is a string.
188

189
  """
190
  return isinstance(val, basestring)
191

    
192

    
193
@WithDesc("EvalToTrue")
194
def TTrue(val):
195
  """Checks if a given value evaluates to a boolean True value.
196

197
  """
198
  return bool(val)
199

    
200

    
201
def TElemOf(target_list):
202
  """Builds a function that checks if a given value is a member of a list.
203

204
  """
205
  def fn(val):
206
    return val in target_list
207

    
208
  return WithDesc("OneOf %s" % (utils.CommaJoin(target_list), ))(fn)
209

    
210

    
211
# Container types
212
@WithDesc("List")
213
def TList(val):
214
  """Checks if the given value is a list.
215

216
  """
217
  return isinstance(val, list)
218

    
219

    
220
@WithDesc("Dictionary")
221
def TDict(val):
222
  """Checks if the given value is a dictionary.
223

224
  """
225
  return isinstance(val, dict)
226

    
227

    
228
def TIsLength(size):
229
  """Check is the given container is of the given size.
230

231
  """
232
  def fn(container):
233
    return len(container) == size
234

    
235
  return WithDesc("Length %s" % (size, ))(fn)
236

    
237

    
238
# Combinator types
239
def TAnd(*args):
240
  """Combine multiple functions using an AND operation.
241

242
  """
243
  def fn(val):
244
    return compat.all(t(val) for t in args)
245

    
246
  return CombinationDesc("and", args, fn)
247

    
248

    
249
def TOr(*args):
250
  """Combine multiple functions using an AND operation.
251

252
  """
253
  def fn(val):
254
    return compat.any(t(val) for t in args)
255

    
256
  return CombinationDesc("or", args, fn)
257

    
258

    
259
def TMap(fn, test):
260
  """Checks that a modified version of the argument passes the given test.
261

262
  """
263
  return WithDesc("Result of %s must be %s" %
264
                  (Parens(fn), Parens(test)))(lambda val: test(fn(val)))
265

    
266

    
267
def TRegex(pobj):
268
  """Checks whether a string matches a specific regular expression.
269

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

272
  """
273
  desc = WithDesc("String matching regex \"%s\"" %
274
                  pobj.pattern.encode("string_escape"))
275

    
276
  return desc(TAnd(TString, pobj.match))
277

    
278

    
279
# Type aliases
280

    
281
#: a non-empty string
282
TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
283

    
284
#: a maybe non-empty string
285
TMaybeString = TOr(TNonEmptyString, TNone)
286

    
287
#: a maybe boolean (bool or none)
288
TMaybeBool = TOr(TBool, TNone)
289

    
290
#: Maybe a dictionary (dict or None)
291
TMaybeDict = TOr(TDict, TNone)
292

    
293
#: a positive integer
294
TPositiveInt = \
295
  TAnd(TInt, WithDesc("EqualGreaterZero")(lambda v: v >= 0))
296

    
297
#: a strictly positive integer
298
TStrictPositiveInt = \
299
  TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
300

    
301
#: a strictly negative integer (0 > value)
302
TStrictNegativeInt = \
303
  TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0)))
304

    
305
#: a positive float
306
TPositiveFloat = \
307
  TAnd(TFloat, WithDesc("EqualGreaterZero")(lambda v: v >= 0.0))
308

    
309
#: Job ID
310
TJobId = TOr(TPositiveInt,
311
             TRegex(re.compile("^%s$" % constants.JOB_ID_TEMPLATE)))
312

    
313
#: Number
314
TNumber = TOr(TInt, TFloat)
315

    
316
#: Relative job ID
317
TRelativeJobId = WithDesc("RelativeJobId")(TStrictNegativeInt)
318

    
319

    
320
def TListOf(my_type):
321
  """Checks if a given value is a list with all elements of the same type.
322

323
  """
324
  desc = WithDesc("List of %s" % (Parens(my_type), ))
325
  return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
326

    
327

    
328
def TDictOf(key_type, val_type):
329
  """Checks a dict type for the type of its key/values.
330

331
  """
332
  desc = WithDesc("Dictionary with keys of %s and values of %s" %
333
                  (Parens(key_type), Parens(val_type)))
334

    
335
  def fn(container):
336
    return (compat.all(key_type(v) for v in container.keys()) and
337
            compat.all(val_type(v) for v in container.values()))
338

    
339
  return desc(TAnd(TDict, fn))
340

    
341

    
342
def _TStrictDictCheck(require_all, exclusive, items, val):
343
  """Helper function for L{TStrictDict}.
344

345
  """
346
  notfound_fn = lambda _: not exclusive
347

    
348
  if require_all and not frozenset(val.keys()).issuperset(items.keys()):
349
    # Requires items not found in value
350
    return False
351

    
352
  return compat.all(items.get(key, notfound_fn)(value)
353
                    for (key, value) in val.items())
354

    
355

    
356
def TStrictDict(require_all, exclusive, items):
357
  """Strict dictionary check with specific keys.
358

359
  @type require_all: boolean
360
  @param require_all: Whether all keys in L{items} are required
361
  @type exclusive: boolean
362
  @param exclusive: Whether only keys listed in L{items} should be accepted
363
  @type items: dictionary
364
  @param items: Mapping from key (string) to verification function
365

366
  """
367
  descparts = ["Dictionary containing"]
368

    
369
  if exclusive:
370
    descparts.append(" none but the")
371

    
372
  if require_all:
373
    descparts.append(" required")
374

    
375
  if len(items) == 1:
376
    descparts.append(" key ")
377
  else:
378
    descparts.append(" keys ")
379

    
380
  descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
381
                                   for (key, value) in items.items()))
382

    
383
  desc = WithDesc("".join(descparts))
384

    
385
  return desc(TAnd(TDict,
386
                   compat.partial(_TStrictDictCheck, require_all, exclusive,
387
                                  items)))
388

    
389

    
390
def TItems(items):
391
  """Checks individual items of a container.
392

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

397
  @type items: list
398
  @param items: List of checks
399

400
  """
401
  assert items, "Need items"
402

    
403
  text = ["Item", "item"]
404
  desc = WithDesc(utils.CommaJoin("%s %s is %s" %
405
                                  (text[int(idx > 0)], idx, Parens(check))
406
                                  for (idx, check) in enumerate(items)))
407

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