Skip to content

Commit 8eaceea

Browse files
committed
Add DynamicClassObject to handle c# dynamic object only
1 parent eb4089c commit 8eaceea

File tree

4 files changed

+240
-169
lines changed

4 files changed

+240
-169
lines changed

src/embed_tests/TestPropertyAccess.cs

Lines changed: 105 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -927,76 +927,6 @@ def SetValue(self):
927927
}
928928
}
929929

930-
private static TestCaseData[] DynamicPropertiesGetterTestCases() => new[]
931-
{
932-
new TestCaseData(true),
933-
new TestCaseData(10),
934-
new TestCaseData(10.1),
935-
new TestCaseData(10.2m),
936-
new TestCaseData("Some string"),
937-
new TestCaseData(new DateTime(2023, 6, 22)),
938-
new TestCaseData(new List<int> { 1, 2, 3, 4, 5 }),
939-
new TestCaseData(new Dictionary<string, int> { { "first", 1 }, { "second", 2 }, { "third", 3 } }),
940-
new TestCaseData(new Fixture()),
941-
};
942-
943-
[TestCaseSource(nameof(DynamicPropertiesGetterTestCases))]
944-
public void TestGetPublicDynamicObjectPropertyWorks(object property)
945-
{
946-
dynamic model = PyModule.FromString("module", @"
947-
from clr import AddReference
948-
AddReference(""Python.EmbeddingTest"")
949-
AddReference(""System"")
950-
951-
from Python.EmbeddingTest import *
952-
953-
class TestGetPublicDynamicObjectPropertyWorks:
954-
def GetValue(self, fixture):
955-
return fixture.SomeProperty
956-
").GetAttr("TestGetPublicDynamicObjectPropertyWorks").Invoke();
957-
958-
dynamic fixture = new DynamicFixture();
959-
fixture.SomeProperty = property;
960-
961-
using (Py.GIL())
962-
{
963-
Assert.AreEqual(property, (model.GetValue(fixture) as PyObject).AsManagedObject(property.GetType()));
964-
}
965-
}
966-
967-
[Test]
968-
public void TestGetNonExistingPublicDynamicObjectPropertyThrows()
969-
{
970-
dynamic model = PyModule.FromString("module", @"
971-
from clr import AddReference
972-
AddReference(""Python.EmbeddingTest"")
973-
AddReference(""System"")
974-
975-
from Python.EmbeddingTest import *
976-
977-
class TestGetNonExistingPublicDynamicObjectPropertyThrows:
978-
def GetValue(self, fixture):
979-
try:
980-
prop = fixture.AnotherProperty
981-
except AttributeError as e:
982-
return e
983-
984-
return None
985-
").GetAttr("TestGetNonExistingPublicDynamicObjectPropertyThrows").Invoke();
986-
987-
dynamic fixture = new DynamicFixture();
988-
fixture.SomeProperty = "Some property";
989-
990-
using (Py.GIL())
991-
{
992-
var result = model.GetValue(fixture) as PyObject;
993-
Assert.IsFalse(result.IsNone());
994-
Assert.AreEqual(result.PyType, Exceptions.AttributeError);
995-
Assert.AreEqual("'Python.EmbeddingTest.TestPropertyAccess+DynamicFixture' object has no attribute 'AnotherProperty'",
996-
result.ToString());
997-
}
998-
}
999-
1000930
public class DynamicFixture : DynamicObject
1001931
{
1002932
private Dictionary<string, object> _properties = new Dictionary<string, object>();
@@ -1026,6 +956,10 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o
1026956
return false;
1027957
}
1028958
}
959+
960+
public Dictionary<string, object> Properties { get { return _properties; } }
961+
962+
public string NonDynamicProperty { get; set; }
1029963
}
1030964

1031965
public class TestPerson : IComparable, IComparable<TestPerson>
@@ -1064,6 +998,76 @@ public bool Equals(TestPerson other)
1064998
}
1065999
}
10661000

