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/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 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); } }