Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decode Python list into IList in function calls #10865

Merged
merged 9 commits into from
Jul 8, 2020
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Libraries/DSCPython/CPythonEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public static object EvaluatePythonScript(
/// </summary>
private static void InitializeEncoders()
{
var encoders = new IPyObjectEncoder[] { new BigIntegerEncoder() };
var encoders = new IPyObjectEncoder[] { new BigIntegerEncoder(), new ListEncoder() };
var decoders = encoders.Cast<IPyObjectDecoder>().ToArray();
Array.ForEach(encoders, e => PyObjectConversions.RegisterEncoder(e));
Array.ForEach(decoders, d => PyObjectConversions.RegisterDecoder(d));
Expand Down
1 change: 1 addition & 0 deletions src/Libraries/DSCPython/DSCPython.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
</Compile>
<Compile Include="Encoders\BigIntegerEncoder.cs" />
<Compile Include="CPythonEvaluator.cs" />
<Compile Include="Encoders\ListEncoder.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
Expand Down
60 changes: 60 additions & 0 deletions src/Libraries/DSCPython/Encoders/ListEncoder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Dynamo.Utilities;
using Python.Runtime;

namespace DSCPython.Encoders
{
internal class ListEncoder : IPyObjectEncoder, IPyObjectDecoder
{
private static readonly Type[] decodableTypes = new Type[]
{
typeof(IList), typeof(ArrayList),
typeof(IList<>), typeof(List<>)
};

public bool CanDecode(PyObject objectType, Type targetType)
{
if (targetType.IsGenericType)
{
targetType = targetType.GetGenericTypeDefinition();
}
return decodableTypes.IndexOf(targetType) >= 0;
}

public bool CanEncode(Type type)
{
return typeof(IList).IsAssignableFrom(type);
}

public bool TryDecode<T>(PyObject pyObj, out T value)
{
if (!PySequence.IsSequenceType(pyObj))
{
value = default;
return false;
}

using (var pyList = PyList.AsList(pyObj))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by this using statement, does AsList return a .net list or a Python List?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a Python list

{
if (typeof(T).IsGenericType)
{
value = pyList.ToList<T>();
}
else
{
value = (T)pyList.ToList();
}
return true;
}
}

public PyObject TryEncode(object value)
{
// This is a no-op to prevent Python.NET from encoding generic lists
// https://github.com/pythonnet/pythonnet/pull/963#issuecomment-642938541
return PyObject.FromManagedObject(value);
Copy link
Member

@mjkkirschner mjkkirschner Jul 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this break examples where I want to call a python function with a list of ints?

def sum(data):
    for i in data:

where data is a .net List<int> ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. Let me try it and come back with the answer.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works. Do you want me to add it to the test?
Screen Shot 2020-07-08 at 12 39 42 PM

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, nothing wrong with more tests?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, so l2 is a .net list because Flatten is a zt function?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's List.Flatten from our core nodes

}
}
}
59 changes: 30 additions & 29 deletions test/Libraries/DynamoPythonTests/PythonEvalTestsWithLibraries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ protected override void GetLibrariesToPreload(List<string> libraries)
};

[Test]
public void TestBigIntegerEncoding()
public void TestBigIntegerEncodingDecoding()
{
string code = @"
import sys
Expand All @@ -46,8 +46,7 @@ from FFITarget import DummyMath
}

[Test]
[Category("Failure")]
public void TestListEncoding()
public void TestListDecoding()
{
string code = @"
import sys
Expand All @@ -56,16 +55,27 @@ import clr
from DSCore import List

l = ['a']
# Python list => .NET IList - Does not work in CPython
l = List.AddItemToEnd('b',l)
# .NET IList => Python list - Does not work in IronPython. Couldn't test CPython because of previous bug
# l.append('c')
l.Add(c)
# Python list => .NET IList
untypedList = List.AddItemToEnd('b', l)
untypedList.Add('c')

l2 = ['a','b']
# Python list => .NET IList<>
typedList = List.SetDifference(l2, l)
typedList.Add('b')

l3 = [[1,2],[3,4]]
# Python list (nested) => .NET IList<IList<>>
flatennedList = List.Flatten(l3)

l4 = []
# Python list (empty) => .NET IList
elementCount = List.Count(l4)

OUT = l
OUT = untypedList, typedList, flatennedList, elementCount
";
var empty = new ArrayList();
var expected = new ArrayList { "a", "b", "c" };
var expected = new ArrayList { new ArrayList { "a", "b", "c" }, new ArrayList { "b", "b" }, new ArrayList { 1, 2, 3, 4 }, 0 };
foreach (var pythonEvaluator in Evaluators)
{
var result = pythonEvaluator(code, empty, empty);
Expand All @@ -86,11 +96,7 @@ from FFITarget import DummyCollection
from DSCore import List
from array import array

# Python array => .NET IList - array is in a builtin library. This does not work in either engine
# native = array('l', [1,2])
# native = List.AddItemToEnd(3, native)

# .NET array => Python list - Works in both engines
# .NET array => Python list
a = DummyCollection.MakeArray(1,2)
a[0] = a[1] + 1
b = len(a)
Expand All @@ -110,8 +116,7 @@ from array import array
}

[Test]
[Category("Failure")]
public void TestTupleEncoding()
public void TestTupleDecoding()
{
string code = @"
import sys
Expand All @@ -122,9 +127,9 @@ from FFITarget import DummyCollection
from DSCore import List

t = (1,2,3)
# Python tuple => .NET array - Works in both
# Python tuple => .NET array
a = DummyCollection.MakeArray(t)
# Python tuple => .NET IList - Does not work in CPython
# Python tuple => .NET IList
l = List.AddItemToEnd(4, t)

OUT = a, l
Expand All @@ -140,8 +145,7 @@ from DSCore import List
}

[Test]
[Category("Failure")]
public void TestRangeEncoding()
public void TestRangeDecodingCPython()
{
string code = @"
import sys
Expand All @@ -154,19 +158,16 @@ from DSCore import List
r = range(0, 10, 2)
# Python range => .NET array - Works in both
a = DummyCollection.MakeArray(r)
# Python range => .NET IList - Does not work in CPython
mjkkirschner marked this conversation as resolved.
Show resolved Hide resolved
l = List.AddItemToEnd(10, r)
# Python range => .NET IList - Does not work in IronPython
l = List.AddItemToEnd(10, range(0, 10, 2))

OUT = a, l
";
var empty = new ArrayList();
var expected = new ArrayList { new ArrayList { 0, 2, 4, 6, 8 }, new ArrayList { 0, 2, 4, 6, 8, 10 } };
foreach (var pythonEvaluator in Evaluators)
{
var result = pythonEvaluator(code, empty, empty);
Assert.IsTrue(result is IEnumerable);
CollectionAssert.AreEqual(expected, result as IEnumerable);
}
var result = DSCPython.CPythonEvaluator.EvaluatePythonScript(code, empty, empty);
Assert.IsTrue(result is IEnumerable);
CollectionAssert.AreEqual(expected, result as IEnumerable);
}

[Test]
Expand Down