Global replace of Nicira Networks.
[sliver-openvswitch.git] / python / ovs / db / data.py
index f71def9..55e7a73 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2009, 2010, 2011 Nicira Networks
+# Copyright (c) 2009, 2010, 2011 Nicira, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -25,10 +25,12 @@ import ovs.db.parser
 from ovs.db import error
 import ovs.db.types
 
+
 class ConstraintViolation(error.Error):
     def __init__(self, msg, json=None):
         error.Error.__init__(self, msg, json, tag="constraint violation")
 
+
 def escapeCString(src):
     dst = []
     for c in src:
@@ -55,9 +57,11 @@ def escapeCString(src):
             dst.append(c)
     return ''.join(dst)
 
+
 def returnUnchanged(x):
     return x
 
+
 class Atom(object):
     def __init__(self, type_, value=None):
         self.type = type_
@@ -81,6 +85,18 @@ class Atom(object):
 
     @staticmethod
     def default(type_):
+        """Returns the default value for the given type_, which must be an
+        instance of ovs.db.types.AtomicType.
+
+        The default value for each atomic type is;
+
+          - 0, for integer or real atoms.
+
+          - False, for a boolean atom.
+
+          - "", for a string atom.
+
+          - The all-zeros UUID, for a UUID atom."""
         return Atom(type_)
 
     def is_default(self):
@@ -91,9 +107,11 @@ class Atom(object):
         type_ = base.type
         json = ovs.db.parser.float_to_int(json)
         if ((type_ == ovs.db.types.IntegerType and type(json) in [int, long])
-            or (type_ == ovs.db.types.RealType and type(json) in [int, long, float])
+            or (type_ == ovs.db.types.RealType
+                and type(json) in [int, long, float])
             or (type_ == ovs.db.types.BooleanType and type(json) == bool)
-            or (type_ == ovs.db.types.StringType and type(json) in [str, unicode])):
+            or (type_ == ovs.db.types.StringType
+                and type(json) in [str, unicode])):
             atom = Atom(type_, json)
         elif type_ == ovs.db.types.UuidType:
             atom = Atom(type_, ovs.ovsuuid.from_json(json, symtab))
@@ -102,12 +120,21 @@ class Atom(object):
         atom.check_constraints(base)
         return atom
 
+    @staticmethod
+    def from_python(base, value):
+        value = ovs.db.parser.float_to_int(value)
+        if type(value) in base.type.python_types:
+            atom = Atom(base.type, value)
+        else:
+            raise error.Error("expected %s, got %s" % (base.type, type(value)))
+        atom.check_constraints(base)
+        return atom
+
     def check_constraints(self, base):
         """Checks whether 'atom' meets the constraints (if any) defined in
         'base' and raises an ovs.db.error.Error if any constraint is violated.
 
         'base' and 'atom' must have the same type.
-
         Checking UUID constraints is deferred to transaction commit time, so
         this function does nothing for UUID constraints."""
         assert base.type == self.type
@@ -144,7 +171,7 @@ class Atom(object):
                 raise ConstraintViolation(
                     '"%s" length %d is greater than maximum allowed '
                     'length %d' % (s, length, base.max_length))
-    
+
     def to_json(self):
         if self.type == ovs.db.types.UuidType:
             return ovs.ovsuuid.to_json(self.value)
@@ -183,6 +210,7 @@ class Atom(object):
             return self.value.value
 
     __need_quotes_re = re.compile("$|true|false|[^_a-zA-Z]|.*[^-._a-zA-Z]")
+
     @staticmethod
     def __string_needs_quotes(s):
         return Atom.__need_quotes_re.match(s)
@@ -221,6 +249,7 @@ class Atom(object):
             raise TypeError
         return Atom(t, x)
 
+
 class Datum(object):
     def __init__(self, type_, values={}):
         self.type = type_
@@ -274,9 +303,9 @@ class Datum(object):
         """Parses 'json' as a datum of the type described by 'type'.  If
         successful, returns a new datum.  On failure, raises an
         ovs.db.error.Error.
-        
+
         Violations of constraints expressed by 'type' are treated as errors.
-        
+
         If 'symtab' is nonnull, then named UUIDs in 'symtab' are accepted.
         Refer to ovsdb/SPECS for information about this, and for the syntax
         that this function accepts."""
