From b00a2e8c734edce2bcc9f1036ad1b6fc667b1083 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 3 Dec 2025 06:56:34 +0000 Subject: [PATCH 1/4] Add test for nested list decoding and improve ListEncoderDecoder Co-authored-by: ipetrov --- DSPythonNet3/DSPythonNet3.csproj | 3 +- DSPythonNet3/Encoders/ListEncodeDecoder.cs | 61 ++++++++++++++++++-- DSpythonNet3Tests/ListEncoderDecoderTests.cs | 47 +++++++++++++++ 3 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 DSpythonNet3Tests/ListEncoderDecoderTests.cs diff --git a/DSPythonNet3/DSPythonNet3.csproj b/DSPythonNet3/DSPythonNet3.csproj index 2554157..4fa0fa6 100644 --- a/DSPythonNet3/DSPythonNet3.csproj +++ b/DSPythonNet3/DSPythonNet3.csproj @@ -1,4 +1,4 @@ - + {F1541C2D-80A9-4FE7-8D9E-75A8B9FF3479} Library @@ -10,6 +10,7 @@ + diff --git a/DSPythonNet3/Encoders/ListEncodeDecoder.cs b/DSPythonNet3/Encoders/ListEncodeDecoder.cs index 91b8c18..6abddf8 100644 --- a/DSPythonNet3/Encoders/ListEncodeDecoder.cs +++ b/DSPythonNet3/Encoders/ListEncodeDecoder.cs @@ -28,18 +28,18 @@ public bool TryDecode(PyObject pyObj, out T value) return false; } - using (var pyList = PyList.AsList(pyObj)) + if (typeof(T).IsGenericType) { - if (typeof(T).IsGenericType) + using (var pyList = PyList.AsList(pyObj)) { value = pyList.ToList(); } - else - { - value = (T)pyList.ToList(); - } return true; } + + var converted = ConvertToArrayList(pyObj); + value = (T)converted; + return true; } public PyObject TryEncode(object value) @@ -57,5 +57,54 @@ bool IPyObjectDecoder.CanDecode(PyType objectType, Type targetType) } return decodableTypes.IndexOf(targetType) >= 0; } + + private static IList ConvertToArrayList(PyObject pyObj) + { + using var pyList = PyList.AsList(pyObj); + var result = new ArrayList(); + foreach (PyObject item in pyList) + { + using (item) + { + result.Add(ConvertItem(item)); + } + } + + return result; + } + + private static object ConvertItem(PyObject item) + { + if (TryGetClrObject(item, out var clrObject)) + { + return clrObject; + } + + if (PyString.IsStringType(item)) + { + return item.AsManagedObject(typeof(string)); + } + + if (PyList.IsListType(item) || PyTuple.IsTupleType(item)) + { + return ConvertToArrayList(item); + } + + return item.AsManagedObject(typeof(object)); + } + + private static bool TryGetClrObject(PyObject pyObj, out object clrObject) + { + try + { + clrObject = pyObj.GetManagedObject(); + return true; + } + catch + { + clrObject = null; + return false; + } + } } } diff --git a/DSpythonNet3Tests/ListEncoderDecoderTests.cs b/DSpythonNet3Tests/ListEncoderDecoderTests.cs new file mode 100644 index 0000000..ba43e9a --- /dev/null +++ b/DSpythonNet3Tests/ListEncoderDecoderTests.cs @@ -0,0 +1,47 @@ +using System.Collections; +using DSPythonNet3; +using DSPythonNet3.Encoders; +using NUnit.Framework; +using Python.Runtime; + +namespace DSPythonNet3Tests +{ + public class ListEncoderDecoderTests + { + [Test] + public void TryDecode_ConvertsNestedPythonListsToClrLists() + { + DSPythonNet3Evaluator.InitializePython(); + + using (Py.GIL()) + using (var scope = Py.CreateScope()) + { + scope.Exec("value = [[1, [2, 3]], ['a', ['b']]]"); + using var pyList = scope.Get("value"); + + var decoder = new ListEncoderDecoder(); + var success = decoder.TryDecode(pyList, out IList result); + + Assert.That(success, Is.True); + Assert.That(result, Is.InstanceOf()); + + var first = result[0] as IList; + Assert.That(first, Is.Not.Null); + Assert.That(first[0], Is.EqualTo(1)); + + var secondLevel = first?[1] as IList; + Assert.That(secondLevel, Is.Not.Null); + Assert.That(secondLevel?[0], Is.EqualTo(2)); + Assert.That(secondLevel?[1], Is.EqualTo(3)); + + var second = result[1] as IList; + Assert.That(second, Is.Not.Null); + Assert.That(second?[0], Is.EqualTo("a")); + + var thirdLevel = second?[1] as IList; + Assert.That(thirdLevel, Is.Not.Null); + Assert.That(thirdLevel?[0], Is.EqualTo("b")); + } + } + } +} From 6ad210a046919c92031b1a322cb0e457a3e6eea0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 3 Dec 2025 07:55:51 +0000 Subject: [PATCH 2/4] Fix: Return false if GetManagedObject returns null Co-authored-by: ipetrov --- DSPythonNet3/Encoders/ListEncodeDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DSPythonNet3/Encoders/ListEncodeDecoder.cs b/DSPythonNet3/Encoders/ListEncodeDecoder.cs index 6abddf8..fd2a48d 100644 --- a/DSPythonNet3/Encoders/ListEncodeDecoder.cs +++ b/DSPythonNet3/Encoders/ListEncodeDecoder.cs @@ -98,7 +98,7 @@ private static bool TryGetClrObject(PyObject pyObj, out object clrObject) try { clrObject = pyObj.GetManagedObject(); - return true; + return clrObject != null; } catch { From fe165c596dba02c7518bbb63a5971666b6b694fc Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 3 Dec 2025 09:47:55 +0000 Subject: [PATCH 3/4] Refactor ListEncoderDecoderTests to use EvaluatePythonScript Co-authored-by: ipetrov --- DSpythonNet3Tests/ListEncoderDecoderTests.cs | 65 +++++++++++--------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/DSpythonNet3Tests/ListEncoderDecoderTests.cs b/DSpythonNet3Tests/ListEncoderDecoderTests.cs index ba43e9a..70838db 100644 --- a/DSpythonNet3Tests/ListEncoderDecoderTests.cs +++ b/DSpythonNet3Tests/ListEncoderDecoderTests.cs @@ -1,8 +1,6 @@ using System.Collections; -using DSPythonNet3; -using DSPythonNet3.Encoders; using NUnit.Framework; -using Python.Runtime; +using DSPythonNet3; namespace DSPythonNet3Tests { @@ -13,35 +11,46 @@ public void TryDecode_ConvertsNestedPythonListsToClrLists() { DSPythonNet3Evaluator.InitializePython(); - using (Py.GIL()) - using (var scope = Py.CreateScope()) - { - scope.Exec("value = [[1, [2, 3]], ['a', ['b']]]"); - using var pyList = scope.Get("value"); - - var decoder = new ListEncoderDecoder(); - var success = decoder.TryDecode(pyList, out IList result); + string code = @" +import clr +clr.AddReference('DSCoreNodes') +from DSCore import List - Assert.That(success, Is.True); - Assert.That(result, Is.InstanceOf()); - - var first = result[0] as IList; - Assert.That(first, Is.Not.Null); - Assert.That(first[0], Is.EqualTo(1)); - - var secondLevel = first?[1] as IList; - Assert.That(secondLevel, Is.Not.Null); - Assert.That(secondLevel?[0], Is.EqualTo(2)); - Assert.That(secondLevel?[1], Is.EqualTo(3)); +data = [[1, 2, 3], [4, 5, 6]] +OUT = data, List.Flatten(data, -1) +"; + var empty = new ArrayList(); + var expected = new ArrayList + { + new ArrayList + { + new ArrayList { 1, 2, 3 }, + new ArrayList { 4, 5, 6 } + }, + new ArrayList { 1, 2, 3, 4, 5, 6 } + }; + + var result = DSPythonNet3Evaluator.EvaluatePythonScript(code, empty, empty); + Assert.That(result, Is.InstanceOf()); + + var normalizedResult = NormalizeResult(result); + CollectionAssert.AreEqual(expected, normalizedResult as IEnumerable); + } - var second = result[1] as IList; - Assert.That(second, Is.Not.Null); - Assert.That(second?[0], Is.EqualTo("a")); + private static object NormalizeResult(object value) + { + if (value is string || value is not IEnumerable enumerable) + { + return value; + } - var thirdLevel = second?[1] as IList; - Assert.That(thirdLevel, Is.Not.Null); - Assert.That(thirdLevel?[0], Is.EqualTo("b")); + var list = new ArrayList(); + foreach (var item in enumerable) + { + list.Add(NormalizeResult(item)); } + + return list; } } } From 1e739b849540c75092e9e0630f9b889165f5b216 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 3 Dec 2025 15:27:03 +0000 Subject: [PATCH 4/4] Test List.Flatten with nested lists and multiple levels Co-authored-by: ipetrov --- DSpythonNet3Tests/ListEncoderDecoderTests.cs | 24 ++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/DSpythonNet3Tests/ListEncoderDecoderTests.cs b/DSpythonNet3Tests/ListEncoderDecoderTests.cs index 70838db..b4d7f8c 100644 --- a/DSpythonNet3Tests/ListEncoderDecoderTests.cs +++ b/DSpythonNet3Tests/ListEncoderDecoderTests.cs @@ -16,17 +16,33 @@ import clr clr.AddReference('DSCoreNodes') from DSCore import List -data = [[1, 2, 3], [4, 5, 6]] -OUT = data, List.Flatten(data, -1) +data = [[[1, 2], [3]], [[4, 5], [6]]] +OUT = data, List.Flatten(data, 1), List.Flatten(data, 2), List.Flatten(data, -1) "; var empty = new ArrayList(); var expected = new ArrayList { new ArrayList { - new ArrayList { 1, 2, 3 }, - new ArrayList { 4, 5, 6 } + new ArrayList + { + new ArrayList { 1, 2 }, + new ArrayList { 3 } + }, + new ArrayList + { + new ArrayList { 4, 5 }, + new ArrayList { 6 } + } }, + new ArrayList + { + new ArrayList { 1, 2 }, + new ArrayList { 3 }, + new ArrayList { 4, 5 }, + new ArrayList { 6 } + }, + new ArrayList { 1, 2, 3, 4, 5, 6 }, new ArrayList { 1, 2, 3, 4, 5, 6 } };