Skip to content

Commit bd34766

Browse files
hroesttimosachsenbergclaude
authored
[TEST] example for enums in namespaces (#131)
* [TEST] example for enums in namespaces * Fix scoped enum handling for namespaced enums - Change from unscoped `cpdef enum` to scoped `cpdef enum class` to properly generate Python Enum classes with type-safe validation - Rename enum identifiers from double underscore (Foo__MyEnum) to single underscore (Foo_MyEnum) for cleaner naming - Add missing wrap-as annotation to MyEnum2 for consistency - Fixes "redeclared" warnings for enum values at module level The scoped enums now use isinstance() type checking instead of numeric range validation, properly distinguishing between different enum types with the same values. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Add documentation and changelog for namespaced enum support - Update CHANGELOG.md with new features for v0.24.0: - Support for enums with same name in different namespaces - Support for arbitrary key types in operator[] - Add comprehensive documentation to enums.pxd explaining the pattern for wrapping namespaced enums with wrap-as annotation - Add documentation to enums.hpp explaining the C++ structure - Add detailed docstring to test_enums() explaining: - How scoped enums are mapped to Python Enum classes - The pattern for handling namespace conflicts - Type-safe enum validation behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Timo Sachsenberg <[email protected]> Co-authored-by: Claude <[email protected]>
1 parent aba2a69 commit bd34766

File tree

4 files changed

+166
-8
lines changed

4 files changed

+166
-8
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
autowrap 0.24.0 (unreleased)
2+
3+
- Support for enums with the same name in different C++ namespaces using
4+
scoped enum declarations with `wrap-as` annotation for renaming
5+
- Support for arbitrary key types in `operator[]` (getitem/setitem), not
6+
just integer types like `size_t`
7+
18
autowrap 0.23.0
29

310
Support for Cython 3.1! This means the removal of some py2 compatibility code, no more python distinction between long and int, some fixes to multiline comment processing.

tests/test_code_generator.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,29 @@
5050

5151

5252
def test_enums():
53+
"""
54+
Test wrapping of C++ enums, including enums with the same name in different namespaces.
55+
56+
This test demonstrates how autowrap handles:
57+
1. Scoped enums (enum class) from C++ mapped to Python Enum classes
58+
2. Enums with the same name in different namespaces (Foo::MyEnum vs Foo2::MyEnum)
59+
3. Type-safe enum validation (passing wrong enum type raises AssertionError)
60+
4. Enum documentation via wrap-doc annotation
61+
62+
Pattern for wrapping namespaced enums in .pxd files:
63+
cpdef enum class Foo_MyEnum "Foo::MyEnum":
64+
# wrap-attach:
65+
# Foo
66+
# wrap-as:
67+
# MyEnum
68+
A
69+
B
70+
C
71+
72+
This creates Foo.MyEnum in Python that maps to Foo::MyEnum in C++.
73+
74+
See tests/test_files/enums.pxd for the full example.
75+
"""
5376
if int(cython_version[0]) < 3:
5477
return
5578
target = os.path.join(test_files, "enums.pyx")
@@ -66,12 +89,26 @@ def test_enums():
6689
include_dirs,
6790
)
6891

92+
# Test 1: Enums with same name in different namespaces are both accessible
93+
# Foo.MyEnum and Foo2.MyEnum are separate enum types
94+
assert mod.Foo.MyEnum
95+
assert mod.Foo.MyEnum.B
96+
assert mod.Foo2.MyEnum
97+
assert mod.Foo2.MyEnum.D
98+
99+
# Test 2: Enum documentation is preserved via wrap-doc
69100
foo = mod.Foo()
70101
my_enum = mod.Foo.MyEnum
71102
assert "Testing Enum documentation." in my_enum.__doc__
103+
104+
# Test 3: Correct enum type is accepted
72105
myenum_a = mod.Foo.MyEnum.A
73-
myenum2_a = mod.Foo.MyEnum2.A
74106
assert foo.enumToInt(myenum_a) == 1
107+
108+
# Test 4: Wrong enum type raises AssertionError (type-safe validation)
109+
# Even though MyEnum2.A has the same numeric value as MyEnum.A,
110+
# it's a different enum type and should be rejected
111+
myenum2_a = mod.Foo.MyEnum2.A
75112
with pytest.raises(AssertionError):
76113
foo.enumToInt(myenum2_a)
77114

tests/test_files/enums.hpp

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,38 @@
1+
/**
2+
* =============================================================================
3+
* Example: C++ Enums in Different Namespaces
4+
* =============================================================================
5+
*
6+
* This file demonstrates C++ code with enums that have the same name but exist
7+
* in different scopes (class Foo and namespace Foo2). See enums.pxd for how to
8+
* wrap these with autowrap to avoid naming conflicts in Python.
9+
*
10+
* C++ Structure:
11+
* - Foo::MyEnum (scoped enum inside class Foo)
12+
* - Foo::MyEnum2 (second enum inside class Foo)
13+
* - Foo2::MyEnum (scoped enum inside namespace Foo2 - same name as Foo::MyEnum)
14+
*
15+
* =============================================================================
16+
*/
17+
18+
// Class with nested scoped enums
119
class Foo
220
{
321
public:
22+
// First enum - will be accessible as Foo.MyEnum in Python
423
enum class MyEnum
524
{
6-
A,B,C
25+
A, B, C
726
};
827

28+
// Second enum in same class - will be accessible as Foo.MyEnum2 in Python
929
enum class MyEnum2
1030
{
11-
A,B,C
31+
A, B, C
1232
};
1333

34+
// Method that accepts MyEnum - demonstrates type-safe enum usage
35+
// In Python, passing Foo2.MyEnum.A will raise AssertionError (wrong type)
1436
int enumToInt(MyEnum e)
1537
{
1638
switch(e)
@@ -19,5 +41,18 @@ class Foo
1941
case MyEnum::B : return 2;
2042
case MyEnum::C : return 3;
2143
}
44+
return 0; // unreachable, but silences compiler warning
45+
};
46+
};
47+
48+
// Separate namespace with an enum of the same name as Foo::MyEnum
49+
// This demonstrates the namespace collision problem that autowrap solves
50+
namespace Foo2
51+
{
52+
// This enum has the same name "MyEnum" as Foo::MyEnum
53+
// In Python, it will be accessible as Foo2.MyEnum (no conflict)
54+
enum class MyEnum
55+
{
56+
A, C, D
2257
};
23-
};
58+
};

