diff --git a/src/embed_tests/ClassManagerTests.cs b/src/embed_tests/ClassManagerTests.cs index 000d6db1d..00fe92549 100644 --- a/src/embed_tests/ClassManagerTests.cs +++ b/src/embed_tests/ClassManagerTests.cs @@ -51,7 +51,20 @@ public class SnakeCaseNamesTesClass public static string SettablePublicStaticStringField = "settable_public_static_string_field"; public string PublicStringProperty { get; set; } = "public_string_property"; + public string PublicStringGetOnlyProperty { get; } = "public_string_get_only_property"; public static string PublicStaticStringProperty { get; set; } = "public_static_string_property"; + public static string PublicStaticReadonlyStringGetterOnlyProperty { get; } = "public_static_readonly_string_getter_only_property"; + public static string PublicStaticReadonlyStringPrivateSetterProperty { get; private set; } = "public_static_readonly_string_private_setter_property"; + public static string PublicStaticReadonlyStringProtectedSetterProperty { get; protected set; } = "public_static_readonly_string_protected_setter_property"; + public static string PublicStaticReadonlyStringInternalSetterProperty { get; internal set; } = "public_static_readonly_string_internal_setter_property"; + public static string PublicStaticReadonlyStringProtectedInternalSetterProperty { get; protected internal set; } = "public_static_readonly_string_protected_internal_setter_property"; + public static string PublicStaticReadonlyStringExpressionBodiedProperty => "public_static_readonly_string_expression_bodied_property"; + + protected string ProtectedStringGetOnlyProperty { get; } = "protected_string_get_only_property"; + protected static string ProtectedStaticStringProperty { get; set; } = "protected_static_string_property"; + protected static string ProtectedStaticReadonlyStringGetterOnlyProperty { get; } = "protected_static_readonly_string_getter_only_property"; + protected static string ProtectedStaticReadonlyStringPrivateSetterProperty { get; private set; } = "protected_static_readonly_string_private_setter_property"; + protected static string ProtectedStaticReadonlyStringExpressionBodiedProperty => "protected_static_readonly_string_expression_bodied_property"; public event EventHandler PublicStringEvent; public static event EventHandler PublicStaticStringEvent; @@ -111,9 +124,9 @@ public void BindsSnakeCaseClassMethods(string originalMethodName, string snakeCa [TestCase("PublicStringField", "public_string_field")] [TestCase("PublicStaticStringField", "public_static_string_field")] [TestCase("PublicReadonlyStringField", "public_readonly_string_field")] - [TestCase("PublicStaticReadonlyStringField", "public_static_readonly_string_field")] // Constants [TestCase("PublicConstStringField", "PUBLIC_CONST_STRING_FIELD")] + [TestCase("PublicStaticReadonlyStringField", "PUBLIC_STATIC_READONLY_STRING_FIELD")] public void BindsSnakeCaseClassFields(string originalFieldName, string snakeCaseFieldName) { using var obj = new SnakeCaseNamesTesClass().ToPython(); @@ -187,14 +200,40 @@ def SetSnakeCaseStaticProperty(value): } [TestCase("PublicStringProperty", "public_string_property")] + [TestCase("PublicStringGetOnlyProperty", "public_string_get_only_property")] [TestCase("PublicStaticStringProperty", "public_static_string_property")] + [TestCase("PublicStaticReadonlyStringPrivateSetterProperty", "public_static_readonly_string_private_setter_property")] + [TestCase("PublicStaticReadonlyStringProtectedSetterProperty", "public_static_readonly_string_protected_setter_property")] + [TestCase("PublicStaticReadonlyStringInternalSetterProperty", "public_static_readonly_string_internal_setter_property")] + [TestCase("PublicStaticReadonlyStringProtectedInternalSetterProperty", "public_static_readonly_string_protected_internal_setter_property")] + [TestCase("ProtectedStringGetOnlyProperty", "protected_string_get_only_property")] + [TestCase("ProtectedStaticStringProperty", "protected_static_string_property")] + [TestCase("ProtectedStaticReadonlyStringPrivateSetterProperty", "protected_static_readonly_string_private_setter_property")] + // Constants + [TestCase("PublicStaticReadonlyStringGetterOnlyProperty", "PUBLIC_STATIC_READONLY_STRING_GETTER_ONLY_PROPERTY")] + [TestCase("PublicStaticReadonlyStringExpressionBodiedProperty", "PUBLIC_STATIC_READONLY_STRING_EXPRESSION_BODIED_PROPERTY")] + [TestCase("ProtectedStaticReadonlyStringGetterOnlyProperty", "PROTECTED_STATIC_READONLY_STRING_GETTER_ONLY_PROPERTY")] + [TestCase("ProtectedStaticReadonlyStringExpressionBodiedProperty", "PROTECTED_STATIC_READONLY_STRING_EXPRESSION_BODIED_PROPERTY")] + public void BindsSnakeCaseClassProperties(string originalPropertyName, string snakeCasePropertyName) { using var obj = new SnakeCaseNamesTesClass().ToPython(); var expectedValue = originalPropertyName switch { "PublicStringProperty" => "public_string_property", + "PublicStringGetOnlyProperty" => "public_string_get_only_property", "PublicStaticStringProperty" => "public_static_string_property", + "PublicStaticReadonlyStringPrivateSetterProperty" => "public_static_readonly_string_private_setter_property", + "PublicStaticReadonlyStringProtectedSetterProperty" => "public_static_readonly_string_protected_setter_property", + "PublicStaticReadonlyStringInternalSetterProperty" => "public_static_readonly_string_internal_setter_property", + "PublicStaticReadonlyStringProtectedInternalSetterProperty" => "public_static_readonly_string_protected_internal_setter_property", + "PublicStaticReadonlyStringGetterOnlyProperty" => "public_static_readonly_string_getter_only_property", + "PublicStaticReadonlyStringExpressionBodiedProperty" => "public_static_readonly_string_expression_bodied_property", + "ProtectedStringGetOnlyProperty" => "protected_string_get_only_property", + "ProtectedStaticStringProperty" => "protected_static_string_property", + "ProtectedStaticReadonlyStringGetterOnlyProperty" => "protected_static_readonly_string_getter_only_property", + "ProtectedStaticReadonlyStringPrivateSetterProperty" => "protected_static_readonly_string_private_setter_property", + "ProtectedStaticReadonlyStringExpressionBodiedProperty" => "protected_static_readonly_string_expression_bodied_property", _ => throw new ArgumentException("Invalid property name") }; diff --git a/src/embed_tests/TestUtil.cs b/src/embed_tests/TestUtil.cs index 0b0c5a84a..a95aa3670 100644 --- a/src/embed_tests/TestUtil.cs +++ b/src/embed_tests/TestUtil.cs @@ -1,3 +1,5 @@ +using System.Reflection; + using NUnit.Framework; using Python.Runtime; @@ -7,6 +9,8 @@ namespace Python.EmbeddingTest [TestFixture] public class TestUtil { + private static BindingFlags _bindingFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; + [TestCase("TestCamelCaseString", "test_camel_case_string")] [TestCase("testCamelCaseString", "test_camel_case_string")] [TestCase("TestCamelCaseString123 ", "test_camel_case_string123")] @@ -19,5 +23,91 @@ public void ConvertsNameToSnakeCase(string name, string expected) { Assert.AreEqual(expected, name.ToSnakeCase()); } + + [TestCase("TestNonConstField1", "test_non_const_field1")] + [TestCase("TestNonConstField2", "test_non_const_field2")] + [TestCase("TestNonConstField3", "test_non_const_field3")] + [TestCase("TestNonConstField4", "test_non_const_field4")] + public void ConvertsNonConstantFieldsToSnakeCase(string fieldName, string expected) + { + var fi = typeof(TestClass).GetField(fieldName, _bindingFlags); + Assert.AreEqual(expected, fi.ToSnakeCase()); + } + + [TestCase("TestConstField1", "TEST_CONST_FIELD1")] + [TestCase("TestConstField2", "TEST_CONST_FIELD2")] + [TestCase("TestConstField3", "TEST_CONST_FIELD3")] + [TestCase("TestConstField4", "TEST_CONST_FIELD4")] + public void ConvertsConstantFieldsToFullCapitalCase(string fieldName, string expected) + { + var fi = typeof(TestClass).GetField(fieldName, _bindingFlags); + Assert.AreEqual(expected, fi.ToSnakeCase()); + } + + [TestCase("TestNonConstProperty1", "test_non_const_property1")] + [TestCase("TestNonConstProperty2", "test_non_const_property2")] + [TestCase("TestNonConstProperty3", "test_non_const_property3")] + [TestCase("TestNonConstProperty4", "test_non_const_property4")] + [TestCase("TestNonConstProperty5", "test_non_const_property5")] + [TestCase("TestNonConstProperty6", "test_non_const_property6")] + [TestCase("TestNonConstProperty7", "test_non_const_property7")] + [TestCase("TestNonConstProperty8", "test_non_const_property8")] + [TestCase("TestNonConstProperty9", "test_non_const_property9")] + [TestCase("TestNonConstProperty10", "test_non_const_property10")] + [TestCase("TestNonConstProperty11", "test_non_const_property11")] + [TestCase("TestNonConstProperty12", "test_non_const_property12")] + [TestCase("TestNonConstProperty13", "test_non_const_property13")] + [TestCase("TestNonConstProperty14", "test_non_const_property14")] + [TestCase("TestNonConstProperty15", "test_non_const_property15")] + [TestCase("TestNonConstProperty16", "test_non_const_property16")] + public void ConvertsNonConstantPropertiesToSnakeCase(string propertyName, string expected) + { + var pi = typeof(TestClass).GetProperty(propertyName, _bindingFlags); + Assert.AreEqual(expected, pi.ToSnakeCase()); + } + + [TestCase("TestConstProperty1", "TEST_CONST_PROPERTY1")] + [TestCase("TestConstProperty2", "TEST_CONST_PROPERTY2")] + [TestCase("TestConstProperty3", "TEST_CONST_PROPERTY3")] + public void ConvertsConstantPropertiesToFullCapitalCase(string propertyName, string expected) + { + var pi = typeof(TestClass).GetProperty(propertyName, _bindingFlags); + Assert.AreEqual(expected, pi.ToSnakeCase()); + } + + private class TestClass + { + public string TestNonConstField1 = "TestNonConstField1"; + protected string TestNonConstField2 = "TestNonConstField2"; + public static string TestNonConstField3 = "TestNonConstField3"; + protected static string TestNonConstField4 = "TestNonConstField4"; + + public const string TestConstField1 = "TestConstField1"; + protected const string TestConstField2 = "TestConstField2"; + public static readonly string TestConstField3 = "TestConstField3"; + protected static readonly string TestConstField4 = "TestConstField4"; + + public string TestNonConstProperty1 { get; set; } = "TestNonConstProperty1"; + protected string TestNonConstProperty2 { get; set; } = "TestNonConstProperty2"; + public string TestNonConstProperty3 { get; } = "TestNonConstProperty3"; + protected string TestNonConstProperty4 { get; } = "TestNonConstProperty4"; + public string TestNonConstProperty5 { get; private set; } = "TestNonConstProperty5"; + protected string TestNonConstProperty6 { get; private set; } = "TestNonConstProperty6"; + public string TestNonConstProperty7 { get; protected set; } = "TestNonConstProperty7"; + public string TestNonConstProperty8 { get; internal set; } = "TestNonConstProperty8"; + public string TestNonConstProperty9 { get; protected internal set; } = "TestNonConstProperty9"; + public static string TestNonConstProperty10 { get; set; } = "TestNonConstProperty10"; + protected static string TestNonConstProperty11 { get; set; } = "TestNonConstProperty11"; + public static string TestNonConstProperty12 { get; private set; } = "TestNonConstProperty12"; + protected static string TestNonConstProperty13 { get; private set; } = "TestNonConstProperty13"; + public static string TestNonConstProperty14 { get; protected set; } = "TestNonConstProperty14"; + public static string TestNonConstProperty15 { get; internal set; } = "TestNonConstProperty15"; + public static string TestNonConstProperty16 { get; protected internal set; } = "TestNonConstProperty16"; + + + public static string TestConstProperty1 => "TestConstProperty1"; + public static string TestConstProperty2 { get; } = "TestConstProperty2"; + protected static string TestConstProperty3 { get; } = "TestConstProperty3"; + } } } diff --git a/src/perf_tests/Python.PerformanceTests.csproj b/src/perf_tests/Python.PerformanceTests.csproj index 2809f2b35..5eb12351e 100644 --- a/src/perf_tests/Python.PerformanceTests.csproj +++ b/src/perf_tests/Python.PerformanceTests.csproj @@ -13,7 +13,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + compile @@ -25,7 +25,7 @@ - + diff --git a/src/runtime/ClassManager.cs b/src/runtime/ClassManager.cs index 4ddb641a2..9368f7059 100644 --- a/src/runtime/ClassManager.cs +++ b/src/runtime/ClassManager.cs @@ -533,7 +533,7 @@ void AddMember(string name, string snakeCasedName, PyObject obj) } ob = new PropertyObject(pi); - AddMember(pi.Name, pi.Name.ToSnakeCase(), ob.AllocObject()); + AddMember(pi.Name, pi.ToSnakeCase(), ob.AllocObject()); continue; case MemberTypes.Field: @@ -543,14 +543,7 @@ void AddMember(string name, string snakeCasedName, PyObject obj) continue; } ob = new FieldObject(fi); - - var pepName = fi.Name.ToSnakeCase(); - if (fi.IsLiteral) - { - pepName = pepName.ToUpper(); - } - - AddMember(fi.Name, pepName, ob.AllocObject()); + AddMember(fi.Name, fi.ToSnakeCase(), ob.AllocObject()); continue; case MemberTypes.Event: diff --git a/src/runtime/Properties/AssemblyInfo.cs b/src/runtime/Properties/AssemblyInfo.cs index e4fe802f6..5a2d04990 100644 --- a/src/runtime/Properties/AssemblyInfo.cs +++ b/src/runtime/Properties/AssemblyInfo.cs @@ -4,5 +4,5 @@ [assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")] [assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")] -[assembly: AssemblyVersion("2.0.30")] -[assembly: AssemblyFileVersion("2.0.30")] +[assembly: AssemblyVersion("2.0.31")] +[assembly: AssemblyFileVersion("2.0.31")] diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index bbb9613a6..803f31dd2 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -5,7 +5,7 @@ Python.Runtime Python.Runtime QuantConnect.pythonnet - 2.0.30 + 2.0.31 false LICENSE https://github.com/pythonnet/pythonnet diff --git a/src/runtime/Util/Util.cs b/src/runtime/Util/Util.cs index 6aa398c91..31142b965 100644 --- a/src/runtime/Util/Util.cs +++ b/src/runtime/Util/Util.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -41,7 +42,7 @@ internal static long ReadInt64(BorrowedReference ob, int offset) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe static T* ReadPtr(BorrowedReference ob, int offset) - where T: unmanaged + where T : unmanaged { Debug.Assert(offset >= 0); IntPtr ptr = Marshal.ReadIntPtr(ob.DangerousGetAddress(), offset); @@ -152,7 +153,7 @@ internal static string ReadStringResource(this System.Reflection.Assembly assemb public static IEnumerator GetEnumerator(this IEnumerator enumerator) => enumerator; public static IEnumerable WhereNotNull(this IEnumerable source) - where T: class + where T : class { foreach (var item in source) { @@ -166,7 +167,7 @@ public static IEnumerable WhereNotNull(this IEnumerable source) /// /// Reference: https://github.com/efcore/EFCore.NamingConventions/blob/main/EFCore.NamingConventions/Internal/SnakeCaseNameRewriter.cs /// - public static string ToSnakeCase(this string name) + public static string ToSnakeCase(this string name, bool constant = false) { var builder = new StringBuilder(name.Length + Math.Min(2, name.Length / 5)); var previousCategory = default(UnicodeCategory?); @@ -196,8 +197,10 @@ public static string ToSnakeCase(this string name) { builder.Append('_'); } - - currentChar = char.ToLower(currentChar, CultureInfo.InvariantCulture); + if (!constant) + { + currentChar = char.ToLower(currentChar, CultureInfo.InvariantCulture); + } break; case UnicodeCategory.LowercaseLetter: @@ -206,6 +209,10 @@ public static string ToSnakeCase(this string name) { builder.Append('_'); } + if (constant) + { + currentChar = char.ToUpper(currentChar, CultureInfo.InvariantCulture); + } break; default: @@ -222,5 +229,25 @@ public static string ToSnakeCase(this string name) return builder.ToString(); } + + /// + /// Converts the specified field name to snake case. + /// const and static readonly fields are considered as constants and are converted to uppercase. + /// + public static string ToSnakeCase(this FieldInfo fieldInfo) + { + return fieldInfo.Name.ToSnakeCase(fieldInfo.IsLiteral || (fieldInfo.IsStatic && fieldInfo.IsInitOnly)); + } + + /// + /// Converts the specified property name to snake case. + /// Static properties without a setter are considered as constants and are converted to uppercase. + /// + public static string ToSnakeCase(this PropertyInfo propertyInfo) + { + var constant = propertyInfo.CanRead && !propertyInfo.CanWrite && + (propertyInfo.GetGetMethod()?.IsStatic ?? propertyInfo.GetGetMethod(nonPublic: true)?.IsStatic ?? false); + return propertyInfo.Name.ToSnakeCase(constant); + } } }