Skip to content

Commit b9f3793

Browse files
authored
Snake-case static readonly fields as constants (#84)
* feat: snake-case static readonly fields as constants (all capital case) * Bump version to 2.0.31 * Add extension methods to get snake-cased name for properties and fields. Add unit tests
1 parent 2d864c2 commit b9f3793

File tree

7 files changed

+169
-20
lines changed

7 files changed

+169
-20
lines changed

src/embed_tests/ClassManagerTests.cs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,20 @@ public class SnakeCaseNamesTesClass
5151
public static string SettablePublicStaticStringField = "settable_public_static_string_field";
5252

5353
public string PublicStringProperty { get; set; } = "public_string_property";
54+
public string PublicStringGetOnlyProperty { get; } = "public_string_get_only_property";
5455
public static string PublicStaticStringProperty { get; set; } = "public_static_string_property";
56+
public static string PublicStaticReadonlyStringGetterOnlyProperty { get; } = "public_static_readonly_string_getter_only_property";
57+
public static string PublicStaticReadonlyStringPrivateSetterProperty { get; private set; } = "public_static_readonly_string_private_setter_property";
58+
public static string PublicStaticReadonlyStringProtectedSetterProperty { get; protected set; } = "public_static_readonly_string_protected_setter_property";
59+
public static string PublicStaticReadonlyStringInternalSetterProperty { get; internal set; } = "public_static_readonly_string_internal_setter_property";
60+
public static string PublicStaticReadonlyStringProtectedInternalSetterProperty { get; protected internal set; } = "public_static_readonly_string_protected_internal_setter_property";
61+
public static string PublicStaticReadonlyStringExpressionBodiedProperty => "public_static_readonly_string_expression_bodied_property";
62+
63+
protected string ProtectedStringGetOnlyProperty { get; } = "protected_string_get_only_property";
64+
protected static string ProtectedStaticStringProperty { get; set; } = "protected_static_string_property";
65+
protected static string ProtectedStaticReadonlyStringGetterOnlyProperty { get; } = "protected_static_readonly_string_getter_only_property";
66+
protected static string ProtectedStaticReadonlyStringPrivateSetterProperty { get; private set; } = "protected_static_readonly_string_private_setter_property";
67+
protected static string ProtectedStaticReadonlyStringExpressionBodiedProperty => "protected_static_readonly_string_expression_bodied_property";
5568

5669
public event EventHandler<string> PublicStringEvent;
5770
public static event EventHandler<string> PublicStaticStringEvent;
@@ -111,9 +124,9 @@ public void BindsSnakeCaseClassMethods(string originalMethodName, string snakeCa
111124
[TestCase("PublicStringField", "public_string_field")]
112125
[TestCase("PublicStaticStringField", "public_static_string_field")]
113126
[TestCase("PublicReadonlyStringField", "public_readonly_string_field")]
114-
[TestCase("PublicStaticReadonlyStringField", "public_static_readonly_string_field")]
115127
// Constants
116128
[TestCase("PublicConstStringField", "PUBLIC_CONST_STRING_FIELD")]
129+
[TestCase("PublicStaticReadonlyStringField", "PUBLIC_STATIC_READONLY_STRING_FIELD")]
117130
public void BindsSnakeCaseClassFields(string originalFieldName, string snakeCaseFieldName)
118131
{
119132
using var obj = new SnakeCaseNamesTesClass().ToPython();
@@ -187,14 +200,40 @@ def SetSnakeCaseStaticProperty(value):
187200
}
188201

189202
[TestCase("PublicStringProperty", "public_string_property")]
203+
[TestCase("PublicStringGetOnlyProperty", "public_string_get_only_property")]
190204
[TestCase("PublicStaticStringProperty", "public_static_string_property")]
205+
[TestCase("PublicStaticReadonlyStringPrivateSetterProperty", "public_static_readonly_string_private_setter_property")]
206+
[TestCase("PublicStaticReadonlyStringProtectedSetterProperty", "public_static_readonly_string_protected_setter_property")]
207+
[TestCase("PublicStaticReadonlyStringInternalSetterProperty", "public_static_readonly_string_internal_setter_property")]
208+
[TestCase("PublicStaticReadonlyStringProtectedInternalSetterProperty", "public_static_readonly_string_protected_internal_setter_property")]
209+
[TestCase("ProtectedStringGetOnlyProperty", "protected_string_get_only_property")]
210+
[TestCase("ProtectedStaticStringProperty", "protected_static_string_property")]
211+
[TestCase("ProtectedStaticReadonlyStringPrivateSetterProperty", "protected_static_readonly_string_private_setter_property")]
212+
// Constants
213+
[TestCase("PublicStaticReadonlyStringGetterOnlyProperty", "PUBLIC_STATIC_READONLY_STRING_GETTER_ONLY_PROPERTY")]
214+
[TestCase("PublicStaticReadonlyStringExpressionBodiedProperty", "PUBLIC_STATIC_READONLY_STRING_EXPRESSION_BODIED_PROPERTY")]
215+
[TestCase("ProtectedStaticReadonlyStringGetterOnlyProperty", "PROTECTED_STATIC_READONLY_STRING_GETTER_ONLY_PROPERTY")]
216+
[TestCase("ProtectedStaticReadonlyStringExpressionBodiedProperty", "PROTECTED_STATIC_READONLY_STRING_EXPRESSION_BODIED_PROPERTY")]
217+
191218
public void BindsSnakeCaseClassProperties(string originalPropertyName, string snakeCasePropertyName)
192219
{
193220
using var obj = new SnakeCaseNamesTesClass().ToPython();
194221
var expectedValue = originalPropertyName switch
195222
{
196223
"PublicStringProperty" => "public_string_property",
224+
"PublicStringGetOnlyProperty" => "public_string_get_only_property",
197225
"PublicStaticStringProperty" => "public_static_string_property",
226+
"PublicStaticReadonlyStringPrivateSetterProperty" => "public_static_readonly_string_private_setter_property",
227+
"PublicStaticReadonlyStringProtectedSetterProperty" => "public_static_readonly_string_protected_setter_property",
228+
"PublicStaticReadonlyStringInternalSetterProperty" => "public_static_readonly_string_internal_setter_property",
229+
"PublicStaticReadonlyStringProtectedInternalSetterProperty" => "public_static_readonly_string_protected_internal_setter_property",
230+
"PublicStaticReadonlyStringGetterOnlyProperty" => "public_static_readonly_string_getter_only_property",
231+
"PublicStaticReadonlyStringExpressionBodiedProperty" => "public_static_readonly_string_expression_bodied_property",
232+
"ProtectedStringGetOnlyProperty" => "protected_string_get_only_property",
233+
"ProtectedStaticStringProperty" => "protected_static_string_property",
234+
"ProtectedStaticReadonlyStringGetterOnlyProperty" => "protected_static_readonly_string_getter_only_property",
235+
"ProtectedStaticReadonlyStringPrivateSetterProperty" => "protected_static_readonly_string_private_setter_property",
236+
"ProtectedStaticReadonlyStringExpressionBodiedProperty" => "protected_static_readonly_string_expression_bodied_property",
198237
_ => throw new ArgumentException("Invalid property name")
199238
};
200239

src/embed_tests/TestUtil.cs

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

35
using Python.Runtime;
@@ -7,6 +9,8 @@ namespace Python.EmbeddingTest
79
[TestFixture]
810
public class TestUtil
911
{
12+
private static BindingFlags _bindingFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
13+
1014
[TestCase("TestCamelCaseString", "test_camel_case_string")]
1115
[TestCase("testCamelCaseString", "test_camel_case_string")]
1216
[TestCase("TestCamelCaseString123 ", "test_camel_case_string123")]
@@ -19,5 +23,91 @@ public void ConvertsNameToSnakeCase(string name, string expected)
1923
{
2024
Assert.AreEqual(expected, name.ToSnakeCase());
2125
}
26+
27+
[TestCase("TestNonConstField1", "test_non_const_field1")]
28+
[TestCase("TestNonConstField2", "test_non_const_field2")]
29+
[TestCase("TestNonConstField3", "test_non_const_field3")]
30+
[TestCase("TestNonConstField4", "test_non_const_field4")]
31+
public void ConvertsNonConstantFieldsToSnakeCase(string fieldName, string expected)
32+
{
33+
var fi = typeof(TestClass).GetField(fieldName, _bindingFlags);
34+
Assert.AreEqual(expected, fi.ToSnakeCase());
35+
}
36+
37+
[TestCase("TestConstField1", "TEST_CONST_FIELD1")]
38+
[TestCase("TestConstField2", "TEST_CONST_FIELD2")]
39+
[TestCase("TestConstField3", "TEST_CONST_FIELD3")]
40+
[TestCase("TestConstField4", "TEST_CONST_FIELD4")]
41+
public void ConvertsConstantFieldsToFullCapitalCase(string fieldName, string expected)
42+
{
43+
var fi = typeof(TestClass).GetField(fieldName, _bindingFlags);
44+
Assert.AreEqual(expected, fi.ToSnakeCase());
45+
}
46+
47+
[TestCase("TestNonConstProperty1", "test_non_const_property1")]
48+
[TestCase("TestNonConstProperty2", "test_non_const_property2")]
49+
[TestCase("TestNonConstProperty3", "test_non_const_property3")]
50+
[TestCase("TestNonConstProperty4", "test_non_const_property4")]
51+
[TestCase("TestNonConstProperty5", "test_non_const_property5")]
52+
[TestCase("TestNonConstProperty6", "test_non_const_property6")]
53+
[TestCase("TestNonConstProperty7", "test_non_const_property7")]
54+
[TestCase("TestNonConstProperty8", "test_non_const_property8")]
55+
[TestCase("TestNonConstProperty9", "test_non_const_property9")]
56+
[TestCase("TestNonConstProperty10", "test_non_const_property10")]
57+
[TestCase("TestNonConstProperty11", "test_non_const_property11")]
58+
[TestCase("TestNonConstProperty12", "test_non_const_property12")]
59+
[TestCase("TestNonConstProperty13", "test_non_const_property13")]
60+
[TestCase("TestNonConstProperty14", "test_non_const_property14")]
61+
[TestCase("TestNonConstProperty15", "test_non_const_property15")]
62+
[TestCase("TestNonConstProperty16", "test_non_const_property16")]
63+
public void ConvertsNonConstantPropertiesToSnakeCase(string propertyName, string expected)
64+
{
65+
var pi = typeof(TestClass).GetProperty(propertyName, _bindingFlags);
66+
Assert.AreEqual(expected, pi.ToSnakeCase());
67+
}
68+
69+
[TestCase("TestConstProperty1", "TEST_CONST_PROPERTY1")]
70+
[TestCase("TestConstProperty2", "TEST_CONST_PROPERTY2")]
71+
[TestCase("TestConstProperty3", "TEST_CONST_PROPERTY3")]
72+
public void ConvertsConstantPropertiesToFullCapitalCase(string propertyName, string expected)
73+
{
74+
var pi = typeof(TestClass).GetProperty(propertyName, _bindingFlags);
75+
Assert.AreEqual(expected, pi.ToSnakeCase());
76+
}
77+
78+
private class TestClass
79+
{
80+
public string TestNonConstField1 = "TestNonConstField1";
81+
protected string TestNonConstField2 = "TestNonConstField2";
82+
public static string TestNonConstField3 = "TestNonConstField3";
83+
protected static string TestNonConstField4 = "TestNonConstField4";
84+
85+
public const string TestConstField1 = "TestConstField1";
86+
protected const string TestConstField2 = "TestConstField2";
87+
public static readonly string TestConstField3 = "TestConstField3";
88+
protected static readonly string TestConstField4 = "TestConstField4";
89+
90+
public string TestNonConstProperty1 { get; set; } = "TestNonConstProperty1";
91+
protected string TestNonConstProperty2 { get; set; } = "TestNonConstProperty2";
92+
public string TestNonConstProperty3 { get; } = "TestNonConstProperty3";
93+
protected string TestNonConstProperty4 { get; } = "TestNonConstProperty4";
94+
public string TestNonConstProperty5 { get; private set; } = "TestNonConstProperty5";
95+
protected string TestNonConstProperty6 { get; private set; } = "TestNonConstProperty6";
96+
public string TestNonConstProperty7 { get; protected set; } = "TestNonConstProperty7";
97+
public string TestNonConstProperty8 { get; internal set; } = "TestNonConstProperty8";
98+
public string TestNonConstProperty9 { get; protected internal set; } = "TestNonConstProperty9";
99+
public static string TestNonConstProperty10 { get; set; } = "TestNonConstProperty10";
100+
protected static string TestNonConstProperty11 { get; set; } = "TestNonConstProperty11";
101+
public static string TestNonConstProperty12 { get; private set; } = "TestNonConstProperty12";
102+
protected static string TestNonConstProperty13 { get; private set; } = "TestNonConstProperty13";
103+
public static string TestNonConstProperty14 { get; protected set; } = "TestNonConstProperty14";
104+
public static string TestNonConstProperty15 { get; internal set; } = "TestNonConstProperty15";
105+
public static string TestNonConstProperty16 { get; protected internal set; } = "TestNonConstProperty16";
106+
107+
108+
public static string TestConstProperty1 => "TestConstProperty1";
109+
public static string TestConstProperty2 { get; } = "TestConstProperty2";
110+
protected static string TestConstProperty3 { get; } = "TestConstProperty3";
111+
}
22112
}
23113
}

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.30" GeneratePathProperty="true">
16+
<PackageReference Include="quantconnect.pythonnet" Version="2.0.31" 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.30\lib\net5.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
28+
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.31\lib\net5.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
2929
</Target>
3030

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

src/runtime/ClassManager.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ void AddMember(string name, string snakeCasedName, PyObject obj)
533533
}
534534

535535
ob = new PropertyObject(pi);
536-
AddMember(pi.Name, pi.Name.ToSnakeCase(), ob.AllocObject());
536+
AddMember(pi.Name, pi.ToSnakeCase(), ob.AllocObject());
537537
continue;
538538

539539
case MemberTypes.Field:
@@ -543,14 +543,7 @@ void AddMember(string name, string snakeCasedName, PyObject obj)
543543
continue;
544544
}
545545
ob = new FieldObject(fi);
546-
547-
var pepName = fi.Name.ToSnakeCase();
548-
if (fi.IsLiteral)
549-
{
550-
pepName = pepName.ToUpper();
551-
}
552-
553-
AddMember(fi.Name, pepName, ob.AllocObject());
546+
AddMember(fi.Name, fi.ToSnakeCase(), ob.AllocObject());
554547
continue;
555548

