From: Vladimír Vondruš Date: Sun, 21 Apr 2019 17:21:40 +0000 (+0200) Subject: documentation/python: parsing types of pybind11 properties. X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~cjwatson/git?a=commitdiff_plain;h=fec100f73ac04a224d3d87330cce81439472e61f;p=blog.git documentation/python: parsing types of pybind11 properties. --- diff --git a/doc/documentation/python.rst b/doc/documentation/python.rst index a1349778..123858c6 100644 --- a/doc/documentation/python.rst +++ b/doc/documentation/python.rst @@ -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 (``…``). diff --git a/documentation/python.py b/documentation/python.py index 03b20f35..33381b23 100755 --- a/documentation/python.py +++ b/documentation/python.py @@ -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 diff --git a/documentation/test_python/pybind_signatures/pybind_signatures.MyClass.html b/documentation/test_python/pybind_signatures/pybind_signatures.MyClass.html index 93360731..4c104a0e 100644 --- a/documentation/test_python/pybind_signatures/pybind_signatures.MyClass.html +++ b/documentation/test_python/pybind_signatures/pybind_signatures.MyClass.html @@ -32,6 +32,7 @@
  • Static methods
  • Methods
  • Special methods
  • +
  • Properties
  • @@ -76,6 +77,15 @@
    Constructor
    +
    +

    Properties

    +
    +
    + foo: float get set +
    +
    A read/write property
    +
    +
    diff --git a/documentation/test_python/pybind_signatures/pybind_signatures.cpp b/documentation/test_python/pybind_signatures/pybind_signatures.cpp index c84b49ba..4cb39638 100644 --- a/documentation/test_python/pybind_signatures/pybind_signatures.cpp +++ b/documentation/test_python/pybind_signatures/pybind_signatures.cpp @@ -26,6 +26,11 @@ struct MyClass { std::pair 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"); } diff --git a/documentation/test_python/test_pybind.py b/documentation/test_python/test_pybind.py index 58914e57..1547430f 100644 --- a/documentation/test_python/test_pybind.py +++ b/documentation/test_python/test_pybind.py @@ -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)