diff --git a/AUTHORS.md b/AUTHORS.md index 98cb1af39..5917944a8 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -28,6 +28,7 @@ - Jeff Reback ([@jreback](https://github.com/jreback)) - Joe Frayne ([@jfrayne](https://github.com/jfrayne)) - John Burnett ([@johnburnett](https://github.com/johnburnett)) +- John Wilkes ([@jbw3](https://github.com/jbw3)) - Luke Stratman ([@lstratman](https://github.com/lstratman)) - Konstantin Posudevskiy ([@konstantin-posudevskiy](https://github.com/konstantin-posudevskiy)) - Matthias Dittrich ([@matthid](https://github.com/matthid)) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d39fc444..c30b4c393 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added `clr.GetClrType` (#432, #433) - Allowed passing `None` for nullable args (#460) - Added keyword arguments based on C# syntax for calling CPython methods (#461) +- Implemented GetDynamicMemberNames() for PyObject to allow dynamic object members to be visible in the debugger (#443) ### Changed diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index fe02b0526..66e8c7165 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -93,6 +93,7 @@ <Compile Include="TestPyList.cs" /> <Compile Include="TestPyLong.cs" /> <Compile Include="TestPyNumber.cs" /> + <Compile Include="TestPyObject.cs" /> <Compile Include="TestPySequence.cs" /> <Compile Include="TestPyString.cs" /> <Compile Include="TestPythonException.cs" /> diff --git a/src/embed_tests/TestPyObject.cs b/src/embed_tests/TestPyObject.cs new file mode 100644 index 000000000..d794ce06e --- /dev/null +++ b/src/embed_tests/TestPyObject.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest +{ + public class TestPyObject + { + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + [Test] + public void TestGetDynamicMemberNames() + { + List<string> expectedMemberNames = new List<string> + { + "add", + "getNumber", + "member1", + "member2" + }; + + PyDict locals = new PyDict(); + + PythonEngine.Exec(@" +class MemberNamesTest(object): + def __init__(self): + self.member1 = 123 + self.member2 = 'Test string' + + def getNumber(self): + return 123 + + def add(self, x, y): + return x + y + +a = MemberNamesTest() +", null, locals.Handle); + + PyObject a = locals.GetItem("a"); + + IEnumerable<string> memberNames = a.GetDynamicMemberNames(); + + foreach (string expectedName in expectedMemberNames) + { + Assert.IsTrue(memberNames.Contains(expectedName), "Could not find member '{0}'.", expectedName); + } + } + } +} diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index 4da74f96a..0e075824a 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Dynamic; using System.Linq.Expressions; @@ -1238,5 +1239,20 @@ public override bool TryUnaryOperation(UnaryOperationBinder binder, out object r result = CheckNone(new PyObject(res)); return true; } + + /// <summary> + /// Returns the enumeration of all dynamic member names. + /// </summary> + /// <remarks> + /// This method exists for debugging purposes only. + /// </remarks> + /// <returns>A sequence that contains dynamic member names.</returns> + public override IEnumerable<string> GetDynamicMemberNames() + { + foreach (PyObject pyObj in Dir()) + { + yield return pyObj.ToString(); + } + } } }