Skip to content

Add codecs for functions #1080

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

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
133 changes: 118 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,124 @@ 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()
{
var codec = FunctionCodec.Instance;

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[]>)));
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 }));
}

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

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);
}
}
}
72 changes: 72 additions & 0 deletions src/embed_tests/TestCallbacks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,77 @@ public void TestNoOverloadException() {
StringAssert.EndsWith(expectedArgTypes, error.Message);
}
}

private class Callables
{
internal object CallFunction0(Func<object> func)
{
return func();
}

internal object CallFunction1(Func<object[], object> func, object arg)
{
return func(new[] { arg});
}

internal void CallAction0(Action func)
{
func();
}

internal void CallAction1(Action<object[]> func, object arg)
{
func(new[] { arg });
}
}

[Test]
public void TestPythonFunctionPassedIntoCLRMethod()
{
var locals = new PyDict();
PythonEngine.Exec(@"
def ret_1():
return 1
def str_len(a):
return len(a)
", null, locals.Handle);

var ret1 = locals.GetItem("ret_1");
var strLen = locals.GetItem("str_len");

var callables = new Callables();

Python.Runtime.Codecs.FunctionCodec.Register();

//ret1. A function with no arguments that returns an integer
//it must be convertible to Action or Func<object> and not to Func<object, object>
{
Assert.IsTrue(Converter.ToManaged(ret1.Handle, typeof(Action), out var result1, false));
Assert.IsTrue(Converter.ToManaged(ret1.Handle, typeof(Func<object>), out var result2, false));

Assert.DoesNotThrow(() => { callables.CallAction0((Action)result1); });
object ret2 = null;
Assert.DoesNotThrow(() => { ret2 = callables.CallFunction0((Func<object>)result2); });
Assert.AreEqual(ret2, 1);
}

//strLen. A function that takes something with a __len__ and returns the result of that function
//It must be convertible to an Action<object[]> and Func<object[], object>) and not to an Action or Func<object>
{
Assert.IsTrue(Converter.ToManaged(strLen.Handle, typeof(Action<object[]>), out var result3, false));
Assert.IsTrue(Converter.ToManaged(strLen.Handle, typeof(Func<object[], object>), out var result4, false));

//try using both func and action to show you can get __len__ of a string but not an integer
Assert.Throws<PythonException>(() => { callables.CallAction1((Action<object[]>)result3, 2); });
Assert.DoesNotThrow(() => { callables.CallAction1((Action<object[]>)result3, "hello"); });
Assert.Throws<PythonException>(() => { callables.CallFunction1((Func<object[], object>)result4, 2); });

object ret2 = null;
Assert.DoesNotThrow(() => { ret2 = callables.CallFunction1((Func<object[], object>)result4, "hello"); });
Assert.AreEqual(ret2, 5);
}

PyObjectConversions.Reset();
}
}
}
Loading