]> git.proxmox.com Git - mirror_ovs.git/blob - python/ovs/db/types.py
python: Style cleanup.
[mirror_ovs.git] / python / ovs / db / types.py
1 # Copyright (c) 2009, 2010, 2011 Nicira Networks
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at:
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import sys
16 import uuid
17
18 from ovs.db import error
19 import ovs.db.parser
20 import ovs.db.data
21 import ovs.ovsuuid
22
23
24 class AtomicType(object):
25 def __init__(self, name, default, python_types):
26 self.name = name
27 self.default = default
28 self.python_types = python_types
29
30 @staticmethod
31 def from_string(s):
32 if s != "void":
33 for atomic_type in ATOMIC_TYPES:
34 if s == atomic_type.name:
35 return atomic_type
36 raise error.Error('"%s" is not an atomic-type' % s, s)
37
38 @staticmethod
39 def from_json(json):
40 if type(json) not in [str, unicode]:
41 raise error.Error("atomic-type expected", json)
42 else:
43 return AtomicType.from_string(json)
44
45 def __str__(self):
46 return self.name
47
48 def to_string(self):
49 return self.name
50
51 def to_json(self):
52 return self.name
53
54 def default_atom(self):
55 return ovs.db.data.Atom(self, self.default)
56
57 VoidType = AtomicType("void", None, ())
58 IntegerType = AtomicType("integer", 0, (int, long))
59 RealType = AtomicType("real", 0.0, (int, long, float))
60 BooleanType = AtomicType("boolean", False, (bool,))
61 StringType = AtomicType("string", "", (str, unicode))
62 UuidType = AtomicType("uuid", ovs.ovsuuid.zero(), (uuid.UUID,))
63
64 ATOMIC_TYPES = [VoidType, IntegerType, RealType, BooleanType, StringType,
65 UuidType]
66
67
68 def escapeCString(src):
69 dst = ""
70 for c in src:
71 if c in "\\\"":
72 dst += "\\" + c
73 elif ord(c) < 32:
74 if c == '\n':
75 dst += '\\n'
76 elif c == '\r':
77 dst += '\\r'
78 elif c == '\a':
79 dst += '\\a'
80 elif c == '\b':
81 dst += '\\b'
82 elif c == '\f':
83 dst += '\\f'
84 elif c == '\t':
85 dst += '\\t'
86 elif c == '\v':
87 dst += '\\v'
88 else:
89 dst += '\\%03o' % ord(c)
90 else:
91 dst += c
92 return dst
93
94
95 def commafy(x):
96 """Returns integer x formatted in decimal with thousands set off by
97 commas."""
98 return _commafy("%d" % x)
99
100
101 def _commafy(s):
102 if s.startswith('-'):
103 return '-' + _commafy(s[1:])
104 elif len(s) <= 3:
105 return s
106 else:
107 return _commafy(s[:-3]) + ',' + _commafy(s[-3:])
108
109
110 def returnUnchanged(x):
111 return x
112
113
114 class BaseType(object):
115 def __init__(self, type_, enum=None, min=None, max=None,
116 min_length=0, max_length=sys.maxint, ref_table_name=None):
117 assert isinstance(type_, AtomicType)
118 self.type = type_
119 self.enum = enum
120 self.min = min
121 self.max = max
122 self.min_length = min_length
123 self.max_length = max_length
124 self.ref_table_name = ref_table_name
125 if ref_table_name:
126 self.ref_type = 'strong'
127 else:
128 self.ref_type = None
129 self.ref_table = None
130
131 def default(self):
132 return ovs.db.data.Atom.default(self.type)
133
134 def __eq__(self, other):
135 if not isinstance(other, BaseType):
136 return NotImplemented
137 return (self.type == other.type and self.enum == other.enum and
138 self.min == other.min and self.max == other.max and
139 self.min_length == other.min_length and
140 self.max_length == other.max_length and
141 self.ref_table_name == other.ref_table_name)
142
143 def __ne__(self, other):
144 if not isinstance(other, BaseType):
145 return NotImplemented
146 else:
147 return not (self == other)
148
149 @staticmethod
150 def __parse_uint(parser, name, default):
151 value = parser.get_optional(name, [int, long])
152 if value is None:
153 value = default
154 else:
155 max_value = 2 ** 32 - 1
156 if not (0 <= value <= max_value):
157 raise error.Error("%s out of valid range 0 to %d"
158 % (name, max_value), value)
159 return value
160
161 @staticmethod
162 def from_json(json):
163 if type(json) in [str, unicode]:
164 return BaseType(AtomicType.from_json(json))
165
166 parser = ovs.db.parser.Parser(json, "ovsdb type")
167 atomic_type = AtomicType.from_json(parser.get("type", [str, unicode]))
168
169 base = BaseType(atomic_type)
170
171 enum = parser.get_optional("enum", [])
172 if enum is not None:
173 base.enum = ovs.db.data.Datum.from_json(
174 BaseType.get_enum_type(base.type), enum)
175 elif base.type == IntegerType:
176 base.min = parser.get_optional("minInteger", [int, long])
177 base.max = parser.get_optional("maxInteger", [int, long])
178 if (base.min is not None and base.max is not None
179 and base.min > base.max):
180 raise error.Error("minInteger exceeds maxInteger", json)
181 elif base.type == RealType:
182 base.min = parser.get_optional("minReal", [int, long, float])
183 base.max = parser.get_optional("maxReal", [int, long, float])
184 if (base.min is not None and base.max is not None
185 and base.min > base.max):
186 raise error.Error("minReal exceeds maxReal", json)
187 elif base.type == StringType:
188 base.min_length = BaseType.__parse_uint(parser, "minLength", 0)
189 base.max_length = BaseType.__parse_uint(parser, "maxLength",
190 sys.maxint)
191 if base.min_length > base.max_length:
192 raise error.Error("minLength exceeds maxLength", json)
193 elif base.type == UuidType:
194 base.ref_table_name = parser.get_optional("refTable", ['id'])
195 if base.ref_table_name:
196 base.ref_type = parser.get_optional("refType", [str, unicode],
197 "strong")
198 if base.ref_type not in ['strong', 'weak']:
199 raise error.Error('refType must be "strong" or "weak" '
200 '(not "%s")' % base.ref_type)
201 parser.finish()
202
203 return base
204
205 def to_json(self):
206 if not self.has_constraints():
207 return self.type.to_json()
208
209 json = {'type': self.type.to_json()}
210
211 if self.enum:
212 json['enum'] = self.enum.to_json()
213
214 if self.type == IntegerType:
215 if self.min is not None:
216 json['minInteger'] = self.min
217 if self.max is not None:
218 json['maxInteger'] = self.max
219 elif self.type == RealType:
220 if self.min is not None:
221 json['minReal'] = self.min
222 if self.max is not None:
223 json['maxReal'] = self.max
224 elif self.type == StringType:
225 if self.min_length != 0:
226 json['minLength'] = self.min_length
227 if self.max_length != sys.maxint:
228 json['maxLength'] = self.max_length
229 elif self.type == UuidType:
230 if self.ref_table_name:
231 json['refTable'] = self.ref_table_name
232 if self.ref_type != 'strong':
233 json['refType'] = self.ref_type
234 return json
235
236 def copy(self):
237 base = BaseType(self.type, self.enum.copy(), self.min, self.max,
238 self.min_length, self.max_length, self.ref_table_name)
239 base.ref_table = self.ref_table
240 return base
241
242 def is_valid(self):
243 if self.type in (VoidType, BooleanType, UuidType):
244 return True
245 elif self.type in (IntegerType, RealType):
246 return self.min is None or self.max is None or self.min <= self.max
247 elif self.type == StringType:
248 return self.min_length <= self.max_length
249 else:
250 return False
251
252 def has_constraints(self):
253 return (self.enum is not None or self.min is not None or
254 self.max is not None or
255 self.min_length != 0 or self.max_length != sys.maxint or
256 self.ref_table_name is not None)
257
258 def without_constraints(self):
259 return BaseType(self.type)
260
261 @staticmethod
262 def get_enum_type(atomic_type):
263 """Returns the type of the 'enum' member for a BaseType whose
264 'type' is 'atomic_type'."""
265 return Type(BaseType(atomic_type), None, 1, sys.maxint)
266
267 def is_ref(self):
268 return self.type == UuidType and self.ref_table_name is not None
269
270 def is_strong_ref(self):
271 return self.is_ref() and self.ref_type == 'strong'
272
273 def is_weak_ref(self):
274 return self.is_ref() and self.ref_type == 'weak'
275
276 def toEnglish(self, escapeLiteral=returnUnchanged):
277 if self.type == UuidType and self.ref_table_name:
278 s = escapeLiteral(self.ref_table_name)
279 if self.ref_type == 'weak':
280 s = "weak reference to " + s
281 return s
282 else:
283 return self.type.to_string()
284
285 def constraintsToEnglish(self, escapeLiteral=returnUnchanged):
286 if self.enum:
287 literals = [value.toEnglish(escapeLiteral)
288 for value in self.enum.values]
289 if len(literals) == 2:
290 english = 'either %s or %s' % (literals[0], literals[1])
291 else:
292 english = 'one of %s, %s, or %s' % (literals[0],
293 ', '.join(literals[1:-1]),
294 literals[-1])
295 elif self.min is not None and self.max is not None:
296 if self.type == IntegerType:
297 english = 'in range %s to %s' % (commafy(self.min),
298 commafy(self.max))
299 else:
300 english = 'in range %g to %g' % (self.min, self.max)
301 elif self.min is not None:
302 if self.type == IntegerType:
303 english = 'at least %s' % commafy(self.min)
304 else:
305 english = 'at least %g' % self.min
306 elif self.max is not None:
307 if self.type == IntegerType:
308 english = 'at most %s' % commafy(self.max)
309 else:
310 english = 'at most %g' % self.max
311 elif self.min_length != 0 and self.max_length != sys.maxint:
312 if self.min_length == self.max_length:
313 english = 'exactly %d characters long' % (self.min_length)
314 else:
315 english = ('between %d and %d characters long'
316 % (self.min_length, self.max_length))
317 elif self.min_length != 0:
318 return 'at least %d characters long' % self.min_length
319 elif self.max_length != sys.maxint:
320 english = 'at most %d characters long' % self.max_length
321 else:
322 english = ''
323
324 return english
325
326 def toCType(self, prefix):
327 if self.ref_table_name:
328 return "struct %s%s *" % (prefix, self.ref_table_name.lower())
329 else:
330 return {IntegerType: 'int64_t ',
331 RealType: 'double ',
332 UuidType: 'struct uuid ',
333 BooleanType: 'bool ',
334 StringType: 'char *'}[self.type]
335
336 def toAtomicType(self):
337 return "OVSDB_TYPE_%s" % self.type.to_string().upper()
338
339 def copyCValue(self, dst, src):
340 args = {'dst': dst, 'src': src}
341 if self.ref_table_name:
342 return ("%(dst)s = %(src)s->header_.uuid;") % args
343 elif self.type == StringType:
344 return "%(dst)s = xstrdup(%(src)s);" % args
345 else:
346 return "%(dst)s = %(src)s;" % args
347
348 def initCDefault(self, var, is_optional):
349 if self.ref_table_name:
350 return "%s = NULL;" % var
351 elif self.type == StringType and not is_optional:
352 return '%s = "";' % var
353 else:
354 pattern = {IntegerType: '%s = 0;',
355 RealType: '%s = 0.0;',
356 UuidType: 'uuid_zero(&%s);',
357 BooleanType: '%s = false;',
358 StringType: '%s = NULL;'}[self.type]
359 return pattern % var
360
361 def cInitBaseType(self, indent, var):
362 stmts = []
363 stmts.append('ovsdb_base_type_init(&%s, %s);' % (
364 var, self.toAtomicType()))
365 if self.enum:
366 stmts.append("%s.enum_ = xmalloc(sizeof *%s.enum_);"
367 % (var, var))
368 stmts += self.enum.cInitDatum("%s.enum_" % var)
369 if self.type == IntegerType:
370 if self.min is not None:
371 stmts.append('%s.u.integer.min = INT64_C(%d);'
372 % (var, self.min))
373 if self.max is not None:
374 stmts.append('%s.u.integer.max = INT64_C(%d);'
375 % (var, self.max))
376 elif self.type == RealType:
377 if self.min is not None:
378 stmts.append('%s.u.real.min = %d;' % (var, self.min))
379 if self.max is not None:
380 stmts.append('%s.u.real.max = %d;' % (var, self.max))
381 elif self.type == StringType:
382 if self.min_length is not None:
383 stmts.append('%s.u.string.minLen = %d;'
384 % (var, self.min_length))
385 if self.max_length != sys.maxint:
386 stmts.append('%s.u.string.maxLen = %d;'
387 % (var, self.max_length))
388 elif self.type == UuidType:
389 if self.ref_table_name is not None:
390 stmts.append('%s.u.uuid.refTableName = "%s";'
391 % (var, escapeCString(self.ref_table_name)))
392 stmts.append('%s.u.uuid.refType = OVSDB_REF_%s;'
393 % (var, self.ref_type.upper()))
394 return '\n'.join([indent + stmt for stmt in stmts])
395
396
397 class Type(object):
398 DEFAULT_MIN = 1
399 DEFAULT_MAX = 1
400
401 def __init__(self, key, value=None, n_min=DEFAULT_MIN, n_max=DEFAULT_MAX):
402 self.key = key
403 self.value = value
404 self.n_min = n_min
405 self.n_max = n_max
406
407 def copy(self):
408 if self.value is None:
409 value = None
410 else:
411 value = self.value.copy()
412 return Type(self.key.copy(), value, self.n_min, self.n_max)
413
414 def __eq__(self, other):
415 if not isinstance(other, Type):
416 return NotImplemented
417 return (self.key == other.key and self.value == other.value and
418 self.n_min == other.n_min and self.n_max == other.n_max)
419
420 def __ne__(self, other):
421 if not isinstance(other, Type):
422 return NotImplemented
423 else:
424 return not (self == other)
425
426 def is_valid(self):
427 return (self.key.type != VoidType and self.key.is_valid() and
428 (self.value is None or
429 (self.value.type != VoidType and self.value.is_valid())) and
430 self.n_min <= 1 <= self.n_max)
431
432 def is_scalar(self):
433 return self.n_min == 1 and self.n_max == 1 and not self.value
434
435 def is_optional(self):
436 return self.n_min == 0 and self.n_max == 1
437
438 def is_composite(self):
439 return self.n_max > 1
440
441 def is_set(self):
442 return self.value is None and (self.n_min != 1 or self.n_max != 1)
443
444 def is_map(self):
445 return self.value is not None
446
447 def is_optional_pointer(self):
448 return (self.is_optional() and not self.value
449 and (self.key.type == StringType or self.key.ref_table_name))
450
451 @staticmethod
452 def __n_from_json(json, default):
453 if json is None:
454 return default
455 elif type(json) == int and 0 <= json <= sys.maxint:
456 return json
457 else:
458 raise error.Error("bad min or max value", json)
459
460 @staticmethod
461 def from_json(json):
462 if type(json) in [str, unicode]:
463 return Type(BaseType.from_json(json))
464
465 parser = ovs.db.parser.Parser(json, "ovsdb type")
466 key_json = parser.get("key", [dict, str, unicode])
467 value_json = parser.get_optional("value", [dict, str, unicode])
468 min_json = parser.get_optional("min", [int])
469 max_json = parser.get_optional("max", [int, str, unicode])
470 parser.finish()
471
472 key = BaseType.from_json(key_json)
473 if value_json:
474 value = BaseType.from_json(value_json)
475 else:
476 value = None
477
478 n_min = Type.__n_from_json(min_json, Type.DEFAULT_MIN)
479
480 if max_json == 'unlimited':
481 n_max = sys.maxint
482 else:
483 n_max = Type.__n_from_json(max_json, Type.DEFAULT_MAX)
484
485 type_ = Type(key, value, n_min, n_max)
486 if not type_.is_valid():
487 raise error.Error("ovsdb type fails constraint checks", json)
488 return type_
489
490 def to_json(self):
491 if self.is_scalar() and not self.key.has_constraints():
492 return self.key.to_json()
493
494 json = {"key": self.key.to_json()}
495 if self.value is not None:
496 json["value"] = self.value.to_json()
497 if self.n_min != Type.DEFAULT_MIN:
498 json["min"] = self.n_min
499 if self.n_max == sys.maxint:
500 json["max"] = "unlimited"
501 elif self.n_max != Type.DEFAULT_MAX:
502 json["max"] = self.n_max
503 return json
504
505 def toEnglish(self, escapeLiteral=returnUnchanged):
506 keyName = self.key.toEnglish(escapeLiteral)
507 if self.value:
508 valueName = self.value.toEnglish(escapeLiteral)
509
510 if self.is_scalar():
511 return keyName
512 elif self.is_optional():
513 if self.value:
514 return "optional %s-%s pair" % (keyName, valueName)
515 else:
516 return "optional %s" % keyName
517 else:
518 if self.n_max == sys.maxint:
519 if self.n_min:
520 quantity = "%d or more " % self.n_min
521 else:
522 quantity = ""
523 elif self.n_min:
524 quantity = "%d to %d " % (self.n_min, self.n_max)
525 else:
526 quantity = "up to %d " % self.n_max
527
528 if self.value:
529 return "map of %s%s-%s pairs" % (quantity, keyName, valueName)
530 else:
531 if keyName.endswith('s'):
532 plural = keyName + "es"
533 else:
534 plural = keyName + "s"
535 return "set of %s%s" % (quantity, plural)
536
537 def constraintsToEnglish(self, escapeLiteral=returnUnchanged):
538 constraints = []
539 keyConstraints = self.key.constraintsToEnglish(escapeLiteral)
540 if keyConstraints:
541 if self.value:
542 constraints.append('key %s' % keyConstraints)
543 else:
544 constraints.append(keyConstraints)
545
546 if self.value:
547 valueConstraints = self.value.constraintsToEnglish(escapeLiteral)
548 if valueConstraints:
549 constraints.append('value %s' % valueConstraints)
550
551 return ', '.join(constraints)
552
553 def cDeclComment(self):
554 if self.n_min == 1 and self.n_max == 1 and self.key.type == StringType:
555 return "\t/* Always nonnull. */"
556 else:
557 return ""
558
559 def cInitType(self, indent, var):
560 initKey = self.key.cInitBaseType(indent, "%s.key" % var)
561 if self.value:
562 initValue = self.value.cInitBaseType(indent, "%s.value" % var)
563 else:
564 initValue = ('%sovsdb_base_type_init(&%s.value, '
565 'OVSDB_TYPE_VOID);' % (indent, var))
566 initMin = "%s%s.n_min = %s;" % (indent, var, self.n_min)
567 if self.n_max == sys.maxint:
568 n_max = "UINT_MAX"
569 else:
570 n_max = self.n_max
571 initMax = "%s%s.n_max = %s;" % (indent, var, n_max)
572 return "\n".join((initKey, initValue, initMin, initMax))