[pygobject] [gi] Register GType for non-GType enums and flags at runtime.



commit 96f7d1aed732db09a74cd463ed894b7347dbcb15
Author: Laszlo Pandy <lpandy src gnome org>
Date:   Sat Feb 19 23:11:25 2011 +0100

    [gi] Register GType for non-GType enums and flags at runtime.
    
    Note: rebuild of gobject-introspection is required for new tests.
    
    Previously non-GType enums used a separate type implemented in
    Python, and non-GType flags had no implementation at all. This
    removes the separate type for enums, and registers a new GType at
    runtime if there isn't one.
    
    This allows non-GType enums and flags to use the same Python type
    as GType enums and flags. This removes duplication of code, and
    make both kinds behave identically.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=642607

 gi/gimodule.c      |  117 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 gi/module.py       |   24 +++++++----
 gi/pygi-info.c     |   15 +++++++
 gi/types.py        |   16 -------
 gobject/pygflags.c |   13 ++++--
 tests/test_gi.py   |   35 +++++++++++++++
 6 files changed, 191 insertions(+), 29 deletions(-)
---
diff --git a/gi/gimodule.c b/gi/gimodule.c
index 55c9d8b..2492668 100644
--- a/gi/gimodule.c
+++ b/gi/gimodule.c
@@ -51,6 +51,63 @@ _wrap_pyg_enum_add (PyObject *self,
 }
 
 static PyObject *
+_wrap_pyg_enum_register_new_gtype_and_add (PyObject *self,
+                                           PyObject *args,
+                                           PyObject *kwargs)
+{
+    static char *kwlist[] = { "info", NULL };
+    PyGIBaseInfo *py_info;
+    GIEnumInfo *info;
+    gint n_values;
+    GEnumValue *g_enum_values;
+    GType g_type;
+    const gchar *type_name;
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs,
+                                      "O:enum_add_make_new_gtype",
+                                      kwlist, (PyObject *)&py_info)) {
+        return NULL;
+    }
+
+    if (!GI_IS_ENUM_INFO (py_info->info) ||
+            g_base_info_get_type ((GIBaseInfo *) py_info->info) != GI_INFO_TYPE_ENUM) {
+        PyErr_SetString (PyExc_TypeError, "info must be an EnumInfo with info type GI_INFO_TYPE_ENUM");
+        return NULL;
+    }
+
+    info = (GIEnumInfo *)py_info->info;
+    n_values = g_enum_info_get_n_values (info);
+    g_enum_values = g_new0 (GEnumValue, n_values + 1);
+
+    for (int i=0; i < n_values; i++) {
+        GIValueInfo *value_info;
+        GEnumValue *enum_value;
+        const gchar *name;
+
+        value_info = g_enum_info_get_value (info, i);
+        name = g_base_info_get_name ((GIBaseInfo *) value_info);
+
+        enum_value = &g_enum_values[i];
+        enum_value->value_nick = g_strdup (name);
+        /* TODO: get "c:identifier" attribute for value_name once GI exposes it in the typelib */
+        enum_value->value_name = enum_value->value_nick;
+        enum_value->value = g_value_info_get_value (value_info);
+
+        g_base_info_unref ((GIBaseInfo *) value_info);
+    }
+
+    g_enum_values[n_values].value = 0;
+    g_enum_values[n_values].value_nick = NULL;
+    g_enum_values[n_values].value_name = NULL;
+
+    type_name = g_base_info_get_name ((GIBaseInfo *) info);
+    type_name = g_strdup (type_name);
+    g_type = g_enum_register_static (type_name, g_enum_values);
+
+    return pyg_enum_add (NULL, g_type_name (g_type), NULL, g_type);
+}
+
+static PyObject *
 _wrap_pyg_flags_add (PyObject *self,
                      PyObject *args,
                      PyObject *kwargs)
@@ -74,6 +131,64 @@ _wrap_pyg_flags_add (PyObject *self,
 }
 
 static PyObject *
