Skip to content

Commit 1d09c98

Browse files
authored
Python functions conversion to managed delegates (#104)
* Convert Python functions to managed delegates * Bump version to 2.0.46 * Support managed delegates wrapped in PyObjects Add more unit tests * Cleanup * Fix enums precedence in MethodBinder
1 parent 5f36aa7 commit 1d09c98

File tree

7 files changed

+493
-16
lines changed

7 files changed

+493
-16
lines changed

src/embed_tests/TestMethodBinder.cs

Lines changed: 349 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def TestEnumerable(self):
6767
public static dynamic Numpy;
6868

6969
[OneTimeSetUp]
70-
public void SetUp()
70+
public void OneTimeSetUp()
7171
{
7272
PythonEngine.Initialize();
7373
using var _ = Py.GIL();
@@ -89,6 +89,15 @@ public void Dispose()
8989
PythonEngine.Shutdown();
9090
}
9191

92+
[SetUp]
93+
public void SetUp()
94+
{
95+
CSharpModel.LastDelegateCalled = null;
96+
CSharpModel.LastFuncCalled = null;
97+
CSharpModel.MethodCalled = null;
98+
CSharpModel.ProvidedArgument = null;
99+
}
100+
92101
[Test]
93102
public void MethodCalledList()
94103
{
@@ -1152,6 +1161,247 @@ def call_method():
11521161
Assert.AreEqual("MethodWithEnumParam With Enum", result.As<string>());
11531162
}
11541163

1164+
[TestCase("call_method_with_func1", "MethodWithFunc1", "func1")]
1165+
[TestCase("call_method_with_func2", "MethodWithFunc2", "func2")]
1166+
[TestCase("call_method_with_func3", "MethodWithFunc3", "func3")]
1167+
[TestCase("call_method_with_func1_lambda", "MethodWithFunc1", "func1")]
1168+
[TestCase("call_method_with_func2_lambda", "MethodWithFunc2", "func2")]
1169+
[TestCase("call_method_with_func3_lambda", "MethodWithFunc3", "func3")]
1170+
public void BindsPythonToCSharpFuncDelegates(string pythonFuncToCall, string expectedCSharpMethodCalled, string expectedPythonFuncCalled)
1171+
{
1172+
using var _ = Py.GIL();
1173+
1174+
var module = PyModule.FromString("BindsPythonToCSharpFuncDelegates", @$"
1175+
from clr import AddReference
1176+
AddReference(""System"")
1177+
from Python.EmbeddingTest import *
1178+
1179+
from System import Func
1180+
1181+
class PythonModel:
1182+
last_delegate_called = None
1183+
1184+
def func1():
1185+
PythonModel.last_delegate_called = 'func1'
1186+
return TestMethodBinder.CSharpModel();
1187+
1188+
def func2(model):
1189+
if model is None or not isinstance(model, TestMethodBinder.CSharpModel):
1190+
raise TypeError(""model must be of type CSharpModel"")
1191+
PythonModel.last_delegate_called = 'func2'
1192+
return model
1193+
1194+
def func3(model1, model2):
1195+
if model1 is None or model2 is None or not isinstance(model1, TestMethodBinder.CSharpModel) or not isinstance(model2, TestMethodBinder.CSharpModel):
1196+
raise TypeError(""model1 and model2 must be of type CSharpModel"")
1197+
PythonModel.last_delegate_called = 'func3'
1198+
return model1
1199+
1200+
def call_method_with_func1():
1201+
return TestMethodBinder.CSharpModel.MethodWithFunc1(func1)
1202+
1203+
def call_method_with_func2():
1204+
return TestMethodBinder.CSharpModel.MethodWithFunc2(func2)
1205+
1206+
def call_method_with_func3():
1207+
return TestMethodBinder.CSharpModel.MethodWithFunc3(func3)
1208+
1209+
def call_method_with_func1_lambda():
1210+
return TestMethodBinder.CSharpModel.MethodWithFunc1(lambda: func1())
1211+
1212+
def call_method_with_func2_lambda():
1213+
return TestMethodBinder.CSharpModel.MethodWithFunc2(lambda model: func2(model))
1214+
1215+
def call_method_with_func3_lambda():
1216+
return TestMethodBinder.CSharpModel.MethodWithFunc3(lambda model1, model2: func3(model1, model2))
1217+
");
1218+
1219+
CSharpModel managedResult = null;
1220+
Assert.DoesNotThrow(() =>
1221+
{
1222+
using var result = module.GetAttr(pythonFuncToCall).Invoke();
1223+
managedResult = result.As<CSharpModel>();
1224+
});
1225+
1226+
Assert.IsNotNull(managedResult);
1227+
Assert.AreEqual(expectedCSharpMethodCalled, CSharpModel.LastDelegateCalled);
1228+
1229+
using var pythonModel = module.GetAttr("PythonModel");
1230+
using var lastDelegateCalled = pythonModel.GetAttr("last_delegate_called");
1231+
Assert.AreEqual(expectedPythonFuncCalled, lastDelegateCalled.As<string>());
1232+
}
1233+
1234+
[TestCase("call_method_with_action1", "MethodWithAction1", "action1")]
1235+
[TestCase("call_method_with_action2", "MethodWithAction2", "action2")]
1236+
[TestCase("call_method_with_action3", "MethodWithAction3", "action3")]
1237+
[TestCase("call_method_with_action1_lambda", "MethodWithAction1", "action1")]
1238+
[TestCase("call_method_with_action2_lambda", "MethodWithAction2", "action2")]
1239+
[TestCase("call_method_with_action3_lambda", "MethodWithAction3", "action3")]
1240+
public void BindsPythonToCSharpActionDelegates(string pythonFuncToCall, string expectedCSharpMethodCalled, string expectedPythonFuncCalled)
1241+
{
1242+
using var _ = Py.GIL();
1243+
1244+
var module = PyModule.FromString("BindsPythonToCSharpActionDelegates", @$"
1245+
from clr import AddReference
1246+
AddReference(""System"")
1247+
from Python.EmbeddingTest import *
1248+
1249+
from System import Func
1250+
1251+
class PythonModel:
1252+
last_delegate_called = None
1253+
1254+
def action1():
1255+
PythonModel.last_delegate_called = 'action1'
1256+
pass
1257+
1258+
def action2(model):
1259+
if model is None or not isinstance(model, TestMethodBinder.CSharpModel):
1260+
raise TypeError(""model must be of type CSharpModel"")
1261+
PythonModel.last_delegate_called = 'action2'
1262+
pass
1263+
1264+
def action3(model1, model2):
1265+
if model1 is None or model2 is None or not isinstance(model1, TestMethodBinder.CSharpModel) or not isinstance(model2, TestMethodBinder.CSharpModel):
1266+
raise TypeError(""model1 and model2 must be of type CSharpModel"")
1267+
PythonModel.last_delegate_called = 'action3'
1268+
pass
1269+
1270+
def call_method_with_action1():
1271+
return TestMethodBinder.CSharpModel.MethodWithAction1(action1)
1272+
1273+
def call_method_with_action2():
1274+
return TestMethodBinder.CSharpModel.MethodWithAction2(action2)
1275+
1276+
def call_method_with_action3():
1277+
return TestMethodBinder.CSharpModel.MethodWithAction3(action3)
1278+
1279+
def call_method_with_action1_lambda():
1280+
return TestMethodBinder.CSharpModel.MethodWithAction1(lambda: action1())
1281+
1282+
def call_method_with_action2_lambda():
1283+
return TestMethodBinder.CSharpModel.MethodWithAction2(lambda model: action2(model))
1284+
1285+
def call_method_with_action3_lambda():
1286+
return TestMethodBinder.CSharpModel.MethodWithAction3(lambda model1, model2: action3(model1, model2))
1287+
");
1288+
1289+
Assert.DoesNotThrow(() =>
1290+
{
1291+
using var result = module.GetAttr(pythonFuncToCall).Invoke();
1292+
});
1293+
1294+
Assert.AreEqual(expectedCSharpMethodCalled, CSharpModel.LastDelegateCalled);
1295+
1296+
using var pythonModel = module.GetAttr("PythonModel");
1297+
using var lastDelegateCalled = pythonModel.GetAttr("last_delegate_called");
1298+
Assert.AreEqual(expectedPythonFuncCalled, lastDelegateCalled.As<string>());
1299+
}
1300+
1301+
[TestCase("call_method_with_func1", "MethodWithFunc1", "TestFunc1")]
1302+
[TestCase("call_method_with_func2", "MethodWithFunc2", "TestFunc2")]
1303+
[TestCase("call_method_with_func3", "MethodWithFunc3", "TestFunc3")]
1304+
public void BindsCSharpFuncFromPythonToCSharpFuncDelegates(string pythonFuncToCall, string expectedMethodCalled, string expectedInnerMethodCalled)
1305+
{
1306+
using var _ = Py.GIL();
1307+
1308+
var module = PyModule.FromString("BindsCSharpFuncFromPythonToCSharpFuncDelegates", @$"
1309+
from clr import AddReference
1310+
AddReference(""System"")
1311+
from Python.EmbeddingTest import *
1312+
1313+
def call_method_with_func1():
1314+
return TestMethodBinder.CSharpModel.MethodWithFunc1(TestMethodBinder.CSharpModel.TestFunc1)
1315+
1316+
def call_method_with_func2():
1317+
return TestMethodBinder.CSharpModel.MethodWithFunc2(TestMethodBinder.CSharpModel.TestFunc2)
1318+
1319+
def call_method_with_func3():
1320+
return TestMethodBinder.CSharpModel.MethodWithFunc3(TestMethodBinder.CSharpModel.TestFunc3)
1321+
");
1322+
1323+
CSharpModel managedResult = null;
1324+
Assert.DoesNotThrow(() =>
1325+
{
1326+
using var result = module.GetAttr(pythonFuncToCall).Invoke();
1327+
managedResult = result.As<CSharpModel>();
1328+
});
1329+
Assert.IsNotNull(managedResult);
1330+
Assert.AreEqual(expectedMethodCalled, CSharpModel.LastDelegateCalled);
1331+
Assert.AreEqual(expectedInnerMethodCalled, CSharpModel.LastFuncCalled);
1332+
}
1333+
1334+
[TestCase("call_method_with_action1", "MethodWithAction1", "TestAction1")]
1335+
[TestCase("call_method_with_action2", "MethodWithAction2", "TestAction2")]
1336+
[TestCase("call_method_with_action3", "MethodWithAction3", "TestAction3")]
1337+
public void BindsCSharpActionFromPythonToCSharpActionDelegates(string pythonFuncToCall, string expectedMethodCalled, string expectedInnerMethodCalled)
1338+
{
1339+
using var _ = Py.GIL();
1340+
1341+
var module = PyModule.FromString("BindsCSharpActionFromPythonToCSharpActionDelegates", @$"
1342+
from clr import AddReference
1343+
AddReference(""System"")
1344+
from Python.EmbeddingTest import *
1345+
1346+
def call_method_with_action1():
1347+
return TestMethodBinder.CSharpModel.MethodWithAction1(TestMethodBinder.CSharpModel.TestAction1)
1348+
1349+
def call_method_with_action2():
1350+
return TestMethodBinder.CSharpModel.MethodWithAction2(TestMethodBinder.CSharpModel.TestAction2)
1351+
1352+
def call_method_with_action3():
1353+
return TestMethodBinder.CSharpModel.MethodWithAction3(TestMethodBinder.CSharpModel.TestAction3)
1354+
");
1355+
1356+
Assert.DoesNotThrow(() =>
1357+
{
1358+
using var result = module.GetAttr(pythonFuncToCall).Invoke();
1359+
});
1360+
Assert.AreEqual(expectedMethodCalled, CSharpModel.LastDelegateCalled);
1361+
Assert.AreEqual(expectedInnerMethodCalled, CSharpModel.LastFuncCalled);
1362+
}
1363+
1364+
[Test]
1365+
public void NumericArgumentsTakePrecedenceOverEnums()
1366+
{
1367+
using var _ = Py.GIL();
1368+
1369+
var module = PyModule.FromString("NumericArgumentsTakePrecedenceOverEnums", @$"
1370+
from clr import AddReference
1371+
AddReference(""System"")
1372+
from Python.EmbeddingTest import *
1373+
from System import DayOfWeek
1374+
1375+
def call_method_with_int():
1376+
TestMethodBinder.CSharpModel().NumericalArgumentMethod(1)
1377+
1378+
def call_method_with_float():
1379+
TestMethodBinder.CSharpModel().NumericalArgumentMethod(0.1)
1380+
1381+
def call_method_with_numpy_float():
1382+
TestMethodBinder.CSharpModel().NumericalArgumentMethod(TestMethodBinder.Numpy.float64(0.1))
1383+
1384+
def call_method_with_enum():
1385+
TestMethodBinder.CSharpModel().NumericalArgumentMethod(DayOfWeek.MONDAY)
1386+
");
1387+
1388+
module.GetAttr("call_method_with_int").Invoke();
1389+
Assert.AreEqual(typeof(int), CSharpModel.ProvidedArgument.GetType());
1390+
Assert.AreEqual(1, CSharpModel.ProvidedArgument);
1391+
1392+
module.GetAttr("call_method_with_float").Invoke();
1393+
Assert.AreEqual(typeof(double), CSharpModel.ProvidedArgument.GetType());
1394+
Assert.AreEqual(0.1d, CSharpModel.ProvidedArgument);
1395+
1396+
module.GetAttr("call_method_with_numpy_float").Invoke();
1397+
Assert.AreEqual(typeof(decimal), CSharpModel.ProvidedArgument.GetType());
1398+
Assert.AreEqual(0.1m, CSharpModel.ProvidedArgument);
1399+
1400+
module.GetAttr("call_method_with_enum").Invoke();
1401+
Assert.AreEqual(typeof(DayOfWeek), CSharpModel.ProvidedArgument.GetType());
1402+
Assert.AreEqual(DayOfWeek.Monday, CSharpModel.ProvidedArgument);
1403+
}
1404+
11551405
// Used to test that we match this function with Py DateTime & Date Objects
11561406
public static int GetMonth(DateTime test)
11571407
{
@@ -1234,6 +1484,10 @@ public void NumericalArgumentMethod(decimal value)
12341484
{
12351485
ProvidedArgument = value;
12361486
}
1487+
public void NumericalArgumentMethod(DayOfWeek value)
1488+
{
1489+
ProvidedArgument = value;
1490+
}
12371491
public void EnumerableKeyValuePair(IEnumerable<KeyValuePair<string, decimal>> value)
12381492
{
12391493
ProvidedArgument = value;
@@ -1288,6 +1542,100 @@ public static void MethodDateTimeAndTimeSpan(CSharpModel pepe, Func<DateTime, Da
12881542
{
12891543
AssertErrorNotOccurred();
12901544
}
1545+
1546+
public static string LastDelegateCalled { get; set; }
1547+
public static string LastFuncCalled { get; set; }
1548+
1549+
public static CSharpModel MethodWithFunc1(Func<CSharpModel> func)
1550+
{
1551+
AssertErrorNotOccurred();
1552+
LastDelegateCalled = "MethodWithFunc1";
1553+
return func();
1554+
}
1555+
1556+
public static CSharpModel MethodWithFunc2(Func<CSharpModel, CSharpModel> func)
1557+
{
1558+
AssertErrorNotOccurred();
1559+
LastDelegateCalled = "MethodWithFunc2";
1560+
return func(new CSharpModel());
1561+
}
1562+
1563+
public static CSharpModel MethodWithFunc3(Func<CSharpModel, CSharpModel, CSharpModel> func)
1564+
{
1565+
AssertErrorNotOccurred();
1566+
LastDelegateCalled = "MethodWithFunc3";
1567+
return func(new CSharpModel(), new CSharpModel());
1568+
}
1569+
1570+
public static void MethodWithAction1(Action action)
1571+
{
1572+
AssertErrorNotOccurred();
1573+
LastDelegateCalled = "MethodWithAction1";
1574+
action();
1575+
}
1576+
1577+
public static void MethodWithAction2(Action<CSharpModel> action)
1578+
{
1579+
AssertErrorNotOccurred();
1580+
LastDelegateCalled = "MethodWithAction2";
1581+
action(new CSharpModel());
1582+
}
1583+
1584+
public static void MethodWithAction3(Action<CSharpModel, CSharpModel> action)
1585+
{
1586+
AssertErrorNotOccurred();
1587+
LastDelegateCalled = "MethodWithAction3";
1588+
action(new CSharpModel(), new CSharpModel());
1589+
}
1590+
1591+
public static CSharpModel TestFunc1()
1592+
{
1593+
LastFuncCalled = "TestFunc1";
1594+
return new CSharpModel();
1595+
}
1596+
1597+
public static CSharpModel TestFunc2(CSharpModel model)
1598+
{
1599+
if (model == null)
1600+
{
1601+
throw new ArgumentNullException(nameof(model));
1602+
}
1603+
LastFuncCalled = "TestFunc2";
1604+
return model;
1605+
}
1606+
1607+
public static CSharpModel TestFunc3(CSharpModel model1, CSharpModel model2)
1608+
{
1609+
if (model1 == null || model2 == null)
1610+
{
1611+
throw new ArgumentNullException(model1 == null ? nameof(model1) : nameof(model2));
1612+
}
1613+
LastFuncCalled = "TestFunc3";
1614+
return model1;
1615+
}
1616+
1617+
public static void TestAction1()
1618+
{
1619+
LastFuncCalled = "TestAction1";
1620+
}
1621+
1622+
public static void TestAction2(CSharpModel model)
1623+
{
1624+
if (model == null)
1625+
{
1626+
throw new ArgumentNullException(nameof(model));
1627+
}
1628+
LastFuncCalled = "TestAction2";
1629+
}
1630+
1631+
public static void TestAction3(CSharpModel model1, CSharpModel model2)
1632+
{
1633+
if (model1 == null || model2 == null)
1634+
{
1635+
throw new ArgumentNullException(model1 == null ? nameof(model1) : nameof(model2));
1636+
}
1637+
LastFuncCalled = "TestAction3";
1638+
}
12911639
}
12921640

12931641
public class TestImplicitConversion

0 commit comments

Comments
 (0)