From a4b246ae07e002d8d6dd9b7963038ccccdd95c02 Mon Sep 17 00:00:00 2001 From: Ashley Whetter Date: Sun, 25 Oct 2020 18:24:09 -0700 Subject: [PATCH] Prepend all overload signatures to docstrings --- docs/advanced/misc.rst | 71 ++++++++++++++++++++++++++++++ include/pybind11/pybind11.h | 14 +++--- tests/test_docstring_options.cpp | 12 +++++ tests/test_docstring_options.py | 43 ++++++++++++++++-- tests/test_factory_constructors.py | 5 ++- 5 files changed, 136 insertions(+), 9 deletions(-) diff --git a/docs/advanced/misc.rst b/docs/advanced/misc.rst index ddd7f3937..8ae90db40 100644 --- a/docs/advanced/misc.rst +++ b/docs/advanced/misc.rst @@ -365,6 +365,77 @@ Note that changes to the settings affect only function bindings created during t lifetime of the ``options`` instance. When it goes out of scope at the end of the module's init function, the default settings are restored to prevent unwanted side effects. +Overloaded functions +-------------------- + +The docstring of an overloaded function is prepended with the signature of each overload. +All overload docstrings are then concatenated together +into sections that are separated by each function signature. +The prepended signatures can be read by tools like Sphinx. + +.. code-block:: cpp + + PYBIND11_MODULE(example, m) { + m.def("add", [](int a, int b)->int { return a + b; }, + "Add two integers together."); + m.def("add", [](float a, float b)->float { return a + b; }, + "Add two floating point numbers together."); + } + +The above example would produce the following docstring: + +.. code-block:: pycon + + >>> help(example.add) + + add(...) + | add(arg0: int, arg1: int) -> int + | add(arg0: float, arg1: float) -> float + | Overloaded function. + | + | 1. add(arg0: int, arg1: int) -> int + | + | Add two integers together. + | + | 2. add(arg0: float, arg1: float) -> float + | + | Add two floating point numbers together. + +Calling ``options.disable_function_signatures()`` as shown previously +will cause the docstrings of overloaded functions to be generated without the section headings. +The prepended overload signatures will remain: + +.. code-block:: cpp + + PYBIND11_MODULE(example, m) { + py::options options; + options.disable_function_signatures(); + + m.def("add", [](int a, int b)->int { return a + b; }, + "A function which adds two numbers.\n"); // Note the additional newline here. + m.def("add", [](float a, float b)->float { return a + b; }, + "Internally, a simple addition is performed."); + m.def("add", [](const py::none&, const py::none&)->py::none { return py::none(); }, + "Both numbers can be None, and None will be returned."); + } + +The above example would produce the following docstring: + +.. code-block:: pycon + + >>> help(example.add) + add(...) + | add(arg0: int, arg1: int) -> int + | add(arg0: float, arg1: float) -> float + | add(arg0: None, arg1: None) -> None + | A function which adds two numbers. + | + | Internally, a simple addition is performed. + | Both numbers can be None, and None will be returned. + +Not every overload must supply a docstring. +You may find it easier for a single overload to supply the entire docstring. + .. [#f4] http://www.sphinx-doc.org .. [#f5] http://github.com/pybind/python_example diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 74919a7d5..2a77a61bc 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -610,11 +610,15 @@ protected: int index = 0; /* Create a nice pydoc rec including all signatures and docstrings of the functions in the overload chain */ - if (chain && options::show_function_signatures()) { - // First a generic signature - signatures += rec->name; - signatures += "(*args, **kwargs)\n"; - signatures += "Overloaded function.\n\n"; + if (chain) { + for (auto *it = chain_start; it != nullptr; it = it->next) { + signatures += rec->name; + signatures += it->signature; + signatures += "\n"; + } + if (options::show_function_signatures()) { + signatures += "Overloaded function.\n\n"; + } } // Then specific overload signatures bool first_user_def = true; diff --git a/tests/test_docstring_options.cpp b/tests/test_docstring_options.cpp index de045a7ca..7984597e9 100644 --- a/tests/test_docstring_options.cpp +++ b/tests/test_docstring_options.cpp @@ -27,8 +27,20 @@ TEST_SUBMODULE(docstring_options, m) { m.def("test_overloaded3", [](int) {}, py::arg("i")); m.def("test_overloaded3", [](double) {}, py::arg("d"), "Overload docstr"); + m.def("test_overloaded4", [](int a, int b)->int { return a + b; }, + "A function which adds two numbers.\n"); + m.def("test_overloaded4", [](float a, float b)->float { return a + b; }, + "Internally, a simple addition is performed."); + m.def("test_overloaded4", [](const py::none&, const py::none&)->py::none { return py::none(); }, + "Both numbers can be None, and None will be returned."); + options.enable_function_signatures(); + m.def("test_overloaded5", [](int a, int b)->int { return a + b; }, + "Add two integers together."); + m.def("test_overloaded5", [](float a, float b)->float { return a + b; }, + "Add two floating point numbers together."); + m.def("test_function3", [](int, int) {}, py::arg("a"), py::arg("b")); m.def("test_function4", [](int, int) {}, py::arg("a"), py::arg("b"), "A custom docstring"); diff --git a/tests/test_docstring_options.py b/tests/test_docstring_options.py index 09fc8ac25..b27057c43 100644 --- a/tests/test_docstring_options.py +++ b/tests/test_docstring_options.py @@ -10,13 +10,50 @@ def test_docstring_options(): assert m.test_function2.__doc__ == "A custom docstring" # docstring specified on just the first overload definition: - assert m.test_overloaded1.__doc__ == "Overload docstring" + assert m.test_overloaded1.__doc__ == ( + "test_overloaded1(i: int) -> None\n" + "test_overloaded1(d: float) -> None\n" + "Overload docstring" + ) # docstring on both overloads: - assert m.test_overloaded2.__doc__ == "overload docstring 1\noverload docstring 2" + assert m.test_overloaded2.__doc__ == ( + "test_overloaded2(i: int) -> None\n" + "test_overloaded2(d: float) -> None\n" + "overload docstring 1\n" + "overload docstring 2" + ) # docstring on only second overload: - assert m.test_overloaded3.__doc__ == "Overload docstr" + assert m.test_overloaded3.__doc__ == ( + "test_overloaded3(i: int) -> None\n" + "test_overloaded3(d: float) -> None\n" + "Overload docstr" + ) + + # Check overload configuration behaviour matches the documentation + assert m.test_overloaded4.__doc__ == ( + "test_overloaded4(arg0: int, arg1: int) -> int\n" + "test_overloaded4(arg0: float, arg1: float) -> float\n" + "test_overloaded4(arg0: None, arg1: None) -> None\n" + "A function which adds two numbers.\n\n" + "Internally, a simple addition is performed.\n" + "Both numbers can be None, and None will be returned." + ) + + assert m.test_overloaded5.__doc__ == ( + "test_overloaded5(arg0: int, arg1: int) -> int\n" + "test_overloaded5(arg0: float, arg1: float) -> float\n" + "Overloaded function.\n" + "\n" + "1. test_overloaded5(arg0: int, arg1: int) -> int\n" + "\n" + "Add two integers together.\n" + "\n" + "2. test_overloaded5(arg0: float, arg1: float) -> float\n" + "\n" + "Add two floating point numbers together.\n" + ) # options.enable_function_signatures() assert m.test_function3.__doc__.startswith("test_function3(a: int, b: int) -> None") diff --git a/tests/test_factory_constructors.py b/tests/test_factory_constructors.py index 0ddad5e32..92e32d1ec 100644 --- a/tests/test_factory_constructors.py +++ b/tests/test_factory_constructors.py @@ -88,7 +88,10 @@ def test_init_factory_signature(msg): assert ( msg(m.TestFactory1.__init__.__doc__) == """ - __init__(*args, **kwargs) + __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: int) -> None + __init__(self: m.factory_constructors.TestFactory1, arg0: str) -> None + __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.pointer_tag) -> None + __init__(self: m.factory_constructors.TestFactory1, arg0: object, arg1: int, arg2: object) -> None Overloaded function. 1. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: int) -> None