@@ -41,6 +41,7 @@ def load_runtime() -> None:
4141 Type ,
4242)
4343from System .Reflection import BindingFlags # pyright: ignore[reportMissingImports] # noqa: E402
44+ from enum import Enum as PyEnum , IntEnum # Python enum types for interop helpers
4445
4546
4647def get_object_types () -> dict [str , "System.RuntimeType" ]:
@@ -63,53 +64,27 @@ def snake_enum_to_pascal(name: str) -> str:
6364
6465
6566class NativeEnum :
66- """Generic base class that dynamically maps .NET enums to Pythonic attributes."""
67+ """Compatibility shim: legacy class providing .name and .value for dynamically-created enums.
68+
69+ New code prefers real stdlib `Enum`/`IntEnum` types created with `make_native_enum`,
70+ but this class is preserved for backwards compatibility.
71+ """
6772
6873 _native_type = None
6974
70- def __init_subclass__ (
71- cls ,
72- native_type : object | None = None ,
73- ** kwargs : dict [str , bool | int | str ],
74- ) -> None :
75- super ().__init_subclass__ (** kwargs )
76-
77- # If class is dynamically constructed using type()
78- if hasattr (cls , "_native_type" ) and cls ._native_type is not None :
79- native_type = cls ._native_type
80-
81- native_names = [
82- str (x )
83- for x in native_type .GetEnumNames () # pyright: ignore[reportAttributeAccessIssue]
84- ]
85- native_values = [
86- int (x )
87- for x in native_type .GetEnumValues () # pyright: ignore[reportAttributeAccessIssue]
88- ]
89-
90- name_to_value = dict (zip (native_names , native_values , strict = True ))
91-
92- for native_name in native_names :
93- py_name = to_snake_case (native_name , upper = True )
94- native_value = name_to_value [native_name ]
95- # Create instance and store on class
96- instance = cls (py_name , native_value )
97- setattr (cls , py_name , instance )
75+ def __init__ (self , py_name : str , native_value : int ) -> None :
76+ # Legacy instance shape (kept for compatibility with older code)
77+ self ._py_name = py_name
78+ self ._py_value = native_value
9879
9980 @property
10081 def name (self ) -> str :
101- """The string name of the enum value."""
10282 return self ._py_name
10383
10484 @property
10585 def value (self ) -> int :
106- """The integer value of the enum."""
10786 return self ._py_value
10887
109- def __init__ (self , py_name : str , native_value : int ) -> None :
110- self ._py_name = py_name
111- self ._py_value = native_value
112-
11388 def __repr__ (self ) -> str :
11489 return f"{ self .__class__ .__name__ } .{ self ._py_name } "
11590
@@ -122,6 +97,55 @@ def __hash__(self) -> int:
12297 return hash (self ._py_value )
12398
12499
100+ # Cache for created python enum classes keyed by dotnet type representation
101+ _native_enum_cache : dict [str , type ] = {}
102+
103+
104+ def make_native_enum (dotnet_type , * , int_enum : bool = True ):
105+ """Create a Python `Enum`/`IntEnum` for a .NET enum type.
106+
107+ The returned class has attached helpers:
108+ - `_native_type` (the original dotnet type)
109+ - `to_dotnet(self)` -> returns a .NET Enum parsed instance when available, otherwise the Pascal-case name
110+ - `from_dotnet(cls, dotnet_value)` -> returns the Python enum member from a .NET value
111+ """
112+ # Cache by dotnet type to ensure repeated lookups return the same Python class
113+ key = str (dotnet_type )
114+ if key in _native_enum_cache :
115+ return _native_enum_cache [key ]
116+
117+ # Delay import to avoid conflicts on module import ordering
118+ from enum import IntEnum
119+
120+ native_names = [str (x ) for x in dotnet_type .GetEnumNames ()]
121+ native_values = [int (x ) for x in dotnet_type .GetEnumValues ()]
122+ members = {to_snake_case (n , upper = True ): v for n , v in zip (native_names , native_values )}
123+
124+ base = IntEnum if int_enum else PyEnum
125+ cls = base (dotnet_type .Name , members )
126+ cls ._native_type = dotnet_type
127+
128+ _native_enum_cache [key ] = cls
129+
130+ # Instance -> dotnet conversion (best effort)
131+ def to_dotnet (self ):
132+ try :
133+ return Enum .Parse (self ._native_type , snake_enum_to_pascal (self .name ))
134+ except Exception :
135+ return snake_enum_to_pascal (self .name )
136+
137+ cls .to_dotnet = to_dotnet
138+
139+ @classmethod
140+ def from_dotnet (cls , dotnet_value ):
141+ return cls (int (dotnet_value ))
142+
143+ cls .from_dotnet = from_dotnet
144+
145+ return cls
146+
147+
148+
125149types = get_object_types ()
126150FormatterType = types ["Formatter" ]
127151FracturedJsonOptionsType = types ["FracturedJsonOptions" ]
@@ -131,7 +155,7 @@ def __hash__(self) -> int:
131155 "FracturedJsonOptions" ,
132156]
133157for enum_name in [x .Name for x in types .values () if x .IsEnum ]:
134- enum_type = type ( enum_name , ( NativeEnum ,), { "_native_type" : types [enum_name ]} )
158+ enum_type = make_native_enum ( types [enum_name ], int_enum = True )
135159 globals ()[enum_name ] = enum_type
136160 __all__ .append (enum_type ) # noqa: PYI056
137161
@@ -181,8 +205,8 @@ def get(self, name: str) -> int | bool | str | NativeEnum:
181205 prop = self ._properties [name ]["prop" ]
182206 if self ._properties [name ]["is_enum" ]:
183207 native_value = prop .GetValue (self ._dotnet_instance )
184- derived_enum = type (prop .Name , ( NativeEnum ,), { "_native_type" : prop . PropertyType } )
185- return derived_enum (to_snake_case ( str ( native_value ), upper = True ), ( int (native_value ) ))
208+ derived_enum = make_native_enum (prop .PropertyType , int_enum = True )
209+ return derived_enum (int (native_value ))
186210
187211 return prop .GetValue (self ._dotnet_instance )
188212
@@ -214,6 +238,9 @@ def _to_dotnet_type(
214238 return Boolean (value )
215239 if target_type .FullName == "System.String" and isinstance (value , str ):
216240 return String (value )
241+ # Accept both legacy `NativeEnum` shim instances and modern Python Enum instances
242+ if target_type .IsEnum and isinstance (value , PyEnum ):
243+ return Enum .Parse (prop .PropertyType , snake_enum_to_pascal (value .name ))
217244 if target_type .IsEnum and isinstance (value , NativeEnum ):
218245 return Enum .Parse (prop .PropertyType , snake_enum_to_pascal (value .name ))
219246 if target_type .IsEnum and isinstance (value , str ):
0 commit comments