Skip to content

Commit 100c038

Browse files
committed
Allow accessing protected properties of managed dynamic objects
1 parent 715caa9 commit 100c038

File tree

2 files changed

+44
-3
lines changed

2 files changed

+44
-3
lines changed

src/embed_tests/TestPropertyAccess.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,12 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o
960960
public Dictionary<string, object> Properties { get { return _properties; } }
961961

962962
public string NonDynamicProperty { get; set; }
963+
964+
protected string NonDynamicProtectedProperty { get; set; } = "Default value";
965+
966+
protected static string NonDynamicProtectedStaticProperty { get; set; } = "Default value";
967+
968+
protected string NonDynamicProtectedField = "Default value";
963969
}
964970

965971
public class TestPerson : IComparable, IComparable<TestPerson>
@@ -1265,6 +1271,37 @@ def SetValue(self, fixture):
12651271
}
12661272
}
12671273

1274+
[TestCase("NonDynamicProtectedProperty")]
1275+
[TestCase("NonDynamicProtectedField")]
1276+
[TestCase("NonDynamicProtectedStaticProperty")]
1277+
public void TestSetPublicNonDynamicObjectProtectedPropertyToActualPropertyWorks(string attributeName)
1278+
{
1279+
var expected = "Non Dynamic Protected Property";
1280+
dynamic model = PyModule.FromString("module", $@"
1281+
from clr import AddReference
1282+
AddReference(""Python.EmbeddingTest"")
1283+
AddReference(""System"")
1284+
1285+
from datetime import datetime
1286+
import System
1287+
from Python.EmbeddingTest import *
1288+
1289+
class RandomTestDynamicClass(TestPropertyAccess.DynamicFixture):
1290+
def SetValue(self):
1291+
self.{attributeName} = ""{expected}""
1292+
").GetAttr("RandomTestDynamicClass").Invoke();
1293+
1294+
using (Py.GIL())
1295+
{
1296+
Assert.AreNotEqual(expected, model.GetAttr(attributeName).As<string>());
1297+
1298+
model.SetValue();
1299+
1300+
Assert.AreEqual(expected, model.GetAttr(attributeName).As<string>());
1301+
Assert.IsFalse(model.Properties.ContainsKey(attributeName).As<bool>());
1302+
}
1303+
}
1304+
12681305
[Explicit]
12691306
[TestCase(true, TestName = "CSharpGetPropertyPerformance")]
12701307
[TestCase(false, TestName = "PythonGetPropertyPerformance")]

src/runtime/Types/DynamicClassObject.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Dynamic;
4+
using System.Reflection;
45
using System.Runtime.CompilerServices;
56

67
using RuntimeBinder = Microsoft.CSharp.RuntimeBinder;
@@ -87,7 +88,7 @@ public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference k
8788
// Do nothing, AttributeError was already raised in Python side and it was not cleared.
8889
}
8990
// Catch C# exceptions and raise them as Python exceptions.
90-
catch(Exception exception)
91+
catch (Exception exception)
9192
{
9293
Exceptions.Clear();
9394
Exceptions.SetError(exception);
@@ -105,9 +106,12 @@ public static int tp_setattro(BorrowedReference ob, BorrowedReference key, Borro
105106
var clrObj = (CLRObject)GetManagedObject(ob)!;
106107
var name = Runtime.GetManagedString(key);
107108

108-
// If the key corresponds to a member of the class, we let the default implementation handle it.
109+
// If the key corresponds to a valid property or field of the class, we let the default implementation handle it.
109110
var clrObjectType = clrObj.inst.GetType();
110-
if (clrObjectType.GetMember(name).Length != 0)
111+
var bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
112+
var property = clrObjectType.GetProperty(name, bindingFlags);
113+
var field = property == null ? clrObjectType.GetField(name, bindingFlags) : null;
114+
if ((property != null && property.SetMethod != null) || field != null)
111115
{
112116
return Runtime.PyObject_GenericSetAttr(ob, key, val);
113117
}

0 commit comments

Comments
 (0)