From b1b4539b0ffba1ddb1fd7ff9f36aca74b293fbdb Mon Sep 17 00:00:00 2001 From: Martin Misol Monzo Date: Wed, 15 Jul 2020 10:27:59 -0400 Subject: [PATCH 1/2] Attempt to simplify (does not work) --- src/Libraries/DSCPython/CPythonEvaluator.cs | 53 +++++++++---------- .../DynamoPythonTests/PythonEvalTests.cs | 29 ++++++++++ 2 files changed, 54 insertions(+), 28 deletions(-) diff --git a/src/Libraries/DSCPython/CPythonEvaluator.cs b/src/Libraries/DSCPython/CPythonEvaluator.cs index 2d9d1b747f2..064e327a12a 100644 --- a/src/Libraries/DSCPython/CPythonEvaluator.cs +++ b/src/Libraries/DSCPython/CPythonEvaluator.cs @@ -195,33 +195,7 @@ public static DataMarshaler OutputMarshaler outputMarshaler.RegisterMarshaler( delegate (PyObject pyObj) { - if (PyList.IsListType(pyObj)) - { - using (var pyList = new PyList(pyObj)) - { - var list = new List(); - foreach (PyObject item in pyList) - { - list.Add(outputMarshaler.Marshal(item)); - } - return list; - } - } - if (PyDict.IsDictType(pyObj)) - { - using (var pyDict = new PyDict(pyObj)) - { - var dict = new Dictionary(); - foreach (PyObject item in pyDict.Items()) - { - dict.Add( - outputMarshaler.Marshal(item.GetItem(0)), - outputMarshaler.Marshal(item.GetItem(1)) - ); - } - return dict; - } - } + // Special case: We want to return a long if possible, but fallback to BigInteger. if (PyLong.IsLongType(pyObj)) { using (var pyLong = PyLong.AsLong(pyObj)) @@ -237,7 +211,30 @@ public static DataMarshaler OutputMarshaler } } - var unmarshalled = pyObj.AsManagedObject(typeof(object)); + // Determine a target type to be caught by our decoders + Type targetType; + if (PyDict.IsDictType(pyObj)) + { + // Dictionaries are also iterable, so we want to check this first + targetType = typeof(IDictionary); + } + else if (PyString.IsStringType(pyObj)) + { + // Strings are iterable, but we want them as strings instead of lists + // Object should also work, but we can be more specific + targetType = typeof(string); + } + else if (PyIter.IsIterable(pyObj)) + { + targetType = typeof(IList); + } + else + { + // Give complete flexibility over the target type + targetType = typeof(object); + } + + var unmarshalled = pyObj.AsManagedObject(targetType); if (unmarshalled is PyObject) { using (unmarshalled as PyObject) diff --git a/test/Libraries/DynamoPythonTests/PythonEvalTests.cs b/test/Libraries/DynamoPythonTests/PythonEvalTests.cs index 0c42644b66f..245db7acaf3 100644 --- a/test/Libraries/DynamoPythonTests/PythonEvalTests.cs +++ b/test/Libraries/DynamoPythonTests/PythonEvalTests.cs @@ -195,5 +195,34 @@ class myobj: Assert.AreEqual("Output could not be converted to a .NET value", exc.Message); } } + + [Test] + public void NonListIterablesCanBeOutput() + { + var code = @" +s = { 'hello' } +fs = frozenset({ 'world' }) +d = { 'one': 1 } +dk = d.keys() +dv = d.values() +di = d.items() + +OUT = s,fs,dk,dv,di +"; + var expected = new ArrayList + { + new ArrayList { "hello" }, + new ArrayList { "world" }, + new ArrayList { "one" }, + new ArrayList { 1 }, + new ArrayList { new ArrayList { "one", 1 } } + }; + var empty = new ArrayList(); + foreach (var pythonEvaluator in Evaluators) + { + var output = pythonEvaluator(code, empty, empty); + Assert.AreEqual(expected, output); + } + } } } From a36433e48b7ec7a36a61f6b9df62133314ad5b0d Mon Sep 17 00:00:00 2001 From: Martin Misol Monzo Date: Wed, 15 Jul 2020 15:20:13 -0400 Subject: [PATCH 2/2] Marshall out iterables as lists in CPython Adds support for marshalling out iterables that are not lists or dictionaries using the CPython engine. In order to achieve this, the check PyIter.IsIterable was used, along with some extra considerations, mentioned in the source with comments. --- src/Libraries/DSCPython/CPythonEvaluator.cs | 66 +++++++++++++-------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/src/Libraries/DSCPython/CPythonEvaluator.cs b/src/Libraries/DSCPython/CPythonEvaluator.cs index 064e327a12a..80fb9300aa3 100644 --- a/src/Libraries/DSCPython/CPythonEvaluator.cs +++ b/src/Libraries/DSCPython/CPythonEvaluator.cs @@ -195,7 +195,44 @@ public static DataMarshaler OutputMarshaler outputMarshaler.RegisterMarshaler( delegate (PyObject pyObj) { - // Special case: We want to return a long if possible, but fallback to BigInteger. + // First, check if we are dealing with a wrapped .NET object. + // This simplifies the cases that come afterwards, as wrapped + // .NET collections pass some Python checks but not others. + var clrObj = pyObj.GetManagedObject(); + if (clrObj != null) + { + return outputMarshaler.Marshal(clrObj); + } + // Dictionaries are iterable, so they should come first + if (PyDict.IsDictType(pyObj)) + { + using (var pyDict = new PyDict(pyObj)) + { + var dict = new Dictionary(); + foreach (PyObject item in pyDict.Items()) + { + dict.Add( + outputMarshaler.Marshal(item.GetItem(0)), + outputMarshaler.Marshal(item.GetItem(1)) + ); + } + return dict; + } + } + // Other iterables should become lists, except for strings + if (PyIter.IsIterable(pyObj) && !PyString.IsStringType(pyObj)) + { + using (var pyList = PyList.AsList(pyObj)) + { + var list = new List(); + foreach (PyObject item in pyList) + { + list.Add(outputMarshaler.Marshal(item)); + } + return list; + } + } + // Special case for big long values: decode them as BigInteger if (PyLong.IsLongType(pyObj)) { using (var pyLong = PyLong.AsLong(pyObj)) @@ -210,31 +247,8 @@ public static DataMarshaler OutputMarshaler } } } - - // Determine a target type to be caught by our decoders - Type targetType; - if (PyDict.IsDictType(pyObj)) - { - // Dictionaries are also iterable, so we want to check this first - targetType = typeof(IDictionary); - } - else if (PyString.IsStringType(pyObj)) - { - // Strings are iterable, but we want them as strings instead of lists - // Object should also work, but we can be more specific - targetType = typeof(string); - } - else if (PyIter.IsIterable(pyObj)) - { - targetType = typeof(IList); - } - else - { - // Give complete flexibility over the target type - targetType = typeof(object); - } - - var unmarshalled = pyObj.AsManagedObject(targetType); + // Default handling for other Python objects + var unmarshalled = pyObj.AsManagedObject(typeof(object)); if (unmarshalled is PyObject) { using (unmarshalled as PyObject)