[pypy-commit] pypy py3.5: Implement __text_signature__ on PyCFunctions

rlamy pypy.commits at gmail.com
Sun Nov 12 17:16:24 EST 2017


Author: Ronan Lamy <ronan.lamy at gmail.com>
Branch: py3.5
Changeset: r92998:a626dd21b1fa
Date: 2017-11-12 20:11 +0000
http://bitbucket.org/pypy/pypy/changeset/a626dd21b1fa/

Log:	Implement __text_signature__ on PyCFunctions

diff --git a/pypy/module/cpyext/methodobject.py b/pypy/module/cpyext/methodobject.py
--- a/pypy/module/cpyext/methodobject.py
+++ b/pypy/module/cpyext/methodobject.py
@@ -43,6 +43,39 @@
     from pypy.module.cpyext.object import _dealloc
     _dealloc(space, py_obj)
 
+def undotted_name(name):
+    """Return the last component of a dotted name"""
+    dotpos = name.rfind('.')
+    if dotpos < 0:
+        return name
+    else:
+        return name[dotpos + 1:]
+
+SIGNATURE_MARKER = ')\n--\n\n'
+
+def extract_doc(raw_doc, name):
+    doc = raw_doc
+    name = undotted_name(name)
+    if raw_doc.startswith(name + '('):
+        end_sig = raw_doc.find(SIGNATURE_MARKER)
+        if end_sig > 0:
+            doc = raw_doc[end_sig + len(SIGNATURE_MARKER):]
+    if not doc:
+        return None
+    return doc
+
+def extract_txtsig(raw_doc, name):
+    name = undotted_name(name)
+    if raw_doc.startswith(name + '('):
+        end_sig = raw_doc.find(SIGNATURE_MARKER)
+        if end_sig > 0:
+            # Notes:
+            # * Parentheses are included
+            # * SIGNATURE_MARKER cannot appear inside name,
+            #   so end_sig > len(name)
+            return raw_doc[len(name): end_sig + 1]
+    return None
+
 class W_PyCFunctionObject(W_Root):
     # TODO create a slightly different class depending on the c_ml_flags
     def __init__(self, space, ml, w_self, w_module=None):
@@ -84,11 +117,22 @@
             raise oefmt(space.w_RuntimeError, "unknown calling convention")
 
     def get_doc(self, space):
-        doc = self.ml.c_ml_doc
-        if doc:
-            return space.newtext(rffi.charp2str(rffi.cast(rffi.CCHARP,doc)))
-        else:
-            return space.w_None
+        c_doc = self.ml.c_ml_doc
+        if c_doc:
+            rawdoc = rffi.charp2str(rffi.cast(rffi.CCHARP, c_doc))
+            doc = extract_doc(rawdoc, self.name)
+            if doc is not None:
+                return space.newtext(doc)
+        return space.w_None
+
+    def get_txtsig(self, space):
+        c_doc = self.ml.c_ml_doc
+        if c_doc:
+            rawdoc = rffi.charp2str(rffi.cast(rffi.CCHARP, c_doc))
+            txtsig = extract_txtsig(rawdoc, self.name)
+            if txtsig is not None:
+                return space.newtext(txtsig)
+        return space.w_None
 
 class W_PyCFunctionObjectNoArgs(W_PyCFunctionObject):
     def call(self, space, w_self, w_args, w_kw):
@@ -289,6 +333,7 @@
     'builtin_function_or_method',
     __call__ = interp2app(cfunction_descr_call),
     __doc__ = GetSetProperty(W_PyCFunctionObject.get_doc),
+    __text_signature__ = GetSetProperty(W_PyCFunctionObject.get_txtsig),
     __module__ = interp_attrproperty_w('w_module', cls=W_PyCFunctionObject),
     __name__ = interp_attrproperty('name', cls=W_PyCFunctionObject,
         wrapfn="newtext_or_none"),
@@ -299,6 +344,7 @@
     'builtin_function_or_method', W_PyCFunctionObject.typedef,
     __call__ = interp2app(cfunction_descr_call_noargs),
     __doc__ = GetSetProperty(W_PyCFunctionObjectNoArgs.get_doc),
+    __text_signature__ = GetSetProperty(W_PyCFunctionObjectNoArgs.get_txtsig),
     __module__ = interp_attrproperty_w('w_module', cls=W_PyCFunctionObjectNoArgs),
     __name__ = interp_attrproperty('name', cls=W_PyCFunctionObjectNoArgs,
         wrapfn="newtext_or_none"),
@@ -309,6 +355,7 @@
     'builtin_function_or_method', W_PyCFunctionObject.typedef,
     __call__ = interp2app(cfunction_descr_call_single_object),
     __doc__ = GetSetProperty(W_PyCFunctionObjectSingleObject.get_doc),
+    __text_signature__ = GetSetProperty(W_PyCFunctionObjectSingleObject.get_txtsig),
     __module__ = interp_attrproperty_w('w_module', cls=W_PyCFunctionObjectSingleObject),
     __name__ = interp_attrproperty('name', cls=W_PyCFunctionObjectSingleObject,
         wrapfn="newtext_or_none"),