+_wrap_pyg_flags_register_new_gtype_and_add (PyObject *self,
+                                            PyObject *args,
+                                            PyObject *kwargs)
+{
+    static char *kwlist[] = { "info", NULL };
+    PyGIBaseInfo *py_info;
+    GIEnumInfo *info;
+    gint n_values;
+    GFlagsValue *g_flags_values;
+    GType g_type;
+    const gchar *type_name;
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs,
+                                      "O:flags_add_make_new_gtype",
+                                      kwlist, (PyObject *)&py_info)) {
+        return NULL;
+    }
+
+    if (!GI_IS_ENUM_INFO (py_info->info) ||
+            g_base_info_get_type ((GIBaseInfo *) py_info->info) != GI_INFO_TYPE_FLAGS) {
+        PyErr_SetString (PyExc_TypeError, "info must be an EnumInfo with info type GI_INFO_TYPE_FLAGS");
+        return NULL;
+    }
+
+    info = (GIEnumInfo *)py_info->info;
+    n_values = g_enum_info_get_n_values (info);
+    g_flags_values = g_new0 (GFlagsValue, n_values + 1);
+
+    for (int i=0; i < n_values; i++) {
+        GIValueInfo *value_info;
+        GFlagsValue *flags_value;
+        const gchar *name;
+
+        value_info = g_enum_info_get_value (info, i);
+        name = g_base_info_get_name ((GIBaseInfo *) value_info);
+
+        flags_value = &g_flags_values[i];
+        flags_value->value_nick = g_strdup (name);
+        /* TODO: get "c:identifier" attribute for value_name once GI exposes it in the typelib */
+        flags_value->value_name = flags_value->value_nick;
+        flags_value->value = g_value_info_get_value (value_info);
+
+        g_base_info_unref ((GIBaseInfo *) value_info);
+    }
+
+    g_flags_values[n_values].value = 0;
+    g_flags_values[n_values].value_nick = NULL;
+    g_flags_values[n_values].value_name = NULL;
+
+    type_name = g_base_info_get_name ((GIBaseInfo *) info);
+    type_name = g_strdup (type_name);
+    g_type = g_flags_register_static (type_name, g_flags_values);
+
+    return pyg_flags_add (NULL, g_type_name (g_type), NULL, g_type);
+}
+
+
+static PyObject *
 _wrap_pyg_set_object_has_new_constructor (PyObject *self,
                                           PyObject *args,
                                           PyObject *kwargs)
@@ -353,7 +468,9 @@ _wrap_pyg_variant_type_from_string (PyObject *self, PyObject *args)
 
 static PyMethodDef _gi_functions[] = {
     { "enum_add", (PyCFunction) _wrap_pyg_enum_add, METH_VARARGS | METH_KEYWORDS },
+    { "enum_register_new_gtype_and_add", (PyCFunction) _wrap_pyg_enum_register_new_gtype_and_add, METH_VARARGS | METH_KEYWORDS },
     { "flags_add", (PyCFunction) _wrap_pyg_flags_add, METH_VARARGS | METH_KEYWORDS },
+    { "flags_register_new_gtype_and_add", (PyCFunction) _wrap_pyg_flags_register_new_gtype_and_add, METH_VARARGS | METH_KEYWORDS },
 
     { "set_object_has_new_constructor", (PyCFunction) _wrap_pyg_set_object_has_new_constructor, METH_VARARGS | METH_KEYWORDS },
     { "register_interface_info", (PyCFunction) _wrap_pyg_register_interface_info, METH_VARARGS },
diff --git a/gi/module.py b/gi/module.py
index 9b935ed..133f3ed 100644
--- a/gi/module.py
+++ b/gi/module.py
@@ -40,12 +40,13 @@ from ._gi import \
     Struct, \
     Boxed, \
     enum_add, \
-    flags_add
+    enum_register_new_gtype_and_add, \
+    flags_add, \
+    flags_register_new_gtype_and_add
 from .types import \
     GObjectMeta, \
     StructMeta, \
-    Function, \
-    Enum
+    Function
 
 repository = Repository.get_default()
 
@@ -102,13 +103,18 @@ class IntrospectionModule(object):
             wrapper = g_type.pytype
 
             if wrapper is None:
-                if g_type.is_a(gobject.TYPE_ENUM):
-                    wrapper = enum_add(g_type)
-                elif g_type.is_a(gobject.TYPE_NONE):
-                    # An enum with a GType of None is an enum without GType
-                    wrapper = type(info.get_name(), (Enum,), {})
+                if info.is_flags():
+                    if g_type.is_a(gobject.TYPE_FLAGS):
+                        wrapper = flags_add(g_type)
+                    else:
+                        assert g_type == gobject.TYPE_NONE
+                        wrapper = flags_register_new_gtype_and_add(info)
                 else:
-                    wrapper = flags_add(g_type)
+                    if g_type.is_a(gobject.TYPE_ENUM):
+                        wrapper = enum_add(g_type)
+                    else:
+                        assert g_type == gobject.TYPE_NONE
+                        wrapper = enum_register_new_gtype_and_add(info)
 
                 wrapper.__info__ = info
                 wrapper.__module__ = 'gi.repository.' + info.get_namespace()
diff --git a/gi/pygi-info.c b/gi/pygi-info.c
index f5dd69f..1bfd7d8 100644
--- a/gi/pygi-info.c
+++ b/gi/pygi-info.c
@@ -923,8 +923,23 @@ _wrap_g_enum_info_get_values (PyGIBaseInfo *self)
     return infos;
 }
 
