From 77d394a8cc648ad384c6d8690ac915d3c4804fbb Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Fri, 29 Nov 2024 16:49:18 -0400 Subject: [PATCH 1/2] Throw AttributeError in tp_getattro for dynamic classes Python api documentation indicates it should throw AttributeError --- src/embed_tests/TestPropertyAccess.cs | 56 +++++++++++++++++++++++++ src/runtime/Types/DynamicClassObject.cs | 7 +++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/embed_tests/TestPropertyAccess.cs b/src/embed_tests/TestPropertyAccess.cs index e10dfadf6..54acc08f0 100644 --- a/src/embed_tests/TestPropertyAccess.cs +++ b/src/embed_tests/TestPropertyAccess.cs @@ -1410,6 +1410,62 @@ def CallDynamicMethodCatchingExceptions(self, fixture, defaultValue): } } + public class ThrowingDynamicFixture : DynamicFixture + { + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + if (!base.TryGetMember(binder, out result)) + { + throw new InvalidOperationException("Member not found"); + } + return true; + } + } + + [Test] + public void TestHasAttrShouldNotThrowIfAttributeIsNotPresentForDynamicClassObjects() + { + using var _ = Py.GIL(); + + dynamic module = PyModule.FromString("TestHasAttrShouldNotThrowIfAttributeIsNotPresentForDynamicClassObjects", @" +from clr import AddReference +AddReference(""Python.EmbeddingTest"") +AddReference(""System"") + +from Python.EmbeddingTest import TestPropertyAccess + +class TestDynamicClass(TestPropertyAccess.ThrowingDynamicFixture): + def __init__(self): + self.test_attribute = 11; + +def has_attribute(obj, attribute): + return hasattr(obj, attribute) +"); + + dynamic fixture = module.GetAttr("TestDynamicClass")(); + dynamic hasAttribute = module.GetAttr("has_attribute"); + + var hasAttributeResult = false; + Assert.DoesNotThrow(() => + { + hasAttributeResult = hasAttribute(fixture, "test_attribute"); + }); + Assert.IsTrue(hasAttributeResult); + + var attribute = 0; + Assert.DoesNotThrow(() => + { + attribute = fixture.test_attribute.As(); + }); + Assert.AreEqual(11, attribute); + + Assert.DoesNotThrow(() => + { + hasAttributeResult = hasAttribute(fixture, "non_existent_attribute"); + }); + Assert.IsFalse(hasAttributeResult); + } + public interface IModel { void InvokeModel(); diff --git a/src/runtime/Types/DynamicClassObject.cs b/src/runtime/Types/DynamicClassObject.cs index 2aa4b935a..94e94b568 100644 --- a/src/runtime/Types/DynamicClassObject.cs +++ b/src/runtime/Types/DynamicClassObject.cs @@ -88,7 +88,12 @@ public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference k catch (Exception exception) { Exceptions.Clear(); - Exceptions.SetError(exception); + // tp_getattro should call PyObject_GenericGetAttr (which we already did) + // which must throw AttributeError if the attribute is not found (see https://docs.python.org/3/c-api/object.html#c.PyObject_GenericGetAttr) + // So if we are throwing anything, it must be AttributeError. + // e.g hasattr uses this method to check if the attribute exists. If we throw anything other than AttributeError, + // hasattr will throw instead of catching and returning False. + Exceptions.SetError(Exceptions.AttributeError, exception.Message); } } From 2f8190b86871044b2337f37867cc6c4391f017f1 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Fri, 29 Nov 2024 16:50:41 -0400 Subject: [PATCH 2/2] Bump version to 2.0.41 --- 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 ba9456e3d..7d6192974 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 7ab968e35..448265145 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.40")] -[assembly: AssemblyFileVersion("2.0.40")] +[assembly: AssemblyVersion("2.0.41")] +[assembly: AssemblyFileVersion("2.0.41")] diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index e0d22a71e..a3fd340be 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.40 + 2.0.41 false LICENSE https://github.com/pythonnet/pythonnet