Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/advanced/misc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

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
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.
Expand Down
16 changes: 16 additions & 0 deletions include/pybind11/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ class options {
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):

static bool show_user_defined_docstrings() {
Expand All @@ -55,6 +65,10 @@ class options {

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.
void *operator new(size_t) = delete;

Expand All @@ -63,6 +77,8 @@ class options {
bool show_user_defined_docstrings = true; //< Include user-supplied texts in docstrings.
bool show_function_signatures = true; //< Include auto-generated function signatures
// in docstrings.
bool show_enum_members_docstring = true; //< Include auto-generated member list in enum
// docstrings.
};

static state &global_state() {
Expand Down
50 changes: 28 additions & 22 deletions include/pybind11/pybind11.h
Original file line number Diff line number Diff line change
Expand Up @@ -1972,29 +1972,35 @@ struct enum_base {
name("name"),
is_method(m_base));

m_base.attr("__doc__") = static_property(
cpp_function(
[](handle arg) -> std::string {
std::string docstring;
dict entries = arg.attr("__entries");
if (((PyTypeObject *) arg.ptr())->tp_doc) {
docstring += std::string(((PyTypeObject *) arg.ptr())->tp_doc) + "\n\n";
}
docstring += "Members:";
for (auto kv : entries) {
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);
if (options::show_enum_members_docstring()) {
m_base.attr("__doc__") = static_property(
cpp_function(
[](handle arg) -> std::string {
std::string docstring;
dict entries = arg.attr("__entries");
if (((PyTypeObject *) arg.ptr())->tp_doc) {
docstring += std::string(
reinterpret_cast<PyTypeObject *>(arg.ptr())->tp_doc);
docstring += "\n\n";
}
}
return docstring;
},
name("__doc__")),
none(),
none(),
"");
docstring += "Members:";
for (auto kv : entries) {
auto key = std::string(pybind11::str(kv.first));
auto comment = kv.second[int_(1)];
docstring += "\n\n ";
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(
[](handle arg) -> dict {
Expand Down
53 changes: 53 additions & 0 deletions tests/test_docstring_options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,57 @@ TEST_SUBMODULE(docstring_options, m) {
&DocstringTestFoo::setValue,
"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);
}
}
23 changes: 23 additions & 0 deletions tests/test_docstring_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,26 @@ def test_docstring_options():
# Suppression of user-defined docstrings for non-function objects
assert not m.DocstringTestFoo.__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