[pygobject] Remove static io_add_watch() binding



commit 366f5d2d3902c6293d0031e0b7dc5d6641a05ac7
Author: Martin Pitt <martinpitt gnome org>
Date:   Fri Oct 26 09:26:17 2012 +0200

    Remove static io_add_watch() binding
    
    Use the GLib API through GI instead, and provide override to keep backwards
    compatible API. Also allow using the actual GLib API, and deprecate all other
    variants:
     - calling with an fd as first argument instead of an IOChannel
     - calling without a priority as second argument

 docs/reference/pyglib-functions.xml |  106 -----------------------------------
 gi/_glib/__init__.py                |    1 -
 gi/_glib/glibmodule.c               |   98 --------------------------------
 gi/_gobject/__init__.py             |    1 -
 gi/overrides/GLib.py                |   51 ++++++++++++++--
 gi/overrides/GObject.py             |    3 +-
 tests/test_glib.py                  |   15 ++++-
 tests/test_iochannel.py             |   70 ++++++++++++++++++++++-
 8 files changed, 126 insertions(+), 219 deletions(-)
---
diff --git a/docs/reference/pyglib-functions.xml b/docs/reference/pyglib-functions.xml
index 7d70a66..936f750 100644
--- a/docs/reference/pyglib-functions.xml
+++ b/docs/reference/pyglib-functions.xml
@@ -13,13 +13,6 @@
 
     <programlisting>
 <methodsynopsis language="python">
-	<methodname><link
-linkend="function-glib--io-add-watch">glib.io_add_watch</link></methodname>
-	<methodparam><parameter>fd</parameter></methodparam>
-	<methodparam><parameter>condition</parameter></methodparam>
-	<methodparam><parameter>callback</parameter></methodparam>
-	<methodparam><parameter>...</parameter></methodparam>
-  </methodsynopsis><methodsynopsis language="python">
         <methodname><link linkend="function-glib--child-watch-add">glib.child_watch_add</link></methodname>
         <methodparam><parameter role="keyword">pid</parameter></methodparam>
         <methodparam><parameter role="keyword">function</parameter></methodparam>
@@ -62,105 +55,6 @@ module but are not directly associated with a specific class.</para>
   <refsect1>
     <title>Functions</title>
 
-    <refsect2 id="function-glib--io-add-watch">
-      <title>glib.io_add_watch</title>
-
-      <programlisting><methodsynopsis language="python">
-	  <methodname>glib.io_add_watch</methodname>
-	  <methodparam><parameter>fd</parameter></methodparam>
-	  <methodparam><parameter>condition</parameter></methodparam>
-	  <methodparam><parameter>callback</parameter></methodparam>
-	  <methodparam><parameter>...</parameter></methodparam>
-	</methodsynopsis></programlisting>
-      <variablelist>
-	<varlistentry>
-	  <term><parameter>fd</parameter>&nbsp;:</term>
-	  <listitem><simpara>a Python file object or an integer file
-descriptor ID</simpara></listitem>
-	</varlistentry>
-	<varlistentry>
-	  <term><parameter>condition</parameter>&nbsp;:</term>
-	  <listitem><simpara>a condition mask</simpara></listitem>
-	</varlistentry>
-	<varlistentry>
-	  <term><parameter>callback</parameter>&nbsp;:</term>
-	  <listitem><simpara>a function to call</simpara></listitem>
-	</varlistentry>
-	<varlistentry>
-	  <term><parameter>...</parameter>&nbsp;:</term>
-	  <listitem><simpara>additional arguments to pass to
-<parameter>callback</parameter></simpara></listitem>
-	</varlistentry>
-	<varlistentry>
-	  <term><emphasis>Returns</emphasis>&nbsp;:</term>
-	  <listitem><simpara>an integer ID of the event source</simpara></listitem>
-	</varlistentry>
-      </variablelist>
-
-      <para>The <function>glib.io_add_watch</function>() function
-arranges for the file (specified by <parameter>fd</parameter>) to be
-monitored by the main loop for the specified
-<parameter>condition</parameter>. <parameter>fd</parameter> may be a Python
-file object or an integer file descriptor. The value of condition is a
-combination of:</para>
-
-      <variablelist>
-	<varlistentry>
-	  <term><literal>glib.IO_IN</literal></term>
-	  <listitem>
-	    <simpara>There is data to read.</simpara>
-	  </listitem>
-	</varlistentry>
-	<varlistentry>
-	  <term><literal>glib.IO_OUT</literal></term>
-	  <listitem>
-	    <simpara>Data can be written (without blocking). </simpara>
-	  </listitem>
-	</varlistentry>
-	<varlistentry>
-	  <term><literal>glib.IO_PRI</literal></term>
-	  <listitem>
-	    <simpara>There is urgent data to read.</simpara>
-	  </listitem>
-	</varlistentry>
-	<varlistentry>
-	  <term><literal>glib.IO_ERR</literal></term>
-	  <listitem>
-	    <simpara>Error condition.</simpara>
-	  </listitem>
-	</varlistentry>
-	<varlistentry>
-	  <term><literal>glib.IO_HUP</literal></term>
-	  <listitem>
-	    <simpara>Hung up (the connection has been broken, usually for
-pipes and sockets).</simpara>
-	  </listitem>
-	</varlistentry>
-      </variablelist>
-
-      <para>Additional arguments to pass to <parameter>callback</parameter>
-can be specified after <parameter>callback</parameter>. The idle priority
-may be specified as a keyword-value pair with the keyword "priority". The
-signature of the callback function is:</para>
-
-      <programlisting>
-  def callback(source, cb_condition, ...)
-</programlisting>
-
-      <para>where <parameter>source</parameter> is
-<parameter>fd</parameter>, the file descriptor;
-<parameter>cb_condition</parameter> is the condition that triggered the
-signal; and, <parameter>...</parameter> are the zero or more arguments that
-were passed to the <function>glib.io_add_watch</function>()
-function.</para>
-
-      <para>If the callback function returns <literal>False</literal> it
-will be automatically removed from the list of event sources and will not be
-called again. If it returns <literal>True</literal> it will be called again
-when the condition is matched.</para>
-
-    </refsect2>
-
     <refsect2 id="function-glib--markup-escape-text">
       <title>glib.markup_escape_text</title>
 
