[pygobject] GVariant add support to maybe types



commit 812cdbfec0c8c9866c339a5eb01268672514d447
Author: Alberto Ruiz <aruiz gnome org>
Date:   Thu Jan 18 18:29:33 2018 +0100

    GVariant add support to maybe types
    
    While adding support to maybe types I realized that the GVariant
    override was replicating a lot of logic from GVariant to parse
    format strings by itself. This patch simplifies the creation of
    GVariants from Python types and relies on GVariantType to interpret
    the format string and walk through its items.
    
    This patch also expands test coverage for maybe types.

 gi/overrides/GLib.py         | 164 ++++++++++++-------------------------------
 tests/test_overrides_glib.py |  49 ++++++++++---
 2 files changed, 86 insertions(+), 127 deletions(-)
---
diff --git a/gi/overrides/GLib.py b/gi/overrides/GLib.py
index 52b6af3b..339d74a1 100644
--- a/gi/overrides/GLib.py
+++ b/gi/overrides/GLib.py
@@ -102,128 +102,53 @@ class _VariantCreator(object):
         'v': GLib.Variant.new_variant,
     }
 
-    def _create(self, format, args):
-        """Create a GVariant object from given format and argument list.
+    def _create(self, format, value):
+        """Create a GVariant object from given format and a value that matches
+        the format.
 
         This method recursively calls itself for complex structures (arrays,
         dictionaries, boxed).
 
-        Return a tuple (variant, rest_format, rest_args) with the generated
-        GVariant, the remainder of the format string, and the remainder of the
-        arguments.
+        Returns the generated GVariant.
 
