Statistics
| Branch: | Tag: | Revision:

root / lib / ht.py @ 7fc548e9

History | View | Annotate | Download (8.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

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

    
30

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

    
33

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

37
  @param text: Text
38

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

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

    
47

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

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

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

60
    """
61
    self._text = text
62
    self._fn = fn
63

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

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

    
70

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

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

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

    
81
  return compat.partial(_DescWrapper, text)
82

    
83

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

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

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

    
100
  return WithDesc(descr)(fn)
101

    
102

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

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

110
  """
111
  return []
112

    
113

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

118
  """
119
  return {}
120

    
121

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

    
125

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

    
129

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

135
  """
136
  return True
137

    
138

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

143
  """
144
  return val is not None
145

    
146

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

151
  """
152
  return val is None
153

    
154

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

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

    
162

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

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

    
175

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

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

    
183

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

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

    
191

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

196
  """
197
  return bool(val)
198

    
199

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

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

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

    
209

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

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

    
218

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

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

    
226

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

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

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

    
236

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

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

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

    
247

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

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

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

    
257

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

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

    
265

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

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

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

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

    
277

    
278
# Type aliases
279

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

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

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

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

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

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

    
300
#: a positive float
301
TPositiveFloat = \
302
  TAnd(TFloat, WithDesc("EqualGreaterZero")(lambda v: v >= 0.0))
303

    
304
#: Job ID
305
TJobId = TOr(TPositiveInt,
306
             TRegex(re.compile("^%s$" % constants.JOB_ID_TEMPLATE)))
307

    
308
#: Number
309
TNumber = TOr(TInt, TFloat)
310

    
311

    
312
def TListOf(my_type):
313
  """Checks if a given value is a list with all elements of the same type.
314

315
  """
316
  desc = WithDesc("List of %s" % (Parens(my_type), ))
317
  return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
318

    
319

    
320
def TDictOf(key_type, val_type):
321
  """Checks a dict type for the type of its key/values.
322

323
  """
324
  desc = WithDesc("Dictionary with keys of %s and values of %s" %
325
                  (Parens(key_type), Parens(val_type)))
326

    
327
  def fn(container):
328
    return (compat.all(key_type(v) for v in container.keys()) and
329
            compat.all(val_type(v) for v in container.values()))
330

    
331
  return desc(TAnd(TDict, fn))
332

    
333

    
334
def _TStrictDictCheck(require_all, exclusive, items, val):
335
  """Helper function for L{TStrictDict}.
336

337
  """
338
  notfound_fn = lambda _: not exclusive
339

    
340
  if require_all and not frozenset(val.keys()).issuperset(items.keys()):
341
    # Requires items not found in value
342
    return False
343

    
344
  return compat.all(items.get(key, notfound_fn)(value)
345
                    for (key, value) in val.items())
346

    
347

    
348
def TStrictDict(require_all, exclusive, items):
349
  """Strict dictionary check with specific keys.
350

351
  @type require_all: boolean
352
  @param require_all: Whether all keys in L{items} are required
353
  @type exclusive: boolean
354
  @param exclusive: Whether only keys listed in L{items} should be accepted
355
  @type items: dictionary
356
  @param items: Mapping from key (string) to verification function
357

358
  """
359
  descparts = ["Dictionary containing"]
360

    
361
  if exclusive:
362
    descparts.append(" none but the")
363

    
364
  if require_all:
365
    descparts.append(" required")
366

    
367
  if len(items) == 1:
368
    descparts.append(" key ")
369
  else:
370
    descparts.append(" keys ")
371

    
372
  descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
373
                                   for (key, value) in items.items()))
374

    
375
  desc = WithDesc("".join(descparts))
376

    
377
  return desc(TAnd(TDict,
378
                   compat.partial(_TStrictDictCheck, require_all, exclusive,
379
                                  items)))
380

    
381

    
382
def TItems(items):
383
  """Checks individual items of a container.
384

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

389
  @type items: list
390
  @param items: List of checks
391

392
  """
393
  assert items, "Need items"
394

    
395
  text = ["Item", "item"]
396
  desc = WithDesc(utils.CommaJoin("%s %s is %s" %
397
                                  (text[int(idx > 0)], idx, Parens(check))
398
                                  for (idx, check) in enumerate(items)))
399

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