[pygobject] Replace GObject notify methods with introspection and python



commit 463f660cd6bb78ae7f2ea7c70c0491e6b4744942
Author: Simon Feltman <sfeltman src gnome org>
Date:   Tue Nov 6 21:34:06 2012 -0800

    Replace GObject notify methods with introspection and python
    
    Replace static context managers for freeze_notify and
    handler_block with python context managers. Remove notify,
    freeze_notify, thaw_notify static methods as the introspection
    versions work fine.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=672727

 gi/_gobject/pygobject.c |  173 +---------------------------------------------
 gi/overrides/GObject.py |   61 +++++++++++++++--
 tests/test_gobject.py   |   26 +++++--
 tests/test_signal.py    |   13 +++-
 4 files changed, 91 insertions(+), 182 deletions(-)
---
diff --git a/gi/_gobject/pygobject.c b/gi/_gobject/pygobject.c
index 23a4284..3d0c819 100644
--- a/gi/_gobject/pygobject.c
+++ b/gi/_gobject/pygobject.c
@@ -1055,115 +1055,6 @@ pygobject_watch_closure(PyObject *self, GClosure *closure)
 }
 
 
-/* -------------- Freeze Notify Context Manager ----------------- */
-
-/**
- * pygcontext_manager_enter
- * @self: Freeze or Block context instance
- *
- * Method used for __enter__ on both GContextFeezeNotify and
- * GContextHandlerBlock. Does nothing since this is an object returned
- * by the freeze_notify() and handler_block() methods which do the actual
- * work of freezing and blocking.
- */
-static PyObject *
-pygcontext_manager_enter(PyObject *self)
-{
-	Py_INCREF(self);
-	return self;
-}
-
-typedef struct {
-    PyObject_HEAD
-    GObject *obj;
-} PyGContextFreezeNotify;
-
-PYGLIB_DEFINE_TYPE("gi._gobject.GContextFreezeNotify",
-		PyGContextFreezeNotify_Type, PyGContextFreezeNotify);
-
-static PyObject *
-pygcontext_freeze_notify_new(GObject *gobj)
-{
-	PyGContextFreezeNotify *context;
-
-	context = PyObject_New(PyGContextFreezeNotify, &PyGContextFreezeNotify_Type);
-	if (context == NULL)
-		return NULL;
-
-	g_object_ref(gobj);
-	context->obj = gobj;
-	return (PyObject*)context;
-}
-
-static void
-pygcontext_freeze_notify_dealloc(PyGContextFreezeNotify* self)
-{
-    g_object_unref(self->obj);
-    self->obj = NULL;
-    PyObject_Del((PyObject*)self);
-}
-
-static PyObject *
-pygcontext_freeze_notify_exit(PyGContextFreezeNotify *self, PyObject *args)
-{
-	g_object_thaw_notify(self->obj);
-	Py_RETURN_NONE;
-}
-
-static PyMethodDef pygcontext_freeze_notify_methods[] = {
-	{"__enter__", (PyCFunction)pygcontext_manager_enter, METH_NOARGS, ""},
-	{"__exit__", (PyCFunction)pygcontext_freeze_notify_exit, METH_VARARGS, ""},
-    {NULL}
-};
-
-/* -------------- Handler Block Context Manager ----------------- */
-typedef struct {
-    PyObject_HEAD
-    GObject *obj;
-    gulong handler_id;
-} PyGContextHandlerBlock;
-
-PYGLIB_DEFINE_TYPE("gi._gobject.GContextHandlerBlock",
-		PyGContextHandlerBlock_Type, PyGContextHandlerBlock);
-
-static PyObject *
-pygcontext_handler_block_new(GObject *gobj, gulong handler_id)
-{
-	PyGContextHandlerBlock *context;
-
-	context = PyObject_New(PyGContextHandlerBlock, &PyGContextHandlerBlock_Type);
-	if (context == NULL)
-		return NULL;
-
-	g_object_ref(gobj);
-	context->obj = gobj;
-	context->handler_id = handler_id;
-	return (PyObject*)context;
-}
-
-static void
-pygcontext_handler_block_dealloc(PyGContextHandlerBlock* self)
-{
-    g_object_unref(self->obj);
-    self->obj = NULL;
-    PyObject_Del((PyObject*)self);
-}
-
-static PyObject *
-pygcontext_handler_block_exit(PyGContextHandlerBlock *self, PyObject *args)
-{
-	g_signal_handler_unblock(self->obj, self->handler_id);
-	Py_RETURN_NONE;
-}
-
-
-static PyMethodDef pygcontext_handler_block_methods[] = {
-	{"__enter__", (PyCFunction)pygcontext_manager_enter, METH_NOARGS, ""},
-	{"__exit__", (PyCFunction)pygcontext_handler_block_exit, METH_VARARGS, ""},
-    {NULL}
-};
-
-
 /* -------------- PyGObject behaviour ----------------- */
 
 PYGLIB_DEFINE_TYPE("gi._gobject.GObject", PyGObject_Type, PyGObject);
