From ad591bfc24ab34b805ecdde15455715e4609d09a Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Fri, 19 Apr 2024 14:29:41 -0400 Subject: [PATCH 1/3] Fix: test for PEP8 properties/fields in dynamic objects --- src/embed_tests/TestPropertyAccess.cs | 27 +++++++++++++++++++++++++ src/runtime/Types/DynamicClassObject.cs | 24 +++++++++++++++++----- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/embed_tests/TestPropertyAccess.cs b/src/embed_tests/TestPropertyAccess.cs index 6aeb1bf4c..e10dfadf6 100644 --- a/src/embed_tests/TestPropertyAccess.cs +++ b/src/embed_tests/TestPropertyAccess.cs @@ -966,6 +966,33 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o protected static string NonDynamicProtectedStaticProperty { get; set; } = "Default value"; protected string NonDynamicProtectedField = "Default value"; + + public string NonDynamicField; + } + + [TestCase("NonDynamicField")] + [TestCase("NonDynamicProperty")] + public void TestDynamicObjectCanAccessCSharpNonDynamicPropertiesAndFieldsWithPEP8Syntax(string name) + { + using var _ = Py.GIL(); + + var model = new DynamicFixture(); + using var pyModel = model.ToPython(); + + var pep8Name = name.ToSnakeCase(); + pyModel.SetAttr(pep8Name, "Piertotum Locomotor".ToPython()); + + Assert.IsFalse(model.Properties.ContainsKey(name)); + Assert.IsFalse(model.Properties.ContainsKey(pep8Name)); + + var value = pyModel.GetAttr(pep8Name).As(); + Assert.AreEqual("Piertotum Locomotor", value); + + var memberInfo = model.GetType().GetMember(name)[0]; + var managedValue = memberInfo.MemberType == MemberTypes.Property + ? ((PropertyInfo)memberInfo).GetValue(model) + : ((FieldInfo)memberInfo).GetValue(model); + Assert.AreEqual(value, managedValue); } public class TestPerson : IComparable, IComparable diff --git a/src/runtime/Types/DynamicClassObject.cs b/src/runtime/Types/DynamicClassObject.cs index b363cdc31..cdba2950a 100644 --- a/src/runtime/Types/DynamicClassObject.cs +++ b/src/runtime/Types/DynamicClassObject.cs @@ -66,10 +66,7 @@ private static CallSite> SetAttrCallSite( /// public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference key) { - var result = Runtime.PyObject_GenericGetAttr(ob, key); - - // If AttributeError was raised, we try to get the attribute from the managed object dynamic properties. - if (Exceptions.ExceptionMatches(Exceptions.AttributeError)) + if (!HasNonDynamicMember(ob, key, out var result)) { var clrObj = (CLRObject)GetManagedObject(ob)!; @@ -111,7 +108,10 @@ public static int tp_setattro(BorrowedReference ob, BorrowedReference key, Borro var bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; var property = clrObjectType.GetProperty(name, bindingFlags); var field = property == null ? clrObjectType.GetField(name, bindingFlags) : null; - if ((property != null && property.SetMethod != null) || field != null) + if ((property != null && property.SetMethod != null) || + field != null || + // Not defined as property in the class, try with the snake case version of it before letting DynamicObject handle it. + HasNonDynamicMember(ob, key, out _, clearExceptions: true)) { return Runtime.PyObject_GenericSetAttr(ob, key, val); } @@ -129,5 +129,19 @@ public static int tp_setattro(BorrowedReference ob, BorrowedReference key, Borro return 0; } + + private static bool HasNonDynamicMember(BorrowedReference ob, BorrowedReference key, out NewReference value, bool clearExceptions = false) + { + value = Runtime.PyObject_GenericGetAttr(ob, key); + // If AttributeError was raised, we try to get the attribute from the managed object dynamic properties. + var result = !Exceptions.ExceptionMatches(Exceptions.AttributeError); + + if (clearExceptions) + { + Exceptions.Clear(); + } + + return result; + } } } From 5ff973d874c8a8f3369b4f8bc16f0ee8bf9f15c5 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Fri, 19 Apr 2024 14:30:22 -0400 Subject: [PATCH 2/3] Bump version to 2.0.35 --- src/perf_tests/Python.PerformanceTests.csproj | 4 ++-- src/runtime/Properties/AssemblyInfo.cs | 4 ++-- src/runtime/Python.Runtime.csproj | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/perf_tests/Python.PerformanceTests.csproj b/src/perf_tests/Python.PerformanceTests.csproj index 52d755cfd..e80971f6f 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/Properties/AssemblyInfo.cs b/src/runtime/Properties/AssemblyInfo.cs index e9e3fca34..20fea811f 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.34")] -[assembly: AssemblyFileVersion("2.0.34")] +[assembly: AssemblyVersion("2.0.35")] +[assembly: AssemblyFileVersion("2.0.35")] diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index edbb4ea83..a3a5f1fdb 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.34 + 2.0.35 false LICENSE https://github.com/pythonnet/pythonnet From 6402fa52affba8aa2e8eb22e5619f24a1ba8dca8 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Fri, 19 Apr 2024 15:21:04 -0400 Subject: [PATCH 3/3] Minor refactor --- src/runtime/Types/DynamicClassObject.cs | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/runtime/Types/DynamicClassObject.cs b/src/runtime/Types/DynamicClassObject.cs index cdba2950a..2aa4b935a 100644 --- a/src/runtime/Types/DynamicClassObject.cs +++ b/src/runtime/Types/DynamicClassObject.cs @@ -66,7 +66,7 @@ private static CallSite> SetAttrCallSite( /// public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference key) { - if (!HasNonDynamicMember(ob, key, out var result)) + if (!TryGetNonDynamicMember(ob, key, out var result)) { var clrObj = (CLRObject)GetManagedObject(ob)!; @@ -100,23 +100,14 @@ public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference k /// public static int tp_setattro(BorrowedReference ob, BorrowedReference key, BorrowedReference val) { - var clrObj = (CLRObject)GetManagedObject(ob)!; - var name = Runtime.GetManagedString(key); - - // If the key corresponds to a valid property or field of the class, we let the default implementation handle it. - var clrObjectType = clrObj.inst.GetType(); - var bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; - var property = clrObjectType.GetProperty(name, bindingFlags); - var field = property == null ? clrObjectType.GetField(name, bindingFlags) : null; - if ((property != null && property.SetMethod != null) || - field != null || - // Not defined as property in the class, try with the snake case version of it before letting DynamicObject handle it. - HasNonDynamicMember(ob, key, out _, clearExceptions: true)) + if (TryGetNonDynamicMember(ob, key, out _, clearExceptions: true)) { return Runtime.PyObject_GenericSetAttr(ob, key, val); } - var callsite = SetAttrCallSite(name, clrObjectType); + var clrObj = (CLRObject)GetManagedObject(ob)!; + var name = Runtime.GetManagedString(key); + var callsite = SetAttrCallSite(name, clrObj.inst.GetType()); try { callsite.Target(callsite, clrObj.inst, PyObject.FromNullableReference(val)); @@ -130,7 +121,7 @@ public static int tp_setattro(BorrowedReference ob, BorrowedReference key, Borro return 0; } - private static bool HasNonDynamicMember(BorrowedReference ob, BorrowedReference key, out NewReference value, bool clearExceptions = false) + private static bool TryGetNonDynamicMember(BorrowedReference ob, BorrowedReference key, out NewReference value, bool clearExceptions = false) { value = Runtime.PyObject_GenericGetAttr(ob, key); // If AttributeError was raised, we try to get the attribute from the managed object dynamic properties.