Skip to content

Commit

Permalink
Add option for enable/disable enum members in docstring. (#2768)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people authored Dec 9, 2022
1 parent 65374c8 commit 0012685
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 22 deletions.
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

0 comments on commit 0012685

Please sign in to comment.