From 2f69153527b1931c3e6496389f892a960cb150da Mon Sep 17 00:00:00 2001 From: Bryn Lloyd <12702862+dyollb@users.noreply.github.com> Date: Tue, 15 Apr 2025 09:55:13 +0200 Subject: [PATCH 1/3] fix missing pythonic type hints for native_enum --- include/pybind11/pybind11.h | 3 +++ tests/test_native_enum.cpp | 6 ++++++ tests/test_native_enum.py | 8 +++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 396fe7cba4..c84b24d3b7 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -160,6 +160,9 @@ inline std::string generate_function_signature(const char *type_caster_name_fiel handle th((PyObject *) tinfo->type); signature += th.attr("__module__").cast() + "." + th.attr("__qualname__").cast(); + } else if (auto th = detail::global_internals_native_enum_type_map_get_item(*t)) { + signature += th.attr("__module__").cast() + "." + + th.attr("__qualname__").cast(); } else if (func_rec->is_new_style_constructor && arg_index == 0) { // A new-style `__init__` takes `self` as `value_and_holder`. // Rewrite it to the proper class type. diff --git a/tests/test_native_enum.cpp b/tests/test_native_enum.cpp index c8fd34df03..6bc0607b8c 100644 --- a/tests/test_native_enum.cpp +++ b/tests/test_native_enum.cpp @@ -40,6 +40,8 @@ enum some_proto_enum : int { Zero, One }; template <> struct is_proto_enum : std::true_type {}; +enum class native { x }; + } // namespace test_native_enum PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) @@ -124,6 +126,8 @@ TEST_SUBMODULE(native_enum, m) { .value("two", class_with_enum::in_class::two) .finalize(); + py::native_enum(m, "Native", "enum.Enum").value("x", native::x).finalize(); + m.def("isinstance_color", [](const py::object &obj) { return py::isinstance(obj); }); m.def("pass_color", [](color e) { return static_cast(e); }); @@ -233,4 +237,6 @@ TEST_SUBMODULE(native_enum, m) { #else m.attr("native_enum_missing_finalize_failure") = "For local testing only: terminates process"; #endif + + m.def("function_with_native_enum", [](native n) { return n; }, py::arg("n")); } diff --git a/tests/test_native_enum.py b/tests/test_native_enum.py index b942fca0d5..e4336cef7b 100644 --- a/tests/test_native_enum.py +++ b/tests/test_native_enum.py @@ -139,7 +139,7 @@ def test_pass_color_success(): def test_pass_color_fail(): with pytest.raises(TypeError) as excinfo: m.pass_color(None) - assert "test_native_enum::color" in str(excinfo.value) + assert "pybind11_tests.native_enum.color" in str(excinfo.value) def test_return_color_success(): @@ -301,3 +301,9 @@ def test_native_enum_missing_finalize_failure(): if not isinstance(m.native_enum_missing_finalize_failure, str): m.native_enum_missing_finalize_failure() pytest.fail("Process termination expected.") + + +def test_function_signature(): + assert m.function_with_native_enum.__doc__.startswith( + "function_with_native_enum(n: pybind11_tests.native_enum.Native) -> pybind11_tests.native_enum.Native" + ) From 5ed573de297dc212f08b77e715b5b908ba0ec15c Mon Sep 17 00:00:00 2001 From: Bryn Lloyd <12702862+dyollb@users.noreply.github.com> Date: Tue, 15 Apr 2025 13:53:31 +0200 Subject: [PATCH 2/3] Fix __qualname__ in native_enum --- include/pybind11/detail/native_enum_data.h | 4 ++++ tests/test_native_enum.cpp | 4 ++++ tests/test_native_enum.py | 21 ++++++++++++--------- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/include/pybind11/detail/native_enum_data.h b/include/pybind11/detail/native_enum_data.h index b12ca6202b..26aa965989 100644 --- a/include/pybind11/detail/native_enum_data.h +++ b/include/pybind11/detail/native_enum_data.h @@ -182,6 +182,10 @@ inline void native_enum_data::finalize() { if (module_name) { py_enum.attr("__module__") = module_name; } + if (hasattr(parent_scope, "__qualname__")) { + const auto parent_qualname = parent_scope.attr("__qualname__").cast(); + py_enum.attr("__qualname__") = str(parent_qualname + "." + enum_name.cast()); + } parent_scope.attr(enum_name) = py_enum; if (export_values_flag) { for (auto member : members) { diff --git a/tests/test_native_enum.cpp b/tests/test_native_enum.cpp index 6bc0607b8c..433af7521d 100644 --- a/tests/test_native_enum.cpp +++ b/tests/test_native_enum.cpp @@ -29,6 +29,8 @@ enum class member_doc { mem0, mem1, mem2 }; struct class_with_enum { enum class in_class { one, two }; + + in_class value = in_class::one; }; // https://github.com/protocolbuffers/protobuf/blob/d70b5c5156858132decfdbae0a1103e6a5cb1345/src/google/protobuf/generated_enum_util.h#L52-L53 @@ -126,6 +128,8 @@ TEST_SUBMODULE(native_enum, m) { .value("two", class_with_enum::in_class::two) .finalize(); + py_class_with_enum.def(py::init()).def_readwrite("value", &class_with_enum::value); + py::native_enum(m, "Native", "enum.Enum").value("x", native::x).finalize(); m.def("isinstance_color", [](const py::object &obj) { return py::isinstance(obj); }); diff --git a/tests/test_native_enum.py b/tests/test_native_enum.py index e4336cef7b..33ad7f7c14 100644 --- a/tests/test_native_enum.py +++ b/tests/test_native_enum.py @@ -84,15 +84,10 @@ def test_enum_members(enum_type, members): def test_pickle_roundtrip(enum_type, members): for name, _ in members: orig = enum_type[name] - if enum_type is m.class_with_enum.in_class: - # This is a general pickle limitation. - with pytest.raises(pickle.PicklingError): - pickle.dumps(orig) - else: - # This only works if __module__ is correct. - serialized = pickle.dumps(orig) - restored = pickle.loads(serialized) - assert restored == orig + # This only works if __module__ is correct. + serialized = pickle.dumps(orig) + restored = pickle.loads(serialized) + assert restored == orig @pytest.mark.parametrize("enum_type", [m.flags_uchar, m.flags_uint]) @@ -303,6 +298,14 @@ def test_native_enum_missing_finalize_failure(): pytest.fail("Process termination expected.") +def test_property_type_hint(): + prop = m.class_with_enum.__dict__["value"] + assert isinstance(prop, property) + assert prop.fget.__doc__.startswith( + "(self: pybind11_tests.native_enum.class_with_enum) -> pybind11_tests.native_enum.class_with_enum.in_class" + ) + + def test_function_signature(): assert m.function_with_native_enum.__doc__.startswith( "function_with_native_enum(n: pybind11_tests.native_enum.Native) -> pybind11_tests.native_enum.Native" From 247a1d4ecf19b5f91135d5afb7407f5956b9a007 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Tue, 15 Apr 2025 14:00:43 -0700 Subject: [PATCH 3/3] =?UTF-8?q?Rename=20`enum=20class=20native`=20?= =?UTF-8?q?=E2=86=92=20`func=5Fsig=5Frendering`.=20Add=20to=20`ENUM=5FTYPE?= =?UTF-8?q?S=5FAND=5FMEMBERS`.=20Move=20new=20code=20around=20to=20fit=20i?= =?UTF-8?q?n=20more=20organically.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "native" is used in so many places here, it would be very difficult to pin-point where the specific enum type is used. Similarly, "value" is difficult to pin-point. Changed to "nested_value". --- tests/test_native_enum.cpp | 17 ++++++++++------- tests/test_native_enum.py | 33 +++++++++++++++++++-------------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/tests/test_native_enum.cpp b/tests/test_native_enum.cpp index 433af7521d..c919cde2f7 100644 --- a/tests/test_native_enum.cpp +++ b/tests/test_native_enum.cpp @@ -30,7 +30,7 @@ enum class member_doc { mem0, mem1, mem2 }; struct class_with_enum { enum class in_class { one, two }; - in_class value = in_class::one; + in_class nested_value = in_class::one; }; // https://github.com/protocolbuffers/protobuf/blob/d70b5c5156858132decfdbae0a1103e6a5cb1345/src/google/protobuf/generated_enum_util.h#L52-L53 @@ -42,7 +42,7 @@ enum some_proto_enum : int { Zero, One }; template <> struct is_proto_enum : std::true_type {}; -enum class native { x }; +enum class func_sig_rendering {}; } // namespace test_native_enum @@ -128,15 +128,20 @@ TEST_SUBMODULE(native_enum, m) { .value("two", class_with_enum::in_class::two) .finalize(); - py_class_with_enum.def(py::init()).def_readwrite("value", &class_with_enum::value); - - py::native_enum(m, "Native", "enum.Enum").value("x", native::x).finalize(); + py_class_with_enum.def(py::init()) + .def_readwrite("nested_value", &class_with_enum::nested_value); m.def("isinstance_color", [](const py::object &obj) { return py::isinstance(obj); }); m.def("pass_color", [](color e) { return static_cast(e); }); m.def("return_color", [](int i) { return static_cast(i); }); + py::native_enum(m, "func_sig_rendering", "enum.Enum").finalize(); + m.def( + "pass_and_return_func_sig_rendering", + [](func_sig_rendering e) { return e; }, + py::arg("e")); + m.def("pass_some_proto_enum", [](some_proto_enum) { return py::none(); }); m.def("return_some_proto_enum", []() { return some_proto_enum::Zero; }); @@ -241,6 +246,4 @@ TEST_SUBMODULE(native_enum, m) { #else m.attr("native_enum_missing_finalize_failure") = "For local testing only: terminates process"; #endif - - m.def("function_with_native_enum", [](native n) { return n; }, py::arg("n")); } diff --git a/tests/test_native_enum.py b/tests/test_native_enum.py index 33ad7f7c14..621a9cfd46 100644 --- a/tests/test_native_enum.py +++ b/tests/test_native_enum.py @@ -54,6 +54,8 @@ ("mem2", 2), ) +FUNC_SIG_RENDERING_MEMBERS = () + ENUM_TYPES_AND_MEMBERS = ( (m.smallenum, SMALLENUM_MEMBERS), (m.color, COLOR_MEMBERS), @@ -62,6 +64,7 @@ (m.flags_uint, FLAGS_UINT_MEMBERS), (m.export_values, EXPORT_VALUES_MEMBERS), (m.member_doc, MEMBER_DOC_MEMBERS), + (m.func_sig_rendering, FUNC_SIG_RENDERING_MEMBERS), (m.class_with_enum.in_class, CLASS_WITH_ENUM_IN_CLASS_MEMBERS), ) @@ -150,6 +153,22 @@ def test_return_color_fail(): assert str(excinfo_cast.value) == str(excinfo_direct.value) +def test_property_type_hint(): + prop = m.class_with_enum.__dict__["nested_value"] + assert isinstance(prop, property) + assert prop.fget.__doc__.startswith( + "(self: pybind11_tests.native_enum.class_with_enum)" + " -> pybind11_tests.native_enum.class_with_enum.in_class" + ) + + +def test_func_sig_rendering(): + assert m.pass_and_return_func_sig_rendering.__doc__.startswith( + "pass_and_return_func_sig_rendering(e: pybind11_tests.native_enum.func_sig_rendering)" + " -> pybind11_tests.native_enum.func_sig_rendering" + ) + + def test_type_caster_enum_type_enabled_false(): # This is really only a "does it compile" test. assert m.pass_some_proto_enum(None) is None @@ -296,17 +315,3 @@ def test_native_enum_missing_finalize_failure(): if not isinstance(m.native_enum_missing_finalize_failure, str): m.native_enum_missing_finalize_failure() pytest.fail("Process termination expected.") - - -def test_property_type_hint(): - prop = m.class_with_enum.__dict__["value"] - assert isinstance(prop, property) - assert prop.fget.__doc__.startswith( - "(self: pybind11_tests.native_enum.class_with_enum) -> pybind11_tests.native_enum.class_with_enum.in_class" - ) - - -def test_function_signature(): - assert m.function_with_native_enum.__doc__.startswith( - "function_with_native_enum(n: pybind11_tests.native_enum.Native) -> pybind11_tests.native_enum.Native" - )