pytevent improvements & Python 3 port

Petr Viktorin pviktori at redhat.com
Tue May 26 06:07:45 MDT 2015


Hello,

The first patch is a fix for a bug in the buildsystem changes for py3.
(It only occurs when building for two Pythons, so Samba is safe.)

I've played a bit with the pytevent wrapper, and found that the types
for timers and FDs are not defined, so, for example, trying to print
them out segfaults:

    >>> import tevent
    >>> ctx = tevent.Context()
    >>> timer = ctx.add_timer(0, lambda t: None)
    >>> timer
    Segmentation fault (core dumped)

I'm sending a quick fix for FDs, and a more comprehensive one for timers.

Also, "Context.add_timer" requires passing a struct timeval cast to a
long inside a Python integer, which is very hard to do portably from
Python. I've added "add_timer_offset", which takes a number of seconds
from now.

I've also ported pytevent to Python 3. The library doesn't deal with
strings much, so the port is trivial.

-- 
Petr Viktorin
-------------- next part --------------
From 7fc58897bb6d506790f515233b9aeba03c8f17cc Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Fri, 22 May 2015 13:06:48 +0200
Subject: [PATCH 1/6] buildtools: Always reset the build environment

In install_library, the Build object's environment was not reset
after an early return, so the extrapython env would be used in
subsequent build steps.
Wrap everything in a try-finally block to make sure the env is reset.

(Almost all of the change is indentation, `git show -w` recommended.)

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 buildtools/wafsamba/samba_install.py | 163 ++++++++++++++++++-----------------
 1 file changed, 82 insertions(+), 81 deletions(-)

diff --git a/buildtools/wafsamba/samba_install.py b/buildtools/wafsamba/samba_install.py
index af8d2ad..3d0c23a 100644
--- a/buildtools/wafsamba/samba_install.py
+++ b/buildtools/wafsamba/samba_install.py
@@ -60,95 +60,96 @@ def install_library(self):
     bld = self.bld
 
     default_env = bld.all_envs['default']
-    if self.env['IS_EXTRA_PYTHON']:
-        bld.all_envs['default'] = bld.all_envs['extrapython']
+    try:
+        if self.env['IS_EXTRA_PYTHON']:
+            bld.all_envs['default'] = bld.all_envs['extrapython']
 
-    install_ldflags = install_rpath(self)
-    build_ldflags   = build_rpath(bld)
+        install_ldflags = install_rpath(self)
+        build_ldflags   = build_rpath(bld)
 
-    if not Options.is_install or not getattr(self, 'samba_install', True):
-        # just need to set the build rpath if we are not installing
-        self.env.RPATH = build_ldflags
-        return
+        if not Options.is_install or not getattr(self, 'samba_install', True):
+            # just need to set the build rpath if we are not installing
+            self.env.RPATH = build_ldflags
+            return
 
-    # setup the install path, expanding variables
-    install_path = getattr(self, 'samba_inst_path', None)
-    if install_path is None:
-        if getattr(self, 'private_library', False):
-            install_path = '${PRIVATELIBDIR}'
+        # setup the install path, expanding variables
+        install_path = getattr(self, 'samba_inst_path', None)
+        if install_path is None:
+            if getattr(self, 'private_library', False):
+                install_path = '${PRIVATELIBDIR}'
+            else:
+                install_path = '${LIBDIR}'
+        install_path = bld.EXPAND_VARIABLES(install_path)
+
+        target_name = self.target
+
+        if install_ldflags != build_ldflags:
+            # we will be creating a new target name, and using that for the
+            # install link. That stops us from overwriting the existing build
+            # target, which has different ldflags
+            self.done_install_library = True
+            t = self.clone(self.env)
+            t.posted = False
+            t.target += '.inst'
+            self.env.RPATH = build_ldflags
         else:
-            install_path = '${LIBDIR}'
-    install_path = bld.EXPAND_VARIABLES(install_path)
-
-    target_name = self.target
-
-    if install_ldflags != build_ldflags:
-        # we will be creating a new target name, and using that for the
-        # install link. That stops us from overwriting the existing build
-        # target, which has different ldflags
-        self.done_install_library = True
-        t = self.clone(self.env)
-        t.posted = False
-        t.target += '.inst'
-        self.env.RPATH = build_ldflags
-    else:
-        t = self
-
-    t.env.RPATH = install_ldflags
-
-    dev_link     = None
-
-    # in the following the names are:
-    # - inst_name is the name with .inst. in it, in the build
-    #   directory
-    # - install_name is the name in the install directory
-    # - install_link is a symlink in the install directory, to install_name
-
-    if getattr(self, 'samba_realname', None):
-        install_name = self.samba_realname
-        install_link = None
-        if getattr(self, 'soname', ''):
+            t = self
+
+        t.env.RPATH = install_ldflags
+
+        dev_link     = None
+
+        # in the following the names are:
+        # - inst_name is the name with .inst. in it, in the build
+        #   directory
+        # - install_name is the name in the install directory
+        # - install_link is a symlink in the install directory, to install_name
+
+        if getattr(self, 'samba_realname', None):
+            install_name = self.samba_realname
+            install_link = None
+            if getattr(self, 'soname', ''):
+                install_link = self.soname
+            if getattr(self, 'samba_type', None) == 'PYTHON':
+                inst_name    = bld.make_libname(t.target, nolibprefix=True, python=True)
+            else:
+                inst_name    = bld.make_libname(t.target)
+        elif self.vnum:
+            vnum_base    = self.vnum.split('.')[0]
+            install_name = bld.make_libname(target_name, version=self.vnum)
+            install_link = bld.make_libname(target_name, version=vnum_base)
+            inst_name    = bld.make_libname(t.target)
+            if not self.private_library:
+                # only generate the dev link for non-bundled libs
+                dev_link     = bld.make_libname(target_name)
+        elif getattr(self, 'soname', ''):
+            install_name = bld.make_libname(target_name)
             install_link = self.soname
-        if getattr(self, 'samba_type', None) == 'PYTHON':
-            inst_name    = bld.make_libname(t.target, nolibprefix=True, python=True)
-        else:
             inst_name    = bld.make_libname(t.target)
-    elif self.vnum:
-        vnum_base    = self.vnum.split('.')[0]
-        install_name = bld.make_libname(target_name, version=self.vnum)
-        install_link = bld.make_libname(target_name, version=vnum_base)
-        inst_name    = bld.make_libname(t.target)
-        if not self.private_library:
-            # only generate the dev link for non-bundled libs
-            dev_link     = bld.make_libname(target_name)
-    elif getattr(self, 'soname', ''):
-        install_name = bld.make_libname(target_name)
-        install_link = self.soname
-        inst_name    = bld.make_libname(t.target)
-    else:
-        install_name = bld.make_libname(target_name)
-        install_link = None
-        inst_name    = bld.make_libname(t.target)
-
-    if t.env.SONAME_ST:
-        # ensure we get the right names in the library
-        if install_link:
-            t.env.append_value('LINKFLAGS', t.env.SONAME_ST % install_link)
         else:
-            t.env.append_value('LINKFLAGS', t.env.SONAME_ST % install_name)
-        t.env.SONAME_ST = ''
-
-    # tell waf to install the library
-    bld.install_as(os.path.join(install_path, install_name),
-                   os.path.join(self.path.abspath(bld.env), inst_name),
-                   chmod=MODE_755)
-    if install_link and install_link != install_name:
-        # and the symlink if needed
-        bld.symlink_as(os.path.join(install_path, install_link), os.path.basename(install_name))
-    if dev_link:
-        bld.symlink_as(os.path.join(install_path, dev_link), os.path.basename(install_name))
+            install_name = bld.make_libname(target_name)
+            install_link = None
+            inst_name    = bld.make_libname(t.target)
 