@@ -362,7 +391,10 @@ class Datum(object):
             return [[k.value, v.value] for k, v in self.values.iteritems()]
         else:
             return [k.value for k in self.values.iterkeys()]
-        
+
+    def as_dict(self):
+        return dict(self.values)
+
     def as_scalar(self):
         if len(self.values) == 1:
             if self.type.is_map():
@@ -373,6 +405,97 @@ class Datum(object):
         else:
             return None
 
+    def to_python(self, uuid_to_row):
+        """Returns this datum's value converted into a natural Python
+        representation of this datum's type, according to the following
+        rules:
+
+        - If the type has exactly one value and it is not a map (that is,
+          self.type.is_scalar() returns True), then the value is:
+
+            * An int or long, for an integer column.
+
+            * An int or long or float, for a real column.
+
+            * A bool, for a boolean column.
+
+            * A str or unicode object, for a string column.
+
+            * A uuid.UUID object, for a UUID column without a ref_table.
+
+            * An object represented the referenced row, for a UUID column with
+              a ref_table.  (For the Idl, this object will be an ovs.db.idl.Row
+              object.)
+
+          If some error occurs (e.g. the database server's idea of the column
+          is different from the IDL's idea), then the default value for the
+          scalar type is used (see Atom.default()).
+
+        - Otherwise, if the type is not a map, then the value is a Python list
+          whose elements have the types described above.
+
+        - Otherwise, the type is a map, and the value is a Python dict that
+          maps from key to value, with key and value types determined as
+          described above.
+
+        'uuid_to_row' must be a function that takes a value and an
+        ovs.db.types.BaseType and translates UUIDs into row objects."""
+        if self.type.is_scalar():
+            value = uuid_to_row(self.as_scalar(), self.type.key)
+            if value is None:
+                return self.type.key.default()
+            else:
+                return value
+        elif self.type.is_map():
+            value = {}
+            for k, v in self.values.iteritems():
+                dk = uuid_to_row(k.value, self.type.key)
+                dv = uuid_to_row(v.value, self.type.value)
+                if dk is not None and dv is not None:
+                    value[dk] = dv
+            return value
+        else:
+            s = set()
+            for k in self.values:
+                dk = uuid_to_row(k.value, self.type.key)
+                if dk is not None:
+                    s.add(dk)
+            return sorted(s)
+
+    @staticmethod
+    def from_python(type_, value, row_to_uuid):
+        """Returns a new Datum with the given ovs.db.types.Type 'type_'.  The
+        new datum's value is taken from 'value', which must take the form
+        described as a valid return value from Datum.to_python() for 'type'.
+
+        Each scalar value within 'value' is initally passed through
+        'row_to_uuid', which should convert objects that represent rows (if
+        any) into uuid.UUID objects and return other data unchanged.
+
+        Raises ovs.db.error.Error if 'value' is not in an appropriate form for
+        'type_'."""
+        d = {}
+        if type(value) == dict:
+            for k, v in value.iteritems():
+                ka = Atom.from_python(type_.key, row_to_uuid(k))
+                va = Atom.from_python(type_.value, row_to_uuid(v))
+                d[ka] = va
+        elif type(value) in (list, tuple):
+            for k in value:
+                ka = Atom.from_python(type_.key, row_to_uuid(k))
+                d[ka] = None
+        else:
+            ka = Atom.from_python(type_.key, row_to_uuid(value))
+            d[ka] = None
+
+        datum = Datum(type_, d)
+        datum.check_constraints()
+        if not datum.conforms_to_type():
+            raise error.Error("%d values when type requires between %d and %d"
+                              % (len(d), type_.n_min, type_.n_max))
+
+        return datum
+
     def __getitem__(self, key):
         if not isinstance(key, Atom):
             key = Atom.new(key)
@@ -390,7 +513,7 @@ class Datum(object):
             return self.values[key].value
         else:
             return default
-        
+
     def __str__(self):
         return self.to_string()
 
@@ -408,7 +531,7 @@ class Datum(object):
 
         for i, key in enumerate(sorted(self.values)):
             s += key.cInitAtom("%s->keys[%d]" % (var, i))
-        
+
         if self.type.value:
             s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
                   % (var, len(self.values), var)]