Skip to content

Add class doc string to native_enum #5617

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 8 commits into from
Apr 15, 2025
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
18 changes: 11 additions & 7 deletions docs/classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ The binding code for this example looks as follows:
.def_readwrite("type", &Pet::type)
.def_readwrite("attr", &Pet::attr);

py::native_enum<Pet::Kind>(pet, "Kind")
py::native_enum<Pet::Kind>(pet, "Kind", "enum.Enum")
.value("Dog", Pet::Kind::Dog)
.value("Cat", Pet::Kind::Cat)
.export_values()
Expand Down Expand Up @@ -593,16 +593,20 @@ once. To achieve this, ``py::native_enum`` acts as a buffer to collect the
name/value pairs. The ``.finalize()`` call uses the accumulated name/value
pairs to build the arguments for constructing a native Python enum type.

The ``py::native_enum`` constructor supports a third optional
``native_type_name`` string argument, with default value ``"enum.Enum"``.
Other types can be specified like this:
The ``py::native_enum`` constructor takes a third argument,
``native_type_name``, which specifies the fully qualified name of the Python
base class to use — e.g., ``"enum.Enum"`` or ``"enum.IntEnum"``. A fourth
optional argument, ``class_doc``, provides the docstring for the generated
class.

For example:

.. code-block:: cpp

py::native_enum<Pet::Kind>(pet, "Kind", "enum.IntEnum")
py::native_enum<Pet::Kind>(pet, "Kind", "enum.IntEnum", "Constant specifying the kind of pet")

Any fully-qualified Python name can be specified. The only requirement is
that the named type is similar to
You may use any fully qualified Python name for ``native_type_name``.
The only requirement is that the named type is similar to
`enum.Enum <https://docs.python.org/3/library/enum.html#enum.Enum>`_
in these ways:

Expand Down
12 changes: 9 additions & 3 deletions include/pybind11/detail/native_enum_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ class native_enum_data {
native_enum_data(const object &parent_scope,
const char *enum_name,
const char *native_type_name,
const char *class_doc,
const std::type_index &enum_type_index)
: enum_name_encoded{enum_name}, native_type_name_encoded{native_type_name},
enum_type_index{enum_type_index}, parent_scope(parent_scope), enum_name{enum_name},
native_type_name{native_type_name}, export_values_flag{false}, finalize_needed{false} {}
native_type_name{native_type_name}, class_doc(class_doc), export_values_flag{false},
finalize_needed{false} {}

void finalize();

Expand Down Expand Up @@ -70,10 +72,11 @@ class native_enum_data {
object parent_scope;
str enum_name;
str native_type_name;
std::string class_doc;

protected:
list members;
list docs;
list member_docs;
bool export_values_flag : 1; // Attention: It is best to keep the bools together.

private:
Expand Down Expand Up @@ -191,7 +194,10 @@ inline void native_enum_data::finalize() {
parent_scope.attr(member_name) = py_enum[member_name];
}
}
for (auto doc : docs) {
if (!class_doc.empty()) {
py_enum.attr("__doc__") = class_doc.c_str();
}
for (auto doc : member_docs) {
py_enum[doc[int_(0)]].attr("__doc__") = doc[int_(1)];
}
global_internals_native_enum_type_map_set_item(enum_type_index, py_enum.release().ptr());
Expand Down
7 changes: 4 additions & 3 deletions include/pybind11/native_enum.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ class native_enum : public detail::native_enum_data {

native_enum(const object &parent_scope,
const char *name,
const char *native_type_name = "enum.Enum")
const char *native_type_name,
const char *class_doc = "")
: detail::native_enum_data(
parent_scope, name, native_type_name, std::type_index(typeid(EnumType))) {
parent_scope, name, native_type_name, class_doc, std::type_index(typeid(EnumType))) {
if (detail::get_local_type_info(typeid(EnumType)) != nullptr
|| detail::get_global_type_info(typeid(EnumType)) != nullptr) {
pybind11_fail(
Expand All @@ -53,7 +54,7 @@ class native_enum : public detail::native_enum_data {
disarm_finalize_check("value after finalize");
members.append(make_tuple(name, static_cast<Underlying>(value)));
if (doc) {
docs.append(make_tuple(name, doc));
member_docs.append(make_tuple(name, doc));
}
arm_finalize_check(); // There was no exception.
return *this;
Expand Down
6 changes: 3 additions & 3 deletions tests/test_native_enum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
TEST_SUBMODULE(native_enum, m) {
using namespace test_native_enum;

py::native_enum<smallenum>(m, "smallenum", "enum.IntEnum")
py::native_enum<smallenum>(m, "smallenum", "enum.IntEnum", "doc smallenum")
.value("a", smallenum::a)
.value("b", smallenum::b)
.value("c", smallenum::c)
Expand All @@ -89,7 +89,7 @@ TEST_SUBMODULE(native_enum, m) {
.value("blue", color::blue)
.finalize();

py::native_enum<altitude>(m, "altitude")
py::native_enum<altitude>(m, "altitude", "enum.Enum")
.value("high", altitude::high)
.value("low", altitude::low)
.finalize();
Expand Down Expand Up @@ -189,7 +189,7 @@ TEST_SUBMODULE(native_enum, m) {
py::native_enum<fake>(m, "fake_double_registration_native_enum", "enum.IntEnum")
.value("x", fake::x)
.finalize();
py::native_enum<fake>(m, "fake_double_registration_native_enum");
py::native_enum<fake>(m, "fake_double_registration_native_enum", "enum.Enum");
});

m.def("native_enum_name_clash", [](py::module_ &m) {
Expand Down
6 changes: 6 additions & 0 deletions tests/test_native_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ def test_export_values():
assert m.exv1 is m.export_values.exv1


def test_class_doc():
pure_native = enum.IntEnum("pure_native", (("mem", 0),))
assert m.smallenum.__doc__ == "doc smallenum"
assert m.color.__doc__ == pure_native.__doc__


def test_member_doc():
pure_native = enum.IntEnum("pure_native", (("mem", 0),))
assert m.member_doc.mem0.__doc__ == "docA"
Expand Down