1001+
private static TestCaseData[] DynamicPropertiesGetterTestCases() => new[]
1002+
{
1003+
new TestCaseData(true),
1004+
new TestCaseData(10),
1005+
new TestCaseData(10.1),
1006+
new TestCaseData(10.2m),
1007+
new TestCaseData("Some string"),
1008+
new TestCaseData(new DateTime(2023, 6, 22)),
1009+
new TestCaseData(new List<int> { 1, 2, 3, 4, 5 }),
1010+
new TestCaseData(new Dictionary<string, int> { { "first", 1 }, { "second", 2 }, { "third", 3 } }),
1011+
new TestCaseData(new Fixture()),
1012+
};
1013+
1014+
[TestCaseSource(nameof(DynamicPropertiesGetterTestCases))]
1015+
public void TestGetPublicDynamicObjectPropertyWorks(object property)
1016+
{
1017+
dynamic model = PyModule.FromString("module", @"
1018+
from clr import AddReference
1019+
AddReference(""Python.EmbeddingTest"")
1020+
AddReference(""System"")
1021+
1022+
from Python.EmbeddingTest import *
1023+
1024+
class TestGetPublicDynamicObjectPropertyWorks:
1025+
def GetValue(self, fixture):
1026+
return fixture.DynamicProperty
1027+
").GetAttr("TestGetPublicDynamicObjectPropertyWorks").Invoke();
1028+
1029+
dynamic fixture = new DynamicFixture();
1030+
fixture.DynamicProperty = property;
1031+
1032+
using (Py.GIL())
1033+
{
1034+
Assert.AreEqual(property, (model.GetValue(fixture) as PyObject).AsManagedObject(property.GetType()));
1035+
}
1036+
}
1037+
1038+
[Test]
1039+
public void TestGetNonExistingPublicDynamicObjectPropertyThrows()
1040+
{
1041+
dynamic model = PyModule.FromString("module", @"
1042+
from clr import AddReference
1043+
AddReference(""Python.EmbeddingTest"")
1044+
AddReference(""System"")
1045+
1046+
from Python.EmbeddingTest import *
1047+
1048+
class TestGetNonExistingPublicDynamicObjectPropertyThrows:
1049+
def GetValue(self, fixture):
1050+
try:
1051+
prop = fixture.AnotherProperty
1052+
except AttributeError as e:
1053+
return e
1054+
1055+
return None
1056+
").GetAttr("TestGetNonExistingPublicDynamicObjectPropertyThrows").Invoke();
1057+
1058+
dynamic fixture = new DynamicFixture();
1059+
fixture.DynamicProperty = "Dynamic property";
1060+
1061+
using (Py.GIL())
1062+
{
1063+
var result = model.GetValue(fixture) as PyObject;
1064+
Assert.IsFalse(result.IsNone());
1065+
Assert.AreEqual(result.PyType, Exceptions.AttributeError);
1066+
Assert.AreEqual("'Python.EmbeddingTest.TestPropertyAccess+DynamicFixture' object has no attribute 'AnotherProperty'",
1067+
result.ToString());
1068+
}
1069+
}
1070+
10671071
private static TestCaseData[] DynamicPropertiesSetterTestCases() => new[]
10681072
{
10691073
new TestCaseData("True", null),
@@ -1093,7 +1097,7 @@ from Python.EmbeddingTest import *
10931097
10941098
class TestGetPublicDynamicObjectPropertyWorks:
10951099
def SetValue(self, fixture):
1096-
fixture.SomeProperty = value
1100+
fixture.DynamicProperty = value
10971101
10981102
def GetPythonValue(self):
10991103
return value
@@ -1107,7 +1111,36 @@ def GetPythonValue(self):
11071111
var expectedAsPyObject = model.GetPythonValue() as PyObject;
11081112
var expected = expectedType != null ? expectedAsPyObject.AsManagedObject(expectedType) : expectedAsPyObject;
11091113

1110-
Assert.AreEqual(expected, fixture.SomeProperty);
1114+
Assert.AreEqual(expected, fixture.DynamicProperty);
1115+
}
1116+
}
1117+
1118+
[Test]
1119+
public void TestSetPublicNonDynamicObjectPropertyToActualPropertyWorks()
1120+
{
1121+
var expected = "Non Dynamic Property";
1122+
dynamic model = PyModule.FromString("module", $@"
1123+
from clr import AddReference
1124+
AddReference(""Python.EmbeddingTest"")
1125+
AddReference(""System"")
1126+
1127+
from datetime import datetime
1128+
import System
1129+
from Python.EmbeddingTest import *
1130+
1131+
class TestGetPublicDynamicObjectPropertyWorks:
1132+
def SetValue(self, fixture):
1133+
fixture.NonDynamicProperty = ""{expected}""
1134+
").GetAttr("TestGetPublicDynamicObjectPropertyWorks").Invoke();
1135+
1136+
var fixture = new DynamicFixture();
1137+
1138+
using (Py.GIL())
1139+
{
1140+
model.SetValue(fixture);
1141+
Assert.AreEqual(expected, fixture.NonDynamicProperty);
1142+
Assert.AreEqual(expected, ((dynamic)fixture).NonDynamicProperty);
1143+
Assert.IsFalse(fixture.Properties.ContainsKey(nameof(fixture.NonDynamicProperty)));
11111144
}
11121145
}
11131146

src/runtime/ClassManager.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics;
4+
using System.Dynamic;
45
using System.Linq;
56
using System.Reflection;
67
using System.Runtime.InteropServices;
@@ -183,6 +184,11 @@ internal static ClassBase CreateClass(Type type)
183184
impl = new KeyValuePairEnumerableObject(type);
184185
}
185186

187+
else if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(type))
188+
{
189+
impl = new DynamicClassObject(type);
190+
}
191+
186192
else if (type.IsInterface)
187193
{
188194
impl = new InterfaceObject(type);
@@ -221,7 +227,7 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p
221227
impl.indexer = info.indexer;
222228
impl.richcompare.Clear();
223229

224-
230+
225231
// Finally, initialize the class __dict__ and return the object.
226232
using var newDict = Runtime.PyObject_GenericGetDict(pyType.Reference);
227233
BorrowedReference dict = newDict.Borrow();

src/runtime/Types/ClassObject.cs

Lines changed: 0 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
using System;
22
using System.Diagnostics;
3-
using System.Dynamic;
43
using System.Linq;
54
using System.Reflection;
6-
using System.Runtime.CompilerServices;
75
using System.Runtime.Serialization;
8-
using RuntimeBinder = Microsoft.CSharp.RuntimeBinder;
96

107
namespace Python.Runtime
118
{
@@ -278,98 +275,5 @@ public override NewReference type_subscript(BorrowedReference idx)
278275
}
279276
return Exceptions.RaiseTypeError("unsubscriptable object");
280277
}
281-
282-
/// <summary>
283-
/// Type __getattro__ implementation.
284-
/// </summary>
285-
public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference key)
286-
{
287-
288-
if (!Runtime.PyString_Check(key))
289-
{
290-
return Exceptions.RaiseTypeError("string expected");
291-
}
292-
293-
var result = Runtime.PyObject_GenericGetAttr(ob, key);
294-
295-
// Property not found, but it can still be a dynamic one if the object is an IDynamicMetaObjectProvider
296-
if (result.IsNull())
297-
{
298-
var clrObj = (CLRObject)GetManagedObject(ob)!;
299-
if (clrObj?.inst is IDynamicMetaObjectProvider)
300-
{
301-
302-
// The call to Runtime.PyObject_GenericGetAttr above ended up with an AttributeError
303-
// for dynamic properties since they are not found.
304-
if (Exceptions.ExceptionMatches(Exceptions.AttributeError))
305-
{
306-
Exceptions.Clear();
307-
}
308-
309-
// TODO: Cache call site.
310-
311-
var name = Runtime.GetManagedString(key);
312-
var binder = RuntimeBinder.Binder.GetMember(
313-
RuntimeBinder.CSharpBinderFlags.None,
314-
name,
315-
clrObj.inst.GetType(),
316-
new[] { RuntimeBinder.CSharpArgumentInfo.Create(RuntimeBinder.CSharpArgumentInfoFlags.None, null) });
317-
var callsite = CallSite<Func<CallSite, object, object>>.Create(binder);
318-
319-
try
320-
{
321-
var res = callsite.Target(callsite, clrObj.inst);
322-
return Converter.ToPython(res);
323-
}
324-
catch (RuntimeBinder.RuntimeBinderException)
325-
{
326-
Exceptions.SetError(Exceptions.AttributeError, $"'{clrObj?.inst.GetType()}' object has no attribute '{name}'");
327-
}
328-
}
329-
}
330-
331-
return result;
332-
}
333-
334-
/// <summary>
335-
/// Type __setattro__ implementation.
336-
/// </summary>
337-
public static int tp_setattro(BorrowedReference ob, BorrowedReference key, BorrowedReference val)
338-
{
339-
if (!Runtime.PyString_Check(key))
340-
{
341-
Exceptions.RaiseTypeError("string expected");
342-
return -1;
343-
}
344-
345-
// If the object is an IDynamicMetaObjectProvider, the property is set as a C# dynamic property, not as a Python attribute
346-
var clrObj = (CLRObject)GetManagedObject(ob)!;
347-
if (clrObj?.inst is IDynamicMetaObjectProvider)
348-
{
349-
// TODO: Cache call site.
350-
351-
var name = Runtime.GetManagedString(key);
352-
var binder = RuntimeBinder.Binder.SetMember(
353-
RuntimeBinder.CSharpBinderFlags.None,
354-
name,
355-
clrObj.inst.GetType(),
356-
new[]
357-
{
358-
RuntimeBinder.CSharpArgumentInfo.Create(RuntimeBinder.CSharpArgumentInfoFlags.None, null),
359-
RuntimeBinder.CSharpArgumentInfo.Create(RuntimeBinder.CSharpArgumentInfoFlags.None, null)
360-
});
361-
var callsite = CallSite<Func<CallSite, object, object, object>>.Create(binder);
362-
363-
var value = ((CLRObject)GetManagedObject(val))?.inst ?? PyObject.FromNullableReference(val);
364-
callsite.Target(callsite, clrObj.inst, value);
365-
366-
return 0;
367-
}
368-
369-
int res = Runtime.PyObject_GenericSetAttr(ob, key, val);
370-
Runtime.PyType_Modified(ob);
371-
372-
return res;
373-
}
374278
}
375279
}

0 commit comments

Comments
 (0)