diff --git a/gi/_glib/__init__.py b/gi/_glib/__init__.py
index 7844c12..0d6d302 100644
--- a/gi/_glib/__init__.py
+++ b/gi/_glib/__init__.py
@@ -49,7 +49,6 @@ child_watch_add = _glib.child_watch_add
 filename_from_utf8 = _glib.filename_from_utf8
 get_current_time = _glib.get_current_time
 glib_version = _glib.glib_version
-io_add_watch = _glib.io_add_watch
 pyglib_version = _glib.pyglib_version
 spawn_async = _glib.spawn_async
 threads_init = _glib.threads_init
diff --git a/gi/_glib/glibmodule.c b/gi/_glib/glibmodule.c
index 40c9638..4e20444 100644
--- a/gi/_glib/glibmodule.c
+++ b/gi/_glib/glibmodule.c
@@ -96,97 +96,6 @@ pyglib_threads_init(PyObject *unused, PyObject *args, PyObject *kwargs)
     return Py_None;
 }
 
-static gboolean
-iowatch_marshal(GIOChannel *source,
-		GIOCondition condition,
-		gpointer user_data)
-{
-    PyGILState_STATE state;
-    PyObject *tuple, *func, *firstargs, *args, *ret;
-    gboolean res;
-
-    g_return_val_if_fail(user_data != NULL, FALSE);
-
-    state = pyglib_gil_state_ensure();
-
-    tuple = (PyObject *)user_data;
-    func = PyTuple_GetItem(tuple, 0);
-
-    /* arg vector is (fd, condtion, *args) */
-    firstargs = Py_BuildValue("(Oi)", PyTuple_GetItem(tuple, 1), condition);
-    args = PySequence_Concat(firstargs, PyTuple_GetItem(tuple, 2));
-    Py_DECREF(firstargs);
-
-    ret = PyObject_CallObject(func, args);
-    Py_DECREF(args);
-    if (!ret) {
-	PyErr_Print();
-	res = FALSE;
-    } else {
-        if (ret == Py_None) {
-            if (PyErr_Warn(PyExc_Warning,
-			   "_glib.io_add_watch callback returned None; "
-                           "should return True/False")) {
-                PyErr_Print();
-            }
-        }
-	res = PyObject_IsTrue(ret);
-	Py_DECREF(ret);
-    }
-
-    pyglib_gil_state_release(state);
-
-    return res;
-}
-
-static PyObject *
-pyglib_io_add_watch(PyObject *self, PyObject *args, PyObject *kwargs)
-{
-    PyObject *first, *pyfd, *callback, *cbargs = NULL, *data;
-    gint fd, priority = G_PRIORITY_DEFAULT, condition;
-    Py_ssize_t len;
-    GIOChannel *iochannel;
-    guint handler_id;
-
-    len = PyTuple_Size(args);
-    if (len < 3) {
-	PyErr_SetString(PyExc_TypeError,
-			"io_add_watch requires at least 3 args");
-	return NULL;
-    }
-    first = PySequence_GetSlice(args, 0, 3);
-    if (!PyArg_ParseTuple(first, "OiO:io_add_watch", &pyfd, &condition,
-			  &callback)) {
-	Py_DECREF(first);
-        return NULL;
-    }
-    Py_DECREF(first);
-    fd = PyObject_AsFileDescriptor(pyfd);
-    if (fd < 0) {
-	return NULL;
-    }
-    if (!PyCallable_Check(callback)) {
-        PyErr_SetString(PyExc_TypeError, "third argument not callable");
-        return NULL;
-    }
-    if (get_handler_priority(&priority, kwargs) < 0)
-	return NULL;
-
-    cbargs = PySequence_GetSlice(args, 3, len);
-    if (cbargs == NULL)
-      return NULL;
-    data = Py_BuildValue("(OON)", callback, pyfd, cbargs);
-    if (data == NULL)
-      return NULL;
-    iochannel = g_io_channel_unix_new(fd);
-    handler_id = g_io_add_watch_full(iochannel, priority, condition,
-				     iowatch_marshal, data,
-				     (GDestroyNotify)_pyglib_destroy_notify);
-    g_io_channel_unref(iochannel);
-    
-    return PYGLIB_PyLong_FromLong(handler_id);
-}
-
 static void
 child_watch_func(GPid pid, gint status, gpointer data)
 {
@@ -290,13 +199,6 @@ static PyMethodDef _glib_functions[] = {
       "Initialize GLib for use from multiple threads. If you also use GTK+\n"
       "itself (i.e. GUI, not just PyGObject), use gtk.gdk.threads_init()\n"
       "instead." },
-    { "io_add_watch",
-      (PyCFunction)pyglib_io_add_watch, METH_VARARGS|METH_KEYWORDS,
-      "io_add_watch(fd, condition, callback, user_data=None) -> source id\n"
-      "  callable receives (fd, condition, user_data)\n"
-      "Arranges for the fd to be monitored by the main loop for the\n"
-      "specified condition. Condition is a combination of glib.IO_IN,\n"
-      "glib.IO_OUT, glib.IO_PRI, gio.IO_ERR and gio.IO_HUB.\n" },
     { "child_watch_add",
       (PyCFunction)pyglib_child_watch_add, METH_VARARGS|METH_KEYWORDS,
       "child_watch_add(pid, callable, user_data=None,\n"
diff --git a/gi/_gobject/__init__.py b/gi/_gobject/__init__.py
index 75f6643..c774df6 100644
--- a/gi/_gobject/__init__.py
+++ b/gi/_gobject/__init__.py
@@ -82,7 +82,6 @@ type_parent = _gobject.type_parent
 type_register = _gobject.type_register
 
 spawn_async = _glib.spawn_async
-io_add_watch = _glib.io_add_watch
 child_watch_add = _glib.child_watch_add
 get_current_time = _glib.get_current_time
 filename_from_utf8 = _glib.filename_from_utf8
diff --git a/gi/overrides/GLib.py b/gi/overrides/GLib.py
index 996bc3d..65c7e8e 100644
--- a/gi/overrides/GLib.py
+++ b/gi/overrides/GLib.py
@@ -20,6 +20,7 @@
 # USA
 
 import signal
+import warnings
 
 from ..module import get_introspection_module
 from .._gi import (variant_new_tuple, variant_type_from_string, source_new,
@@ -596,6 +597,46 @@ def timeout_add_seconds(interval, function, user_data=_unspecified, priority=GLi
 __all__.append('timeout_add_seconds')
 
 
+# The real GLib API is io_add_watch(IOChannel, priority, condition, callback,
+# user_data). This needs to take into account several deprecated APIs:
+# - calling with an fd as first argument
+# - calling without a priority as second argument
+# and the usual "call without user_data", in which case the callback does not
+# get an user_data either.
+def io_add_watch(channel, priority, condition, callback=_unspecified, user_data=_unspecified):
+    if not isinstance(priority, int) or isinstance(priority, GLib.IOCondition):
+        warnings.warn('Calling io_add_watch without priority as second argument is deprecated',
+                      DeprecationWarning)
+        # shift the arguments around
+        user_data = callback
+        callback = condition
+        condition = priority
+        priority = GLib.PRIORITY_DEFAULT
+
+    if user_data is _unspecified:
+        # we have to call the callback without the user_data argument
+        func = lambda channel, cond, data: callback(channel, cond)
+        user_data = None
+    else:
+        func = callback
+
+    # backwards compatibility: Allow calling with fd
+    if isinstance(channel, int):
+        warnings.warn('Calling io_add_watch with a file descriptor is deprecated; call it with a GLib.IOChannel object',
+                      DeprecationWarning)
+        func_fdtransform = lambda _, cond, data: func(channel, cond, data)
+        real_channel = GLib.IOChannel.unix_new(channel)
+    else:
+        assert isinstance(channel, GLib.IOChannel)
+        func_fdtransform = func
+        real_channel = channel
+
+    return GLib.io_add_watch(real_channel, priority, condition,
+                             func_fdtransform, user_data)
+
+__all__.append('io_add_watch')
+
+
 # backwards compatible API
 class IOChannel(GLib.IOChannel):
     def __new__(cls, filedes=None, filename=None, mode=None, hwnd=None):
@@ -655,13 +696,9 @@ class IOChannel(GLib.IOChannel):
 
     def add_watch(self, condition, callback, user_data=_unspecified,
                   priority=GLib.PRIORITY_DEFAULT):
-        if user_data is _unspecified:
-            # we have to call the callback without the user_data argument
-            func = lambda channel, cond, data: callback(channel, cond)
-            user_data = None
-        else:
-            func = callback
-        return GLib.io_add_watch(self, priority, condition, func, user_data)
+        return io_add_watch(self, priority, condition, callback, user_data)
+
+    add_watch = deprecated(add_watch, 'GLib.io_add_watch()')
 
     def __iter__(self):
         return self
diff --git a/gi/overrides/GObject.py b/gi/overrides/GObject.py
index 305a25f..7925dab 100644
--- a/gi/overrides/GObject.py
+++ b/gi/overrides/GObject.py
@@ -31,7 +31,8 @@ for name in ['markup_escape_text', 'get_application_name',
              'filename_display_name', 'uri_list_extract_uris',
              'MainLoop', 'MainContext', 'main_context_default',
              'source_remove', 'Source', 'Idle', 'Timeout', 'PollFD',
-             'idle_add', 'timeout_add', 'timeout_add_seconds']:
+             'idle_add', 'timeout_add', 'timeout_add_seconds',
+             'io_add_watch']:
     globals()[name] = gi.overrides.deprecated(getattr(GLib, name), 'GLib.' + name)
     __all__.append(name)
 
diff --git a/tests/test_glib.py b/tests/test_glib.py
index 9170496..f2f28aa 100644
--- a/tests/test_glib.py
+++ b/tests/test_glib.py
@@ -3,6 +3,7 @@
 
 import unittest
 import os.path
+import warnings
 
 from gi.repository import GLib
 
@@ -110,7 +111,12 @@ https://my.org/q?x=1&y=2
             call_data.append((fd, condition, os.read(fd, 1)))
             return True
 
-        GLib.io_add_watch(r, GLib.IOCondition.IN, cb)
+        # io_add_watch() takes an IOChannel, calling with an fd is deprecated
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            GLib.io_add_watch(r, GLib.IOCondition.IN, cb)
+            self.assertTrue(issubclass(warn[0].category, DeprecationWarning))
+
         ml = GLib.MainLoop()
         GLib.timeout_add(10, lambda: os.write(w, b'a') and False)
         GLib.timeout_add(100, lambda: os.write(w, b'b') and False)
@@ -128,7 +134,12 @@ https://my.org/q?x=1&y=2
             call_data.append((fd, condition, os.read(fd, 1), data))
             return True
 
-        GLib.io_add_watch(r, GLib.IOCondition.IN, cb, 'moo')
+        # io_add_watch() takes an IOChannel, calling with an fd is deprecated
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            GLib.io_add_watch(r, GLib.IOCondition.IN, cb, 'moo')
+            self.assertTrue(issubclass(warn[0].category, DeprecationWarning))
+
         ml = GLib.MainLoop()
         GLib.timeout_add(10, lambda: os.write(w, b'a') and False)
         GLib.timeout_add(100, lambda: os.write(w, b'b') and False)
diff --git a/tests/test_iochannel.py b/tests/test_iochannel.py
index 85af822..e0eca2a 100644
--- a/tests/test_iochannel.py
+++ b/tests/test_iochannel.py
@@ -6,6 +6,7 @@ import tempfile
 import os.path
 import fcntl
 import shutil
+import warnings
 
 from gi.repository import GLib
 
@@ -198,7 +199,7 @@ second line
         self.assertEqual(os.read(r, 10), b'\x03\x04')
         os.close(r)
 
-    def test_add_watch_no_data(self):
+    def test_deprecated_add_watch_no_data(self):
         (r, w) = os.pipe()
 
         ch = GLib.IOChannel(filedes=r)
@@ -213,7 +214,11 @@ second line
             cb_reads.append(channel.read())
             return True
 
-        ch.add_watch(GLib.IOCondition.IN, cb)
+        # io_add_watch() method is deprecated, use GLib.io_add_watch
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            ch.add_watch(GLib.IOCondition.IN, cb)
+            self.assertTrue(issubclass(warn[0].category, DeprecationWarning))
 
         ml = GLib.MainLoop()
 
@@ -241,7 +246,11 @@ second line
             return True
 
         ml = GLib.MainLoop()
-        id = ch.add_watch(GLib.IOCondition.IN, cb, 'hello', GLib.PRIORITY_HIGH)
+        # io_add_watch() method is deprecated, use GLib.io_add_watch
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            id = ch.add_watch(GLib.IOCondition.IN, cb, 'hello', GLib.PRIORITY_HIGH)
+            self.assertTrue(issubclass(warn[0].category, DeprecationWarning))
 
         self.assertEqual(ml.get_context().find_source_by_id(id).priority,
                          GLib.PRIORITY_HIGH)
@@ -253,6 +262,61 @@ second line
 
         self.assertEqual(cb_reads, [b'a', b'b'])
 
+    def test_add_watch_no_data(self):
+        (r, w) = os.pipe()
+
+        ch = GLib.IOChannel(filedes=r)
+        ch.set_encoding(None)
+        ch.set_flags(ch.get_flags() | GLib.IOFlags.NONBLOCK)
+
+        cb_reads = []
+
+        def cb(channel, condition):
+            self.assertEqual(channel, ch)
+            self.assertEqual(condition, GLib.IOCondition.IN)
+            cb_reads.append(channel.read())
+            return True
+
+        id = GLib.io_add_watch(ch, GLib.PRIORITY_HIGH, GLib.IOCondition.IN, cb)
+
+        ml = GLib.MainLoop()
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_HIGH)
+        GLib.timeout_add(10, lambda: os.write(w, b'a') and False)
+        GLib.timeout_add(100, lambda: os.write(w, b'b') and False)
+        GLib.timeout_add(200, ml.quit)
+        ml.run()
+
+        self.assertEqual(cb_reads, [b'a', b'b'])
+
+    def test_add_watch_with_data(self):
+        (r, w) = os.pipe()
+
+        ch = GLib.IOChannel(filedes=r)
+        ch.set_encoding(None)
+        ch.set_flags(ch.get_flags() | GLib.IOFlags.NONBLOCK)
+
+        cb_reads = []
+
+        def cb(channel, condition, data):
+            self.assertEqual(channel, ch)
+            self.assertEqual(condition, GLib.IOCondition.IN)
+            self.assertEqual(data, 'hello')
+            cb_reads.append(channel.read())
+            return True
+
+        id = GLib.io_add_watch(ch, GLib.PRIORITY_HIGH, GLib.IOCondition.IN, cb, 'hello')
+
+        ml = GLib.MainLoop()
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_HIGH)
+        GLib.timeout_add(10, lambda: os.write(w, b'a') and False)
+        GLib.timeout_add(100, lambda: os.write(w, b'b') and False)
+        GLib.timeout_add(200, ml.quit)
+        ml.run()
+
+        self.assertEqual(cb_reads, [b'a', b'b'])
+
     def test_backwards_compat_flags(self):
         self.assertEqual(GLib.IOCondition.IN, GLib.IO_IN)
         self.assertEqual(GLib.IOFlags.NONBLOCK, GLib.IO_FLAG_NONBLOCK)



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