Skip to content

Commit 65d2ad3

Browse files
committed
Keep dynamic class properties as python objects.
This avoids loosing python object references when they are instances of PythonClasses that inherit C# classes.
1 parent 55ac3b4 commit 65d2ad3

File tree

2 files changed

+69
-6
lines changed

2 files changed

+69
-6
lines changed

src/embed_tests/TestPropertyAccess.cs

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,67 @@ def GetValue(self, fixture):
10961096
}
10971097
}
10981098

1099+
public class CSharpTestClass
1100+
{
1101+
public string CSharpProperty { get; set; }
1102+
}
1103+
1104+
[Test]
1105+
public void TestKeepsPythonReferenceForDynamicPropertiesFromPythonClassDerivedFromCSharpClass()
1106+
{
1107+
var expectedCSharpPropertyValue = "C# property";
1108+
var expectedPythonPropertyValue = "Python property";
1109+
1110+
var testModule = PyModule.FromString("module", $@"
1111+
from clr import AddReference
1112+
AddReference(""Python.EmbeddingTest"")
1113+
AddReference(""System"")
1114+
1115+
from Python.EmbeddingTest import TestPropertyAccess
1116+
1117+
class PythonTestClass(TestPropertyAccess.CSharpTestClass):
1118+
def __init__(self):
1119+
super().__init__()
1120+
1121+
def SetPythonObjectToFixture(fixture: TestPropertyAccess.DynamicFixture) -> None:
1122+
obj = PythonTestClass()
1123+
obj.CSharpProperty = '{expectedCSharpPropertyValue}'
1124+
obj.PythonProperty = '{expectedPythonPropertyValue}'
1125+
fixture.PythonClassObject = obj
1126+
1127+
def AssertPythonClassObjectType(fixture: TestPropertyAccess.DynamicFixture) -> None:
1128+
if type(fixture.PythonClassObject) != PythonTestClass:
1129+
raise Exception('PythonClassObject is not of type PythonTestClass')
1130+
1131+
def AccessCSharpProperty(fixture: TestPropertyAccess.DynamicFixture) -> str:
1132+
return fixture.PythonClassObject.CSharpProperty
1133+
1134+
def AccessPythonProperty(fixture: TestPropertyAccess.DynamicFixture) -> str:
1135+
return fixture.PythonClassObject.PythonProperty
1136+
");
1137+
1138+
dynamic fixture = new DynamicFixture();
1139+
1140+
using (Py.GIL())
1141+
{
1142+
dynamic SetPythonObjectToFixture = testModule.GetAttr("SetPythonObjectToFixture");
1143+
SetPythonObjectToFixture(fixture);
1144+
1145+
dynamic AssertPythonClassObjectType = testModule.GetAttr("AssertPythonClassObjectType");
1146+
Assert.DoesNotThrow(() => AssertPythonClassObjectType(fixture));
1147+
1148+
// Access the C# class property
1149+
dynamic AccessCSharpProperty = testModule.GetAttr("AccessCSharpProperty");
1150+
Assert.AreEqual(expectedCSharpPropertyValue, AccessCSharpProperty(fixture).As<string>());
1151+
Assert.AreEqual(expectedCSharpPropertyValue, fixture.PythonClassObject.CSharpProperty.As<string>());
1152+
1153+
// Access the Python class property
1154+
dynamic AccessPythonProperty = testModule.GetAttr("AccessPythonProperty");
1155+
Assert.AreEqual(expectedPythonPropertyValue, AccessPythonProperty(fixture).As<string>());
1156+
Assert.AreEqual(expectedPythonPropertyValue, fixture.PythonClassObject.PythonProperty.As<string>());
1157+
}
1158+
}
1159+
10991160
private static TestCaseData[] DynamicPropertiesSetterTestCases() => new[]
11001161
{
11011162
new TestCaseData("True", null),
@@ -1136,10 +1197,15 @@ def GetPythonValue(self):
11361197
using (Py.GIL())
11371198
{
11381199
model.SetValue(fixture);
1200+
11391201
var expectedAsPyObject = model.GetPythonValue() as PyObject;
1140-
var expected = expectedType != null ? expectedAsPyObject.AsManagedObject(expectedType) : expectedAsPyObject;
1202+
Assert.AreEqual(expectedAsPyObject, fixture.DynamicProperty);
1203+
1204+
if (expectedType != null)
1205+
{
1206+
Assert.AreEqual(expectedAsPyObject.AsManagedObject(expectedType), fixture.DynamicProperty.AsManagedObject(expectedType));
1207+
}
11411208

1142-
Assert.AreEqual(expected, fixture.DynamicProperty);
11431209
}
11441210
}
11451211

src/runtime/Types/DynamicClassObject.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,10 @@ public static int tp_setattro(BorrowedReference ob, BorrowedReference key, Borro
112112
return Runtime.PyObject_GenericSetAttr(ob, key, val);
113113
}
114114

115-
// If the value is a managed object, we get it from the reference. If it is a Python object, we assign it as is.
116-
var value = ((CLRObject)GetManagedObject(val))?.inst ?? PyObject.FromNullableReference(val);
117-
118115
var callsite = SetAttrCallSite(name, clrObjectType);
119116
try
120117
{
121-
callsite.Target(callsite, clrObj.inst, value);
118+
callsite.Target(callsite, clrObj.inst, PyObject.FromNullableReference(val));
122119
}
123120
// Catch C# exceptions and raise them as Python exceptions.
124121
catch (Exception exception)

0 commit comments

Comments
 (0)