[pygobject] Add support for overriding GObject.Object



commit 3fcd987272a779e5ee9173a2c3a043b4b7475842
Author: Simon Feltman <sfeltman src gnome org>
Date:   Tue Oct 23 13:56:32 2012 -0700

    Add support for overriding GObject.Object
    
    Shift pygi module mechanics so the introspection generated 'Object'
    class becomes derived from the static GObject class. Add initial
    GObject.Object override which sets all static methods back essentially
    leapfrogging the introspection methods. This sets the stage for having
    the ability to remove static methods piecemeal in favor of
    introspection/python in future commits.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=672727

 gi/module.py             |   30 ++++++++++--------
 gi/overrides/GObject.py  |   74 ++++++++++++++++++++++++++++++++++++++++++++++
 gi/pygobject-external.h  |   10 +-----
 gi/types.py              |    7 ++++
 tests/test_gobject.py    |   31 ++++++++++++++++---
 tests/testhelpermodule.c |    2 +-
 6 files changed, 126 insertions(+), 28 deletions(-)
---
diff --git a/gi/module.py b/gi/module.py
index 3d18796..db5adf5 100644
--- a/gi/module.py
+++ b/gi/module.py
@@ -81,15 +81,15 @@ def get_parent_for_object(object_info):
     parent_object_info = object_info.get_parent()
 
     if not parent_object_info:
+        # Special case GObject.Object as being derived from the static GObject.
+        if object_info.get_namespace() == 'GObject' and object_info.get_name() == 'Object':
+            return GObject
+
         return object
 
     namespace = parent_object_info.get_namespace()
     name = parent_object_info.get_name()
 
-    # Workaround for GObject.Object and GObject.InitiallyUnowned.
-    if namespace == 'GObject' and name == 'Object' or name == 'InitiallyUnowned':
-        return GObject
-
     module = __import__('gi.repository.%s' % namespace, fromlist=[name])
     return getattr(module, name)
 
@@ -171,13 +171,6 @@ class IntrospectionModule(object):
         elif isinstance(info, RegisteredTypeInfo):
             g_type = info.get_g_type()
 
-            # Check if there is already a Python wrapper.
-            if g_type != TYPE_NONE:
-                type_ = g_type.pytype
-                if type_ is not None:
-                    self.__dict__[name] = type_
-                    return type_
-
             # Create a wrapper.
             if isinstance(info, ObjectInfo):
                 parent = get_parent_for_object(info)
@@ -204,6 +197,17 @@ class IntrospectionModule(object):
             else:
                 raise NotImplementedError(info)
 