-        If args is None, then this won't actually consume any arguments, and
-        just parse the format string and generate empty GVariant structures.
-        This is required for creating empty dictionaries or arrays.
+        If value is None it will generate an empty GVariant container type.
         """
-        # leaves (simple types)
-        constructor = self._LEAF_CONSTRUCTORS.get(format[0])
-        if constructor:
-            if args is not None:
-                if not args:
-                    raise TypeError('not enough arguments for GVariant format string')
-                v = constructor(args[0])
-                return (v, format[1:], args[1:])
-            else:
-                return (None, format[1:], None)
-
-        if format[0] == '(':
-            return self._create_tuple(format, args)
-
-        if format.startswith('a{'):
-            return self._create_dict(format, args)
-
-        if format[0] == 'a':
-            return self._create_array(format, args)
-
-        raise NotImplementedError('cannot handle GVariant type ' + format)
-
-    def _create_tuple(self, format, args):
-        """Handle the case where the outermost type of format is a tuple."""
-
-        format = format[1:]  # eat the '('
-        if args is None:
-            # empty value: we need to call _create() to parse the subtype
-            rest_format = format
-            while rest_format:
-                if rest_format.startswith(')'):
-                    break
-                rest_format = self._create(rest_format, None)[1]
-            else:
-                raise TypeError('tuple type string not closed with )')
-
-            rest_format = rest_format[1:]  # eat the )
-            return (None, rest_format, None)
+        gvtype = GLib.VariantType(format)
+        if format in self._LEAF_CONSTRUCTORS:
+            return self._LEAF_CONSTRUCTORS[format](value)
+
+        # Since we discarded all leaf types, this must be a container
+        builder = GLib.VariantBuilder.new(gvtype)
+        if value is None:
+            return builder.end()
+
+        if gvtype.is_maybe():
+            builder.add_value(self._create(gvtype.element().dup_string(), value))
+            return builder.end()
+
+        if not isinstance(value, (dict, tuple, list)):
+            raise TypeError("Could not create array, tuple or dictionary entry from non iterable value %s 
%s" %
+                    (format, value))
+
+        if gvtype.is_tuple() and gvtype.n_items() != len(value):
+            raise TypeError("Tuple mismatches value's number of elements %s %s" % (format, value))
+        if gvtype.is_dict_entry() and len(value) != 2:
+            raise TypeError("Dictionary entries must have two elements %s %s" % (format, value))
+
+        if gvtype.is_array():
+            element_type = gvtype.element().dup_string()
+            if isinstance(value, dict):
+                value = value.items()
+            for i in value:
+                builder.add_value(self._create(element_type, i))
         else:
-            if not args or not isinstance(args[0], tuple):
-                raise TypeError('expected tuple argument')
-
-            builder = GLib.VariantBuilder.new(variant_type_from_string('r'))
-            for i in range(len(args[0])):
-                if format.startswith(')'):
-                    raise TypeError('too many arguments for tuple signature')
-
-                (v, format, _) = self._create(format, args[0][i:])
-                builder.add_value(v)
-            args = args[1:]
-            if not format.startswith(')'):
-                raise TypeError('tuple type string not closed with )')
-
-            rest_format = format[1:]  # eat the )
-            return (builder.end(), rest_format, args)
-
-    def _create_dict(self, format, args):
-        """Handle the case where the outermost type of format is a dict."""
-
-        builder = None
-        if args is None or not args[0]:
-            # empty value: we need to call _create() to parse the subtype,
-            # and specify the element type precisely
-            rest_format = self._create(format[2:], None)[1]
-            rest_format = self._create(rest_format, None)[1]
-            if not rest_format.startswith('}'):
-                raise TypeError('dictionary type string not closed with }')
-            rest_format = rest_format[1:]  # eat the }
-            element_type = format[:len(format) - len(rest_format)]
-            builder = GLib.VariantBuilder.new(variant_type_from_string(element_type))
-        else:
-            builder = GLib.VariantBuilder.new(variant_type_from_string('a{?*}'))
-            for k, v in args[0].items():
-                (key_v, rest_format, _) = self._create(format[2:], [k])
-                (val_v, rest_format, _) = self._create(rest_format, [v])
-
-                if not rest_format.startswith('}'):
-                    raise TypeError('dictionary type string not closed with }')
-                rest_format = rest_format[1:]  # eat the }
-
-                entry = GLib.VariantBuilder.new(variant_type_from_string('{?*}'))
-                entry.add_value(key_v)
-                entry.add_value(val_v)
-                builder.add_value(entry.end())
-
-        if args is not None:
-            args = args[1:]
-        return (builder.end(), rest_format, args)
-
-    def _create_array(self, format, args):
-        """Handle the case where the outermost type of format is an array."""
-
-        builder = None
-        if args is None or not args[0]:
-            # empty value: we need to call _create() to parse the subtype,
-            # and specify the element type precisely
-            rest_format = self._create(format[1:], None)[1]
-            element_type = format[:len(format) - len(rest_format)]
-            builder = GLib.VariantBuilder.new(variant_type_from_string(element_type))
-        else:
-            builder = GLib.VariantBuilder.new(variant_type_from_string('a*'))
-            for i in range(len(args[0])):
-                (v, rest_format, _) = self._create(format[1:], args[0][i:])
-                builder.add_value(v)
-        if args is not None:
-            args = args[1:]
-        return (builder.end(), rest_format, args)
+            remainer_format = format[1:]
+            for i in value:
+                dup = variant_type_from_string(remainer_format).dup_string()
+                builder.add_value(self._create(dup, i))
+                remainer_format = remainer_format[len(dup):]
 
+        return builder.end()
 
 class Variant(GLib.Variant):
     def __new__(cls, format_string, value):
@@ -238,10 +163,10 @@ class Variant(GLib.Variant):
           GLib.Variant('(asa{sv})', ([], {'foo': GLib.Variant('b', True),
                                           'bar': GLib.Variant('i', 2)}))
         """
+        if not GLib.VariantType.string_is_valid (format_string):
+            raise TypeError("Invalid GVariant format string '%s'", format_string)
         creator = _VariantCreator()
-        (v, rest_format, _) = creator._create(format_string, [value])
-        if rest_format:
-            raise TypeError('invalid remaining format string: "%s"' % rest_format)
+        v = creator._create(format_string, value)
         v.format_string = format_string
         return v
 
