summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristoph Reiter <creiter@src.gnome.org>2015-06-08 18:14:08 +0200
committerChristoph Reiter <creiter@src.gnome.org>2015-10-27 09:30:45 +0100
commit175d10665472e6f4090d707e3b89255814c932b1 (patch)
tree9686dacb6aa22489ebe62832cb85fb5447fed396
parentb1788c9a445c8a820121c42260bcbdbc3ae8dfba (diff)
downloadpygobject-175d10665472e6f4090d707e3b89255814c932b1.tar.gz
Use a named tuple for returning multiple values
>>> v = Gtk.Button().get_alignment() >>> v (xalign=0.5, yalign=0.5) >>> v.xalign 0.5 For each GICallable a new gi._gi.ResultTuple subclass is created which knows the return value names of that callable and displays them in __repr__, __dir__ and allows to access tuple items by name. The subclass is cached in PyGICallableCache. To reduce the number of small tuple allocations use a free list similar to the one used for pure tuples in CPython. https://bugzilla.gnome.org/show_bug.cgi?id=727374
-rw-r--r--gi/Makefile.am2
-rw-r--r--gi/gimodule.c1
-rw-r--r--gi/pygi-cache.c34
-rw-r--r--gi/pygi-cache.h6
-rw-r--r--gi/pygi-invoke.c35
-rw-r--r--gi/pygi-private.h1
-rw-r--r--gi/pygi-resulttuple.c354
-rw-r--r--gi/pygi-resulttuple.h34
-rw-r--r--gi/pyglib-python-compat.h5
-rw-r--r--tests/Makefile.am1
-rw-r--r--tests/test_resulttuple.py87
11 files changed, 543 insertions, 17 deletions
diff --git a/gi/Makefile.am b/gi/Makefile.am
index 2e61af8d..314fd413 100644
--- a/gi/Makefile.am
+++ b/gi/Makefile.am
@@ -78,6 +78,8 @@ _gi_la_SOURCES = \
pygi-source.h \
pygi-argument.c \
pygi-argument.h \
+ pygi-resulttuple.c \
+ pygi-resulttuple.h \
pygi-type.c \
pygi-type.h \
pygi-boxed.c \
diff --git a/gi/gimodule.c b/gi/gimodule.c
index cc8fd667..44f09013 100644
--- a/gi/gimodule.c
+++ b/gi/gimodule.c
@@ -664,6 +664,7 @@ PYGLIB_MODULE_START(_gi, "_gi")
_pygi_struct_register_types (module);
_pygi_boxed_register_types (module);
_pygi_ccallback_register_types (module);
+ pygi_resulttuple_register_types (module);
PyGIWarning = PyErr_NewException ("gi.PyGIWarning", PyExc_Warning, NULL);
diff --git a/gi/pygi-cache.c b/gi/pygi-cache.c
index 84cc4634..0869e390 100644
--- a/gi/pygi-cache.c
+++ b/gi/pygi-cache.c
@@ -465,6 +465,9 @@ _callable_cache_generate_args_cache_real (PyGICallableCache *callable_cache,
PyGIArgCache *return_cache;
PyGIDirection return_direction;
gssize last_explicit_arg_index;
+ PyObject *tuple_names;
+ GSList *arg_cache_item;
+ PyTypeObject* resulttuple_type;
/* Return arguments are always considered out */
return_direction = _pygi_get_direction (callable_cache, GI_DIRECTION_OUT);
@@ -641,6 +644,36 @@ _callable_cache_generate_args_cache_real (PyGICallableCache *callable_cache,
}
}
+ if (!return_cache->is_skipped && return_cache->type_tag != GI_TYPE_TAG_VOID) {
+ callable_cache->has_return = TRUE;
+ }
+
+ tuple_names = PyList_New (0);
+ if (callable_cache->has_return) {
+ PyList_Append (tuple_names, Py_None);
+ }
+
+ arg_cache_item = callable_cache->to_py_args;
+ while (arg_cache_item) {
+ const gchar *arg_name = ((PyGIArgCache *)arg_cache_item->data)->arg_name;
+ PyObject *arg_string = PYGLIB_PyUnicode_FromString (arg_name);
+ PyList_Append (tuple_names, arg_string);
+ Py_DECREF (arg_string);
+ arg_cache_item = arg_cache_item->next;
+ }
+
+ /* No need to create a tuple type if there aren't multiple values */
+ if (PyList_Size (tuple_names) > 1) {
+ resulttuple_type = pygi_resulttuple_new_type (tuple_names);
+ if (resulttuple_type == NULL) {
+ Py_DECREF (tuple_names);
+ return FALSE;
+ } else {
+ callable_cache->resulttuple_type = resulttuple_type;
+ }
+ }
+ Py_DECREF (tuple_names);
+
return TRUE;
}
@@ -651,6 +684,7 @@ _callable_cache_deinit_real (PyGICallableCache *cache)
g_slist_free (cache->arg_name_list);
g_hash_table_destroy (cache->arg_name_hash);
g_ptr_array_unref (cache->args_cache);
+ Py_XDECREF (cache->resulttuple_type);
if (cache->return_cache != NULL)
pygi_arg_cache_free (cache->return_cache);
diff --git a/gi/pygi-cache.h b/gi/pygi-cache.h
index ad417554..4dfabd86 100644
--- a/gi/pygi-cache.h
+++ b/gi/pygi-cache.h
@@ -183,6 +183,12 @@ struct _PyGICallableCache
* This is used for the length of PyGIInvokeState.out_values */
gssize n_to_py_args;
+ /* If the callable return value gets used */
+ gboolean has_return;
+
+ /* The type used for returning multiple values or NULL */
+ PyTypeObject* resulttuple_type;
+
/* Number of out args for g_function_info_invoke that will be skipped
* when marshaling to Python due to them being implicitly available
* (list/array length).
diff --git a/gi/pygi-invoke.c b/gi/pygi-invoke.c
index 0290fc98..02de46ee 100644
--- a/gi/pygi-invoke.c
+++ b/gi/pygi-invoke.c
@@ -553,8 +553,7 @@ _invoke_marshal_out_args (PyGIInvokeState *state, PyGIFunctionCache *function_ca
PyGICallableCache *cache = (PyGICallableCache *) function_cache;
PyObject *py_out = NULL;
PyObject *py_return = NULL;
- gssize total_out_args = cache->n_to_py_args;
- gboolean has_return = FALSE;
+ gssize n_out_args = cache->n_to_py_args - cache->n_to_py_child_args;
if (cache->return_cache) {
if (!cache->return_cache->is_skipped) {
@@ -567,12 +566,6 @@ _invoke_marshal_out_args (PyGIInvokeState *state, PyGIFunctionCache *function_ca
cache);
return NULL;
}
-
-
- if (cache->return_cache->type_tag != GI_TYPE_TAG_VOID) {
- total_out_args++;
- has_return = TRUE;
- }
} else {
if (cache->return_cache->transfer == GI_TRANSFER_EVERYTHING) {
PyGIMarshalCleanupFunc to_py_cleanup =
@@ -588,9 +581,7 @@ _invoke_marshal_out_args (PyGIInvokeState *state, PyGIFunctionCache *function_ca
}
}
- total_out_args -= cache->n_to_py_child_args;
-
- if (cache->n_to_py_args - cache->n_to_py_child_args == 0) {
+ if (n_out_args == 0) {
if (cache->return_cache->is_skipped && state->error == NULL) {
/* we skip the return value and have no (out) arguments to return,
* so py_return should be NULL. But we must not return NULL,
@@ -602,7 +593,7 @@ _invoke_marshal_out_args (PyGIInvokeState *state, PyGIFunctionCache *function_ca
}
py_out = py_return;
- } else if (total_out_args == 1) {
+ } else if (!cache->has_return && n_out_args == 1) {
/* if we get here there is one out arg an no return */
PyGIArgCache *arg_cache = (PyGIArgCache *)cache->to_py_args->data;
py_out = arg_cache->to_py_marshaller (state,
@@ -617,16 +608,26 @@ _invoke_marshal_out_args (PyGIInvokeState *state, PyGIFunctionCache *function_ca
}
} else {
+ /* return a tuple */
gssize py_arg_index = 0;
GSList *cache_item = cache->to_py_args;
- /* return a tuple */
- py_out = PyTuple_New (total_out_args);
- if (has_return) {
+ gssize tuple_len = cache->has_return + n_out_args;
+
+ py_out = pygi_resulttuple_new (cache->resulttuple_type, tuple_len);
+
+ if (py_out == NULL) {
+ pygi_marshal_cleanup_args_to_py_parameter_fail (state,
+ cache,
+ py_arg_index);
+ return NULL;
+ }
+
+ if (cache->has_return) {
PyTuple_SET_ITEM (py_out, py_arg_index, py_return);
py_arg_index++;
}
- for(; py_arg_index < total_out_args; py_arg_index++) {
+ for (; py_arg_index < tuple_len; py_arg_index++) {
PyGIArgCache *arg_cache = (PyGIArgCache *)cache_item->data;
PyObject *py_obj = arg_cache->to_py_marshaller (state,
cache,
@@ -634,7 +635,7 @@ _invoke_marshal_out_args (PyGIInvokeState *state, PyGIFunctionCache *function_ca
state->args[arg_cache->c_arg_index].arg_pointer.v_pointer);
if (py_obj == NULL) {
- if (has_return)
+ if (cache->has_return)
py_arg_index--;
pygi_marshal_cleanup_args_to_py_parameter_fail (state,
diff --git a/gi/pygi-private.h b/gi/pygi-private.h
index af12f1ca..f70aec44 100644
--- a/gi/pygi-private.h
+++ b/gi/pygi-private.h
@@ -32,6 +32,7 @@
#include "pygi-invoke.h"
#include "pygi-cache.h"
#include "pygi-source.h"
+#include "pygi-resulttuple.h"
G_BEGIN_DECLS
#if PY_VERSION_HEX >= 0x03000000
diff --git a/gi/pygi-resulttuple.c b/gi/pygi-resulttuple.c
new file mode 100644
index 00000000..9d0b455e
--- /dev/null
+++ b/gi/pygi-resulttuple.c
@@ -0,0 +1,354 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2015 Christoph Reiter <reiter.christoph@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pygi-resulttuple.h"
+#include "pyglib-private.h"
+
+static char repr_format_key[] = "__repr_format";
+static char tuple_indices_key[] = "__tuple_indices";
+
+/* A free list similar to the one used for the CPython tuple. Difference
+ * is that zero length tuples aren't cached (as we don't need them)
+ * and that the freelist is smaller as we don't free it with the cyclic GC
+ * as CPython does. This wastes 21kB max.
+ */
+#define PyGIResultTuple_MAXSAVESIZE 10
+#define PyGIResultTuple_MAXFREELIST 100
+static PyObject *free_list[PyGIResultTuple_MAXSAVESIZE];
+static int numfree[PyGIResultTuple_MAXSAVESIZE];
+
+PYGLIB_DEFINE_TYPE ("gi._gi.ResultTuple", PyGIResultTuple_Type, PyTupleObject)
+
+/**
+ * ResultTuple.__repr__() implementation.
+ * Takes the _ResultTuple.__repr_format format string and applies the tuple
+ * values to it.
+ */
+static PyObject*
+resulttuple_repr(PyObject *self) {
+ PyObject *format, *repr, *format_attr;
+
+ format_attr = PYGLIB_PyUnicode_FromString (repr_format_key);
+ format = PyTuple_Type.tp_getattro (self, format_attr);
+ Py_DECREF (format_attr);
+ if (format == NULL)
+ return NULL;
+ repr = PYGLIB_PyUnicode_Format (format, self);
+ Py_DECREF (format);
+ return repr;
+}
+
+/**
+ * PyGIResultTuple_Type.tp_getattro implementation.
+ * Looks up the tuple index in _ResultTuple.__tuple_indices and returns the
+ * tuple item.
+ */
+static PyObject*
+resulttuple_getattro(PyObject *self, PyObject *name) {
+ PyObject *mapping, *index, *mapping_attr, *item;
+
+ mapping_attr = PYGLIB_PyUnicode_FromString (tuple_indices_key);
+ mapping = PyTuple_Type.tp_getattro (self, mapping_attr);
+ Py_DECREF (mapping_attr);
+ if (mapping == NULL)
+ return NULL;
+ g_assert (PyDict_Check (mapping));
+ index = PyDict_GetItem (mapping, name);
+
+ if (index != NULL) {
+ item = PyTuple_GET_ITEM (self, PYGLIB_PyLong_AsSsize_t (index));
+ Py_INCREF (item);
+ } else {
+ item = PyTuple_Type.tp_getattro (self, name);
+ }
+ Py_DECREF (mapping);
+
+ return item;
+}
+
+/**
+ * ResultTuple.__reduce__() implementation.
+ * Always returns (tuple, tuple(self))
+ * Needed so that pickling doesn't depend on our tuple subclass and unpickling
+ * works without it. As a result unpickle will give back in a normal tuple.
+ */
+static PyObject *
+resulttuple_reduce(PyObject *self)
+{
+ PyObject *tuple = PySequence_Tuple (self);
+ if (tuple == NULL)
+ return NULL;
+ return Py_BuildValue ("(O, (N))", &PyTuple_Type, tuple);
+}
+
+/**
+ * Extends __dir__ with the extra attributes accessible through
+ * resulttuple_getattro()
+ */
+static PyObject *
+resulttuple_dir(PyObject *self)
+{
+ PyObject *mapping_attr;
+ PyObject *items = NULL;
+ PyObject *mapping = NULL;
+ PyObject *mapping_values = NULL;
+ PyObject *result = NULL;
+
+ mapping_attr = PYGLIB_PyUnicode_FromString (tuple_indices_key);
+ mapping = PyTuple_Type.tp_getattro (self, mapping_attr);
+ Py_DECREF (mapping_attr);
+ if (mapping == NULL)
+ goto error;
+ items = PyObject_Dir ((PyObject*)self->ob_type);
+ if (items == NULL)
+ goto error;
+ mapping_values = PyDict_Keys (mapping);
+ if (mapping_values == NULL)
+ goto error;
+ result = PySequence_InPlaceConcat (items, mapping_values);
+
+error:
+ Py_XDECREF (items);
+ Py_XDECREF (mapping);
+ Py_XDECREF (mapping_values);
+
+ return result;
+}
+
+/**
+ * resulttuple_new_type:
+ * @args: one list object containing tuple item names and None
+ *
+ * Exposes pygi_resulttuple_new_type() as ResultTuple._new_type()
+ * to allow creation of result types for unit tests.
+ *
+ * Returns: A new PyTypeObject which is a subclass of PyGIResultTuple_Type
+ * or %NULL in case of an error.
+ */
+static PyObject *
+resulttuple_new_type(PyObject *self, PyObject *args) {
+ PyObject *tuple_names, *new_type;
+
+ if (!PyArg_ParseTuple (args, "O:ResultTuple._new_type", &tuple_names))
+ return NULL;
+
+ if (!PyList_Check (tuple_names)) {
+ Py_DECREF (tuple_names);
+ PyErr_SetString (PyExc_TypeError, "not a list");
+ return NULL;
+ }
+
+ new_type = (PyObject *)pygi_resulttuple_new_type (tuple_names);
+ Py_DECREF (tuple_names);
+ return new_type;
+}
+
+static PyMethodDef resulttuple_methods[] = {
+ {"__reduce__", (PyCFunction)resulttuple_reduce, METH_NOARGS},
+ {"__dir__", (PyCFunction)resulttuple_dir, METH_NOARGS},
+ {"_new_type", (PyCFunction)resulttuple_new_type,
+ METH_VARARGS | METH_STATIC},
+ {NULL, NULL, 0},
+};
+
+/**
+ * pygi_resulttuple_new_type:
+ * @tuple_names: A python list containing str or None items.
+ *
+ * Similar to namedtuple() creates a new tuple subclass which
+ * allows to access items by name and have a pretty __repr__.
+ * Each item in the passed name list corresponds to an item with
+ * the same index in the tuple class. If the name is None the item/index
+ * is unnamed.
+ *
+ * Returns: A new PyTypeObject which is a subclass of PyGIResultTuple_Type
+ * or %NULL in case of an error.
+ */
+PyTypeObject*
+pygi_resulttuple_new_type(PyObject *tuple_names) {
+ PyTypeObject *new_type;
+ PyObject *class_dict, *format_string, *empty_format, *named_format,
+ *format_list, *sep, *index_dict, *slots, *paren_format, *new_type_args,
+ *paren_string;
+ Py_ssize_t len, i;
+
+ g_assert (PyList_Check (tuple_names));
+
+ class_dict = PyDict_New ();
+
+ /* To save some memory don't use an instance dict */
+ slots = PyTuple_New (0);
+ PyDict_SetItemString (class_dict, "__slots__", slots);
+ Py_DECREF (slots);
+
+ format_list = PyList_New (0);
+ index_dict = PyDict_New ();
+
+ empty_format = PYGLIB_PyUnicode_FromString ("%r");
+ named_format = PYGLIB_PyUnicode_FromString ("%s=%%r");
+ len = PyList_Size (tuple_names);
+ for (i = 0; i < len; i++) {
+ PyObject *item, *named_args, *named_build, *index;
+ item = PyList_GET_ITEM (tuple_names, i);
+ if (item == Py_None) {
+ PyList_Append (format_list, empty_format);
+ } else {
+ named_args = Py_BuildValue ("(O)", item);
+ named_build = PYGLIB_PyUnicode_Format (named_format, named_args);
+ Py_DECREF (named_args);
+ PyList_Append (format_list, named_build);
+ Py_DECREF (named_build);
+ index = PYGLIB_PyLong_FromSsize_t (i);
+ PyDict_SetItem (index_dict, item, index);
+ Py_DECREF (index);
+ }
+ }
+ Py_DECREF (empty_format);
+ Py_DECREF (named_format);
+
+ sep = PYGLIB_PyUnicode_FromString (", ");
+ format_string = PyObject_CallMethod (sep, "join", "O", format_list);
+ Py_DECREF (sep);
+ Py_DECREF (format_list);
+ paren_format = PYGLIB_PyUnicode_FromString ("(%s)");
+ paren_string = PYGLIB_PyUnicode_Format (paren_format, format_string);
+ Py_DECREF (paren_format);
+ Py_DECREF (format_string);
+
+ PyDict_SetItemString (class_dict, repr_format_key, paren_string);
+ Py_DECREF (paren_string);
+
+ PyDict_SetItemString (class_dict, tuple_indices_key, index_dict);
+ Py_DECREF (index_dict);
+
+ new_type_args = Py_BuildValue ("s(O)O", "_ResultTuple",
+ &PyGIResultTuple_Type, class_dict);
+ new_type = (PyTypeObject *)PyType_Type.tp_new (&PyType_Type,
+ new_type_args, NULL);
+ Py_DECREF (new_type_args);
+ Py_DECREF (class_dict);
+
+ if (new_type != NULL) {
+ /* disallow subclassing as that would break the free list caching
+ * since we assume that all subclasses use PyTupleObject */
+ new_type->tp_flags &= ~Py_TPFLAGS_BASETYPE;
+ }
+
+ return new_type;
+}
+
+
+/**
+ * pygi_resulttuple_new:
+ * @subclass: A PyGIResultTuple_Type subclass which will be the type of the
+ * returned instance.
+ * @len: Length of the returned tuple
+ *
+ * Like PyTuple_New(). Return an uninitialized tuple of the given @length.
+ *
+ * Returns: An instance of @subclass or %NULL on error.
+ */
+PyObject *
+pygi_resulttuple_new(PyTypeObject *subclass, Py_ssize_t len) {
+ PyObject *self;
+ Py_ssize_t i;
+
+ /* Check the free list for a tuple object with the needed size;
+ * clear it and change the class to ours.
+ */
+ if (len > 0 && len < PyGIResultTuple_MAXSAVESIZE) {
+ self = free_list[len];
+ if (self != NULL) {
+ free_list[len] = PyTuple_GET_ITEM (self, 0);
+ numfree[len]--;
+ for (i=0; i < len; i++) {
+ PyTuple_SET_ITEM (self, i, NULL);
+ }
+ Py_TYPE (self) = subclass;
+ Py_INCREF (subclass);
+ _Py_NewReference (self);
+ PyObject_GC_Track (self);
+ return self;
+ }
+ }
+
+ /* For zero length tuples and in case the free list is empty, alloc
+ * as usual.
+ */
+ return subclass->tp_alloc (subclass, len);
+}
+
+static void resulttuple_dealloc(PyObject *self) {
+ Py_ssize_t i, len;
+
+ PyObject_GC_UnTrack (self);
+ Py_TRASHCAN_SAFE_BEGIN (self)
+
+ /* Free the tuple items and, if there is space, save the tuple object
+ * pointer to the front of the free list for its size. Otherwise free it.
+ */
+ len = Py_SIZE (self);
+ if (len > 0) {
+ for (i=0; i < len; i++) {
+ Py_XDECREF (PyTuple_GET_ITEM (self, i));
+ }
+
+ if (len < PyGIResultTuple_MAXSAVESIZE && numfree[len] < PyGIResultTuple_MAXFREELIST) {
+ PyTuple_SET_ITEM (self, 0, free_list[len]);
+ numfree[len]++;
+ free_list[len] = self;
+ goto done;
+ }
+ }
+
+ Py_TYPE (self)->tp_free (self);
+
+done:
+ Py_TRASHCAN_SAFE_END (self)
+}
+
+/**
+ * pygi_resulttuple_register_types:
+ * @module: A Python modules to which ResultTuple gets added to.
+ *
+ * Initializes the ResultTuple class and adds it to the passed @module.
+ *
+ * Returns: -1 on error, 0 on success.
+ */
+int pygi_resulttuple_register_types(PyObject *module) {
+
+ PyGIResultTuple_Type.tp_base = &PyTuple_Type;
+ PyGIResultTuple_Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+ PyGIResultTuple_Type.tp_repr = (reprfunc)resulttuple_repr;
+ PyGIResultTuple_Type.tp_getattro = (getattrofunc)resulttuple_getattro;
+ PyGIResultTuple_Type.tp_methods = resulttuple_methods;
+ PyGIResultTuple_Type.tp_dealloc = (destructor)resulttuple_dealloc;
+
+ if (PyType_Ready (&PyGIResultTuple_Type))
+ return -1;
+
+ Py_INCREF (&PyGIResultTuple_Type);
+ if (PyModule_AddObject (module, "ResultTuple",
+ (PyObject *)&PyGIResultTuple_Type)) {
+ Py_DECREF (&PyGIResultTuple_Type);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/gi/pygi-resulttuple.h b/gi/pygi-resulttuple.h
new file mode 100644
index 00000000..3f63ca0c
--- /dev/null
+++ b/gi/pygi-resulttuple.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2015 Christoph Reiter <reiter.christoph@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_RESULTTUPLE_H__
+#define __PYGI_RESULTTUPLE_H__
+
+#include "Python.h"
+
+int
+pygi_resulttuple_register_types (PyObject *d);
+
+PyTypeObject *
+pygi_resulttuple_new_type (PyObject *tuple_names);
+
+PyObject*
+pygi_resulttuple_new (PyTypeObject *subclass, Py_ssize_t len);
+
+#endif /* __PYGI_RESULTTUPLE_H__ */
diff --git a/gi/pyglib-python-compat.h b/gi/pyglib-python-compat.h
index 58c8cf9b..7b67d55a 100644
--- a/gi/pyglib-python-compat.h
+++ b/gi/pyglib-python-compat.h
@@ -48,6 +48,7 @@
#define PYGLIB_PyUnicode_Type PyString_Type
#define PYGLIB_PyUnicode_InternFromString PyString_InternFromString
#define PYGLIB_PyUnicode_InternInPlace PyString_InternInPlace
+#define PYGLIB_PyUnicode_Format PyString_Format
#define PYGLIB_PyBytes_FromString PyString_FromString
#define PYGLIB_PyBytes_FromStringAndSize PyString_FromStringAndSize
@@ -61,6 +62,7 @@
#define PYGLIB_PyLong_FromSsize_t PyInt_FromSsize_t
#define PYGLIB_PyLong_FromSize_t PyInt_FromSize_t
#define PYGLIB_PyLong_AsLong PyInt_AsLong
+#define PYGLIB_PyLong_AsSsize_t PyInt_AsSsize_t
#define PYGLIB_PyLongObject PyIntObject
#define PYGLIB_PyLong_Type PyInt_Type
#define PYGLIB_PyLong_AS_LONG PyInt_AS_LONG
@@ -166,11 +168,14 @@ PyTypeObject symbol = { \
#define PYGLIB_PyUnicode_Type PyUnicode_Type
#define PYGLIB_PyUnicode_InternFromString PyUnicode_InternFromString
#define PYGLIB_PyUnicode_InternInPlace PyUnicode_InternInPlace
+#define PYGLIB_PyUnicode_Format PyUnicode_Format
#define PYGLIB_PyLong_Check PyLong_Check
#define PYGLIB_PyLong_FromLong PyLong_FromLong
+#define PYGLIB_PyLong_FromSsize_t PyLong_FromSsize_t
#define PYGLIB_PyLong_FromSize_t PyLong_FromSize_t
#define PYGLIB_PyLong_AsLong PyLong_AsLong
+#define PYGLIB_PyLong_AsSsize_t PyLong_AsSsize_t
#define PYGLIB_PyLong_AS_LONG(o) PyLong_AS_LONG((PyObject*)(o))
#define PYGLIB_PyLongObject PyLongObject
#define PYGLIB_PyLong_Type PyLong_Type
diff --git a/tests/Makefile.am b/tests/Makefile.am
index b36ff519..9d5db5e0 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -116,6 +116,7 @@ EXTRA_DIST = \
test_generictreemodel.py \
test_docstring.py \
test_repository.py \
+ test_resulttuple.py \
compat_test_pygtk.py \
gi/__init__.py \
gi/overrides/__init__.py \
diff --git a/tests/test_resulttuple.py b/tests/test_resulttuple.py
new file mode 100644
index 00000000..20f80f3c
--- /dev/null
+++ b/tests/test_resulttuple.py
@@ -0,0 +1,87 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2015 Christoph Reiter <reiter.christoph@gmail.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
+# USA
+
+import unittest
+import pickle
+
+import gi
+from gi.repository import GIMarshallingTests
+from gi.repository import Regress
+
+
+ResultTuple = gi._gi.ResultTuple
+
+
+class TestResultTuple(unittest.TestCase):
+
+ def test_base(self):
+ self.assertTrue(issubclass(ResultTuple, tuple))
+
+ def test_create(self):
+ new = ResultTuple._new_type([None, "foo", None, "bar"])
+ self.assertTrue(issubclass(new, ResultTuple))
+
+ def test_repr_dir(self):
+ new = ResultTuple._new_type([None, "foo", None, "bar"])
+ inst = new([1, 2, 3, "a"])
+
+ self.assertEqual(repr(inst), "(1, foo=2, 3, bar='a')")
+ self.assertTrue("foo" in dir(inst))
+
+ def test_repr_dir_empty(self):
+ new = ResultTuple._new_type([])
+ inst = new()
+ self.assertEqual(repr(inst), "()")
+ dir(inst)
+
+ def test_getatttr(self):
+ new = ResultTuple._new_type([None, "foo", None, "bar"])
+ inst = new([1, 2, 3, "a"])
+
+ self.assertTrue(hasattr(inst, "foo"))
+ self.assertEqual(inst.foo, inst[1])
+ self.assertRaises(AttributeError, getattr, inst, "nope")
+
+ def test_pickle(self):
+ new = ResultTuple._new_type([None, "foo", None, "bar"])
+ inst = new([1, 2, 3, "a"])
+
+ inst2 = pickle.loads(pickle.dumps(inst))
+ self.assertEqual(inst2, inst)
+ self.assertTrue(isinstance(inst2, tuple))
+ self.assertFalse(isinstance(inst2, new))
+
+ def test_gi(self):
+ res = GIMarshallingTests.init_function([])
+ self.assertEqual(repr(res), "(True, argv=[])")
+
+ res = GIMarshallingTests.array_return_etc(5, 9)
+ self.assertEqual(repr(res), "([5, 0, 1, 9], sum=14)")
+
+ res = GIMarshallingTests.array_out_etc(-5, 9)
+ self.assertEqual(repr(res), "(ints=[-5, 0, 1, 9], sum=4)")
+
+ cb = lambda: (1, 2)
+ res = GIMarshallingTests.callback_multiple_out_parameters(cb)
+ self.assertEqual(repr(res), "(a=1.0, b=2.0)")
+
+ def test_regress(self):
+ res = Regress.TestObj().skip_return_val(50, 42.0, 60, 2, 3)
+ self.assertEqual(repr(res), "(out_b=51, inout_d=61, out_sum=32)")