Skip to content

Commit

Permalink
Bug dynamic class throwing on hasattr (#96)
Browse files Browse the repository at this point in the history
* Throw AttributeError in tp_getattro for dynamic classes

Python api documentation indicates it should throw AttributeError

* Bump version to 2.0.41
  • Loading branch information
jhonabreul authored Dec 2, 2024
1 parent fc4e67e commit 360948f
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 6 deletions.
56 changes: 56 additions & 0 deletions src/embed_tests/TestPropertyAccess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>();
});
Assert.AreEqual(11, attribute);

Assert.DoesNotThrow(() =>
{
hasAttributeResult = hasAttribute(fixture, "non_existent_attribute");
});
Assert.IsFalse(hasAttributeResult);
}

public interface IModel
{
void InvokeModel();
Expand Down
4 changes: 2 additions & 2 deletions src/perf_tests/Python.PerformanceTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
<PackageReference Include="quantconnect.pythonnet" Version="2.0.40" GeneratePathProperty="true">
<PackageReference Include="quantconnect.pythonnet" Version="2.0.41" GeneratePathProperty="true">
<IncludeAssets>compile</IncludeAssets>
</PackageReference>
</ItemGroup>
Expand All @@ -25,7 +25,7 @@
</Target>

<Target Name="CopyBaseline" AfterTargets="Build">
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.40\lib\net6.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.41\lib\net6.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
</Target>

<Target Name="CopyNewBuild" AfterTargets="Build">
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
2 changes: 1 addition & 1 deletion src/runtime/Python.Runtime.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<RootNamespace>Python.Runtime</RootNamespace>
<AssemblyName>Python.Runtime</AssemblyName>
<PackageId>QuantConnect.pythonnet</PackageId>
<Version>2.0.40</Version>
<Version>2.0.41</Version>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<RepositoryUrl>https://github.com/pythonnet/pythonnet</RepositoryUrl>
Expand Down
7 changes: 6 additions & 1 deletion src/runtime/Types/DynamicClassObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down

0 comments on commit 360948f

Please sign in to comment.