tests/test_files/enums.pxd

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,104 @@
11
# cython: language_level=2
2+
#
3+
# =============================================================================
4+
# Example: Wrapping C++ Enums in Different Namespaces
5+
# =============================================================================
6+
#
7+
# This file demonstrates how to wrap C++ enums that have the same name but
8+
# exist in different namespaces (e.g., Foo::MyEnum and Foo2::MyEnum).
9+
#
10+
# THE PROBLEM:
11+
# When two C++ classes/namespaces define enums with the same name, Cython
12+
# cannot distinguish them at the module level, causing naming conflicts.
13+
#
14+
# THE SOLUTION:
15+
# Use a unique Cython identifier with a C++ name string to map to the
16+
# actual C++ enum, then use wrap-as to expose it with the desired Python name.
17+
#
18+
# PATTERN:
19+
# cpdef enum class <UniqueID> "<C++::FullName>":
20+
# # wrap-attach:
21+
# # <ClassName>
22+
# # wrap-as:
23+
# # <PythonName>
24+
#
25+
# EXAMPLE:
26+
# For Foo::MyEnum and Foo2::MyEnum, declare them as:
27+
# - Foo_MyEnum "Foo::MyEnum" -> exposed as Foo.MyEnum in Python
28+
# - Foo2_MyEnum "Foo2::MyEnum" -> exposed as Foo2.MyEnum in Python
29+
#
30+
# IMPORTANT:
31+
# - Use `cpdef enum class` (not `cpdef enum`) for scoped enums
32+
# - Scoped enums generate Python Enum classes with proper type checking
33+
# - The wrap-attach directive attaches the enum to the specified class
34+
# - The wrap-as directive controls the Python-visible name
35+
#
36+
# RESULT IN PYTHON:
37+
# foo = Foo()
38+
# foo.enumToInt(Foo.MyEnum.A) # Works - correct enum type
39+
# foo.enumToInt(Foo2.MyEnum.A) # Raises AssertionError - wrong enum type
40+
#
41+
# =============================================================================
242

343
cdef extern from "enums.hpp":
444
cdef cppclass Foo:
45+
# Method accepts Foo_MyEnum (which maps to Foo::MyEnum in C++)
46+
int enumToInt(Foo_MyEnum e)
47+
48+
cdef extern from "enums.hpp":
49+
cdef cppclass Foo2:
50+
# Foo2 has no methods, but we still wrap it to attach its enum
51+
pass
552

6-
int enumToInt(MyEnum e)
753

54+
# -----------------------------------------------------------------------------
55+
# Enums in the "Foo" namespace
56+
# -----------------------------------------------------------------------------
857
cdef extern from "enums.hpp" namespace "Foo":
958

10-
cpdef enum class MyEnum "Foo::MyEnum":
59+
# Foo::MyEnum - attached to class Foo, exposed as Foo.MyEnum
60+
cpdef enum class Foo_MyEnum "Foo::MyEnum":
1161
# wrap-attach:
1262
# Foo
1363
#
64+
# wrap-as:
65+
# MyEnum
66+
#
1467
# wrap-doc:
1568
# Testing Enum documentation.
1669
A
1770
B
1871
C
1972

20-
cpdef enum class MyEnum2 "Foo::MyEnum2":
73+
# Foo::MyEnum2 - a second enum in the same class
74+
cpdef enum class Foo_MyEnum2 "Foo::MyEnum2":
2175
# wrap-attach:
2276
# Foo
77+
#
78+
# wrap-as:
79+
# MyEnum2
2380
A
2481
B
25-
C
82+
C
83+
84+
85+
# -----------------------------------------------------------------------------
86+
# Enums in the "Foo2" namespace
87+
# -----------------------------------------------------------------------------
88+
cdef extern from "enums.hpp" namespace "Foo2":
89+
90+
# Foo2::MyEnum - same name as Foo::MyEnum but in different namespace
91+
# Attached to Foo2 class, exposed as Foo2.MyEnum (no conflict with Foo.MyEnum)
92+
cpdef enum class Foo2_MyEnum "Foo2::MyEnum":
93+
# wrap-attach:
94+
# Foo2
95+
#
96+
# wrap-as:
97+
# MyEnum
98+
#
99+
# wrap-doc:
100+
# This is a second enum in another namespace.
101+
A
102+
C
103+
D
104+

0 commit comments

Comments
 (0)