+static PyObject *
+_wrap_g_enum_info_is_flags (PyGIBaseInfo *self)
+{
+    GIInfoType info_type = g_base_info_get_type ((GIBaseInfo *) self->info);
+
+    if (info_type == GI_INFO_TYPE_ENUM) {
+        Py_RETURN_FALSE;
+    } else if (info_type == GI_INFO_TYPE_FLAGS) {
+        Py_RETURN_TRUE;
+    } else {
+        g_assert_not_reached();
+    }
+}
+
 static PyMethodDef _PyGIEnumInfo_methods[] = {
     { "get_values", (PyCFunction) _wrap_g_enum_info_get_values, METH_NOARGS },
+    { "is_flags", (PyCFunction) _wrap_g_enum_info_is_flags, METH_NOARGS },
     { NULL, NULL, 0 }
 };
 
diff --git a/gi/types.py b/gi/types.py
index 37cf499..c2a8b35 100644
--- a/gi/types.py
+++ b/gi/types.py
@@ -250,19 +250,3 @@ class StructMeta(type, MetaClassHelper):
                     not method_info.get_arguments():
                 cls.__new__ = staticmethod(Constructor(method_info))
                 break
-
-class Enum(int):
-    # Only subclasses of this type should be instantiated.
-    # Each subclass requires an __info__ attribute,
-    # which is not declared here because enums do not share the same gi type.
-    def __init__(self, value):
-        int.__init__(value)
-
-    def __repr__(self):
-        value_name = str(self)
-        for value_info in self.__info__.get_values():
-            if self == value_info.get_value():
-                value_name = value_info.get_name().upper()
-        return "<enum %s of type %s.%s>" % (value_name,
-                                            self.__info__.get_namespace(),
-                                            self.__info__.get_name())
diff --git a/gobject/pygflags.c b/gobject/pygflags.c
index 936f314..ce99a86 100644
--- a/gobject/pygflags.c
+++ b/gobject/pygflags.c
@@ -172,13 +172,18 @@ pyg_flags_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
 
     pyint = PYGLIB_PyLong_FromLong(value);
     ret = PyDict_GetItem(values, pyint);
+    if (!ret) {
+        PyErr_Clear();
+
+        ret = pyg_flags_val_new((PyObject *)type, gtype, pyint);
+        g_assert(ret != NULL);
+    } else {
+        Py_INCREF(ret);
+    }
+
     Py_DECREF(pyint);
     Py_DECREF(values);
 
-    if (ret)
-        Py_INCREF(ret);
-    else
-        PyErr_Format(PyExc_ValueError, "invalid flag value: %ld", value);
     return ret;
 }
 