@@ -1705,46 +1596,6 @@ pygobject_bind_property(PyGObject *self, PyObject *args)
 }
 
 static PyObject *
-pygobject_freeze_notify(PyGObject *self, PyObject *args)
-{
-    if (!PyArg_ParseTuple(args, ":GObject.freeze_notify"))
-	return NULL;
-
-    CHECK_GOBJECT(self);
-
-    g_object_freeze_notify(self->obj);
-    return pygcontext_freeze_notify_new(self->obj);
-}
-
-static PyObject *
-pygobject_notify(PyGObject *self, PyObject *args)
-{
-    char *property_name;
-
-    if (!PyArg_ParseTuple(args, "s:GObject.notify", &property_name))
-	return NULL;
-
-    CHECK_GOBJECT(self);
-
-    g_object_notify(self->obj, property_name);
-    Py_INCREF(Py_None);
-    return Py_None;
-}
-
-static PyObject *
-pygobject_thaw_notify(PyGObject *self, PyObject *args)
-{
-    if (!PyArg_ParseTuple(args, ":GObject.thaw_notify"))
-	return NULL;
-    
-    CHECK_GOBJECT(self);
-    
-    g_object_thaw_notify(self->obj);
-    Py_INCREF(Py_None);
-    return Py_None;
-}
-
-static PyObject *
 pygobject_connect(PyGObject *self, PyObject *args)
 {
     PyObject *first, *callback, *extra_args, *repr = NULL;
@@ -1998,11 +1849,12 @@ pygobject_handler_block(PyGObject *self, PyObject *args)
 
     if (!PyArg_ParseTuple(args, "k:GObject.handler_block", &handler_id))
 	return NULL;
-    
+
     CHECK_GOBJECT(self);
-    
+
     g_signal_handler_block(self->obj, handler_id);
-    return pygcontext_handler_block_new(self->obj, handler_id);
+    Py_INCREF(Py_None);
+    return Py_None;
 }
 
 static PyObject *
