[pygobject] Add doc strings showing method signatures for gi methods



commit 13629f5a9c9a7022f3521a3616d9ce8fa4a6161b
Author: Simon Feltman <s feltman gmail com>
Date:   Thu Aug 16 15:09:08 2012 -0700

    Add doc strings showing method signatures for gi methods
    
    Add signature based doc string to all methods pulled in from
    introspection. For example: Gtk.SpinButton.get_icon_area.__doc__
    get_icon_area(self, icon_pos:Gtk.EntryIconPosition) -> icon_area:cairo.RectangleInt
    
    https://bugzilla.gnome.org/show_bug.cgi?id=681967

 gi/overrides/GIMarshallingTests.py |    1 +
 gi/pygi-info.c                     |  103 ++++++++++++++++++++++++++++++++---
 gi/pygi-type.c                     |   60 +++++++++++++++++++++
 gi/pygi-type.h                     |    1 +
 gi/types.py                        |   92 +++++++++++++++++++++++++-------
 tests/test_gi.py                   |   30 ++++++++++
 6 files changed, 259 insertions(+), 28 deletions(-)
---
diff --git a/gi/overrides/GIMarshallingTests.py b/gi/overrides/GIMarshallingTests.py
index af2529a..cc967b4 100644
--- a/gi/overrides/GIMarshallingTests.py
+++ b/gi/overrides/GIMarshallingTests.py
@@ -63,6 +63,7 @@ class OverridesObject(GIMarshallingTests.OverridesObject):
         return self
 
     def method(self):
+        """Overridden doc string."""
         return GIMarshallingTests.OverridesObject.method(self) / 7
 
 OverridesObject = override(OverridesObject)
diff --git a/gi/pygi-info.c b/gi/pygi-info.c
index dc99a83..e726b2d 100644
--- a/gi/pygi-info.c
+++ b/gi/pygi-info.c
@@ -357,13 +357,91 @@ static PyMethodDef _PyGIPropertyInfo_methods[] = {
     { NULL, NULL, 0 }
 };
 
+
 /* ArgInfo */
 PYGLIB_DEFINE_TYPE ("gi.ArgInfo", PyGIArgInfo_Type, PyGIBaseInfo);
 
+static PyObject *
+_wrap_g_arg_info_get_direction (PyGIBaseInfo *self)
+{
+    return PyLong_FromLong (
+	    g_arg_info_get_direction ((GIArgInfo*)self->info) );
+}
+
+static PyObject *
+_wrap_g_arg_info_is_caller_allocates (PyGIBaseInfo *self)
+{
+    return PyBool_FromLong (
+	    g_arg_info_is_caller_allocates ((GIArgInfo*)self->info) );
+}
+
+static PyObject *
+_wrap_g_arg_info_is_return_value (PyGIBaseInfo *self)
+{
+    return PyBool_FromLong (
+	    g_arg_info_is_return_value ((GIArgInfo*)self->info) );
+}
+
+static PyObject *
+_wrap_g_arg_info_is_optional (PyGIBaseInfo *self)
+{
+    return PyBool_FromLong (
+	    g_arg_info_is_optional ((GIArgInfo*)self->info) );
+}
+
+static PyObject *
+_wrap_g_arg_info_may_be_null (PyGIBaseInfo *self)
+{
+    return PyBool_FromLong (
+	    g_arg_info_may_be_null ((GIArgInfo*)self->info) );
+}
+
+/* _g_arg_get_pytype_hint
+ *
+ * Returns new value reference to a string hinting at the python type
+ * which can be used for the given gi argument info.
+ */
+static PyObject *
+_g_arg_get_pytype_hint (PyGIBaseInfo *self)
+{
+    GIArgInfo *arg_info = (GIArgInfo*)self->info;
+    GITypeInfo type_info;
+    g_arg_info_load_type(arg_info, &type_info);
+    GITypeTag type_tag = g_type_info_get_tag(&type_info);
+
+    /* First attempt getting a python type object. */
+    PyObject *py_type = _pygi_get_py_type_hint(type_tag);
+    if (py_type != Py_None && PyObject_HasAttrString(py_type, "__name__")) {
+	PyObject *name = PyObject_GetAttrString(py_type, "__name__");
+	Py_DecRef(py_type);
+	return name;
+    } else {
+	Py_DecRef(py_type);
+	if (type_tag == GI_TYPE_TAG_INTERFACE) {
+	    GIBaseInfo *iface = g_type_info_get_interface(&type_info);
+	    gchar *name = g_strdup_printf("%s.%s",
+		    g_base_info_get_namespace(iface),
+		    g_base_info_get_name (iface));
+	    g_base_info_unref(iface);
+	    PyObject *py_string = PYGLIB_PyUnicode_FromString(name);
+	    g_free(name);
+	    return py_string;
+	}
+	return PYGLIB_PyUnicode_FromString(g_type_tag_to_string(type_tag));
+    }
+}
+
 static PyMethodDef _PyGIArgInfo_methods[] = {
+    { "get_direction", (PyCFunction) _wrap_g_arg_info_get_direction, METH_NOARGS },
+    { "is_caller_allocates", (PyCFunction) _wrap_g_arg_info_is_caller_allocates, METH_NOARGS },
+    { "is_return_value", (PyCFunction) _wrap_g_arg_info_is_return_value, METH_NOARGS },
+    { "is_optional", (PyCFunction) _wrap_g_arg_info_is_optional, METH_NOARGS },
+    { "may_be_null", (PyCFunction) _wrap_g_arg_info_may_be_null, METH_NOARGS },
+    { "get_pytype_hint", (PyCFunction) _g_arg_get_pytype_hint, METH_NOARGS },
     { NULL, NULL, 0 }
 };
 