diff --git a/pypy/module/cpyext/test/docstrings.c b/pypy/module/cpyext/test/docstrings.c
new file mode 100644
--- /dev/null
+++ b/pypy/module/cpyext/test/docstrings.c
@@ -0,0 +1,149 @@
+#include "Python.h"
+
+static PyObject *
+test_with_docstring(PyObject *self)
+{
+    Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(empty_doc,
+""
+);
+
+PyDoc_STRVAR(no_sig,
+"This docstring has no signature."
+);
+
+PyDoc_STRVAR(invalid_sig,
+"invalid_sig($module, /, boo)\n"
+"\n"
+"This docstring has an invalid signature."
+);
+
+PyDoc_STRVAR(invalid_sig2,
+"invalid_sig2($module, /, boo)\n"
+"\n"
+"--\n"
+"\n"
+"This docstring also has an invalid signature."
+);
+
+PyDoc_STRVAR(with_sig,
+"with_sig($module, /, sig)\n"
+"--\n"
+"\n"
+"This docstring has a valid signature."
+);
+
+PyDoc_STRVAR(with_sig_but_no_doc,
+"with_sig_but_no_doc($module, /, sig)\n"
+"--\n"
+"\n"
+);
+
+PyDoc_STRVAR(with_signature_and_extra_newlines,
+"with_signature_and_extra_newlines($module, /, parameter)\n"
+"--\n"
+"\n"
+"\n"
+"This docstring has a valid signature and some extra newlines."
+);
+
+
+static PyMethodDef methods[] = {
+    {"no_doc",
+        (PyCFunction)test_with_docstring, METH_NOARGS},
+    {"empty_doc",
+        (PyCFunction)test_with_docstring, METH_NOARGS,
+        empty_doc},
+    {"no_sig",
+        (PyCFunction)test_with_docstring, METH_NOARGS,
+        no_sig},
+    {"invalid_sig",
+        (PyCFunction)test_with_docstring, METH_NOARGS,
+        invalid_sig},
+    {"invalid_sig2",
+        (PyCFunction)test_with_docstring, METH_NOARGS,
+        invalid_sig2},
+    {"with_sig",
+        (PyCFunction)test_with_docstring, METH_NOARGS,
+        with_sig},
+    {"with_sig_but_no_doc",
+        (PyCFunction)test_with_docstring, METH_NOARGS,
+        with_sig_but_no_doc},
+    {"with_signature_and_extra_newlines",
+        (PyCFunction)test_with_docstring, METH_NOARGS,
+        with_signature_and_extra_newlines},
+    {NULL, NULL} /* sentinel */
+};
+
+
+static PyType_Slot HeapType_slots[] = {
+    {Py_tp_doc, "HeapType()\n--\n\nA type with a signature"},
+    {0, 0},
+};
+
+static PyType_Spec HeapType_spec = {
+    "docstrings.HeapType",
+    sizeof(PyObject),
+    0,
+    Py_TPFLAGS_DEFAULT,
+    HeapType_slots
+};
+
+static PyTypeObject SomeType = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "docstrings.SomeType",      /* tp_name */
+    sizeof(PyObject),           /* tp_basicsize */
+    0,                         /* tp_itemsize */
+    0,                         /* tp_dealloc */
+    0,                         /* tp_print */
+    0,                         /* tp_getattr */
+    0,                         /* tp_setattr */
+    0,                         /* tp_reserved */
+    0,                         /* tp_repr */
+    0,                         /* tp_as_number */
+    0,                         /* tp_as_sequence */
+    0,                         /* tp_as_mapping */
+    0,                         /* tp_hash  */
+    0,                         /* tp_call */
+    0,                         /* tp_str */
+    0,                         /* tp_getattro */
+    0,                         /* tp_setattro */
+    0,                         /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT,        /* tp_flags */
+    "SomeType()\n--\n\nA type with a signature",    /* tp_doc */
+};
+
+
+static struct PyModuleDef def = {
+    PyModuleDef_HEAD_INIT,
+    "docstrings",
+    NULL,
+    -1,
+    methods,
+    NULL,
+    NULL,
+    NULL,
+    NULL
+};
+
+
+PyMODINIT_FUNC
+PyInit_docstrings(void)
+{
+    PyObject *m, *tmp;
+    m = PyModule_Create(&def);
+    if (m == NULL)
+        return NULL;
+    tmp = PyType_FromSpec(&HeapType_spec);
+    if (tmp == NULL)
+        return NULL;
+    if (PyModule_AddObject(m, "HeapType", tmp) != 0)
+        return NULL;
+    if (PyType_Ready(&SomeType) < 0)
+        return NULL;
+    if (PyModule_AddObject(m, "SomeType", (PyObject*)&SomeType) != 0)
+        return NULL;
+    return m;
+}
diff --git a/pypy/module/cpyext/test/test_methodobject.py b/pypy/module/cpyext/test/test_methodobject.py
--- a/pypy/module/cpyext/test/test_methodobject.py
+++ b/pypy/module/cpyext/test/test_methodobject.py
@@ -100,3 +100,23 @@
         assert mod.check(A) == 0
         assert mod.check(A.meth) == 0
         assert mod.check(A.stat) == 0
+
+    def test_text_signature(self):
+        mod = self.import_module('docstrings')
+        assert mod.no_doc.__doc__ is None
+        assert mod.no_doc.__text_signature__ is None
+        assert mod.empty_doc.__doc__ is None
+        assert mod.empty_doc.__text_signature__ is None
+        assert mod.no_sig.__doc__
+        assert mod.no_sig.__text_signature__ is None
+        assert mod.invalid_sig.__doc__
+        assert mod.invalid_sig.__text_signature__ is None
+        assert mod.invalid_sig2.__doc__
+        assert mod.invalid_sig2.__text_signature__ is None
+        assert mod.with_sig.__doc__
+        assert mod.with_sig.__text_signature__ == '($module, /, sig)'
+        assert mod.with_sig_but_no_doc.__doc__ is None
+        assert mod.with_sig_but_no_doc.__text_signature__ == '($module, /, sig)'
+        assert mod.with_signature_and_extra_newlines.__doc__
+        assert (mod.with_signature_and_extra_newlines.__text_signature__ ==
+                '($module, /, parameter)')


More information about the pypy-commit mailing list