-    bld.all_envs['default'] = default_env
+        if t.env.SONAME_ST:
+            # ensure we get the right names in the library
+            if install_link:
+                t.env.append_value('LINKFLAGS', t.env.SONAME_ST % install_link)
+            else:
+                t.env.append_value('LINKFLAGS', t.env.SONAME_ST % install_name)
+            t.env.SONAME_ST = ''
+
+        # tell waf to install the library
+        bld.install_as(os.path.join(install_path, install_name),
+                       os.path.join(self.path.abspath(bld.env), inst_name),
+                       chmod=MODE_755)
+        if install_link and install_link != install_name:
+            # and the symlink if needed
+            bld.symlink_as(os.path.join(install_path, install_link), os.path.basename(install_name))
+        if dev_link:
+            bld.symlink_as(os.path.join(install_path, dev_link), os.path.basename(install_name))
+    finally:
+        bld.all_envs['default'] = default_env
 
 
 @feature('cshlib')
-- 
2.1.0


From 17a459af8959091ea7c705d998322ae74d50aa20 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Thu, 4 Dec 2014 12:44:56 +0100
Subject: [PATCH 2/6] pytevent: Better error and reference handling

py_backend_list:
- Handle cases of PyString_FromString or PyList_Append failing.
- Properly decrease the reference count of the returned strings.

py_register_backend:
- Decref "name" after use

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/tevent/pytevent.c | 31 +++++++++++++++++++++++++------
 1 file changed, 25 insertions(+), 6 deletions(-)

diff --git a/lib/tevent/pytevent.c b/lib/tevent/pytevent.c
index af3f9d6..4de0e3d 100644
--- a/lib/tevent/pytevent.c
+++ b/lib/tevent/pytevent.c
@@ -177,14 +177,18 @@ static PyObject *py_register_backend(PyObject *self, PyObject *args)
 
 	if (!PyString_Check(name)) {
 		PyErr_SetNone(PyExc_TypeError);
+		Py_DECREF(name);
 		return NULL;
 	}
 
 	if (!tevent_register_backend(PyString_AsString(name), &py_tevent_ops)) { /* FIXME: What to do with backend */
 		PyErr_SetNone(PyExc_RuntimeError);
+		Py_DECREF(name);
 		return NULL;
 	}
 
+	Py_DECREF(name);
+
 	Py_RETURN_NONE;
 }
 
