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/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 diff --git a/src/runtime/Types/DynamicClassObject.cs b/src/runtime/Types/DynamicClassObject.cs index b363cdc31..2aa4b935a 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 (!TryGetNonDynamicMember(ob, key, out var result)) { var clrObj = (CLRObject)GetManagedObject(ob)!; @@ -103,20 +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) + 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)); @@ -129,5 +120,19 @@ public static int tp_setattro(BorrowedReference ob, BorrowedReference key, Borro return 0; } + + 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. + var result = !Exceptions.ExceptionMatches(Exceptions.AttributeError); + + if (clearExceptions) + { + Exceptions.Clear(); + } + + return result; + } } }