Skip to content

Commit 223e2e9

Browse files
dyollbrwgk
andauthored
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 <[email protected]> Co-authored-by: Ralf W. Grosse-Kunstleve <[email protected]>
1 parent 3c58634 commit 223e2e9

File tree

4 files changed

+44
-10
lines changed

4 files changed

+44
-10
lines changed

include/pybind11/detail/native_enum_data.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ inline void native_enum_data::finalize() {
182182
if (module_name) {
183183
py_enum.attr("__module__") = module_name;
184184
}
185+
if (hasattr(parent_scope, "__qualname__")) {
186+
const auto parent_qualname = parent_scope.attr("__qualname__").cast<std::string>();
187+
py_enum.attr("__qualname__") = str(parent_qualname + "." + enum_name.cast<std::string>());
188+
}
185189
parent_scope.attr(enum_name) = py_enum;
186190
if (export_values_flag) {
187191
for (auto member : members) {

include/pybind11/pybind11.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ inline std::string generate_function_signature(const char *type_caster_name_fiel
160160
handle th((PyObject *) tinfo->type);
161161
signature += th.attr("__module__").cast<std::string>() + "."
162162
+ th.attr("__qualname__").cast<std::string>();
163+
} else if (auto th = detail::global_internals_native_enum_type_map_get_item(*t)) {
164+
signature += th.attr("__module__").cast<std::string>() + "."
165+
+ th.attr("__qualname__").cast<std::string>();
163166
} else if (func_rec->is_new_style_constructor && arg_index == 0) {
164167
// A new-style `__init__` takes `self` as `value_and_holder`.
165168
// Rewrite it to the proper class type.

tests/test_native_enum.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ enum class member_doc { mem0, mem1, mem2 };
2929

3030
struct class_with_enum {
3131
enum class in_class { one, two };
32+
33+
in_class nested_value = in_class::one;
3234
};
3335

3436
// 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 };
4042
template <>
4143
struct is_proto_enum<some_proto_enum> : std::true_type {};
4244

45+
enum class func_sig_rendering {};
46+
4347
} // namespace test_native_enum
4448

4549
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
@@ -124,11 +128,20 @@ TEST_SUBMODULE(native_enum, m) {
124128
.value("two", class_with_enum::in_class::two)
125129
.finalize();
126130

131+
py_class_with_enum.def(py::init())
132+
.def_readwrite("nested_value", &class_with_enum::nested_value);
133+
127134
m.def("isinstance_color", [](const py::object &obj) { return py::isinstance<color>(obj); });
128135

129136
m.def("pass_color", [](color e) { return static_cast<int>(e); });
130137
m.def("return_color", [](int i) { return static_cast<color>(i); });
131138

139+
py::native_enum<func_sig_rendering>(m, "func_sig_rendering", "enum.Enum").finalize();
140+
m.def(
141+
"pass_and_return_func_sig_rendering",
142+
[](func_sig_rendering e) { return e; },
143+
py::arg("e"));
144+
132145
m.def("pass_some_proto_enum", [](some_proto_enum) { return py::none(); });
133146
m.def("return_some_proto_enum", []() { return some_proto_enum::Zero; });
134147

tests/test_native_enum.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
("mem2", 2),
5555
)
5656

57+
FUNC_SIG_RENDERING_MEMBERS = ()
58+
5759
ENUM_TYPES_AND_MEMBERS = (
5860
(m.smallenum, SMALLENUM_MEMBERS),
5961
(m.color, COLOR_MEMBERS),
@@ -62,6 +64,7 @@
6264
(m.flags_uint, FLAGS_UINT_MEMBERS),
6365
(m.export_values, EXPORT_VALUES_MEMBERS),
6466
(m.member_doc, MEMBER_DOC_MEMBERS),
67+
(m.func_sig_rendering, FUNC_SIG_RENDERING_MEMBERS),
6568
(m.class_with_enum.in_class, CLASS_WITH_ENUM_IN_CLASS_MEMBERS),
6669
)
6770

@@ -84,15 +87,10 @@ def test_enum_members(enum_type, members):
8487
def test_pickle_roundtrip(enum_type, members):
8588
for name, _ in members:
8689
orig = enum_type[name]
87-
if enum_type is m.class_with_enum.in_class:
88-
# This is a general pickle limitation.
89-
with pytest.raises(pickle.PicklingError):
90-
pickle.dumps(orig)
91-
else:
92-
# This only works if __module__ is correct.
93-
serialized = pickle.dumps(orig)
94-
restored = pickle.loads(serialized)
95-
assert restored == orig
90+
# This only works if __module__ is correct.
91+
serialized = pickle.dumps(orig)
92+
restored = pickle.loads(serialized)
93+
assert restored == orig
9694

9795

9896
@pytest.mark.parametrize("enum_type", [m.flags_uchar, m.flags_uint])
@@ -139,7 +137,7 @@ def test_pass_color_success():
139137
def test_pass_color_fail():
140138
with pytest.raises(TypeError) as excinfo:
141139
m.pass_color(None)
142-
assert "test_native_enum::color" in str(excinfo.value)
140+
assert "pybind11_tests.native_enum.color" in str(excinfo.value)
143141

144142

145143
def test_return_color_success():
@@ -155,6 +153,22 @@ def test_return_color_fail():
155153
assert str(excinfo_cast.value) == str(excinfo_direct.value)
156154

157155

156+
def test_property_type_hint():
157+
prop = m.class_with_enum.__dict__["nested_value"]
158+
assert isinstance(prop, property)
159+
assert prop.fget.__doc__.startswith(
160+
"(self: pybind11_tests.native_enum.class_with_enum)"
161+
" -> pybind11_tests.native_enum.class_with_enum.in_class"
162+
)
163+
164+
165+
def test_func_sig_rendering():
166+
assert m.pass_and_return_func_sig_rendering.__doc__.startswith(
167+
"pass_and_return_func_sig_rendering(e: pybind11_tests.native_enum.func_sig_rendering)"
168+
" -> pybind11_tests.native_enum.func_sig_rendering"
169+
)
170+
171+
158172
def test_type_caster_enum_type_enabled_false():
159173
# This is really only a "does it compile" test.
160174
assert m.pass_some_proto_enum(None) is None

0 commit comments

Comments
 (0)