+
 /* TypeInfo */
 PYGLIB_DEFINE_TYPE ("gi.TypeInfo", PyGITypeInfo_Type, PyGIBaseInfo);
 
@@ -1644,27 +1722,34 @@ _pygi_info_register_types (PyObject *m)
     if (PyModule_AddObject(m, "BaseInfo", (PyObject *)&PyGIBaseInfo_Type))
         return;
 
+    if (PyModule_AddObject(m, "DIRECTION_IN", PyLong_FromLong(GI_DIRECTION_IN)))
+        return;
+    if (PyModule_AddObject(m, "DIRECTION_OUT", PyLong_FromLong(GI_DIRECTION_OUT)))
+        return;
+    if (PyModule_AddObject(m, "DIRECTION_INOUT", PyLong_FromLong(GI_DIRECTION_INOUT)))
+        return;
+
     _PyGI_REGISTER_TYPE (m, PyGIUnresolvedInfo_Type, UnresolvedInfo,
                          PyGIBaseInfo_Type);
-    _PyGI_REGISTER_TYPE (m, PyGICallableInfo_Type, CallableInfo, 
+    _PyGI_REGISTER_TYPE (m, PyGICallableInfo_Type, CallableInfo,
                          PyGIBaseInfo_Type);
     _PyGI_REGISTER_TYPE (m, PyGICallbackInfo_Type, CallbackInfo,
                          PyGIBaseInfo_Type);
-    _PyGI_REGISTER_TYPE (m, PyGIFunctionInfo_Type, FunctionInfo, 
+    _PyGI_REGISTER_TYPE (m, PyGIFunctionInfo_Type, FunctionInfo,
                          PyGICallableInfo_Type);
-    _PyGI_REGISTER_TYPE (m, PyGIRegisteredTypeInfo_Type, RegisteredTypeInfo, 
+    _PyGI_REGISTER_TYPE (m, PyGIRegisteredTypeInfo_Type, RegisteredTypeInfo,
                          PyGIBaseInfo_Type);
-    _PyGI_REGISTER_TYPE (m, PyGIStructInfo_Type, StructInfo, 
+    _PyGI_REGISTER_TYPE (m, PyGIStructInfo_Type, StructInfo,
                          PyGIRegisteredTypeInfo_Type);
-    _PyGI_REGISTER_TYPE (m, PyGIEnumInfo_Type, EnumInfo, 
+    _PyGI_REGISTER_TYPE (m, PyGIEnumInfo_Type, EnumInfo,
                          PyGIRegisteredTypeInfo_Type);
-    _PyGI_REGISTER_TYPE (m, PyGIObjectInfo_Type, ObjectInfo, 
+    _PyGI_REGISTER_TYPE (m, PyGIObjectInfo_Type, ObjectInfo,
                          PyGIRegisteredTypeInfo_Type);
-    _PyGI_REGISTER_TYPE (m, PyGIInterfaceInfo_Type, InterfaceInfo, 
+    _PyGI_REGISTER_TYPE (m, PyGIInterfaceInfo_Type, InterfaceInfo,
                          PyGIRegisteredTypeInfo_Type);
-    _PyGI_REGISTER_TYPE (m, PyGIConstantInfo_Type, ConstantInfo, 
+    _PyGI_REGISTER_TYPE (m, PyGIConstantInfo_Type, ConstantInfo,
                          PyGIBaseInfo_Type);
-    _PyGI_REGISTER_TYPE (m, PyGIValueInfo_Type, ValueInfo, 
+    _PyGI_REGISTER_TYPE (m, PyGIValueInfo_Type, ValueInfo,
                          PyGIBaseInfo_Type);
     _PyGI_REGISTER_TYPE (m, PyGIFieldInfo_Type, FieldInfo,
                          PyGIBaseInfo_Type);
diff --git a/gi/pygi-type.c b/gi/pygi-type.c
index 129ea98..dfaadb0 100644
--- a/gi/pygi-type.c
+++ b/gi/pygi-type.c
@@ -23,6 +23,8 @@
 
 #include "pygi-private.h"
 
+#include <pyglib-python-compat.h>
+
 
 PyObject *
 _pygi_type_import_by_name (const char *namespace_,
@@ -97,3 +99,61 @@ _pygi_type_get_from_g_type (GType g_type)
     return py_type;
 }
 
+/* _pygi_get_py_type_hint
+ *
+ * This gives a hint to what python type might be used as
+ * a particular gi type.
+ */
+PyObject *
+_pygi_get_py_type_hint(GITypeTag type_tag)
+{
+    PyObject *type = Py_None;
+
+    switch (type_tag) {
+        case GI_TYPE_TAG_BOOLEAN:
+            type = (PyObject *) &PyBool_Type;
+            break;
+
+        case GI_TYPE_TAG_INT8:
+        case GI_TYPE_TAG_UINT8:
+        case GI_TYPE_TAG_INT16:
+        case GI_TYPE_TAG_UINT16:
+        case GI_TYPE_TAG_INT32:
+        case GI_TYPE_TAG_UINT32:
+        case GI_TYPE_TAG_INT64:
+        case GI_TYPE_TAG_UINT64:
+            type = (PyObject *) &PYGLIB_PyLong_Type;
+            break;
+
+        case GI_TYPE_TAG_FLOAT:
+        case GI_TYPE_TAG_DOUBLE:
+            type = (PyObject *) &PyFloat_Type;
+            break;
+
+        case GI_TYPE_TAG_GLIST:
+        case GI_TYPE_TAG_GSLIST:
+        case GI_TYPE_TAG_ARRAY:
+            type = (PyObject *) &PyList_Type;
+            break;
+
+        case GI_TYPE_TAG_GHASH:
+            type = (PyObject *) &PyDict_Type;
+            break;
+
+        case GI_TYPE_TAG_UTF8:
+        case GI_TYPE_TAG_FILENAME:
+        case GI_TYPE_TAG_UNICHAR:
+            type = (PyObject *) &PYGLIB_PyUnicode_Type;
+            break;
+
+        case GI_TYPE_TAG_INTERFACE:
+        case GI_TYPE_TAG_GTYPE:
+        case GI_TYPE_TAG_ERROR:
+        case GI_TYPE_TAG_VOID:
+            break;
+    }
+
+    Py_INCREF(type);
+    return type;
+}
+
diff --git a/gi/pygi-type.h b/gi/pygi-type.h
index bb43d19..01b5994 100644
--- a/gi/pygi-type.h
+++ b/gi/pygi-type.h
@@ -39,6 +39,7 @@ PyObject *_pygi_type_import_by_gi_info (GIBaseInfo *info);
 
 PyObject *_pygi_type_get_from_g_type (GType g_type);
 
+PyObject *_pygi_get_py_type_hint (GITypeTag type_tag);
 
 G_END_DECLS
 
diff --git a/gi/types.py b/gi/types.py
index af3310a..a7e9e4d 100644
--- a/gi/types.py
+++ b/gi/types.py
@@ -23,6 +23,7 @@
 from __future__ import absolute_import
 
 import sys
+
 from . import _gobject
 from ._gobject._gobject import GInterface
 from ._gobject.constants import TYPE_INVALID
@@ -32,55 +33,108 @@ from ._gi import \
     ObjectInfo, \
     StructInfo, \
     VFuncInfo, \
+    FunctionInfo, \
     register_interface_info, \
-    hook_up_vfunc_implementation
+    hook_up_vfunc_implementation, \
+    DIRECTION_IN, \
+    DIRECTION_OUT, \
+    DIRECTION_INOUT
 
 
 StructInfo  # pyflakes
 
-if sys.version_info > (3, 0):
+if (3, 0) <= sys.version_info < (3, 3):
+    # callable not available for python 3.0 thru 3.2
     def callable(obj):
         return hasattr(obj, '__call__')
 
 
-def Function(info):
+def split_function_info_args(info):
+    """Split a functions args into a tuple of two lists.
+
+    Note that args marked as DIRECTION_INOUT will be in both lists.
+
+    :Returns:
+        Tuple of (in_args, out_args)
+    """
+    in_args = []
+    out_args = []
+    for arg in info.get_arguments():
+        direction = arg.get_direction()
+        if direction in (DIRECTION_IN, DIRECTION_INOUT):
+            in_args.append(arg)
+        if direction in (DIRECTION_OUT, DIRECTION_INOUT):
+            out_args.append(arg)
+    return (in_args, out_args)
+
+
+def get_callable_info_doc_string(info):
+    """Build a signature string which can be used for documentation."""
+    in_args, out_args = split_function_info_args(info)
+    in_args_strs = []
+    if isinstance(info, VFuncInfo):
+        in_args_strs = ['self']
+    elif isinstance(info, FunctionInfo):
+        if info.is_method():
+            in_args_strs = ['self']
+        elif info.is_constructor():
+            in_args_strs = ['cls']
+
+    for arg in in_args:
+        argstr = arg.get_name() + ':' + arg.get_pytype_hint()
+        if arg.is_optional():
+            argstr += '=<optional>'
+        in_args_strs.append(argstr)
+    in_args_str = ', '.join(in_args_strs)
+
+    if out_args:
+        out_args_str = ', '.join(arg.get_name() + ':' + arg.get_pytype_hint() \
+                                 for arg in out_args)
+        return '%s(%s) -> %s' % (info.get_name(), in_args_str, out_args_str)
+    else:
+        return '%s(%s)' % (info.get_name(), in_args_str)
+
+
+def wraps_callable_info(info):
+    """Similar to functools.wraps but with specific GICallableInfo support."""
+    def update_func(func):
+        func.__info__ = info
+        func.__name__ = info.get_name()
+        func.__module__ = info.get_namespace()
+        func.__doc__ = get_callable_info_doc_string(info)
+        return func
+    return update_func
+
 
+def Function(info):
+    """Warps GIFunctionInfo"""
+    @wraps_callable_info(info)
     def function(*args, **kwargs):
         return info.invoke(*args, **kwargs)
-    function.__info__ = info
-    function.__name__ = info.get_name()
-    function.__module__ = info.get_namespace()
 
     return function
 
 
 class NativeVFunc(object):
-
+    """Wraps GINativeVFuncInfo"""
     def __init__(self, info):
-        self._info = info
+        self.__info__ = info
 
     def __get__(self, instance, klass):
+        @wraps_callable_info(self.__info__)
         def native_vfunc(*args, **kwargs):
-            return self._info.invoke(klass.__gtype__, *args, **kwargs)
-        native_vfunc.__info__ = self._info
-        native_vfunc.__name__ = self._info.get_name()
-        native_vfunc.__module__ = self._info.get_namespace()
-
+            return self.__info__.invoke(klass.__gtype__, *args, **kwargs)
         return native_vfunc
 
 
 def Constructor(info):
-
+    """Warps GIFunctionInfo with get_constructor() == True"""
+    @wraps_callable_info(info)
     def constructor(cls, *args, **kwargs):
         cls_name = info.get_container().get_name()
         if cls.__name__ != cls_name:
             raise TypeError('%s constructor cannot be used to create instances of a subclass' % cls_name)
         return info.invoke(cls, *args, **kwargs)
-
-    constructor.__info__ = info
-    constructor.__name__ = info.get_name()
-    constructor.__module__ = info.get_namespace()
-
     return constructor
 
 
diff --git a/tests/test_gi.py b/tests/test_gi.py
index f65cf74..76f4c3d 100644
--- a/tests/test_gi.py
+++ b/tests/test_gi.py
@@ -2601,3 +2601,33 @@ class TestObjectInfo(unittest.TestCase):
         repo = gi.gi.Repository.get_default()
         info = repo.find_by_name('GObject', 'Object')
         self.assertFalse(info.get_abstract())
+
+
+class TestSignatureArgs(unittest.TestCase):
+    def test_split_args_multi_out(self):
+        in_args, out_args = gi.types.split_function_info_args(GIMarshallingTests.int_out_out.__info__)
+        self.assertEqual(len(in_args), 0)
+        self.assertEqual(len(out_args), 2)
+        self.assertEqual(out_args[0].get_pytype_hint(), 'int')
+        self.assertEqual(out_args[1].get_pytype_hint(), 'int')
+
+    def test_split_args_inout(self):
+        in_args, out_args = gi.types.split_function_info_args(GIMarshallingTests.long_inout_max_min.__info__)
+        self.assertEqual(len(in_args), 1)
+        self.assertEqual(len(out_args), 1)
+        self.assertEqual(in_args[0].get_name(), out_args[0].get_name())
+        self.assertEqual(in_args[0].get_pytype_hint(), out_args[0].get_pytype_hint())
+
+    def test_split_args_none(self):
+        obj = GIMarshallingTests.Object(int=33)
+        in_args, out_args = gi.types.split_function_info_args(obj.none_inout.__info__)
+        self.assertEqual(len(in_args), 1)
+        self.assertEqual(len(out_args), 1)
+
+    def test_final_signature_with_full_inout(self):
+        self.assertEqual(GIMarshallingTests.Object.full_inout.__doc__,
+                         'full_inout(object:GIMarshallingTests.Object) -> object:GIMarshallingTests.Object')
+
+    def test_overridden_doc_is_not_clobbered(self):
+        self.assertEqual(GIMarshallingTests.OverridesObject.method.__doc__,
+                         'Overridden doc string.')



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