chiark / gitweb /
documentation/python: parsing types of pybind11 properties.
authorVladimír Vondruš <mosra@centrum.cz>
Sun, 21 Apr 2019 17:21:40 +0000 (19:21 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Mon, 22 Apr 2019 15:53:36 +0000 (17:53 +0200)
doc/documentation/python.rst
documentation/python.py
documentation/test_python/pybind_signatures/pybind_signatures.MyClass.html
documentation/test_python/pybind_signatures/pybind_signatures.cpp
documentation/test_python/test_pybind.py

index a13497788c2b42c8979fe4b115c38a50b97cbdc2..123858c64c02f479d33ed37b5acdbaa4d4e6aece 100644 (file)
@@ -528,8 +528,8 @@ has to do a few pybind11-specific workarounds to generate expected output. This
 behavior is not enabled by default as it *might* have unwanted consequences in
 pure Python code, enable it using the :py:`PYBIND11_COMPATIBILITY` option.
 
-`Function signatures`_
-----------------------
+`Function signatures, property annotations`_
+--------------------------------------------
 
 For reasons explained in :gh:`pybind/pybind11#990`, pybind11 is not able to
 provide function signatures through introspection and thus the script falls
@@ -538,6 +538,8 @@ docstring instead. By default, unless :cpp:`py::arg()` is used, function
 arguments are positional-only (shown as :py:`arg0`, :py:`arg1`, ...) and marked
 as such in the output.
 
+Similarly, property types are extracted from getter docstrings.
+
 The signature parsing can't handle all cases and, especially when templated C++
 type names leak through, it may fail to extract the argument names. If that
 happens, the function signature shows just an ellipsis (``…``).
index 03b20f35d50114e887e5818a89a5ead363ca8940..33381b239bdb15b98a6da4dcc30ab19e7c7305de 100755 (executable)
@@ -160,7 +160,7 @@ def is_enum(state: State, object) -> bool:
 def make_url(path: List[str]) -> str:
     return '.'.join(path) + '.html'
 
-_pybind_name_rx = re.compile('[a-zA-Z0-9_]+')
+_pybind_name_rx = re.compile('[a-zA-Z0-9_]*')
 _pybind_arg_name_rx = re.compile('[*a-zA-Z0-9_]+')
 _pybind_type_rx = re.compile('[a-zA-Z0-9_.]+')
 _pybind_default_value_rx = re.compile('[^,)]+')
@@ -512,7 +512,7 @@ def extract_function_doc(state: State, parent, path: List[str], function) -> Lis
 
         return [out]
 
-def extract_property_doc(path: List[str], property):
+def extract_property_doc(state: State, path: List[str], property):
     assert inspect.isdatadescriptor(property)
 
     out = Empty()
@@ -526,7 +526,11 @@ def extract_property_doc(path: List[str], property):
         signature = inspect.signature(property.fget)
         out.type = extract_annotation(signature.return_annotation)
     except ValueError:
-        out.type = None
+        # pybind11 properties have the type in the docstring
+        if state.config['PYBIND11_COMPATIBILITY']:
+            out.type = parse_pybind_signature(property.fget.__doc__)[3]
+        else:
+            out.type = None
 
     return out
 
@@ -800,7 +804,7 @@ def render_class(state: State, path, class_, env):
         subpath = path + [name]
         if not object.__doc__: logging.warning("%s is undocumented", '.'.join(subpath))
 
-        page.properties += [extract_property_doc(subpath, object)]
+        page.properties += [extract_property_doc(state, subpath, object)]
 
     # Get data
     # TODO: unify this query
index 933607317ccb369152a26d38cdf2df3a06b4623f..4c104a0e82d390fbb280a58503f208da743e8f88 100644 (file)
@@ -32,6 +32,7 @@
                 <li><a href="#staticmethods">Static methods</a></li>
                 <li><a href="#methods">Methods</a></li>
                 <li><a href="#dunder-methods">Special methods</a></li>
+                <li><a href="#properties">Properties</a></li>
               </ul>
             </li>
           </ul>
             <dd>Constructor</dd>
           </dl>
         </section>
+        <section id="properties">
+          <h2><a href="#properties">Properties</a></h2>
+          <dl class="m-doc">
+            <dt>
+              <a href="" class="m-doc-self">foo</a>: float <span class="m-label m-flat m-success">get set</span>
+            </dt>
+            <dd>A read/write property</dd>
+          </dl>
+        </section>
       </div>
     </div>
   </div>
index c84b49ba985d337b5d835a8e0d81118ebcc4e5b7..4cb39638aa59268c74349cdf29efdbe6592fb121 100644 (file)
@@ -26,6 +26,11 @@ struct MyClass {
     std::pair<float, int> instanceFunction(int, const std::string&) { return {0.5f, 42}; }
 
     int another() { return 42; }
+
+    float foo() const { return _foo; }
+    void setFoo(float foo) { _foo = foo; }
+
+    private: float _foo = 0.0f;
 };
 
 PYBIND11_MODULE(pybind_signatures, m) {
@@ -45,5 +50,6 @@ PYBIND11_MODULE(pybind_signatures, m) {
         .def(py::init(), "Constructor")
         .def("instance_function", &MyClass::instanceFunction, "Instance method with positional-only args")
         .def("instance_function_kwargs", &MyClass::instanceFunction, "Instance method with position or keyword args", py::arg("hey"), py::arg("what") = "eh?")
-        .def("another", &MyClass::another, "Instance method with no args, 'self' is thus position-only");
+        .def("another", &MyClass::another, "Instance method with no args, 'self' is thus position-only")
+        .def_property("foo", &MyClass::foo, &MyClass::setFoo, "A read/write property");
 }
index 58914e57472a26a1b48e06b15c9a36162b77cf93..1547430f90b8c9df8f3ccdbd86333baef373f5fd 100644 (file)
@@ -116,6 +116,11 @@ class Signature(unittest.TestCase):
             'foo(a: int, b: Math::Vector<4, UnsignedInt>)\n\nThis is text!!'),
             ('foo', 'This is text!!', [('…', None, None)], None))
 
+    def test_no_name(self):
+        self.assertEqual(parse_pybind_signature(
+            '(arg0: MyClass) -> float'),
+            ('', '', [('arg0', 'MyClass', None)], 'float'))
+
 class Signatures(BaseTestCase):
     def __init__(self, *args, **kwargs):
         super().__init__(__file__, 'signatures', *args, **kwargs)