Skip to content

Commit 77d394a

Browse files
committed
Throw AttributeError in tp_getattro for dynamic classes
Python api documentation indicates it should throw AttributeError
1 parent fc4e67e commit 77d394a

File tree

2 files changed

+62
-1
lines changed

2 files changed

+62
-1
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/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)