Add option for enable/disable enum members in docstring. (#2768)

* Add option for enable/disable enum members in docstring

* Add tests for disable enum members docstring option

* Add docstring options to documentation

* style: pre-commit fixes

* Fix typos in documentation

* Improve documentation wording

* Apply suggestions by @Skylion007

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Ralf W. Grosse-Kunstleve <rwgk@google.com>
This commit is contained in:
Frank 2022-12-09 08:10:10 +01:00 committed by GitHub
parent 65374c8e62
commit 00126859a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 129 additions and 22 deletions

View File

@ -324,6 +324,15 @@ The class ``options`` allows you to selectively suppress auto-generated signatur
m.def("add", [](int a, int b) { return a + b; }, "A function which adds two numbers"); m.def("add", [](int a, int b) { return a + b; }, "A function which adds two numbers");
} }
pybind11 also appends all members of an enum to the resulting enum docstring.
This default behavior can be disabled by using the ``disable_enum_members_docstring()``
function of the ``options`` class.
With ``disable_user_defined_docstrings()`` all user defined docstrings of
``module_::def()``, ``class_::def()`` and ``enum_()`` are disabled, but the
function signatures and enum members are included in the docstring, unless they
are disabled separately.
Note that changes to the settings affect only function bindings created during the Note that changes to the settings affect only function bindings created during the
lifetime of the ``options`` instance. When it goes out of scope at the end of the module's init function, 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. the default settings are restored to prevent unwanted side effects.

View File

