diff options
author | Christoph Reiter <creiter@src.gnome.org> | 2015-06-08 18:14:08 +0200 |
---|---|---|
committer | Christoph Reiter <creiter@src.gnome.org> | 2015-10-27 09:30:45 +0100 |
commit | 175d10665472e6f4090d707e3b89255814c932b1 (patch) | |
tree | 9686dacb6aa22489ebe62832cb85fb5447fed396 | |
parent | b1788c9a445c8a820121c42260bcbdbc3ae8dfba (diff) | |
download | pygobject-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.am | 2 | ||||
-rw-r--r-- | gi/gimodule.c | 1 | ||||
-rw-r--r-- | gi/pygi-cache.c | 34 | ||||
-rw-r--r-- | gi/pygi-cache.h | 6 | ||||
-rw-r--r-- | gi/pygi-invoke.c | 35 | ||||
-rw-r--r-- | gi/pygi-private.h | 1 | ||||
-rw-r--r-- | gi/pygi-resulttuple.c | 354 | ||||
-rw-r--r-- | gi/pygi-resulttuple.h | 34 | ||||
-rw-r--r-- | gi/pyglib-python-compat.h | 5 | ||||
-rw-r--r-- | tests/Makefile.am | 1 | ||||
-rw-r--r-- | tests/test_resulttuple.py | 87 |
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)") |