+            # Check if there is already a Python wrapper that is not a parent class
+            # of the wrapper being created. If it is a parent, it is ok to clobber
+            # g_type.pytype with a new child class wrapper of the existing parent.
+            # Note that the return here never occurs under normal circumstances due
+            # to caching on the __dict__ itself.
+            if g_type != TYPE_NONE:
+                type_ = g_type.pytype
+                if type_ is not None and type_ not in bases:
+                    self.__dict__[name] = type_
+                    return type_
+
             name = info.get_name()
             dict_ = {
                 '__info__': info,
@@ -223,9 +227,9 @@ class IntrospectionModule(object):
         else:
             raise NotImplementedError(info)
 
-        # Cache the newly created attribute wrapper which will then be
+        # Cache the newly created wrapper which will then be
         # available directly on this introspection module instead of being
-        # retrieved through the __getattr__ we are currently in.
+        # lazily constructed through the __getattr__ we are currently in.
         self.__dict__[name] = wrapper
         return wrapper
 
diff --git a/gi/overrides/GObject.py b/gi/overrides/GObject.py
index fc63363..9435ba8 100644
--- a/gi/overrides/GObject.py
+++ b/gi/overrides/GObject.py
@@ -3,6 +3,7 @@
 #
 # Copyright (C) 2012 Canonical Ltd.
 # Author: Martin Pitt <martin pitt ubuntu com>
+# Copyright (C) 2012 Simon Feltman <sfeltman src gnome org>
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -24,7 +25,9 @@ from collections import namedtuple
 
 import gi.overrides
 import gi.module
+from gi.overrides import override
 from gi.repository import GLib
+
 from gi._gobject import _gobject
 from gi._gobject import propertyhelper
 from gi._gobject import signalhelper
@@ -276,6 +279,77 @@ def signal_query(id_or_name, type_=None):
 __all__.append('signal_query')
 
 
+class Object(GObjectModule.Object):
+    def _unsupported_method(self, *args, **kargs):
+        raise RuntimeError('This method is currently unsupported.')
+
+    def _unsupported_data_method(self, *args, **kargs):
+        raise RuntimeError('Data access methods are unsupported. '
+                           'Use normal Python attributes instead')
+
+    # Generic data methods are not needed in python as it can be handled
+    # with standard attribute access: https://bugzilla.gnome.org/show_bug.cgi?id=641944
+    get_data = _unsupported_data_method
+    get_qdata = _unsupported_data_method
+    set_data = _unsupported_data_method
+    steal_data = _unsupported_data_method
+    steal_qdata = _unsupported_data_method
+    replace_data = _unsupported_data_method
+    replace_qdata = _unsupported_data_method
+
+    # The following methods as unsupported until we verify
+    # they work as gi methods.
+    bind_property_full = _unsupported_method
+    compat_control = _unsupported_method
+    force_floating = _unsupported_method
+    interface_find_property = _unsupported_method
+    interface_install_property = _unsupported_method
+    interface_list_properties = _unsupported_method
+    is_floating = _unsupported_method
+    notify_by_pspec = _unsupported_method
+    ref = _unsupported_method
+    ref_count = _unsupported_method
+    ref_sink = _unsupported_method
+    run_dispose = _unsupported_method
+    unref = _unsupported_method
+    watch_closure = _unsupported_method
+
+    # The following methods are static APIs which need to leap frog the
+    # gi methods until we verify the gi methods can replace them.
+    get_property = _gobject.GObject.get_property
+    get_properties = _gobject.GObject.get_properties
+    set_property = _gobject.GObject.set_property
+    set_properties = _gobject.GObject.set_properties
+    bind_property = _gobject.GObject.bind_property
+    freeze_notify = _gobject.GObject.freeze_notify
+    notify = _gobject.GObject.notify
+    thaw_notify = _gobject.GObject.thaw_notify
+    connect = _gobject.GObject.connect
+    connect_after = _gobject.GObject.connect_after
+    connect_object = _gobject.GObject.connect_object
+    connect_object_after = _gobject.GObject.connect_object_after
+    disconnect = _gobject.GObject.disconnect
+    disconnect_by_func = _gobject.GObject.disconnect_by_func
+    handler_disconnect = _gobject.GObject.handler_disconnect
+    handler_is_connected = _gobject.GObject.handler_is_connected
+    handler_block = _gobject.GObject.handler_block
+    handler_unblock = _gobject.GObject.handler_unblock
+    handler_block_by_func = _gobject.GObject.handler_block_by_func
+    handler_unblock_by_func = _gobject.GObject.handler_unblock_by_func
+    emit = _gobject.GObject.emit
+    emit_stop_by_name = _gobject.GObject.emit_stop_by_name
+    stop_emission = _gobject.GObject.stop_emission
+    chain = _gobject.GObject.chain
+    weak_ref = _gobject.GObject.weak_ref
+    __copy__ = _gobject.GObject.__copy__
+    __deepcopy__ = _gobject.GObject.__deepcopy__
+
+
+Object = override(Object)
+GObject = Object
+__all__ += ['Object', 'GObject']
+
+
 Property = propertyhelper.Property
 Signal = signalhelper.Signal
 SignalOverride = signalhelper.SignalOverride
diff --git a/gi/pygobject-external.h b/gi/pygobject-external.h
index 113c229..4a1456d 100644
--- a/gi/pygobject-external.h
+++ b/gi/pygobject-external.h
@@ -26,10 +26,8 @@
 
 G_BEGIN_DECLS
 
-static PyTypeObject *_PyGObject_Type;
 static PyTypeObject *_PyGTypeWrapper_Type;
 
-#define PyGObject_Type (*_PyGObject_Type)
 #define PyGTypeWrapper_Type (*_PyGTypeWrapper_Type)
 
 __attribute__ ( (unused))
@@ -45,7 +43,7 @@ _pygobject_import (void)
         return 1;
     }
 
-    from_list = Py_BuildValue ("(ss)", "GObject", "GType");
+    from_list = Py_BuildValue ("(s)", "GType");
     if (from_list == NULL) {
         return -1;
     }
@@ -58,12 +56,6 @@ _pygobject_import (void)
         return -1;
     }
 