@@ -684,9 +688,10 @@ static PyObject *py_set_default_backend(PyObject *self, PyObject *args)
 
 static PyObject *py_backend_list(PyObject *self)
 {
-	PyObject *ret;
-	int i;
-	const char **backends;
+	PyObject *ret = NULL;
+	PyObject *string = NULL;
+	int i, result;
+	const char **backends = NULL;
 
 	ret = PyList_New(0);
 	if (ret == NULL) {
@@ -696,16 +701,30 @@ static PyObject *py_backend_list(PyObject *self)
 	backends = tevent_backend_list(NULL);
 	if (backends == NULL) {
 		PyErr_SetNone(PyExc_RuntimeError);
-		Py_DECREF(ret);
-		return NULL;
+		goto err;
 	}
 	for (i = 0; backends[i]; i++) {
-		PyList_Append(ret, PyString_FromString(backends[i]));
+		string = PyString_FromString(backends[i]);
+		if (!string) {
+			goto err;
+		}
+		result = PyList_Append(ret, string);
+		if (result) {
+			goto err;
+		}
+		Py_DECREF(string);
+		string = NULL;
 	}
 
 	talloc_free(backends);
 
 	return ret;
+
+err:
+	Py_XDECREF(ret);
+	Py_XDECREF(string);
+	talloc_free(backends);
+	return NULL;
 }
 
 static PyMethodDef tevent_methods[] = {
-- 
2.1.0


From 82425014c65442f3916358301f770b927dfd5e1e Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Fri, 22 May 2015 13:29:11 +0200
Subject: [PATCH 3/6] pytevent: Define missing TeventFd_Type object

The type objects for Fd was declared but never defined,
resulting in segfaults when it was used.
Define it.

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/tevent/pytevent.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/lib/tevent/pytevent.c b/lib/tevent/pytevent.c
index 4de0e3d..a495da5 100644
--- a/lib/tevent/pytevent.c
+++ b/lib/tevent/pytevent.c
@@ -415,6 +415,19 @@ static void py_fd_handler(struct tevent_context *ev,
 	Py_XDECREF(ret);
 }
 
+static void py_tevent_fp_dealloc(TeventFd_Object *self)
+{
+	talloc_free(self->fd);
+	PyObject_Del(self);
+}
+
+static PyTypeObject TeventFd_Type = {
+	.tp_name = "tevent.Fd",
+	.tp_basicsize = sizeof(TeventFd_Object),
+	.tp_dealloc = (destructor)py_tevent_fp_dealloc,
+	.tp_flags = Py_TPFLAGS_DEFAULT,
+};
+
 static PyObject *py_tevent_context_add_fd(TeventContext_Object *self, PyObject *args)
 {
 	int fd, flags;
-- 
2.1.0


From d2859d6030b6929caac710a03c0310041f1e8084 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Tue, 26 May 2015 13:25:12 +0200
Subject: [PATCH 4/6] pytalloc: Improve timer wrapper, and test it

Using Context.add_timer resulted in crashes due to missing type object
and bad reference handling.
Add a TeventTimer_Type struct, and introduce a clear ownership/lifetime model.

Add a "add_timer_offset" to allow adding timers from Python. (add_timer
requires passing struct timeval as a Python integer, which can't really
be done portably).

Add tests.

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/tevent/bindings.py |  52 ++++++++++++++++-
 lib/tevent/pytevent.c  | 147 +++++++++++++++++++++++++++++++++++++++++++------
 lib/tevent/wscript     |   5 +-
 3 files changed, 185 insertions(+), 19 deletions(-)

diff --git a/lib/tevent/bindings.py b/lib/tevent/bindings.py
index 1060caf..55aafbb 100644
--- a/lib/tevent/bindings.py
+++ b/lib/tevent/bindings.py
@@ -22,8 +22,10 @@
 #   License along with this library; if not, see <http://www.gnu.org/licenses/>.
 
 import signal
+from unittest import TestCase, TestProgram
+import gc
+
 import _tevent
-from unittest import TestCase
 
 class BackendListTests(TestCase):
 
@@ -60,3 +62,51 @@ class ContextTests(TestCase):
     def test_add_signal(self):
         sig = self.ctx.add_signal(signal.SIGINT, 0, lambda callback: None)
         self.assertTrue(isinstance(sig, _tevent.Signal))
+
+    def test_timer(self):
+        """Test a timer is can be scheduled"""
+        collecting_list = []
+        # time "0" has already passed, callback will be scheduled immediately
+        timer = self.ctx.add_timer(0, lambda t: collecting_list.append(True))
+        self.assertTrue(timer.active)
+        self.assertEqual(collecting_list, [])
+        self.ctx.loop_once()
+        self.assertFalse(timer.active)
+        self.assertEqual(collecting_list, [True])
+
+    def test_timer_deallocate_timer(self):
+        """Test timer is scheduled even if reference to it isn't held"""
+        collecting_list = []
+        def callback(t):
+            collecting_list.append(True)
+        timer = self.ctx.add_timer(0, lambda t: collecting_list.append(True))
+        gc.collect()
+        self.assertEqual(collecting_list, [])
+        self.ctx.loop_once()
+        self.assertEqual(collecting_list, [True])
+
+    def test_timer_deallocate_context(self):
+        """Test timer is unscheduled when context is freed"""
+        collecting_list = []
+        def callback(t):
+            collecting_list.append(True)
+        timer = self.ctx.add_timer(0, lambda t: collecting_list.append(True))
+        self.assertTrue(timer.active)
+        del self.ctx
+        gc.collect()
+        self.assertEqual(collecting_list, [])
+        self.assertFalse(timer.active)
+
+    def test_timer_offset(self):
+        """Test scheduling timer with an offset"""
+        collecting_list = []
+        self.ctx.add_timer_offset(0.2, lambda t: collecting_list.append(2))
+        self.ctx.add_timer_offset(0.1, lambda t: collecting_list.append(1))
+        self.assertEqual(collecting_list, [])
+        self.ctx.loop_once()
+        self.assertEqual(collecting_list, [1])
+        self.ctx.loop_once()
+        self.assertEqual(collecting_list, [1, 2])
+
+if __name__ == '__main__':
+    TestProgram()
diff --git a/lib/tevent/pytevent.c b/lib/tevent/pytevent.c
index a495da5..a04d565 100644
--- a/lib/tevent/pytevent.c
+++ b/lib/tevent/pytevent.c
@@ -50,6 +50,7 @@ typedef struct {
 typedef struct {
 	PyObject_HEAD
 	struct tevent_timer *timer;
+	PyObject *callback;
 } TeventTimer_Object;
 
 typedef struct {
@@ -372,38 +373,148 @@ static void py_timer_handler(struct tevent_context *ev,
 				       struct timeval current_time,
 				       void *private_data)
 {
-	PyObject *callback = private_data, *ret;
-	ret = PyObject_CallFunction(callback, "l", te);
+	TeventTimer_Object *self = private_data;
+	PyObject *ret;
+
+	ret = PyObject_CallFunction(self->callback, "l", te);
+	if (ret == NULL) {
+		/* No Python stack to propagate exception to; just print traceback */
+		PyErr_PrintEx(0);
+	}
 	Py_XDECREF(ret);
 }
 
-static PyObject *py_tevent_context_add_timer(TeventContext_Object *self, PyObject *args)
+static void py_tevent_timer_dealloc(TeventTimer_Object *self)
 {
-	TeventTimer_Object *ret;
-	struct timeval next_event;
-	struct tevent_timer *timer;
-	PyObject *handler;
-	if (!PyArg_ParseTuple(args, "lO", &next_event, &handler))
-		return NULL;
-
-	timer = tevent_add_timer(self->ev, NULL, next_event, py_timer_handler,
-							 handler);
-	if (timer == NULL) {
-		PyErr_SetNone(PyExc_RuntimeError);
-		return NULL;
+	if (self->timer) {
+		talloc_free(self->timer);
 	}
+	Py_DECREF(self->callback);
+	PyObject_Del(self);
+}
+
+static int py_tevent_timer_traverse(TeventTimer_Object *self, visitproc visit, void *arg)
+{
+	Py_VISIT(self->callback);
+	return 0;
+}
+
+static PyObject* py_tevent_timer_get_active(TeventTimer_Object *self) {
+	return PyBool_FromLong(self->timer != NULL);
+}
+
+struct PyGetSetDef py_tevent_timer_getset[] = {
+	{
+		.name = "active",
+		.get = (getter)py_tevent_timer_get_active,
+		.doc = "true if the timer is scheduled to run",
+	},
+	{NULL},
+};
+
+static PyTypeObject TeventTimer_Type = {
+	.tp_name = "tevent.Timer",
+	.tp_basicsize = sizeof(TeventTimer_Object),
+	.tp_dealloc = (destructor)py_tevent_timer_dealloc,
+	.tp_traverse = (traverseproc)py_tevent_timer_traverse,
+	.tp_getset = py_tevent_timer_getset,
+	.tp_flags = Py_TPFLAGS_DEFAULT,
+};
+
+static int timer_destructor(void* ptr)
+{
+	TeventTimer_Object *obj = *(TeventTimer_Object **)ptr;
+	obj->timer = NULL;
+	Py_DECREF(obj);
+	return 0;
+}
+
+static PyObject *py_tevent_context_add_timer_internal(TeventContext_Object *self,
+                                                      struct timeval next_event,
+                                                      PyObject *callback)
+{
+	/* Ownership notes:
+	 *
+	 * There are 5 pieces in play; two tevent contexts and 3 Python objects:
+	 * - The tevent timer
+	 * - The tevent context
+	 * - The Python context -- "self"
+	 * - The Python timer (TeventTimer_Object) -- "ret"
+	 * - The Python callback function -- "callback"
+	 *
+	 * We only use the Python context for getting the tevent context,
+	 * afterwards it can be destroyed.
+	 *
+	 * The tevent context owns the tevent timer.
+	 *
+	 * The tevent timer holds a reference to the Python timer, so the Python
+	 * timer must always outlive the tevent timer.
+	 * The Python timer has a pointer to the tevent timer; a destructor is
+	 * used to set this to NULL when the tevent timer is deallocated.
+	 *
+	 * The tevent timer can be deallocated in these cases:
+	 *  1) when the context is destroyed
+	 *  2) after the event fires
+	 *  Posssibly, API might be added to cancel (free the tevent timer).
+	 *
+	 * The Python timer holds a reference to the callback.
+	 */
+	TeventTimer_Object *ret;
+	TeventTimer_Object **tmp_context;
 
 	ret = PyObject_New(TeventTimer_Object, &TeventTimer_Type);
 	if (ret == NULL) {
 		PyErr_NoMemory();
-		talloc_free(timer);
 		return NULL;
 	}
-	ret->timer = timer;
+	Py_INCREF(callback);
+	ret->callback = callback;
+	ret->timer = tevent_add_timer(self->ev, NULL, next_event, py_timer_handler,
+	                              ret);
+	if (ret->timer == NULL) {
+		Py_DECREF(ret);
+		PyErr_SetString(PyExc_RuntimeError, "Could not initialize timer");
+		return NULL;
+	}
+	tmp_context = talloc(ret->timer, TeventTimer_Object*);
+	if (tmp_context == NULL) {
+		talloc_free(ret->timer);
+		Py_DECREF(ret);
+		PyErr_SetString(PyExc_RuntimeError, "Could not initialize timer");
+		return NULL;
+	}
+	Py_INCREF(ret);
+	*tmp_context = ret;
+	talloc_set_destructor(tmp_context, timer_destructor);
 
 	return (PyObject *)ret;
 }
 
+static PyObject *py_tevent_context_add_timer(TeventContext_Object *self, PyObject *args)
+{
+	struct timeval next_event;
+	PyObject *callback;
+	if (!PyArg_ParseTuple(args, "lO", &next_event, &callback))
+		return NULL;
+
+	return py_tevent_context_add_timer_internal(self, next_event, callback);
+}
+
+static PyObject *py_tevent_context_add_timer_offset(TeventContext_Object *self, PyObject *args)
+{
+	struct timeval next_event;
+	double offset;
+	int seconds;
+	PyObject *callback;
+	if (!PyArg_ParseTuple(args, "dO", &offset, &callback))
+		return NULL;
+
+	seconds = offset;
+	offset -= seconds;
+	next_event = tevent_timeval_current_ofs(seconds, (int)(offset*1000000));
+	return py_tevent_context_add_timer_internal(self, next_event, callback);
+}
+
 static void py_fd_handler(struct tevent_context *ev,
 				    struct tevent_fd *fde,
 				    uint16_t flags,
@@ -479,6 +590,8 @@ static PyMethodDef py_tevent_context_methods[] = {
 		METH_VARARGS, "S.add_signal(signum, sa_flags, handler) -> signal" },
 	{ "add_timer", (PyCFunction)py_tevent_context_add_timer,
 		METH_VARARGS, "S.add_timer(next_event, handler) -> timer" },
+	{ "add_timer_offset", (PyCFunction)py_tevent_context_add_timer_offset,
+		METH_VARARGS, "S.add_timer(offset_seconds, handler) -> timer" },
 	{ "add_fd", (PyCFunction)py_tevent_context_add_fd, 
 		METH_VARARGS, "S.add_fd(fd, flags, handler) -> fd" },
 #ifdef TEVENT_DEPRECATED
diff --git a/lib/tevent/wscript b/lib/tevent/wscript
index 026cd9a..d0f1ac1 100755
--- a/lib/tevent/wscript
+++ b/lib/tevent/wscript
@@ -13,7 +13,7 @@ while not os.path.exists(srcdir+'/buildtools') and len(srcdir.split('/')) < 5:
     srcdir = srcdir + '/..'
 sys.path.insert(0, srcdir + '/buildtools/wafsamba')
 
-import wafsamba, samba_dist, Options, Logs
+import wafsamba, samba_dist, samba_utils, Options, Logs
 
 samba_dist.DIST_DIRS('lib/tevent:. lib/replace:lib/replace lib/talloc:lib/talloc buildtools:buildtools third_party/waf:third_party/waf')
 
@@ -130,6 +130,9 @@ def test(ctx):
     '''test tevent'''
     print("The tevent testsuite is part of smbtorture in samba4")
 
+    pyret = samba_utils.RUN_PYTHON_TESTS(['bindings.py'])
+    sys.exit(pyret)
+
 
 def dist():
     '''makes a tarball for distribution'''
-- 
2.1.0


From 82df172792d82f517046536621503ea8733c9d4b Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Fri, 22 May 2015 11:47:56 +0200
Subject: [PATCH 5/6] pytevent: Port to Python 3

- Use PyStr (String on py2, Unicode on py3) for text strings
- Use PyLong instead of PyInt on Python 3
- Use new module initialization

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/tevent/pytevent.c | 70 +++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 57 insertions(+), 13 deletions(-)

diff --git a/lib/tevent/pytevent.c b/lib/tevent/pytevent.c
index a04d565..5725ae3 100644
--- a/lib/tevent/pytevent.c
+++ b/lib/tevent/pytevent.c
@@ -25,6 +25,17 @@
 #include <Python.h>
 #include <tevent.h>
 
+#if PY_MAJOR_VERSION >= 3
+#define PyStr_Check PyUnicode_Check
+#define PyStr_FromString PyUnicode_FromString
+#define PyStr_AsUTF8 PyUnicode_AsUTF8
+#define PyInt_FromLong PyLong_FromLong
+#else
+#define PyStr_Check PyString_Check
+#define PyStr_FromString PyString_FromString
+#define PyStr_AsUTF8 PyString_AsString
+#endif
+
 void init_tevent(void);
 
 typedef struct {
@@ -176,13 +187,13 @@ static PyObject *py_register_backend(PyObject *self, PyObject *args)
 		return NULL;
 	}
 
-	if (!PyString_Check(name)) {
+	if (!PyStr_Check(name)) {
 		PyErr_SetNone(PyExc_TypeError);
 		Py_DECREF(name);
 		return NULL;
 	}
 
-	if (!tevent_register_backend(PyString_AsString(name), &py_tevent_ops)) { /* FIXME: What to do with backend */
+	if (!tevent_register_backend(PyStr_AsUTF8(name), &py_tevent_ops)) { /* FIXME: What to do with backend */
 		PyErr_SetNone(PyExc_RuntimeError);
 		Py_DECREF(name);
 		return NULL;
@@ -830,7 +841,7 @@ static PyObject *py_backend_list(PyObject *self)
 		goto err;
 	}
 	for (i = 0; backends[i]; i++) {
-		string = PyString_FromString(backends[i]);
+		string = PyStr_FromString(backends[i]);
 		if (!string) {
 			goto err;
 		}
@@ -863,31 +874,48 @@ static PyMethodDef tevent_methods[] = {
 	{ NULL },
 };
 
-void init_tevent(void)
+#define MODULE_DOC PyDoc_STR("Python wrapping of talloc-maintained objects.")
+
+#if PY_MAJOR_VERSION >= 3
+static struct PyModuleDef moduledef = {
+	PyModuleDef_HEAD_INIT,
+	.m_name = "_tevent",
+	.m_doc = MODULE_DOC,
+	.m_size = -1,
+	.m_methods = tevent_methods,
+};
+#endif
+
+PyObject * module_init(void);
+PyObject * module_init(void)
 {
 	PyObject *m;
 
 	if (PyType_Ready(&TeventContext_Type) < 0)
-		return;
+		return NULL;
 
 	if (PyType_Ready(&TeventQueue_Type) < 0)
-		return;
+		return NULL;
 
 	if (PyType_Ready(&TeventReq_Type) < 0)
-		return;
+		return NULL;
 
 	if (PyType_Ready(&TeventSignal_Type) < 0)
-		return;
+		return NULL;
 
 	if (PyType_Ready(&TeventTimer_Type) < 0)
-		return;
+		return NULL;
 
 	if (PyType_Ready(&TeventFd_Type) < 0)
-		return;
+		return NULL;
 
-	m = Py_InitModule3("_tevent", tevent_methods, "Tevent integration for twisted.");
+#if PY_MAJOR_VERSION >= 3
+	m = PyModule_Create(&moduledef);
+#else
+	m = Py_InitModule3("_tevent", tevent_methods, MODULE_DOC);
+#endif
 	if (m == NULL)
-		return;
+		return NULL;
 
 	Py_INCREF(&TeventContext_Type);
 	PyModule_AddObject(m, "Context", (PyObject *)&TeventContext_Type);
@@ -907,5 +935,21 @@ void init_tevent(void)
 	Py_INCREF(&TeventFd_Type);
 	PyModule_AddObject(m, "Fd", (PyObject *)&TeventFd_Type);
 
-	PyModule_AddObject(m, "__version__", PyString_FromString(PACKAGE_VERSION));
+	PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION);
+
+	return m;
+}
+
+#if PY_MAJOR_VERSION >= 3
+PyMODINIT_FUNC PyInit__tevent(void);
+PyMODINIT_FUNC PyInit__tevent(void)
+{
+	return module_init();
+}
+#else
+void init_tevent(void);
+void init_tevent(void)
+{
+	module_init();
 }
+#endif
-- 
2.1.0


From c79947c11e49ec55d2987d505de01f87b6e86076 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Fri, 22 May 2015 11:52:39 +0200
Subject: [PATCH 6/6] pytevent: Build for two versions of Python at once

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/tevent/wscript | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)

diff --git a/lib/tevent/wscript b/lib/tevent/wscript
index d0f1ac1..16a56c3 100755
--- a/lib/tevent/wscript
+++ b/lib/tevent/wscript
@@ -113,18 +113,21 @@ def build(bld):
                           private_library=private_library)
 
     if not bld.CONFIG_SET('USING_SYSTEM_PYTEVENT') and not bld.env.disable_python:
-        bld.SAMBA_PYTHON('pytevent',
-                         'pytevent.c',
-                         deps='tevent',
-                         realname='_tevent.so',
-                         cflags='-DPACKAGE_VERSION=\"%s\"' % VERSION)
+        for env in bld.gen_python_environments(['PKGCONFIGDIR']):
+            bld.SAMBA_PYTHON('_tevent',
+                            'pytevent.c',
+                            deps='tevent',
+                            realname='_tevent.so',
+                            cflags='-DPACKAGE_VERSION=\"%s\"' % VERSION)
+
+
+            bld.INSTALL_WILDCARD('${PYTHONARCHDIR}', 'tevent.py', flat=False)
+
         # install out various python scripts for use by make test
         bld.SAMBA_SCRIPT('tevent_python',
                          pattern='tevent.py',
                          installdir='python')
 
-        bld.INSTALL_WILDCARD('${PYTHONARCHDIR}', 'tevent.py', flat=False)
-
 
 def test(ctx):
     '''test tevent'''
-- 
2.1.0


More information about the samba-technical mailing list