Commit 0fd900d3 authored by Simon Feltman's avatar Simon Feltman Committed by Martin Pitt
Browse files

Add context management to freeze_notify() and handler_block().

These methods now return a context manager object. Within the __exit__ method
thaw_notify() and handler_unblock() are called respectively. This allows
statements like the following:

with obj.freeze_notify():
    obj.props.width = 100
    obj.props.height = 100
    obj.props.opacity = 0.5

This does not affect standard usage of these methods.

https://bugzilla.gnome.org/show_bug.cgi?id=672324

Signed-off-by: default avatarMartin Pitt <martinpitt@gnome.org>
parent c0922589
......@@ -993,6 +993,116 @@ pygobject_watch_closure(PyObject *self, GClosure *closure)
g_closure_add_invalidate_notifier(closure, data, pygobject_unwatch_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);
......@@ -1380,12 +1490,11 @@ 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);
Py_INCREF(Py_None);
return Py_None;
return pygcontext_freeze_notify_new(self->obj);
}
static PyObject *
......@@ -1395,9 +1504,9 @@ pygobject_notify(PyGObject *self, PyObject *args)
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;
......@@ -1666,8 +1775,7 @@ pygobject_handler_block(PyGObject *self, PyObject *args)
CHECK_GOBJECT(self);
g_signal_handler_block(self->obj, handler_id);
Py_INCREF(Py_None);
return Py_None;
return pygcontext_handler_block_new(self->obj, handler_id);
}
static PyObject *
......@@ -2318,4 +2426,18 @@ pygobject_object_register_types(PyObject *d)
if (PyType_Ready(&PyGObjectWeakRef_Type) < 0)
return;
PyDict_SetItemString(d, "GObjectWeakRef", (PyObject *) &PyGObjectWeakRef_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;
}
......@@ -190,3 +190,161 @@ class TestPythonReferenceCounting(unittest.TestCase):
def testNewSubclassInstanceHasTwoRefsUsingGObjectNew(self):
obj = GObject.new(A)
self.assertEquals(sys.getrefcount(obj), 2)
class TestContextManagers(unittest.TestCase):
class ContextTestObject(GObject.GObject):
prop = GObject.Property(default=0, type=int)
def on_prop_set(self, obj, prop):
# Handler which tracks property changed notifications.
self.tracking.append(obj.get_property(prop.name))
def setUp(self):
self.tracking = []
self.obj = self.ContextTestObject()
self.handler = self.obj.connect('notify::prop', self.on_prop_set)
def testFreezeNotifyContext(self):
# Verify prop tracking list
self.assertEqual(self.tracking, [])
self.obj.props.prop = 1
self.assertEqual(self.tracking, [1])
self.obj.props.prop = 2
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.
with self.obj.freeze_notify():
self.assertEqual(self.obj.__grefcount__, 2)
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
# count goes back down.
self.assertEqual(self.obj.props.prop, 3)
self.assertEqual(self.tracking, [1, 2, 3])
self.assertEqual(self.obj.__grefcount__, 1)
def testHandlerBlockContext(self):
# Verify prop tracking list
self.assertEqual(self.tracking, [])
self.obj.props.prop = 1
self.assertEqual(self.tracking, [1])
self.obj.props.prop = 2
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.
with self.obj.handler_block(self.handler):
self.assertEqual(self.obj.__grefcount__, 2)
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 should have stayed the same and the GObject ref
# count goes back down.
self.assertEqual(self.obj.props.prop, 3)
self.assertEqual(self.tracking, [1, 2])
self.assertEqual(self.obj.__grefcount__, 1)
def testFreezeNotifyContextNested(self):
self.assertEqual(self.tracking, [])
with self.obj.freeze_notify():
self.obj.props.prop = 1
self.assertEqual(self.tracking, [])
with self.obj.freeze_notify():
self.obj.props.prop = 2
self.assertEqual(self.tracking, [])
with self.obj.freeze_notify():
self.obj.props.prop = 3
self.assertEqual(self.tracking, [])
self.assertEqual(self.tracking, [])
self.assertEqual(self.tracking, [])
# Finally after last context, the notifications should have collapsed
# and the last one sent.
self.assertEqual(self.tracking, [3])
def testHandlerBlockContextNested(self):
self.assertEqual(self.tracking, [])
with self.obj.handler_block(self.handler):
self.obj.props.prop = 1
self.assertEqual(self.tracking, [])
with self.obj.handler_block(self.handler):
self.obj.props.prop = 2
self.assertEqual(self.tracking, [])
with self.obj.handler_block(self.handler):
self.obj.props.prop = 3
self.assertEqual(self.tracking, [])
self.assertEqual(self.tracking, [])
self.assertEqual(self.tracking, [])
# Finally after last context, the notifications should have collapsed
# and the last one sent.
self.assertEqual(self.obj.props.prop, 3)
self.assertEqual(self.tracking, [])
def testFreezeNotifyNormalUsageRefCounts(self):
# Ensure ref counts without using methods as context managers
# maintain the same count.
self.assertEqual(self.obj.__grefcount__, 1)
self.obj.freeze_notify()
self.assertEqual(self.obj.__grefcount__, 1)
self.obj.thaw_notify()
self.assertEqual(self.obj.__grefcount__, 1)
def testHandlerBlockNormalUsageRefCounts(self):
self.assertEqual(self.obj.__grefcount__, 1)
self.obj.handler_block(self.handler)
self.assertEqual(self.obj.__grefcount__, 1)
self.obj.handler_unblock(self.handler)
self.assertEqual(self.obj.__grefcount__, 1)
def testFreezeNotifyContextError(self):
# Test an exception occurring within a freeze context exits the context
try:
with self.obj.freeze_notify():
self.obj.props.prop = 1
self.assertEqual(self.tracking, [])
raise ValueError('Simulation')
except ValueError:
pass
# Verify the property set within the context called notify.
self.assertEqual(self.obj.props.prop, 1)
self.assertEqual(self.tracking, [1])
# Verify we are still not in a frozen context.
self.obj.props.prop = 2
self.assertEqual(self.tracking, [1, 2])
def testHandlerBlockContextError(self):
# Test an exception occurring within a handler block exits the context
try:
with self.obj.handler_block(self.handler):
self.obj.props.prop = 1
self.assertEqual(self.tracking, [])
raise ValueError('Simulation')
except ValueError:
pass
# Verify the property set within the context didn't call notify.
self.assertEqual(self.obj.props.prop, 1)
self.assertEqual(self.tracking, [])
# Verify we are still not in a handler block context.
self.obj.props.prop = 2
self.assertEqual(self.tracking, [2])
if __name__ == '__main__':
unittest.main()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment