Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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
197 changes: 182 additions & 15 deletions src/embed_tests/Codecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,34 @@ namespace Python.EmbeddingTest {
using Python.Runtime;
using Python.Runtime.Codecs;

public class Codecs {
public class Codecs
{
[SetUp]
public void SetUp() {
public void SetUp()
{
PythonEngine.Initialize();
}

[TearDown]
public void Dispose() {
public void Dispose()
{
PythonEngine.Shutdown();
}

[Test]
public void ConversionsGeneric() {
public void ConversionsGeneric()
{
ConversionsGeneric<ValueTuple<int, string, object>, ValueTuple>();
}

static void ConversionsGeneric<T, TTuple>() {
static void ConversionsGeneric<T, TTuple>()
{
TupleCodec<TTuple>.Register();
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
T restored = default;
using (Py.GIL())
using (var scope = Py.CreateScope()) {
using (var scope = Py.CreateScope())
{
void Accept(T value) => restored = value;
var accept = new Action<T>(Accept).ToPython();
scope.Set(nameof(tuple), tuple);
Expand All @@ -38,15 +44,18 @@ static void ConversionsGeneric<T, TTuple>() {
}

[Test]
public void ConversionsObject() {
public void ConversionsObject()
{
ConversionsObject<ValueTuple<int, string, object>, ValueTuple>();
}
static void ConversionsObject<T, TTuple>() {
static void ConversionsObject<T, TTuple>()
{
TupleCodec<TTuple>.Register();
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
T restored = default;
using (Py.GIL())
using (var scope = Py.CreateScope()) {
using (var scope = Py.CreateScope())
{
void Accept(object value) => restored = (T)value;
var accept = new Action<object>(Accept).ToPython();
scope.Set(nameof(tuple), tuple);
Expand All @@ -57,30 +66,188 @@ static void ConversionsObject<T, TTuple>() {
}

[Test]
public void TupleRoundtripObject() {
public void TupleRoundtripObject()
{
TupleRoundtripObject<ValueTuple<int, string, object>, ValueTuple>();
}
static void TupleRoundtripObject<T, TTuple>() {
static void TupleRoundtripObject<T, TTuple>()
{
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
using (Py.GIL()) {
using (Py.GIL())
{
var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple);
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out object restored));
Assert.AreEqual(expected: tuple, actual: restored);
}
}

[Test]
public void TupleRoundtripGeneric() {
public void TupleRoundtripGeneric()
{
TupleRoundtripGeneric<ValueTuple<int, string, object>, ValueTuple>();
}

static void TupleRoundtripGeneric<T, TTuple>() {
static void TupleRoundtripGeneric<T, TTuple>()
{
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
using (Py.GIL()) {
using (Py.GIL())
{
var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple);
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out T restored));
Assert.AreEqual(expected: tuple, actual: restored);
}
}

[Test]
public void FunctionAction()
{
FunctionCodec.Register();
var codec = FunctionCodec.Instance;

//decoding - python functions to C# actions
{
PyInt x = new PyInt(1);
PyDict y = new PyDict();
//non-callables can't be decoded into Action
Assert.IsFalse(codec.CanDecode(x, typeof(Action)));
Assert.IsFalse(codec.CanDecode(y, typeof(Action)));

var locals = new PyDict();
PythonEngine.Exec(@"
def foo():
return 1
def bar(a):
return 2
", null, locals.Handle);

//foo, the function with no arguments
var fooFunc = locals.GetItem("foo");
Assert.IsFalse(codec.CanDecode(fooFunc, typeof(bool)));

//CanDecode does not work for variadic actions
//Assert.IsFalse(codec.CanDecode(fooFunc, typeof(Action<object[]>)));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@lostmsu because of the interface of CanDecode this doesn't work. Since I defined functions in C# to use object[] I cannot determine the number of arguments without an instance.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You can create a special delegate type just for variadic parameter handling, and check for it explicitly in the codec.
E.g. delegate void VariadicAction<T>(params T[] args)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@lostmsu The issue is with the number of arguments, not the type of the arguments

Copy link
Copy Markdown
Contributor Author

@koubaa koubaa Mar 9, 2020

Choose a reason for hiding this comment

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

@lostmsu I think I get your point. Do you mean rather than Action<object[]> and Func<object[], object> I can simply use VariadicAction so if I have a situation like this:

C#

public int CallMe(Func<int, int, string, double, object> func) {
    return func(1,2, "hello", 4.4, new SomeObject());
}

Python

def call_me(i, j, name, real_value, obj):
    return 1

You expect that given typeof(func) you can convert it to VariadicAction and call Invoke on it dynamically? I can try and I think this may work except it may have a restriction to use the same type (preferably object) in all of the argument slots.

Assert.IsTrue(codec.CanDecode(fooFunc, typeof(Action)));

Action fooAction;
Assert.IsTrue(codec.TryDecode(fooFunc, out fooAction));
Assert.DoesNotThrow(() => fooAction());

//bar, the function with an argument
var barFunc = locals.GetItem("bar");
Assert.IsFalse(codec.CanDecode(barFunc, typeof(bool)));
//Assert.IsFalse(codec.CanDecode(barFunc, typeof(Action)));
Assert.IsTrue(codec.CanDecode(barFunc, typeof(Action<object[]>)));

Action<object[]> barAction;
Assert.IsTrue(codec.TryDecode(barFunc, out barAction));
Assert.DoesNotThrow(() => barAction(new[] { (object)true }));
}

//encoding, C# actions to python functions
{
//can't decode non-actions
Assert.IsFalse(codec.CanEncode(typeof(int)));
Assert.IsFalse(codec.CanEncode(typeof(Dictionary<string, int>)));

Action foo = () => { };
Assert.IsTrue(codec.CanEncode(foo.GetType()));

Assert.DoesNotThrow(() => { codec.TryEncode(foo); });

Action<object[]> bar = (object[] args) => { var z = args.Length; };
Assert.IsTrue(codec.CanEncode(bar.GetType()));
Assert.DoesNotThrow(() => { codec.TryEncode(bar); });
}
}

[Test]
public void FunctionFunc()
{
FunctionCodec.Register();
var codec = FunctionCodec.Instance;

//decoding - python functions to C# funcs
{
PyInt x = new PyInt(1);
PyDict y = new PyDict();
//non-callables can't be decoded into Func
Assert.IsFalse(codec.CanDecode(x, typeof(Func<object>)));
Assert.IsFalse(codec.CanDecode(y, typeof(Func<object>)));

var locals = new PyDict();
PythonEngine.Exec(@"
def foo():
return 1
def bar(a):
return 2
", null, locals.Handle);

//foo, the function with no arguments
var fooFunc = locals.GetItem("foo");
Assert.IsFalse(codec.CanDecode(fooFunc, typeof(bool)));

//CanDecode does not work for variadic actions
//Assert.IsFalse(codec.CanDecode(fooFunc, typeof(Func<object[], object>)));
Assert.IsTrue(codec.CanDecode(fooFunc, typeof(Func<object>)));

Func<object> foo;
Assert.IsTrue(codec.TryDecode(fooFunc, out foo));
object res1 = null;
Assert.DoesNotThrow(() => res1 = foo());
Assert.AreEqual(res1, 1);

//bar, the function with an argument
var barFunc = locals.GetItem("bar");
Assert.IsFalse(codec.CanDecode(barFunc, typeof(bool)));
//Assert.IsFalse(codec.CanDecode(barFunc, typeof(Func<object>)));
Assert.IsTrue(codec.CanDecode(barFunc, typeof(Func<object[], object>)));

Func<object[], object> bar;
Assert.IsTrue(codec.TryDecode(barFunc, out bar));
object res2 = null;
Assert.DoesNotThrow(() => res2 = bar(new[] { (object)true }));
Assert.AreEqual(res2, 2);
}

//encoding, C# funcs to python functions
{
Func<object> foo = () => { return 1; };
Assert.IsTrue(codec.CanEncode(foo.GetType()));

PyObject ret1 = null;
Assert.DoesNotThrow(() => { ret1 = codec.TryEncode(foo); });
//call ret1
Assert.IsTrue(ret1.IsCallable());

var pyArgs1 = new PyObject[0];
using (Py.GIL())
{
var pyResult = ret1.Invoke(pyArgs1);
Runtime.XIncref(pyResult.Handle);
object result;
Converter.ToManaged(pyResult.Handle, typeof(object), out result, true);
Assert.AreEqual(result, 1);
}

Func<object[], object> bar = (object[] args) => {
return args.Length;
};
Assert.IsTrue(codec.CanEncode(bar.GetType()));
PyObject ret2 = null;
Assert.DoesNotThrow(() => { ret2 = codec.TryEncode(bar); });
//call ret2
Assert.IsTrue(ret2.IsCallable());

var pyArgs2 = new PyObject[2] { new PyInt(1), new PyFloat(2.2) };
using (Py.GIL())
{
var pyResult = ret2.Invoke(pyArgs2);
Runtime.XIncref(pyResult.Handle);
object result;
Converter.ToManaged(pyResult.Handle, typeof(object), out result, true);
Assert.AreEqual(result, 2);
}
}
}
}
}
Loading