Skip to content

Commit 360948f

Browse files
authored
Bug dynamic class throwing on hasattr (#96)
* Throw AttributeError in tp_getattro for dynamic classes Python api documentation indicates it should throw AttributeError * Bump version to 2.0.41
1 parent fc4e67e commit 360948f

File tree

5 files changed

+67
-6
lines changed

5 files changed

+67
-6
lines changed

src/embed_tests/TestPropertyAccess.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,6 +1410,62 @@ def CallDynamicMethodCatchingExceptions(self, fixture, defaultValue):
14101410
}
14111411
}
14121412

1413+
public class ThrowingDynamicFixture : DynamicFixture
1414+
{
1415+
public override bool TryGetMember(GetMemberBinder binder, out object result)
1416+
{
1417+
if (!base.TryGetMember(binder, out result))
1418+
{
1419+
throw new InvalidOperationException("Member not found");
1420+
}
1421+
return true;
1422+
}
1423+
}
1424+
1425+
[Test]
1426+
public void TestHasAttrShouldNotThrowIfAttributeIsNotPresentForDynamicClassObjects()
1427+
{
1428+
using var _ = Py.GIL();
1429+
1430+
dynamic module = PyModule.FromString("TestHasAttrShouldNotThrowIfAttributeIsNotPresentForDynamicClassObjects", @"
1431+
from clr import AddReference
1432+
AddReference(""Python.EmbeddingTest"")
1433+
AddReference(""System"")
1434+
1435+
from Python.EmbeddingTest import TestPropertyAccess
1436+
1437+
class TestDynamicClass(TestPropertyAccess.ThrowingDynamicFixture):
1438+
def __init__(self):
1439+
self.test_attribute = 11;
1440+
1441+
def has_attribute(obj, attribute):
1442+
return hasattr(obj, attribute)
1443+
");
1444+
1445+
dynamic fixture = module.GetAttr("TestDynamicClass")();
1446+
dynamic hasAttribute = module.GetAttr("has_attribute");
1447+
1448+
var hasAttributeResult = false;
1449+
Assert.DoesNotThrow(() =>
1450+
{
1451+
hasAttributeResult = hasAttribute(fixture, "test_attribute");
1452+
});
1453+
Assert.IsTrue(hasAttributeResult);
1454+
1455+
var attribute = 0;
1456+
Assert.DoesNotThrow(() =>
1457+
{
1458+
attribute = fixture.test_attribute.As<int>();
1459+
});
1460+
Assert.AreEqual(11, attribute);
1461+
1462+
Assert.DoesNotThrow(() =>
1463+
{
1464+
hasAttributeResult = hasAttribute(fixture, "non_existent_attribute");
1465+
});
1466+
Assert.IsFalse(hasAttributeResult);
1467+
}
1468+
14131469
public interface IModel
14141470
{
14151471
void InvokeModel();

src/perf_tests/Python.PerformanceTests.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1414
</PackageReference>
1515
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
16-
<PackageReference Include="quantconnect.pythonnet" Version="2.0.40" GeneratePathProperty="true">
16+
<PackageReference Include="quantconnect.pythonnet" Version="2.0.41" GeneratePathProperty="true">
1717
<IncludeAssets>compile</IncludeAssets>
1818
</PackageReference>
1919
</ItemGroup>
@@ -25,7 +25,7 @@
2525
</Target>
2626

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

3131
<Target Name="CopyNewBuild" AfterTargets="Build">

src/runtime/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
[assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]
55
[assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]
66

7-
[assembly: AssemblyVersion("2.0.40")]
8-
[assembly: AssemblyFileVersion("2.0.40")]
7+
[assembly: AssemblyVersion("2.0.41")]
8+
[assembly: AssemblyFileVersion("2.0.41")]

src/runtime/Python.Runtime.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<RootNamespace>Python.Runtime</RootNamespace>
66
<AssemblyName>Python.Runtime</AssemblyName>
77
<PackageId>QuantConnect.pythonnet</PackageId>
8-
<Version>2.0.40</Version>
8+
<Version>2.0.41</Version>
99
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
1010
<PackageLicenseFile>LICENSE</PackageLicenseFile>
1111
<RepositoryUrl>https://github.com/pythonnet/pythonnet</RepositoryUrl>

src/runtime/Types/DynamicClassObject.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,12 @@ public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference k
8888
catch (Exception exception)
8989
{
9090
Exceptions.Clear();
91-
Exceptions.SetError(exception);
91+
// tp_getattro should call PyObject_GenericGetAttr (which we already did)
92+
// which must throw AttributeError if the attribute is not found (see https://docs.python.org/3/c-api/object.html#c.PyObject_GenericGetAttr)
93+
// So if we are throwing anything, it must be AttributeError.
94+
// e.g hasattr uses this method to check if the attribute exists. If we throw anything other than AttributeError,
95+
// hasattr will throw instead of catching and returning False.
96+
Exceptions.SetError(Exceptions.AttributeError, exception.Message);
9297
}
9398
}
9499

0 commit comments

Comments
 (0)