Add class doc string to native_enum (#5617)

* add class doc string to native_enum

* adapt doc argument name

* fix test, make class enum doc None by default

* fix other python versions?

* make clang-tidy happy

* rename 'enum_doc' to 'class_doc'

* update documentation

* [skip ci] Polish changed documentation (mostly done by ChatGPT).

---------

Co-authored-by: Bryn Lloyd <12702862+dyollb@users.noreply.github.com>
Co-authored-by: Ralf W. Grosse-Kunstleve <rgrossekunst@nvidia.com>
This commit is contained in:
Bryn Lloyd
2025-04-15 08:19:06 +02:00
committed by GitHub
parent b3bb31ca51
commit 3c586340fb
5 changed files with 33 additions and 16 deletions

View File

@@ -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()
@@ -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:

View File

@@ -29,10 +29,12 @@ public:
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();
@@ -70,10 +72,11 @@ private:
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:
@@ -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());

View File

@@ -24,9 +24,10 @@ public:
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(
@@ -53,7 +54,7 @@ public:
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;

View File

@@ -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)
@@ -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();
@@ -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) {

View File

@@ -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"