Skip to content

Commit 07285dd

Browse files
committed
feat: bind snake case name fields along with original method .net to python
1 parent 283a52f commit 07285dd

File tree

2 files changed

+103
-13
lines changed

2 files changed

+103
-13
lines changed

src/embed_tests/ClassManagerTests.cs

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System;
2+
13
using NUnit.Framework;
24

35
using Python.Runtime;
@@ -29,7 +31,16 @@ public void NestedClassDerivingFromParent()
2931

3032
public class SnakeCaseNamesTesClass
3133
{
32-
// Purposely long method name to test snake case conversion
34+
// Purposely long names to test snake case conversion
35+
36+
public string PublicStringField = "public_string_field";
37+
public const string PublicConstStringField = "public_const_string_field";
38+
public readonly string PublicReadonlyStringField = "public_readonly_string_field";
39+
public static string PublicStaticStringField = "public_static_string_field";
40+
public static readonly string PublicStaticReadonlyStringField = "public_static_readonly_string_field";
41+
42+
public static string SettablePublicStaticStringField = "settable_public_static_string_field";
43+
3344
public int AddNumbersAndGetHalf(int a, int b)
3445
{
3546
return (a + b) / 2;
@@ -49,11 +60,89 @@ public void BindsSnakeCaseClassMethods(string originalMethodName, string snakeCa
4960
using var a = 10.ToPython();
5061
using var b = 20.ToPython();
5162

52-
var camelCaseResult = obj.InvokeMethod(originalMethodName, a, b).As<int>();
53-
var snakeCaseResult = obj.InvokeMethod(snakeCaseMethodName, a, b).As<int>();
63+
var originalMethodResult = obj.InvokeMethod(originalMethodName, a, b).As<int>();
64+
var snakeCaseMethodResult = obj.InvokeMethod(snakeCaseMethodName, a, b).As<int>();
5465

55-
Assert.AreEqual(15, camelCaseResult);
56-
Assert.AreEqual(camelCaseResult, snakeCaseResult);
66+
Assert.AreEqual(15, originalMethodResult);
67+
Assert.AreEqual(originalMethodResult, snakeCaseMethodResult);
68+
}
69+
70+
[TestCase("PublicStringField", "public_string_field")]
71+
[TestCase("PublicConstStringField", "public_const_string_field")]
72+
[TestCase("PublicReadonlyStringField", "public_readonly_string_field")]
73+
[TestCase("PublicStaticStringField", "public_static_string_field")]
74+
[TestCase("PublicStaticReadonlyStringField", "public_static_readonly_string_field")]
75+
public void BindsSnakeCaseClassFields(string originalFieldName, string snakeCaseFieldName)
76+
{
77+
using var obj = new SnakeCaseNamesTesClass().ToPython();
78+
79+
var expectedValue = originalFieldName switch
80+
{
81+
"PublicStringField" => "public_string_field",
82+
"PublicConstStringField" => "public_const_string_field",
83+
"PublicReadonlyStringField" => "public_readonly_string_field",
84+
"PublicStaticStringField" => "public_static_string_field",
85+
"PublicStaticReadonlyStringField" => "public_static_readonly_string_field",
86+
_ => throw new ArgumentException("Invalid field name")
87+
};
88+
89+
var originalFieldValue = obj.GetAttr(originalFieldName).As<string>();
90+
var snakeCaseFieldValue = obj.GetAttr(snakeCaseFieldName).As<string>();
91+
92+
Assert.AreEqual(expectedValue, originalFieldValue);
93+
Assert.AreEqual(expectedValue, snakeCaseFieldValue);
94+
}
95+
96+
[Test]
97+
public void CanSetFieldUsingSnakeCaseName()
98+
{
99+
var obj = new SnakeCaseNamesTesClass();
100+
using var pyObj = obj.ToPython();
101+
102+
// Try with the original field name
103+
var newValue1 = "new value 1";
104+
using var pyNewValue1 = newValue1.ToPython();
105+
pyObj.SetAttr("PublicStringField", pyNewValue1);
106+
Assert.AreEqual(newValue1, obj.PublicStringField);
107+
108+
// Try with the snake case field name
109+
var newValue2 = "new value 2";
110+
using var pyNewValue2 = newValue2.ToPython();
111+
pyObj.SetAttr("public_string_field", pyNewValue2);
112+
Assert.AreEqual(newValue2, obj.PublicStringField);
113+
}
114+
115+
[Test]
116+
public void CanSetStaticFieldUsingSnakeCaseName()
117+
{
118+
using (Py.GIL())
119+
{
120+
var module = PyModule.FromString("module", $@"
121+
from clr import AddReference
122+
AddReference(""Python.EmbeddingTest"")
123+
AddReference(""System"")
124+
125+
from Python.EmbeddingTest import *
126+
127+
def SetCamelCaseStaticProperty(value):
128+
ClassManagerTests.SnakeCaseNamesTesClass.PublicStaticStringField = value
129+
130+
def SetSnakeCaseStaticProperty(value):
131+
ClassManagerTests.SnakeCaseNamesTesClass.public_static_string_field = value
132+
");
133+
134+
// Try with the original field name
135+
var newValue1 = "new value 1";
136+
using var pyNewValue1 = newValue1.ToPython();
137+
module.InvokeMethod("SetCamelCaseStaticProperty", pyNewValue1);
138+
Assert.AreEqual(newValue1, SnakeCaseNamesTesClass.PublicStaticStringField);
139+
140+
// Try with the snake case field name
141+
var newValue2 = "new value 2";
142+
using var pyNewValue2 = newValue2.ToPython();
143+
module.InvokeMethod("SetSnakeCaseStaticProperty", pyNewValue2);
144+
Assert.AreEqual(newValue2, SnakeCaseNamesTesClass.PublicStaticStringField);
145+
}
57146
}
58147

59148
#endregion

src/runtime/ClassManager.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -448,21 +448,21 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
448448
if (name == "__init__" && !impl.HasCustomNew())
449449
continue;
450450

451-
List<MethodBase> methodList;
452-
var names = new List<string> { name };
453-
if (!meth.IsSpecialName && !OperatorMethod.IsOperatorMethod(meth))
451+
if (!methods.TryGetValue(name, out var methodList))
454452
{
455-
names.Add(name.ToSnakeCase());
453+
methodList = methods[name] = new List<MethodBase>();
456454
}
457-
foreach (var currentName in names.Distinct())
455+
methodList.Add(meth);
456+
457+
if (!meth.IsSpecialName && !OperatorMethod.IsOperatorMethod(meth))
458458
{
459-
if (!methods.TryGetValue(currentName, out methodList))
459+
name = name.ToSnakeCase();
460+
if (!methods.TryGetValue(name, out methodList))
460461
{
461-
methodList = methods[currentName] = new List<MethodBase>();
462+
methodList = methods[name] = new List<MethodBase>();
462463
}
463464
methodList.Add(meth);
464465
}
465-
466466
continue;
467467

468468
case MemberTypes.Constructor when !impl.HasCustomNew():
@@ -514,6 +514,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
514514
}
515515
ob = new FieldObject(fi);
516516
ci.members[mi.Name] = ob.AllocObject();
517+
ci.members[mi.Name.ToSnakeCase()] = ob.AllocObject();
517518
continue;
518519

519520
case MemberTypes.Event:

0 commit comments

Comments
 (0)