]>
git.proxmox.com Git - ovs.git/blob - python/ovs/db/data.py
1 # Copyright (c) 2009, 2010, 2011, 2014, 2016 Nicira, Inc.
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:
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
25 import ovs
.socket_util
26 from ovs
.db
import error
29 class ConstraintViolation(error
.Error
):
30 def __init__(self
, msg
, json
=None):
31 error
.Error
.__init
__(self
, msg
, json
, tag
="constraint violation")
34 def escapeCString(src
):
55 dst
.append('\\%03o' % ord(c
))
61 def returnUnchanged(x
):
65 @functools.total_ordering
67 def __init__(self
, type_
, value
=None):
72 self
.value
= type_
.default_atom()
74 def __eq__(self
, other
):
75 if not isinstance(other
, Atom
) or self
.type != other
.type:
77 return True if self
.value
== other
.value
else False
79 def __lt__(self
, other
):
80 if not isinstance(other
, Atom
) or self
.type != other
.type:
82 return True if self
.value
< other
.value
else False
84 def __cmp__(self
, other
):
85 if not isinstance(other
, Atom
) or self
.type != other
.type:
87 elif self
.value
< other
.value
:
89 elif self
.value
> other
.value
:
95 return hash(self
.value
)
99 """Returns the default value for the given type_, which must be an
100 instance of ovs.db.types.AtomicType.
102 The default value for each atomic type is;
104 - 0, for integer or real atoms.
106 - False, for a boolean atom.
108 - "", for a string atom.
110 - The all-zeros UUID, for a UUID atom."""
113 def is_default(self
):
114 return self
== self
.default(self
.type)
117 def from_json(base
, json
, symtab
=None):
119 json
= ovs
.db
.parser
.float_to_int(json
)
121 real_types
.extend([float])
122 real_types
= tuple(real_types
)
123 if ((type_
== ovs
.db
.types
.IntegerType
124 and isinstance(json
, int))
125 or (type_
== ovs
.db
.types
.RealType
126 and isinstance(json
, real_types
))
127 or (type_
== ovs
.db
.types
.BooleanType
and isinstance(json
, bool))
128 or (type_
== ovs
.db
.types
.StringType
129 and isinstance(json
, str))):
130 atom
= Atom(type_
, json
)
131 elif type_
== ovs
.db
.types
.UuidType
:
132 atom
= Atom(type_
, ovs
.ovsuuid
.from_json(json
, symtab
))
134 raise error
.Error("expected %s" % type_
.to_string(), json
)
135 atom
.check_constraints(base
)
139 def from_python(base
, value
):
140 value
= ovs
.db
.parser
.float_to_int(value
)
141 if isinstance(value
, base
.type.python_types
):
142 atom
= Atom(base
.type, value
)
144 raise error
.Error("expected %s, got %s" % (base
.type, type(value
)))
145 atom
.check_constraints(base
)
148 def check_constraints(self
, base
):
149 """Checks whether 'atom' meets the constraints (if any) defined in
150 'base' and raises an ovs.db.error.Error if any constraint is violated.
152 'base' and 'atom' must have the same type.
153 Checking UUID constraints is deferred to transaction commit time, so
154 this function does nothing for UUID constraints."""
155 assert base
.type == self
.type
156 if base
.enum
is not None and self
not in base
.enum
:
157 raise ConstraintViolation(
158 "%s is not one of the allowed values (%s)"
159 % (self
.to_string(), base
.enum
.to_string()))
160 elif base
.type in [ovs
.db
.types
.IntegerType
, ovs
.db
.types
.RealType
]:
161 if ((base
.min is None or self
.value
>= base
.min) and
162 (base
.max is None or self
.value
<= base
.max)):
164 elif base
.min is not None and base
.max is not None:
165 raise ConstraintViolation(
166 "%s is not in the valid range %.15g to %.15g (inclusive)"
167 % (self
.to_string(), base
.min, base
.max))
168 elif base
.min is not None:
169 raise ConstraintViolation(
170 "%s is less than minimum allowed value %.15g"
171 % (self
.to_string(), base
.min))
173 raise ConstraintViolation(
174 "%s is greater than maximum allowed value %.15g"
175 % (self
.to_string(), base
.max))
176 elif base
.type == ovs
.db
.types
.StringType
:
177 # XXX The C version validates that the string is valid UTF-8 here.
178 # Do we need to do that in Python too?
181 if length
< base
.min_length
:
182 raise ConstraintViolation(
183 '"%s" length %d is less than minimum allowed length %d'
184 % (s
, length
, base
.min_length
))
185 elif length
> base
.max_length
:
186 raise ConstraintViolation(
187 '"%s" length %d is greater than maximum allowed '
188 'length %d' % (s
, length
, base
.max_length
))
191 if self
.type == ovs
.db
.types
.UuidType
:
192 return ovs
.ovsuuid
.to_json(self
.value
)
196 def cInitAtom(self
, var
):
197 if self
.type == ovs
.db
.types
.IntegerType
:
198 return '.integer = %d' % self
.value
199 elif self
.type == ovs
.db
.types
.RealType
:
200 return '.real = %.15g' % self
.value
201 elif self
.type == ovs
.db
.types
.BooleanType
:
203 return '.boolean = true'
205 return '.boolean = false'
206 elif self
.type == ovs
.db
.types
.StringType
:
207 return '.string = "%s"' % escapeCString(self
.value
)
208 elif self
.type == ovs
.db
.types
.UuidType
:
209 return '.uuid = %s' % ovs
.ovsuuid
.to_c_assignment(self
.value
)
211 def toEnglish(self
, escapeLiteral
=returnUnchanged
):
212 if self
.type == ovs
.db
.types
.IntegerType
:
213 return '%d' % self
.value
214 elif self
.type == ovs
.db
.types
.RealType
:
215 return '%.15g' % self
.value
216 elif self
.type == ovs
.db
.types
.BooleanType
:
221 elif self
.type == ovs
.db
.types
.StringType
:
222 return escapeLiteral(self
.value
)
223 elif self
.type == ovs
.db
.types
.UuidType
:
224 return self
.value
.value
226 __need_quotes_re
= re
.compile("$|true|false|[^_a-zA-Z]|.*[^-._a-zA-Z]")
229 def __string_needs_quotes(s
):
230 return Atom
.__need
_quotes
_re
.match(s
)
233 if self
.type == ovs
.db
.types
.IntegerType
:
234 return '%d' % self
.value
235 elif self
.type == ovs
.db
.types
.RealType
:
236 return '%.15g' % self
.value
237 elif self
.type == ovs
.db
.types
.BooleanType
:
242 elif self
.type == ovs
.db
.types
.StringType
:
243 if Atom
.__string
_needs
_quotes
(self
.value
):
244 return ovs
.json
.to_string(self
.value
)
247 elif self
.type == ovs
.db
.types
.UuidType
:
248 return str(self
.value
)
252 if isinstance(x
, int):
253 t
= ovs
.db
.types
.IntegerType
254 elif isinstance(x
, float):
255 t
= ovs
.db
.types
.RealType
256 elif isinstance(x
, bool):
257 t
= ovs
.db
.types
.BooleanType
258 elif isinstance(x
, str):
259 t
= ovs
.db
.types
.StringType
260 elif isinstance(x
, uuid
):
261 t
= ovs
.db
.types
.UuidType
267 @functools.total_ordering
269 def __init__(self
, type_
, values
={}):
273 def __eq__(self
, other
):
274 if not isinstance(other
, Datum
):
275 return NotImplemented
276 return True if self
.values
== other
.values
else False
278 def __lt__(self
, other
):
279 if not isinstance(other
, Datum
):
280 return NotImplemented
281 return True if self
.values
< other
.values
else False
283 def __cmp__(self
, other
):
284 if not isinstance(other
, Datum
):
285 return NotImplemented
286 elif self
.values
< other
.values
:
288 elif self
.values
> other
.values
:
295 def __contains__(self
, item
):
296 return item
in self
.values
299 return Datum(self
.type, dict(self
.values
))
306 values
= {type_
.key
.default(): type_
.value
.default()}
308 values
= {type_
.key
.default(): None}
309 return Datum(type_
, values
)
311 def is_default(self
):
312 return self
== Datum
.default(self
.type)
314 def check_constraints(self
):
315 """Checks that each of the atoms in 'datum' conforms to the constraints
316 specified by its 'type' and raises an ovs.db.error.Error.
318 This function is not commonly useful because the most ordinary way to
319 obtain a datum is ultimately via Datum.from_json() or Atom.from_json(),
320 which check constraints themselves."""
321 for keyAtom
, valueAtom
in self
.values
.items():
322 keyAtom
.check_constraints(self
.type.key
)
323 if valueAtom
is not None:
324 valueAtom
.check_constraints(self
.type.value
)
327 def from_json(type_
, json
, symtab
=None):
328 """Parses 'json' as a datum of the type described by 'type'. If
329 successful, returns a new datum. On failure, raises an
332 Violations of constraints expressed by 'type' are treated as errors.
334 If 'symtab' is nonnull, then named UUIDs in 'symtab' are accepted.
335 Refer to RFC 7047 for information about this, and for the syntax
336 that this function accepts."""
337 is_map
= type_
.is_map()
339 (isinstance(json
, list) and len(json
) > 0 and json
[0] == "set")):
345 inner
= ovs
.db
.parser
.unwrap_json(json
, class_
, [list, tuple],
348 if n
< type_
.n_min
or n
> type_
.n_max
:
349 raise error
.Error("%s must have %d to %d members but %d are "
350 "present" % (class_
, type_
.n_min
,
355 for element
in inner
:
357 key
, value
= ovs
.db
.parser
.parse_json_pair(element
)
358 keyAtom
= Atom
.from_json(type_
.key
, key
, symtab
)
359 valueAtom
= Atom
.from_json(type_
.value
, value
, symtab
)
361 keyAtom
= Atom
.from_json(type_
.key
, element
, symtab
)
364 if keyAtom
in values
:
366 raise error
.Error("map contains duplicate key")
368 raise error
.Error("set contains duplicate")
370 values
[keyAtom
] = valueAtom
372 return Datum(type_
, values
)
374 keyAtom
= Atom
.from_json(type_
.key
, json
, symtab
)
375 return Datum(type_
, {keyAtom
: None})
378 if self
.type.is_map():
379 return ["map", [[k
.to_json(), v
.to_json()]
380 for k
, v
in sorted(self
.values
.items())]]
381 elif len(self
.values
) == 1:
382 key
= next(iter(self
.values
.keys()))
385 return ["set", [k
.to_json() for k
in sorted(self
.values
.keys())]]
389 if self
.type.n_max
> 1 or len(self
.values
) == 0:
390 if self
.type.is_map():
401 for i
, key
in enumerate(sorted(self
.values
)):
405 s
.append(key
.to_string())
406 if self
.type.is_map():
408 s
.append(self
.values
[key
].to_string())
414 def diff(self
, datum
):
415 if self
.type.n_max
> 1 or len(self
.values
) == 0:
416 for k
, v
in datum
.values
.items():
417 if k
in self
.values
and v
== self
.values
[k
]:
427 if self
.type.is_map():
428 return [[k
.value
, v
.value
] for k
, v
in self
.values
.items()]
430 return [k
.value
for k
in self
.values
.keys()]
433 return dict(self
.values
)
436 if len(self
.values
) == 1:
437 if self
.type.is_map():
438 k
, v
= next(iter(self
.values
.items()))
439 return [k
.value
, v
.value
]
441 return next(iter(self
.values
.keys())).value
445 def to_python(self
, uuid_to_row
):
446 """Returns this datum's value converted into a natural Python
447 representation of this datum's type, according to the following
450 - If the type has exactly one value and it is not a map (that is,
451 self.type.is_scalar() returns True), then the value is:
453 * An int or long, for an integer column.
455 * An int or long or float, for a real column.
457 * A bool, for a boolean column.
459 * A str or unicode object, for a string column.
461 * A uuid.UUID object, for a UUID column without a ref_table.
463 * An object represented the referenced row, for a UUID column with
464 a ref_table. (For the Idl, this object will be an ovs.db.idl.Row
467 If some error occurs (e.g. the database server's idea of the column
468 is different from the IDL's idea), then the default value for the
469 scalar type is used (see Atom.default()).
471 - Otherwise, if the type is not a map, then the value is a Python list
472 whose elements have the types described above.
474 - Otherwise, the type is a map, and the value is a Python dict that
475 maps from key to value, with key and value types determined as
478 'uuid_to_row' must be a function that takes a value and an
479 ovs.db.types.BaseType and translates UUIDs into row objects."""
480 if self
.type.is_scalar():
481 value
= uuid_to_row(self
.as_scalar(), self
.type.key
)
483 return self
.type.key
.default()
486 elif self
.type.is_map():
488 for k
, v
in self
.values
.items():
489 dk
= uuid_to_row(k
.value
, self
.type.key
)
490 dv
= uuid_to_row(v
.value
, self
.type.value
)
491 if dk
is not None and dv
is not None:
496 for k
in self
.values
:
497 dk
= uuid_to_row(k
.value
, self
.type.key
)
503 def from_python(type_
, value
, row_to_uuid
):
504 """Returns a new Datum with the given ovs.db.types.Type 'type_'. The
505 new datum's value is taken from 'value', which must take the form
506 described as a valid return value from Datum.to_python() for 'type'.
508 Each scalar value within 'value' is initially passed through
509 'row_to_uuid', which should convert objects that represent rows (if
510 any) into uuid.UUID objects and return other data unchanged.
512 Raises ovs.db.error.Error if 'value' is not in an appropriate form for
515 if isinstance(value
, dict):
516 for k
, v
in value
.items():
517 ka
= Atom
.from_python(type_
.key
, row_to_uuid(k
))
518 va
= Atom
.from_python(type_
.value
, row_to_uuid(v
))
520 elif isinstance(value
, (list, set, tuple)):
522 ka
= Atom
.from_python(type_
.key
, row_to_uuid(k
))
525 ka
= Atom
.from_python(type_
.key
, row_to_uuid(value
))
528 datum
= Datum(type_
, d
)
529 datum
.check_constraints()
530 if not datum
.conforms_to_type():
531 raise error
.Error("%d values when type requires between %d and %d"
532 % (len(d
), type_
.n_min
, type_
.n_max
))
536 def __getitem__(self
, key
):
537 if not isinstance(key
, Atom
):
539 if not self
.type.is_map():
541 elif key
not in self
.values
:
544 return self
.values
[key
].value
546 def get(self
, key
, default
=None):
547 if not isinstance(key
, Atom
):
549 if key
in self
.values
:
550 return self
.values
[key
].value
555 return self
.to_string()
557 def conforms_to_type(self
):
559 return self
.type.n_min
<= n
<= self
.type.n_max
561 def cDeclareDatum(self
, name
):
564 return ["static struct ovsdb_datum %s = { .n = 0 };"]
566 s
= ["static union ovsdb_atom %s_keys[%d] = {" % (name
, n
)]
567 for key
in sorted(self
.values
):
568 s
+= [" { %s }," % key
.cInitAtom(key
)]
572 s
= ["static union ovsdb_atom %s_values[%d] = {" % (name
, n
)]
573 for k
, v
in sorted(self
.values
.items()):
574 s
+= [" { %s }," % v
.cInitAtom(v
)]
577 s
+= ["static struct ovsdb_datum %s = {" % name
]
578 s
+= [" .n = %d," % n
]
579 s
+= [" .keys = %s_keys," % name
]
581 s
+= [" .values = %s_values," % name
]