diff --git a/tests/test_gi.py b/tests/test_gi.py
index 22ff2c4..c12863b 100644
--- a/tests/test_gi.py
+++ b/tests/test_gi.py
@@ -1018,10 +1018,15 @@ class TestGFlags(unittest.TestCase):
         self.assertTrue(isinstance(GIMarshallingTests.Flags.VALUE1, GIMarshallingTests.Flags))
         self.assertTrue(isinstance(GIMarshallingTests.Flags.VALUE2, GIMarshallingTests.Flags))
         self.assertTrue(isinstance(GIMarshallingTests.Flags.VALUE3, GIMarshallingTests.Flags))
+        # __or__() operation should still return an instance, not an int.
+        self.assertTrue(isinstance(GIMarshallingTests.Flags.VALUE1 | GIMarshallingTests.Flags.VALUE2,
+                                   GIMarshallingTests.Flags))
         self.assertEquals(1 << 1, GIMarshallingTests.Flags.VALUE2)
 
     def test_flags_in(self):
         GIMarshallingTests.flags_in(GIMarshallingTests.Flags.VALUE2)
+        # result of __or__() operation should still be valid instance, not an int.
+        GIMarshallingTests.flags_in(GIMarshallingTests.Flags.VALUE2 | GIMarshallingTests.Flags.VALUE2)
         GIMarshallingTests.flags_in_zero(Number(0))
 
         self.assertRaises(TypeError, GIMarshallingTests.flags_in, 1 << 1)
@@ -1037,6 +1042,36 @@ class TestGFlags(unittest.TestCase):
         self.assertTrue(isinstance(flags, GIMarshallingTests.Flags))
         self.assertEquals(flags, GIMarshallingTests.Flags.VALUE1)
 
+class TestNoTypeFlags(unittest.TestCase):
+
+    def test_flags(self):
+        self.assertTrue(issubclass(GIMarshallingTests.NoTypeFlags, GObject.GFlags))
+        self.assertTrue(isinstance(GIMarshallingTests.NoTypeFlags.VALUE1, GIMarshallingTests.NoTypeFlags))
+        self.assertTrue(isinstance(GIMarshallingTests.NoTypeFlags.VALUE2, GIMarshallingTests.NoTypeFlags))
+        self.assertTrue(isinstance(GIMarshallingTests.NoTypeFlags.VALUE3, GIMarshallingTests.NoTypeFlags))
+        # __or__() operation should still return an instance, not an int.
+        self.assertTrue(isinstance(GIMarshallingTests.NoTypeFlags.VALUE1 | GIMarshallingTests.NoTypeFlags.VALUE2,
+                                   GIMarshallingTests.NoTypeFlags))
+        self.assertEquals(1 << 1, GIMarshallingTests.NoTypeFlags.VALUE2)
+
+    def test_flags_in(self):
+        GIMarshallingTests.no_type_flags_in(GIMarshallingTests.NoTypeFlags.VALUE2)
+        GIMarshallingTests.no_type_flags_in(GIMarshallingTests.NoTypeFlags.VALUE2 | GIMarshallingTests.NoTypeFlags.VALUE2)
+        GIMarshallingTests.no_type_flags_in_zero(Number(0))
+
+        self.assertRaises(TypeError, GIMarshallingTests.no_type_flags_in, 1 << 1)
+        self.assertRaises(TypeError, GIMarshallingTests.no_type_flags_in, 'GIMarshallingTests.NoTypeFlags.VALUE2')
+
+    def test_flags_out(self):
+        flags = GIMarshallingTests.no_type_flags_out()
+        self.assertTrue(isinstance(flags, GIMarshallingTests.NoTypeFlags))
+        self.assertEquals(flags, GIMarshallingTests.NoTypeFlags.VALUE2)
+
+    def test_flags_inout(self):
+        flags = GIMarshallingTests.no_type_flags_inout(GIMarshallingTests.NoTypeFlags.VALUE2)
+        self.assertTrue(isinstance(flags, GIMarshallingTests.NoTypeFlags))
+        self.assertEquals(flags, GIMarshallingTests.NoTypeFlags.VALUE1)
+
 
 class TestStructure(unittest.TestCase):
 



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