Browse Source

Fix missing pythonic type hints for native_enum (#5619)

* fix missing pythonic type hints for native_enum

* Fix __qualname__ in native_enum

* Rename `enum class native` → `func_sig_rendering`. Add to `ENUM_TYPES_AND_MEMBERS`. Move new code around to fit in more organically.

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

---------

Co-authored-by: Bryn Lloyd <12702862+dyollb@users.noreply.github.com>
Co-authored-by: Ralf W. Grosse-Kunstleve <rgrossekunst@nvidia.com>
pull/5621/head
Bryn Lloyd 3 months ago committed by GitHub
parent
commit
223e2e9da2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      include/pybind11/detail/native_enum_data.h
  2. 3
      include/pybind11/pybind11.h
  3. 13
      tests/test_native_enum.cpp
  4. 34
      tests/test_native_enum.py

4
include/pybind11/detail/native_enum_data.h

@ -182,6 +182,10 @@ inline void native_enum_data::finalize() { @@ -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<std::string>();
py_enum.attr("__qualname__") = str(parent_qualname + "." + enum_name.cast<std::string>());
}
parent_scope.attr(enum_name) = py_enum;
if (export_values_flag) {
for (auto member : members) {

3
include/pybind11/pybind11.h

@ -160,6 +160,9 @@ inline std::string generate_function_signature(const char *type_caster_name_fiel @@ -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<std::string>() + "."
+ th.attr("__qualname__").cast<std::string>();
} else if (auto th = detail::global_internals_native_enum_type_map_get_item(*t)) {
signature += th.attr("__module__").cast<std::string>() + "."
+ th.attr("__qualname__").cast<std::string>();
} 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.

13
tests/test_native_enum.cpp

@ -29,6 +29,8 @@ enum class member_doc { mem0, mem1, mem2 }; @@ -29,6 +29,8 @@ enum class member_doc { mem0, mem1, mem2 };
struct class_with_enum {
enum class in_class { one, two };
in_class nested_value = in_class::one;
};
// https://github.com/protocolbuffers/protobuf/blob/d70b5c5156858132decfdbae0a1103e6a5cb1345/src/google/protobuf/generated_enum_util.h#L52-L53
@ -40,6 +42,8 @@ enum some_proto_enum : int { Zero, One }; @@ -40,6 +42,8 @@ enum some_proto_enum : int { Zero, One };
template <>
struct is_proto_enum<some_proto_enum> : std::true_type {};
enum class func_sig_rendering {};
} // namespace test_native_enum
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
@ -124,11 +128,20 @@ TEST_SUBMODULE(native_enum, m) { @@ -124,11 +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("nested_value", &class_with_enum::nested_value);
m.def("isinstance_color", [](const py::object &obj) { return py::isinstance<color>(obj); });
m.def("pass_color", [](color e) { return static_cast<int>(e); });
m.def("return_color", [](int i) { return static_cast<color>(i); });
py::native_enum<func_sig_rendering>(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; });

34
tests/test_native_enum.py

@ -54,6 +54,8 @@ MEMBER_DOC_MEMBERS = ( @@ -54,6 +54,8 @@ MEMBER_DOC_MEMBERS = (
("mem2", 2),
)
FUNC_SIG_RENDERING_MEMBERS = ()
ENUM_TYPES_AND_MEMBERS = (
(m.smallenum, SMALLENUM_MEMBERS),
(m.color, COLOR_MEMBERS),
@ -62,6 +64,7 @@ ENUM_TYPES_AND_MEMBERS = ( @@ -62,6 +64,7 @@ ENUM_TYPES_AND_MEMBERS = (
(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),
)
@ -84,15 +87,10 @@ def test_enum_members(enum_type, members): @@ -84,15 +87,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])
@ -139,7 +137,7 @@ def test_pass_color_success(): @@ -139,7 +137,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():
@ -155,6 +153,22 @@ def test_return_color_fail(): @@ -155,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

Loading…
Cancel
Save