-    _PyGObject_Type = (PyTypeObject *) PyObject_GetAttrString (module, "GObject");
-    if (_PyGObject_Type == NULL) {
-        retval = -1;
-        goto out;
-    }
-
     _PyGTypeWrapper_Type = (PyTypeObject *) PyObject_GetAttrString (module, "GType");
     if (_PyGTypeWrapper_Type == NULL) {
         retval = -1;
diff --git a/gi/types.py b/gi/types.py
index 945d3b6..fb4b5f8 100644
--- a/gi/types.py
+++ b/gi/types.py
@@ -171,6 +171,11 @@ class MetaClassHelper(object):
         if class_info is None or not isinstance(class_info, ObjectInfo):
             return
 
+        # Special case skipping of vfuncs for GObject.Object because they will break
+        # the static bindings which will try to use them.
+        if cls.__module__ == 'gi.repository.GObject' and cls.__name__ == 'Object':
+            return
+
         for vfunc_info in class_info.get_vfuncs():
             name = 'do_%s' % vfunc_info.get_name()
             value = NativeVFunc(vfunc_info)
@@ -183,8 +188,10 @@ def find_vfunc_info_in_interface(bases, vfunc_name):
         # This can be seen in IntrospectionModule.__getattr__() in module.py.
         # We do not need to search regular classes here, only wrapped interfaces.
         # We also skip GInterface, because it is not wrapped and has no __info__ attr.
+        # Skip bases without __info__ (static _gobject._gobject.GObject)
         if base is GInterface or\
                 not issubclass(base, GInterface) or\
+                not '__info__' in base.__dict__ or\
                 not isinstance(base.__info__, InterfaceInfo):
             continue
 
diff --git a/tests/test_gobject.py b/tests/test_gobject.py
index 751cc02..80d0f64 100644
--- a/tests/test_gobject.py
+++ b/tests/test_gobject.py
@@ -1,21 +1,42 @@
 # -*- Mode: Python -*-
 
+import sys
 import gc
 import unittest
 import warnings
 
 from gi.repository import GObject
 from gi import PyGIDeprecationWarning
-import sys
+from gi.module import get_introspection_module
+from gi._gobject import _gobject
+
 import testhelper
 
 
 class TestGObjectAPI(unittest.TestCase):
-    def test_module(self):
-        obj = GObject.GObject()
+    def test_gobject_inheritance(self):
+        # GObject.Object is a class hierarchy as follows:
+        # overrides.Object -> introspection.Object -> static.GObject
+        GIObjectModule = get_introspection_module('GObject')
+        self.assertTrue(issubclass(GObject.Object, GIObjectModule.Object))
+        self.assertTrue(issubclass(GIObjectModule.Object, _gobject.GObject))
+
+        self.assertEqual(_gobject.GObject.__gtype__, GObject.TYPE_OBJECT)
+        self.assertEqual(GIObjectModule.Object.__gtype__, GObject.TYPE_OBJECT)
+        self.assertEqual(GObject.Object.__gtype__, GObject.TYPE_OBJECT)
+
+        # The pytype wrapper should hold the outer most Object class from overrides.
+        self.assertEqual(GObject.TYPE_OBJECT.pytype, GObject.Object)
+
+    @unittest.skipIf(sys.version_info[:2] < (2, 7), 'Python 2.7 is required')
+    def test_gobject_unsupported_overrides(self):
+        obj = GObject.Object()
+
+        with self.assertRaisesRegex(RuntimeError, 'Data access methods are unsupported.*'):
+            obj.get_data()
 
-        self.assertEqual(obj.__module__,
-                         'gi._gobject._gobject')
+        with self.assertRaisesRegex(RuntimeError, 'This method is currently unsupported.*'):
+            obj.force_floating()
 
     def test_compat_api(self):
         with warnings.catch_warnings(record=True) as w:
diff --git a/tests/testhelpermodule.c b/tests/testhelpermodule.c
index 8d8175e..16bb39e 100644
--- a/tests/testhelpermodule.c
+++ b/tests/testhelpermodule.c
@@ -606,7 +606,7 @@ PYGLIB_MODULE_START(testhelper, "testhelper")
 
   d = PyModule_GetDict(module);
 
-  if ((m = PyImport_ImportModule("gi._gobject")) != NULL) {
+  if ((m = PyImport_ImportModule("gi._gobject._gobject")) != NULL) {
     PyObject *moddict = PyModule_GetDict(m);
     
     _PyGObject_Type = (PyTypeObject *)PyDict_GetItemString(moddict, "GObject");



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