@@ -2363,9 +2215,6 @@ static PyMethodDef pygobject_methods[] = {
     { "set_property", (PyCFunction)pygobject_set_property, METH_VARARGS },
     { "set_properties", (PyCFunction)pygobject_set_properties, METH_VARARGS|METH_KEYWORDS },
     { "bind_property", (PyCFunction)pygobject_bind_property, METH_VARARGS|METH_KEYWORDS },
-    { "freeze_notify", (PyCFunction)pygobject_freeze_notify, METH_VARARGS },
-    { "notify", (PyCFunction)pygobject_notify, METH_VARARGS },
-    { "thaw_notify", (PyCFunction)pygobject_thaw_notify, METH_VARARGS },
     { "connect", (PyCFunction)pygobject_connect, METH_VARARGS },
     { "connect_after", (PyCFunction)pygobject_connect_after, METH_VARARGS },
     { "connect_object", (PyCFunction)pygobject_connect_object, METH_VARARGS },
@@ -2722,18 +2571,4 @@ pygobject_object_register_types(PyObject *d)
     if (PyType_Ready(&PyGBindingWeakRef_Type) < 0)
         return;
     PyDict_SetItemString(d, "GBindingWeakRef", (PyObject *) &PyGBindingWeakRef_Type);
-
-    PyGContextFreezeNotify_Type.tp_dealloc = (destructor)pygcontext_freeze_notify_dealloc;
-    PyGContextFreezeNotify_Type.tp_flags = Py_TPFLAGS_DEFAULT;
-    PyGContextFreezeNotify_Type.tp_doc = "Context manager for freeze/thaw of GObjects";
-    PyGContextFreezeNotify_Type.tp_methods = pygcontext_freeze_notify_methods;
-    if (PyType_Ready(&PyGContextFreezeNotify_Type) < 0)
-        return;
-
-    PyGContextHandlerBlock_Type.tp_dealloc = (destructor)pygcontext_handler_block_dealloc;
-    PyGContextHandlerBlock_Type.tp_flags = Py_TPFLAGS_DEFAULT;
-    PyGContextHandlerBlock_Type.tp_doc = "Context manager for handler blocking of GObjects";
-    PyGContextHandlerBlock_Type.tp_methods = pygcontext_handler_block_methods;
-    if (PyType_Ready(&PyGContextHandlerBlock_Type) < 0)
-        return;
 }
diff --git a/gi/overrides/GObject.py b/gi/overrides/GObject.py
index 9435ba8..1827433 100644
--- a/gi/overrides/GObject.py
+++ b/gi/overrides/GObject.py
@@ -279,6 +279,29 @@ def signal_query(id_or_name, type_=None):
 __all__.append('signal_query')
 
 
+class _HandlerBlockManager(object):
+    def __init__(self, obj, handler_id):
+        self.obj = obj
+        self.handler_id = handler_id
+
+    def __enter__(self):
+        pass
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        _gobject.GObject.handler_unblock(self.obj, self.handler_id)
+
+
+class _FreezeNotifyManager(object):
+    def __init__(self, obj):
+        self.obj = obj
+
+    def __enter__(self):
+        pass
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self.obj.thaw_notify()
+
+
 class Object(GObjectModule.Object):
     def _unsupported_method(self, *args, **kargs):
         raise RuntimeError('This method is currently unsupported.')
@@ -321,9 +344,6 @@ class Object(GObjectModule.Object):
     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
@@ -332,8 +352,6 @@ class Object(GObjectModule.Object):
     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
@@ -344,6 +362,39 @@ class Object(GObjectModule.Object):
     __copy__ = _gobject.GObject.__copy__
     __deepcopy__ = _gobject.GObject.__deepcopy__
 
+    def handler_block(self, handler_id):
+        """Blocks the signal handler from being invoked until handler_unblock() is called.
+
+        Returns a context manager which optionally can be used to
+        automatically unblock the handler:
+
+        >>> with obj.handler_block(id):
+        >>>    pass
+        """
+
+        # Note Object.handler_block is a static method specific to pygobject and not
+        # found in introspection. We need to continue using the static method
+        # until we figure out a technique to call the global signal_handler_block.
+        # But this requires a gpointer to the Object which we currently don't have
+        # access to in python.
+        _gobject.GObject.handler_block(self, handler_id)
+        return _HandlerBlockManager(self, handler_id)
+
+    def freeze_notify(self):
+        """Freezes the object's property-changed notification queue.
+
+        This will freeze the object so that "notify" signals are blocked until
+        the thaw_notify() method is called.
+
+        Returns a context manager which optionally can be used to
+        automatically thaw notifications:
+
+        >>> with obj.freeze_notify():
+        >>>     pass
+        """
+        super(Object, self).freeze_notify()
+        return _FreezeNotifyManager(self)
+
 
 Object = override(Object)
 GObject = Object
diff --git a/tests/test_gobject.py b/tests/test_gobject.py
index 80d0f64..99f471b 100644
--- a/tests/test_gobject.py
+++ b/tests/test_gobject.py
@@ -271,20 +271,26 @@ class TestContextManagers(unittest.TestCase):
         self.assertEqual(self.tracking, [1, 2])
         self.assertEqual(self.obj.__grefcount__, 1)
 
