[pygobject] Add support for default arguments annotated with allow-none
- From: Simon Feltman <sfeltman src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pygobject] Add support for default arguments annotated with allow-none
- Date: Thu, 26 Sep 2013 11:47:49 +0000 (UTC)
commit 510789d52e9e2fd863d26613f3282364eb175601
Author: Simon Feltman <sfeltman src gnome org>
Date: Sun Jul 28 14:44:51 2013 -0700
Add support for default arguments annotated with allow-none
Support default value of NULL for tail end arguments which are
marked with allow-none.
The implementation uses a place holder object for un-supplied arguments
which are annotated with allow-none. This is then used later during
marshaling to supply NULL as the default.
Additionally support an implicit default for callback user_data
using the same technique.
https://bugzilla.gnome.org/show_bug.cgi?id=640812
gi/gimodule.c | 6 +++
gi/pygi-cache.c | 103 +++++++++++++++++++++++++++++----------------
gi/pygi-cache.h | 8 ++++
gi/pygi-invoke.c | 42 +++++++++++++------
gi/pygi-marshal-from-py.c | 5 ++
gi/pygi.h | 1 +
tests/test_everything.py | 16 +++++++
tests/test_gi.py | 30 +++++++++++++
8 files changed, 162 insertions(+), 49 deletions(-)
---
diff --git a/gi/gimodule.c b/gi/gimodule.c
index 12dfb36..9dfbd4f 100644
--- a/gi/gimodule.c
+++ b/gi/gimodule.c
@@ -29,6 +29,7 @@
#include <pyglib-python-compat.h>
PyObject *PyGIDeprecationWarning;
+PyObject *_PyGIDefaultArgPlaceholder;
static PyObject *
_wrap_pyg_enum_add (PyObject *self,
@@ -656,6 +657,11 @@ PYGLIB_MODULE_START(_gi, "_gi")
PyExc_DeprecationWarning, NULL);
#endif
+ /* Place holder object used to fill in "from Python" argument lists
+ * for values not supplied by the caller but support a GI default.
+ */
+ _PyGIDefaultArgPlaceholder = PyObject_New(PyObject, &PyType_Type);
+
Py_INCREF(PyGIDeprecationWarning);
PyModule_AddObject(module, "PyGIDeprecationWarning", PyGIDeprecationWarning);
diff --git a/gi/pygi-cache.c b/gi/pygi-cache.c
index f8573fc..8459449 100644
--- a/gi/pygi-cache.c
+++ b/gi/pygi-cache.c
@@ -523,6 +523,7 @@ _arg_cache_from_py_interface_callback_setup (PyGIArgCache *arg_cache,
PyGIArgCache *user_data_arg_cache = _arg_cache_alloc ();
user_data_arg_cache->meta_type = PYGI_META_ARG_TYPE_CHILD_WITH_PYARG;
user_data_arg_cache->direction = PYGI_DIRECTION_FROM_PYTHON;
+ user_data_arg_cache->has_default = TRUE; /* always allow user data with a NULL default. */
_pygi_callable_cache_set_arg (callable_cache, callback_cache->user_data_index,
user_data_arg_cache);
}
@@ -692,6 +693,26 @@ _arg_cache_new_for_interface (GIInterfaceInfo *iface_info,
return arg_cache;
}
+/* _arg_info_default_value
+ * info:
+ * arg: (out): GIArgument to fill in with default value.
+ *
+ * This is currently a place holder API which only supports "allow-none" pointer args.
+ * Once defaults are part of the GI API, we can replace this with: g_arg_info_default_value
+ * https://bugzilla.gnome.org/show_bug.cgi?id=558620
+ *
+ * Returns: TRUE if the given argument supports a default value and was filled in.
+ */
+static gboolean
+_arg_info_default_value (GIArgInfo *info, GIArgument *arg)
+{
+ if (g_arg_info_may_be_null (info)) {
+ arg->v_pointer = NULL;
+ return TRUE;
+ }
+ return FALSE;
+}
+
PyGIArgCache *
_arg_cache_new (GITypeInfo *type_info,
PyGICallableCache *callable_cache,
@@ -895,6 +916,11 @@ _arg_cache_new (GITypeInfo *type_info,
arg_cache->type_info = type_info;
if (arg_info != NULL) {
+ if (!arg_cache->has_default) {
+ /* It is possible has_default was set somewhere else */
+ arg_cache->has_default = _arg_info_default_value (arg_info,
+ &arg_cache->default_value);
+ }
arg_cache->arg_name = g_base_info_get_name ((GIBaseInfo *) arg_info);
arg_cache->allow_none = g_arg_info_may_be_null (arg_info);
@@ -908,40 +934,6 @@ _arg_cache_new (GITypeInfo *type_info,
return arg_cache;
}
-static void
-_arg_name_list_generate (PyGICallableCache *callable_cache)
-{
- GSList * arg_name_list = NULL;
- int i;
-
- if (callable_cache->arg_name_hash == NULL) {
- callable_cache->arg_name_hash = g_hash_table_new (g_str_hash, g_str_equal);
- } else {
- g_hash_table_remove_all (callable_cache->arg_name_hash);
- }
-
- for (i=0; i < _pygi_callable_cache_args_len (callable_cache); i++) {
- PyGIArgCache *arg_cache = NULL;
-
- arg_cache = _pygi_callable_cache_get_arg (callable_cache, i);
-
- if (arg_cache->meta_type != PYGI_META_ARG_TYPE_CHILD &&
- arg_cache->meta_type != PYGI_META_ARG_TYPE_CLOSURE &&
- (arg_cache->direction == PYGI_DIRECTION_FROM_PYTHON ||
- arg_cache->direction == PYGI_DIRECTION_BIDIRECTIONAL)) {
-
- gpointer arg_name = (gpointer)arg_cache->arg_name;
-
- arg_name_list = g_slist_prepend (arg_name_list, arg_name);
- if (arg_name != NULL) {
- g_hash_table_insert (callable_cache->arg_name_hash, arg_name, arg_name);
- }
- }
- }
-
- callable_cache->arg_name_list = g_slist_reverse (arg_name_list);
-}
-
static PyGIDirection
_pygi_get_direction (PyGICallableCache *callable_cache, GIDirection gi_direction)
{
@@ -1041,7 +1033,6 @@ _args_cache_generate (GICallableInfo *callable_info,
_pygi_callable_cache_set_arg (callable_cache, arg_index, arg_cache);
direction = PYGI_DIRECTION_FROM_PYTHON;
- arg_cache->arg_name = g_base_info_get_name ((GIBaseInfo *) arg_info);
arg_cache->direction = direction;
arg_cache->meta_type = PYGI_META_ARG_TYPE_CLOSURE;
arg_cache->c_arg_index = i;
@@ -1117,11 +1108,51 @@ _args_cache_generate (GICallableInfo *callable_info,
g_base_info_unref (type_info);
}
+ /* Ensure arguments always have a name when available */
+ arg_cache->arg_name = g_base_info_get_name ((GIBaseInfo *) arg_info);
+
g_base_info_unref ( (GIBaseInfo *)arg_info);
}
- _arg_name_list_generate (callable_cache);
+ if (callable_cache->arg_name_hash == NULL) {
+ callable_cache->arg_name_hash = g_hash_table_new (g_str_hash, g_str_equal);
+ } else {
+ g_hash_table_remove_all (callable_cache->arg_name_hash);
+ }
+ callable_cache->n_py_required_args = 0;
+
+ /* Reverse loop through all the arguments to setup arg_name_list/hash
+ * and find the number of required arguments */
+ for (i=((gssize)_pygi_callable_cache_args_len (callable_cache))-1; i >= 0; i--) {
+ PyGIArgCache *arg_cache = _pygi_callable_cache_get_arg (callable_cache, i);
+
+ if (arg_cache->meta_type != PYGI_META_ARG_TYPE_CHILD &&
+ arg_cache->meta_type != PYGI_META_ARG_TYPE_CLOSURE &&
+ arg_cache->direction & PYGI_DIRECTION_FROM_PYTHON) {
+
+ /* Setup arg_name_list and arg_name_hash */
+ gpointer arg_name = (gpointer)arg_cache->arg_name;
+ callable_cache->arg_name_list = g_slist_prepend (callable_cache->arg_name_list,
+ arg_name);
+ if (arg_name != NULL) {
+ g_hash_table_insert (callable_cache->arg_name_hash,
+ arg_name,
+ GINT_TO_POINTER(i));
+ }
+
+ /* The first tail argument without a default will force all the preceding
+ * argument defaults off. This limits support of default args to the
+ * tail of an args list.
+ */
+ if (callable_cache->n_py_required_args > 0) {
+ arg_cache->has_default = FALSE;
+ callable_cache->n_py_required_args += 1;
+ } else if (!arg_cache->has_default) {
+ callable_cache->n_py_required_args += 1;
+ }
+ }
+ }
return TRUE;
}
diff --git a/gi/pygi-cache.h b/gi/pygi-cache.h
index e0ef5d3..65e2de5 100644
--- a/gi/pygi-cache.h
+++ b/gi/pygi-cache.h
@@ -105,6 +105,7 @@ struct _PyGIArgCache
gboolean is_caller_allocates;
gboolean is_skipped;
gboolean allow_none;
+ gboolean has_default;
PyGIDirection direction;
GITransfer transfer;
@@ -121,6 +122,9 @@ struct _PyGIArgCache
gssize c_arg_index;
gssize py_arg_index;
+
+ /* Set when has_default is true. */
+ GIArgument default_value;
};
typedef struct _PyGISequenceCache
@@ -188,6 +192,10 @@ struct _PyGICallableCache
/* Number of Python arguments expected for invoking the gi function. */
gssize n_py_args;
+
+ /* Minimum number of args required to call the callable from Python.
+ * This count does not include args with defaults. */
+ gssize n_py_required_args;
};
void _pygi_arg_cache_clear (PyGIArgCache *cache);
diff --git a/gi/pygi-invoke.c b/gi/pygi-invoke.c
index 7abd9e6..caa72be 100644
--- a/gi/pygi-invoke.c
+++ b/gi/pygi-invoke.c
@@ -120,7 +120,11 @@ _check_for_unexpected_kwargs (const gchar *function_name,
}
}
- if (g_hash_table_lookup (arg_name_hash, PyBytes_AsString(key)) == NULL) {
+ /* Use extended lookup because it returns whether or not the key actually
+ * exists in the hash table. g_hash_table_lookup returns NULL for keys not
+ * found which maps to index 0 for our hash lookup.
+ */
+ if (!g_hash_table_lookup_extended (arg_name_hash, PyBytes_AsString(key), NULL, NULL)) {
PyErr_Format (PyExc_TypeError,
"%.200s() got an unexpected keyword argument '%.400s'",
function_name,
@@ -209,17 +213,27 @@ _py_args_combine_and_check_length (PyGICallableCache *cache,
PyTuple_SET_ITEM (combined_py_args, i, kw_arg_item);
} else if (kw_arg_item == NULL && py_arg_item == NULL) {
- PyErr_Format (PyExc_TypeError,
- "%.200s() takes exactly %d %sargument%s (%zd given)",
- function_name,
- n_expected_args,
- n_py_kwargs > 0 ? "non-keyword " : "",
- n_expected_args == 1 ? "" : "s",
- n_py_args);
-
- Py_DECREF (combined_py_args);
- return NULL;
-
+ /* If the argument supports a default, use a place holder in the
+ * argument tuple, this will be checked later during marshaling.
+ */
+ int arg_cache_index = -1;
+ if (arg_name != NULL)
+ arg_cache_index = GPOINTER_TO_INT (g_hash_table_lookup (cache->arg_name_hash, arg_name));
+ if (arg_cache_index >= 0 && _pygi_callable_cache_get_arg (cache, arg_cache_index)->has_default) {
+ Py_INCREF (_PyGIDefaultArgPlaceholder);
+ PyTuple_SET_ITEM (combined_py_args, i, _PyGIDefaultArgPlaceholder);
+ } else {
+ PyErr_Format (PyExc_TypeError,
+ "%.200s() takes exactly %d %sargument%s (%zd given)",
+ function_name,
+ n_expected_args,
+ n_py_kwargs > 0 ? "non-keyword " : "",
+ n_expected_args == 1 ? "" : "s",
+ n_py_args);
+
+ Py_DECREF (combined_py_args);
+ return NULL;
+ }
} else if (kw_arg_item != NULL && py_arg_item != NULL) {
PyErr_Format (PyExc_TypeError,
"%.200s() got multiple values for keyword argument '%.200s'",
@@ -490,7 +504,9 @@ _invoke_marshal_in_args (PyGIInvokeState *state, PyGICallableCache *cache)
}
c_arg = state->args[i];
- if (arg_cache->from_py_marshaller != NULL) {
+ if (py_arg == _PyGIDefaultArgPlaceholder) {
+ *c_arg = arg_cache->default_value;
+ } else if (arg_cache->from_py_marshaller != NULL) {
gboolean success;
if (!arg_cache->allow_none && py_arg == Py_None) {
PyErr_Format (PyExc_TypeError,
diff --git a/gi/pygi-marshal-from-py.c b/gi/pygi-marshal-from-py.c
index 73356b1..22571ef 100644
--- a/gi/pygi-marshal-from-py.c
+++ b/gi/pygi-marshal-from-py.c
@@ -1255,6 +1255,11 @@ _pygi_marshal_from_py_interface_callback (PyGIInvokeState *state,
py_user_data = PyTuple_GetItem (state->py_in_args, user_data_cache->py_arg_index);
if (!py_user_data)
return FALSE;
+ /* NULL out user_data if it was not supplied and the default arg placeholder
+ * was used instead.
+ */
+ if (py_user_data == _PyGIDefaultArgPlaceholder)
+ py_user_data = NULL;
}
}
diff --git a/gi/pygi.h b/gi/pygi.h
index 3bf40bb..dcd91b3 100644
--- a/gi/pygi.h
+++ b/gi/pygi.h
@@ -33,6 +33,7 @@
#include "pygi-cache.h"
extern PyObject *PyGIDeprecationWarning;
+extern PyObject *_PyGIDefaultArgPlaceholder;
typedef struct {
PyObject_HEAD
diff --git a/tests/test_everything.py b/tests/test_everything.py
index b2f0528..0c6533a 100644
--- a/tests/test_everything.py
+++ b/tests/test_everything.py
@@ -717,6 +717,22 @@ class TestCallbacks(unittest.TestCase):
self.assertEqual(TestCallbacks.called, 100)
+ def test_callback_userdata_none_default_arg(self):
+ TestCallbacks.called = 0
+ userdata_list = []
+
+ def callback(userdata):
+ userdata_list.append(userdata)
+ TestCallbacks.called += 1
+ return TestCallbacks.called
+
+ for i in range(100):
+ val = Everything.test_callback_user_data(callback)
+ self.assertEqual(val, i + 1)
+
+ self.assertEqual(TestCallbacks.called, 100)
+ self.assertSequenceEqual(userdata_list, [None] * 100)
+
def test_async_ready_callback(self):
TestCallbacks.called = False
TestCallbacks.main_loop = GLib.MainLoop()
diff --git a/tests/test_gi.py b/tests/test_gi.py
index da55187..8cb1c12 100644
--- a/tests/test_gi.py
+++ b/tests/test_gi.py
@@ -2642,6 +2642,36 @@ class TestKeywordArgs(unittest.TestCase):
GIMarshallingTests.int_three_in_three_out(1, c=4, **d)
self.assertEqual(d, d2)
+ @unittest.skipUnless(hasattr(GIMarshallingTests, 'int_one_in_utf8_two_in_one_allows_none'),
+ 'Requires newer GIMarshallingTests')
+ def test_allow_none_as_default(self):
+ GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, '3', '4')
+ GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, '3')
+ GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2)
+ GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, d='4')
+
+ GIMarshallingTests.array_in_utf8_two_in_out_of_order('1', [-1, 0, 1, 2])
+ GIMarshallingTests.array_in_utf8_two_in_out_of_order('1', [-1, 0, 1, 2], '2')
+ self.assertRaises(TypeError,
+ GIMarshallingTests.array_in_utf8_two_in_out_of_order,
+ [-1, 0, 1, 2], a='1')
+ self.assertRaises(TypeError,
+ GIMarshallingTests.array_in_utf8_two_in_out_of_order,
+ [-1, 0, 1, 2])
+
+ GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], '1', '2')
+ GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], '1')
+ GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2])
+ GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], b='2')
+
+ GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none(1, '2', '3')
+ self.assertRaises(TypeError,
+ GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none,
+ 1, '3')
+ self.assertRaises(TypeError,
+ GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none,
+ 1, c='3')
+
class TestPropertiesObject(unittest.TestCase):
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]