556549
case MemberTypes.Event:

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.30")]
8-
[assembly: AssemblyFileVersion("2.0.30")]
7+
[assembly: AssemblyVersion("2.0.31")]
8+
[assembly: AssemblyFileVersion("2.0.31")]

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.30</Version>
8+
<Version>2.0.31</Version>
99
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
1010
<PackageLicenseFile>LICENSE</PackageLicenseFile>
1111
<RepositoryUrl>https://github.com/pythonnet/pythonnet</RepositoryUrl>

src/runtime/Util/Util.cs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Diagnostics;
44
using System.Globalization;
55
using System.IO;
6+
using System.Reflection;
67
using System.Runtime.CompilerServices;
78
using System.Runtime.InteropServices;
89
using System.Text;
@@ -41,7 +42,7 @@ internal static long ReadInt64(BorrowedReference ob, int offset)
4142

4243
[MethodImpl(MethodImplOptions.AggressiveInlining)]
4344
internal unsafe static T* ReadPtr<T>(BorrowedReference ob, int offset)
44-
where T: unmanaged
45+
where T : unmanaged
4546
{
4647
Debug.Assert(offset >= 0);
4748
IntPtr ptr = Marshal.ReadIntPtr(ob.DangerousGetAddress(), offset);
@@ -152,7 +153,7 @@ internal static string ReadStringResource(this System.Reflection.Assembly assemb
152153
public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> enumerator) => enumerator;
153154

154155
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source)
155-
where T: class
156+
where T : class
156157
{
157158
foreach (var item in source)
158159
{
@@ -166,7 +167,7 @@ public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source)
166167
/// <remarks>
167168
/// Reference: https://github.com/efcore/EFCore.NamingConventions/blob/main/EFCore.NamingConventions/Internal/SnakeCaseNameRewriter.cs
168169
/// </remarks>
169-
public static string ToSnakeCase(this string name)
170+
public static string ToSnakeCase(this string name, bool constant = false)
170171
{
171172
var builder = new StringBuilder(name.Length + Math.Min(2, name.Length / 5));
172173
var previousCategory = default(UnicodeCategory?);
@@ -196,8 +197,10 @@ public static string ToSnakeCase(this string name)
196197
{
197198
builder.Append('_');
198199
}
199-
200-
currentChar = char.ToLower(currentChar, CultureInfo.InvariantCulture);
200+
if (!constant)
201+
{
202+
currentChar = char.ToLower(currentChar, CultureInfo.InvariantCulture);
203+
}
201204
break;
202205

203206
case UnicodeCategory.LowercaseLetter:
@@ -206,6 +209,10 @@ public static string ToSnakeCase(this string name)
206209
{
207210
builder.Append('_');
208211
}
212+
if (constant)
213+
{
214+
currentChar = char.ToUpper(currentChar, CultureInfo.InvariantCulture);
215+
}
209216
break;
210217

211218
default:
@@ -222,5 +229,25 @@ public static string ToSnakeCase(this string name)
222229

223230
return builder.ToString();
224231
}
232+
233+
/// <summary>
234+
/// Converts the specified field name to snake case.
235+
/// const and static readonly fields are considered as constants and are converted to uppercase.
236+
/// </summary>
237+
public static string ToSnakeCase(this FieldInfo fieldInfo)
238+
{
239+
return fieldInfo.Name.ToSnakeCase(fieldInfo.IsLiteral || (fieldInfo.IsStatic && fieldInfo.IsInitOnly));
240+
}
241+
242+
/// <summary>
243+
/// Converts the specified property name to snake case.
244+
/// Static properties without a setter are considered as constants and are converted to uppercase.
245+
/// </summary>
246+
public static string ToSnakeCase(this PropertyInfo propertyInfo)
247+
{
248+
var constant = propertyInfo.CanRead && !propertyInfo.CanWrite &&
249+
(propertyInfo.GetGetMethod()?.IsStatic ?? propertyInfo.GetGetMethod(nonPublic: true)?.IsStatic ?? false);
250+
return propertyInfo.Name.ToSnakeCase(constant);
251+
}
225252
}
226253
}

0 commit comments

Comments
 (0)