-        # Using the context manager the tracking list should not be affected
-        # and the GObject reference count should go up.
+        pyref_count = sys.getrefcount(self.obj)
+
+        # Using the context manager the tracking list should not be affected.
+        # The GObject reference count should stay the same and the python
+        # object ref-count should go up.
         with self.obj.freeze_notify():
-            self.assertEqual(self.obj.__grefcount__, 2)
+            self.assertEqual(self.obj.__grefcount__, 1)
+            self.assertEqual(sys.getrefcount(self.obj), pyref_count + 1)
             self.obj.props.prop = 3
             self.assertEqual(self.obj.props.prop, 3)
             self.assertEqual(self.tracking, [1, 2])
 
         # After the context manager, the prop should have been modified,
-        # the tracking list will be modified, and the GObject ref
+        # the tracking list will be modified, and the python object ref
         # count goes back down.
+        gc.collect()
         self.assertEqual(self.obj.props.prop, 3)
         self.assertEqual(self.tracking, [1, 2, 3])
         self.assertEqual(self.obj.__grefcount__, 1)
+        self.assertEqual(sys.getrefcount(self.obj), pyref_count)
 
     def test_handler_block_context(self):
         # Verify prop tracking list
@@ -295,10 +301,14 @@ class TestContextManagers(unittest.TestCase):
         self.assertEqual(self.tracking, [1, 2])
         self.assertEqual(self.obj.__grefcount__, 1)
 
-        # Using the context manager the tracking list should not be affected
-        # and the GObject reference count should go up.
+        pyref_count = sys.getrefcount(self.obj)
+
+        # Using the context manager the tracking list should not be affected.
+        # The GObject reference count should stay the same and the python
+        # object ref-count should go up.
         with self.obj.handler_block(self.handler):
-            self.assertEqual(self.obj.__grefcount__, 2)
+            self.assertEqual(self.obj.__grefcount__, 1)
+            self.assertEqual(sys.getrefcount(self.obj), pyref_count + 1)
             self.obj.props.prop = 3
             self.assertEqual(self.obj.props.prop, 3)
             self.assertEqual(self.tracking, [1, 2])
@@ -306,9 +316,11 @@ class TestContextManagers(unittest.TestCase):
         # After the context manager, the prop should have been modified
         # the tracking list should have stayed the same and the GObject ref
         # count goes back down.
+        gc.collect()
         self.assertEqual(self.obj.props.prop, 3)
         self.assertEqual(self.tracking, [1, 2])
         self.assertEqual(self.obj.__grefcount__, 1)
+        self.assertEqual(sys.getrefcount(self.obj), pyref_count)
 
     def test_freeze_notify_context_nested(self):
         self.assertEqual(self.tracking, [])
diff --git a/tests/test_signal.py b/tests/test_signal.py
index ec58336..fb2cb24 100644
--- a/tests/test_signal.py
+++ b/tests/test_signal.py
@@ -596,7 +596,8 @@ class TestSignalDecorator(unittest.TestCase):
 
 class TestSignalConnectors(unittest.TestCase):
     class CustomButton(GObject.GObject):
-        value = 0
+        on_notify_called = False
+        value = GObject.Property(type=int)
 
         @GObject.Signal(arg_types=(int,))
         def clicked(self, value):
@@ -610,6 +611,16 @@ class TestSignalConnectors(unittest.TestCase):
         self.obj = obj
         self.value = value
 
+    def test_signal_notify(self):
+        def on_notify(obj, param):
+            obj.on_notify_called = True
+
+        obj = self.CustomButton()
+        obj.connect('notify', on_notify)
+        self.assertFalse(obj.on_notify_called)
+        obj.notify('value')
+        self.assertTrue(obj.on_notify_called)
+
     def test_signal_emit(self):
         # standard callback connection with different forms of emit.
         obj = self.CustomButton()



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