@ -47,6 +47,16 @@ public:
return *this; return *this;
} }
options &disable_enum_members_docstring() & {
global_state().show_enum_members_docstring = false;
return *this;
}
options &enable_enum_members_docstring() & {
global_state().show_enum_members_docstring = true;
return *this;
}
// Getter methods (return the global state): // Getter methods (return the global state):
static bool show_user_defined_docstrings() { static bool show_user_defined_docstrings() {
@ -55,6 +65,10 @@ public:
static bool show_function_signatures() { return global_state().show_function_signatures; } static bool show_function_signatures() { return global_state().show_function_signatures; }
static bool show_enum_members_docstring() {
return global_state().show_enum_members_docstring;
}
// This type is not meant to be allocated on the heap. // This type is not meant to be allocated on the heap.
void *operator new(size_t) = delete; void *operator new(size_t) = delete;
@ -63,6 +77,8 @@ private:
bool show_user_defined_docstrings = true; //< Include user-supplied texts in docstrings. bool show_user_defined_docstrings = true; //< Include user-supplied texts in docstrings.
bool show_function_signatures = true; //< Include auto-generated function signatures bool show_function_signatures = true; //< Include auto-generated function signatures
// in docstrings. // in docstrings.
bool show_enum_members_docstring = true; //< Include auto-generated member list in enum
// docstrings.
}; };
static state &global_state() { static state &global_state() {

View File

@ -1972,29 +1972,35 @@ struct enum_base {
name("name"), name("name"),
is_method(m_base)); is_method(m_base));
m_base.attr("__doc__") = static_property( if (options::show_enum_members_docstring()) {
cpp_function( m_base.attr("__doc__") = static_property(
[](handle arg) -> std::string { cpp_function(
std::string docstring; [](handle arg) -> std::string {
dict entries = arg.attr("__entries"); std::string docstring;
if (((PyTypeObject *) arg.ptr())->tp_doc) { dict entries = arg.attr("__entries");
docstring += std::string(((PyTypeObject *) arg.ptr())->tp_doc) + "\n\n"; if (((PyTypeObject *) arg.ptr())->tp_doc) {
} docstring += std::string(
docstring += "Members:"; reinterpret_cast<PyTypeObject *>(arg.ptr())->tp_doc);
for (auto kv : entries) { docstring += "\n\n";
auto key = std::string(pybind11::str(kv.first));
auto comment = kv.second[int_(1)];
docstring += "\n\n " + key;
if (!comment.is_none()) {
docstring += " : " + (std::string) pybind11::str(comment);
} }
} docstring += "Members:";
return docstring; for (auto kv : entries) {
}, auto key = std::string(pybind11::str(kv.first));
name("__doc__")), auto comment = kv.second[int_(1)];
none(), docstring += "\n\n ";
none(), docstring += key;
""); if (!comment.is_none()) {
docstring += " : ";
docstring += pybind11::str(comment).cast<std::string>();
}
}
return docstring;
},
name("__doc__")),
none(),
none(),
"");
}
m_base.attr("__members__") = static_property(cpp_function( m_base.attr("__members__") = static_property(cpp_function(
[](handle arg) -> dict { [](handle arg) -> dict {

View File

@ -85,4 +85,57 @@ TEST_SUBMODULE(docstring_options, m) {
&DocstringTestFoo::setValue, &DocstringTestFoo::setValue,
"This is a property docstring"); "This is a property docstring");
} }
{
enum class DocstringTestEnum1 { Member1, Member2 };
py::enum_<DocstringTestEnum1>(m, "DocstringTestEnum1", "Enum docstring")
.value("Member1", DocstringTestEnum1::Member1)
.value("Member2", DocstringTestEnum1::Member2);
}
{
py::options options;
options.enable_enum_members_docstring();
enum class DocstringTestEnum2 { Member1, Member2 };
py::enum_<DocstringTestEnum2>(m, "DocstringTestEnum2", "Enum docstring")
.value("Member1", DocstringTestEnum2::Member1)
.value("Member2", DocstringTestEnum2::Member2);
}
{
py::options options;
options.disable_enum_members_docstring();
enum class DocstringTestEnum3 { Member1, Member2 };
py::enum_<DocstringTestEnum3>(m, "DocstringTestEnum3", "Enum docstring")
.value("Member1", DocstringTestEnum3::Member1)
.value("Member2", DocstringTestEnum3::Member2);
}
{
py::options options;
options.disable_user_defined_docstrings();
enum class DocstringTestEnum4 { Member1, Member2 };
py::enum_<DocstringTestEnum4>(m, "DocstringTestEnum4", "Enum docstring")
.value("Member1", DocstringTestEnum4::Member1)
.value("Member2", DocstringTestEnum4::Member2);
}
{
py::options options;
options.disable_user_defined_docstrings();
options.disable_enum_members_docstring();
enum class DocstringTestEnum5 { Member1, Member2 };
py::enum_<DocstringTestEnum5>(m, "DocstringTestEnum5", "Enum docstring")
.value("Member1", DocstringTestEnum5::Member1)
.value("Member2", DocstringTestEnum5::Member2);
}
} }

View File

@ -39,3 +39,26 @@ def test_docstring_options():
# Suppression of user-defined docstrings for non-function objects # Suppression of user-defined docstrings for non-function objects
assert not m.DocstringTestFoo.__doc__ assert not m.DocstringTestFoo.__doc__
assert not m.DocstringTestFoo.value_prop.__doc__ assert not m.DocstringTestFoo.value_prop.__doc__
# Check existig behaviour of enum docstings
assert (
m.DocstringTestEnum1.__doc__
== "Enum docstring\n\nMembers:\n\n Member1\n\n Member2"
)
# options.enable_enum_members_docstring()
assert (
m.DocstringTestEnum2.__doc__
== "Enum docstring\n\nMembers:\n\n Member1\n\n Member2"
)
# options.disable_enum_members_docstring()
assert m.DocstringTestEnum3.__doc__ == "Enum docstring"
# options.disable_user_defined_docstrings()
assert m.DocstringTestEnum4.__doc__ == "Members:\n\n Member1\n\n Member2"
# options.disable_user_defined_docstrings()
# options.disable_enum_members_docstring()
# When all options are disabled, no docstring (instead of an empty one) should be generated
assert m.DocstringTestEnum5.__doc__ is None