Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 30f32b9

Browse files
authored
Support pythonic manipulation of managed enums (#101)
* Support pythonic manipulation of managed enums. Add support for 'len' method, 'in' operator and iteration of enum types. * Minor fixes and unit tests * Bump version to 2.0.43
1 parent dff82a1 commit 30f32b9

File tree

5 files changed

+159
-5
lines changed

5 files changed

+159
-5
lines changed

src/embed_tests/ClassManagerTests.cs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,6 +1003,86 @@ def call(instance):
10031003
}
10041004

10051005
#endregion
1006+
1007+
public enum TestEnum
1008+
{
1009+
FirstEnumValue,
1010+
SecondEnumValue,
1011+
ThirdEnumValue
1012+
}
1013+
1014+
[Test]
1015+
public void EnumPythonOperationsCanBePerformedOnManagedEnum()
1016+
{
1017+
using (Py.GIL())
1018+
{
1019+
var module = PyModule.FromString("EnumPythonOperationsCanBePerformedOnManagedEnum", $@"
1020+
from clr import AddReference
1021+
AddReference(""Python.EmbeddingTest"")
1022+
1023+
from Python.EmbeddingTest import *
1024+
1025+
def get_enum_values():
1026+
return [x for x in ClassManagerTests.TestEnum]
1027+
1028+
def count_enum_values():
1029+
return len(ClassManagerTests.TestEnum)
1030+
1031+
def is_enum_value_defined(value):
1032+
return value in ClassManagerTests.TestEnum
1033+
");
1034+
1035+
using var pyEnumValues = module.InvokeMethod("get_enum_values");
1036+
var enumValues = pyEnumValues.As<List<TestEnum>>();
1037+
1038+
var expectedEnumValues = Enum.GetValues<TestEnum>();
1039+
CollectionAssert.AreEquivalent(expectedEnumValues, enumValues);
1040+
1041+
using var pyEnumCount = module.InvokeMethod("count_enum_values");
1042+
var enumCount = pyEnumCount.As<int>();
1043+
Assert.AreEqual(expectedEnumValues.Length, enumCount);
1044+
1045+
var validEnumValues = expectedEnumValues
1046+
.SelectMany(x => new object[] { x, (int)x, Enum.GetName(x.GetType(), x) })
1047+
.Select(x => (x, true));
1048+
var invalidEnumValues = new object[] { 5, "INVALID_ENUM_VALUE" }.Select(x => (x, false));
1049+
1050+
foreach (var (enumValue, isValid) in validEnumValues.Concat(invalidEnumValues))
1051+
{
1052+
using var pyEnumValue = enumValue.ToPython();
1053+
using var pyIsDefined = module.InvokeMethod("is_enum_value_defined", pyEnumValue);
1054+
var isDefined = pyIsDefined.As<bool>();
1055+
Assert.AreEqual(isValid, isDefined, $"Failed for {enumValue} ({enumValue.GetType()})");
1056+
}
1057+
}
1058+
}
1059+
1060+
[Test]
1061+
public void EnumInterableOperationsNotSupportedForManagedNonEnumTypes()
1062+
{
1063+
using (Py.GIL())
1064+
{
1065+
var module = PyModule.FromString("EnumInterableOperationsNotSupportedForManagedNonEnumTypes", $@"
1066+
from clr import AddReference
1067+
AddReference(""Python.EmbeddingTest"")
1068+
1069+
from Python.EmbeddingTest import *
1070+
1071+
def get_enum_values():
1072+
return [x for x in ClassManagerTests]
1073+
1074+
def count_enum_values():
1075+
return len(ClassManagerTests)
1076+
1077+
def is_enum_value_defined():
1078+
return 1 in ClassManagerTests
1079+
");
1080+
1081+
Assert.Throws<PythonException>(() => module.InvokeMethod("get_enum_values"));
1082+
Assert.Throws<PythonException>(() => module.InvokeMethod("count_enum_values"));
1083+
Assert.Throws<PythonException>(() => module.InvokeMethod("is_enum_value_defined"));
1084+
}
1085+
}
10061086
}
10071087

10081088
public class NestedTestParent

src/perf_tests/Python.PerformanceTests.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1414
</PackageReference>
1515
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
16-
<PackageReference Include="quantconnect.pythonnet" Version="2.0.42" GeneratePathProperty="true">
16+
<PackageReference Include="quantconnect.pythonnet" Version="2.0.43" GeneratePathProperty="true">
1717
<IncludeAssets>compile</IncludeAssets>
1818
</PackageReference>
1919
</ItemGroup>
@@ -25,7 +25,7 @@
2525
</Target>
2626

2727
<Target Name="CopyBaseline" AfterTargets="Build">
28-
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.42\lib\net9.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
28+
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.43\lib\net9.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
2929
</Target>
3030

3131
<Target Name="CopyNewBuild" AfterTargets="Build">

src/runtime/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
[assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]
55
[assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]
66

7-
[assembly: AssemblyVersion("2.0.42")]
8-
[assembly: AssemblyFileVersion("2.0.42")]
7+
[assembly: AssemblyVersion("2.0.43")]
8+
[assembly: AssemblyFileVersion("2.0.43")]

src/runtime/Python.Runtime.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<RootNamespace>Python.Runtime</RootNamespace>
66
<AssemblyName>Python.Runtime</AssemblyName>
77
<PackageId>QuantConnect.pythonnet</PackageId>
8-
<Version>2.0.42</Version>
8+
<Version>2.0.43</Version>
99
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
1010
<PackageLicenseFile>LICENSE</PackageLicenseFile>
1111
<RepositoryUrl>https://github.com/pythonnet/pythonnet</RepositoryUrl>

src/runtime/Types/MetaType.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,5 +359,79 @@ public static NewReference __subclasscheck__(BorrowedReference tp, BorrowedRefer
359359
{
360360
return DoInstanceCheck(tp, args, true);
361361
}
362+
363+
/// <summary>
364+
/// Standard iteration support Enums. This allows natural interation
365+
/// over the available values an Enum defines.
366+
/// </summary>
367+
public static NewReference tp_iter(BorrowedReference tp)
368+
{
369+
if (!TryGetEnumType(tp, out var type))
370+
{
371+
return default;
372+
}
373+
var values = Enum.GetValues(type);
374+
return new Iterator(values.GetEnumerator(), type).Alloc();
375+
}
376+
377+
/// <summary>
378+
/// Implements __len__ for Enum types.
379+
/// </summary>
380+
public static int mp_length(BorrowedReference tp)
381+
{
382+
if (!TryGetEnumType(tp, out var type))
383+
{
384+
return -1;
385+
}
386+
return Enum.GetValues(type).Length;
387+
}
388+
389+
/// <summary>
390+
/// Implements __contains__ for Enum types.
391+
/// </summary>
392+
public static int sq_contains(BorrowedReference tp, BorrowedReference v)
393+
{
394+
if (!TryGetEnumType(tp, out var type))
395+
{
396+
return -1;
397+
}
398+
399+
if (!Converter.ToManaged(v, type, out var enumValue, false) &&
400+
!Converter.ToManaged(v, typeof(int), out enumValue, false) &&
401+
!Converter.ToManaged(v, typeof(string), out enumValue, false))
402+
{
403+
Exceptions.SetError(Exceptions.TypeError,
404+
$"invalid parameter type for sq_contains: should be {Converter.GetTypeByAlias(v)}, found {type}");
405+
return -1;
406+
}
407+
408+
return Enum.IsDefined(type, enumValue) ? 1 : 0;
409+
}
410+
411+
private static bool TryGetEnumType(BorrowedReference tp, out Type type)
412+
{
413+
type = null;
414+
var cb = GetManagedObject(tp) as ClassBase;
415+
if (cb == null)
416+
{
417+
Exceptions.SetError(Exceptions.TypeError, "invalid object");
418+
return false;
419+
}
420+
421+
if (!cb.type.Valid)
422+
{
423+
Exceptions.SetError(Exceptions.TypeError, "invalid type");
424+
return false;
425+
}
426+
427+
if (!cb.type.Value.IsEnum)
428+
{
429+
Exceptions.SetError(Exceptions.TypeError, "uniterable type");
430+
return false;
431+
}
432+
433+
type = cb.type.Value;
434+
return true;
435+
}
362436
}
363437
}

0 commit comments

Comments
 (0)