@@ -338,8 +263,9 @@ class Variant(GLib.Variant):
 
         # maybe
         if self.get_type_string().startswith('m'):
-            m = self.get_maybe()
-            return m.unpack() if m else None
+            if not self.n_children():
+                return None
+            return self.get_child_value(0).unpack()
 
         raise NotImplementedError('unsupported GVariant type ' + self.get_type_string())
 
diff --git a/tests/test_overrides_glib.py b/tests/test_overrides_glib.py
index 891923eb..a7b3f1fa 100644
--- a/tests/test_overrides_glib.py
+++ b/tests/test_overrides_glib.py
@@ -140,6 +140,35 @@ class TestGVariant(unittest.TestCase):
         self.assertEqual(variant.get_type_string(), 'aai')
         self.assertEqual(variant.unpack(), [[1, 2], [3, 4, 5]])
 
+    def test_create_maybe(self):
+        variant = GLib.Variant('mai', None)
+        self.assertEqual(variant.get_type_string(), 'mai')
+        self.assertEqual(variant.n_children(), 0)
+        self.assertEqual(variant.unpack(), None)
+
+        variant = GLib.Variant('mai', [])
+        self.assertEqual(variant.get_type_string(), 'mai')
+        self.assertEqual(variant.n_children(), 1)
+
+        variant = GLib.Variant('mami', [None])
+        self.assertEqual(variant.get_type_string(), 'mami')
+        self.assertEqual(variant.n_children(), 1)
+
+        variant = GLib.Variant('mami', [None, 13, None])
+        self.assertEqual(variant.get_type_string(), 'mami')
+        self.assertEqual(variant.n_children(), 1)
+        array = variant.get_child_value(0)
+        self.assertEqual(array.n_children(), 3)
+
+        element = array.get_child_value(0)
+        self.assertEqual(element.n_children(), 0)
+        element = array.get_child_value(1)
+        self.assertEqual(element.n_children(), 1)
+        self.assertEqual(element.get_child_value(0).get_int32(), 13)
+        element = array.get_child_value(2)
+        self.assertEqual(element.n_children(), 0)
+
+
     def test_create_complex(self):
         variant = GLib.Variant('(as)', ([],))
         self.assertEqual(variant.get_type_string(), '(as)')
@@ -232,8 +261,8 @@ class TestGVariant(unittest.TestCase):
         self.assertRaises(TypeError, GLib.Variant, '(ss)', 'mec', 'mac')
         self.assertRaises(TypeError, GLib.Variant, '(s)', 'hello')
 
-        # unimplemented data type
-        self.assertRaises(NotImplementedError, GLib.Variant, 'Q', 1)
+        # invalid format string
+        self.assertRaises(TypeError, GLib.Variant, 'Q', 1)
 
         # invalid types
         self.assertRaises(TypeError, GLib.Variant, '(ii', (42, 3))
@@ -280,12 +309,16 @@ class TestGVariant(unittest.TestCase):
         self.assertEqual(res, {'key1': 1, 'key2': 2})
 
         # maybe
-        v = GLib.Variant.new_maybe(GLib.VariantType.new('i'), GLib.Variant('i', 1))
-        res = v.unpack()
-        self.assertEqual(res, 1)
-        v = GLib.Variant.new_maybe(GLib.VariantType.new('i'), None)
-        res = v.unpack()
-        self.assertEqual(res, None)
+        v = GLib.Variant('mi', 1)
+        self.assertEqual(v.unpack(), 1)
+        v = GLib.Variant('mi', None)
+        self.assertEqual(v.unpack(), None)
+        v = GLib.Variant('mai', [])
+        self.assertEqual(v.unpack(), [])
+        v = GLib.Variant('m()', ())
+        self.assertEqual(v.unpack(), ())
+        v = GLib.Variant('mami', [None, 1, None])
+        self.assertEqual(v.unpack(), [None, 1, None])
 
     def test_iteration(self):
         # array index access


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]