Skip to content

Commit ebf5a2b

Browse files
author
denfromufa
authored
Merge branch 'master' into add_scope
2 parents 60ce28b + ce14424 commit ebf5a2b

File tree

5 files changed

+243
-18
lines changed

5 files changed

+243
-18
lines changed

src/embed_tests/Python.EmbeddingTest.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@
9797
<Compile Include="TestPythonException.cs" />
9898
<Compile Include="TestPythonEngineProperties.cs" />
9999
<Compile Include="TestPyTuple.cs" />
100+
<Compile Include="TestNamedArguments.cs" />
101+
<Compile Include="TestPyWith.cs" />
100102
<Compile Include="TestRuntime.cs" />
101103
<Compile Include="TestPyScope.cs" />
102104
</ItemGroup>

src/embed_tests/TestNamedArguments.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using NUnit.Framework;
3+
using Python.Runtime;
4+
5+
namespace Python.EmbeddingTest
6+
{
7+
public class TestNamedArguments
8+
{
9+
[OneTimeSetUp]
10+
public void SetUp()
11+
{
12+
PythonEngine.Initialize();
13+
}
14+
15+
[OneTimeTearDown]
16+
public void Dispose()
17+
{
18+
PythonEngine.Shutdown();
19+
}
20+
21+
/// <summary>
22+
/// Test named arguments support through Py.kw method
23+
/// </summary>
24+
[Test]
25+
public void TestKeywordArgs()
26+
{
27+
dynamic a = CreateTestClass();
28+
var result = (int)a.Test3(2, Py.kw("a4", 8));
29+
30+
Assert.AreEqual(12, result);
31+
}
32+
33+
34+
/// <summary>
35+
/// Test keyword arguments with .net named arguments
36+
/// </summary>
37+
[Test]
38+
public void TestNamedArgs()
39+
{
40+
dynamic a = CreateTestClass();
41+
var result = (int)a.Test3(2, a4: 8);
42+
43+
Assert.AreEqual(12, result);
44+
}
45+
46+
47+
48+
private static PyObject CreateTestClass()
49+
{
50+
var locals = new PyDict();
51+
52+
PythonEngine.Exec(@"
53+
class cmTest3:
54+
def Test3(self, a1 = 1, a2 = 1, a3 = 1, a4 = 1):
55+
return a1 + a2 + a3 + a4
56+
57+
a = cmTest3()
58+
", null, locals.Handle);
59+
60+
return locals.GetItem("a");
61+
}
62+
63+
}
64+
}

src/embed_tests/TestPyWith.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using System;
2+
using NUnit.Framework;
3+
using Python.Runtime;
4+
5+
namespace Python.EmbeddingTest
6+
{
7+
public class TestPyWith
8+
{
9+
[OneTimeSetUp]
10+
public void SetUp()
11+
{
12+
PythonEngine.Initialize();
13+
}
14+
15+
[OneTimeTearDown]
16+
public void Dispose()
17+
{
18+
PythonEngine.Shutdown();
19+
}
20+
21+
/// <summary>
22+
/// Test that exception is raised in context manager that ignores it.
23+
/// </summary>
24+
[Test]
25+
public void TestWithPositive()
26+
{
27+
var locals = new PyDict();
28+
29+
PythonEngine.Exec(@"
30+
class CmTest:
31+
def __enter__(self):
32+
print('Enter')
33+
return self
34+
def __exit__(self, t, v, tb):
35+
# Exception not handled, return will be False
36+
print('Exit')
37+
def fail(self):
38+
return 5 / 0
39+
40+
a = CmTest()
41+
", null, locals.Handle);
42+
43+
var a = locals.GetItem("a");
44+
45+
try
46+
{
47+
Py.With(a, cmTest =>
48+
{
49+
cmTest.fail();
50+
});
51+
}
52+
catch (PythonException e)
53+
{
54+
Assert.IsTrue(e.Message.Contains("ZeroDivisionError"));
55+
}
56+
}
57+
58+
59+
/// <summary>
60+
/// Test that exception is not raised in context manager that handles it
61+
/// </summary>
62+
[Test]
63+
public void TestWithNegative()
64+
{
65+
var locals = new PyDict();
66+
67+
PythonEngine.Exec(@"
68+
class CmTest:
69+
def __enter__(self):
70+
print('Enter')
71+
return self
72+
def __exit__(self, t, v, tb):
73+
# Signal exception is handled by returning true
74+
return True
75+
def fail(self):
76+
return 5 / 0
77+
78+
a = CmTest()
79+
", null, locals.Handle);
80+
81+
var a = locals.GetItem("a");
82+
Py.With(a, cmTest =>
83+
{
84+
cmTest.fail();
85+
});
86+
}
87+
}
88+
}

src/runtime/pyobject.cs

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections;
33
using System.Dynamic;
44
using System.Linq.Expressions;
@@ -936,6 +936,34 @@ public override bool TrySetMember(SetMemberBinder binder, object value)
936936
return true;
937937
}
938938

939+
private void GetArgs(object[] inargs, CallInfo callInfo, out PyTuple args, out PyDict kwargs)
940+
{
941+
if (callInfo == null || callInfo.ArgumentNames.Count == 0)
942+
{
943+
GetArgs(inargs, out args, out kwargs);
944+
return;
945+
}
946+
947+
// Support for .net named arguments
948+
var namedArgumentCount = callInfo.ArgumentNames.Count;
949+
var regularArgumentCount = callInfo.ArgumentCount - namedArgumentCount;
950+
951+
var argTuple = Runtime.PyTuple_New(regularArgumentCount);
952+
for (int i = 0; i < regularArgumentCount; ++i)
953+
{
954+
AddArgument(argTuple, i, inargs[i]);
955+
}
956+
args = new PyTuple(argTuple);
957+
958+
var namedArgs = new object[namedArgumentCount * 2];
959+
for (int i = 0; i < namedArgumentCount; ++i)
960+
{
961+
namedArgs[i * 2] = callInfo.ArgumentNames[i];
962+
namedArgs[i * 2 + 1] = inargs[regularArgumentCount + i];
963+
}
964+
kwargs = Py.kw(namedArgs);
965+
}
966+
939967
private void GetArgs(object[] inargs, out PyTuple args, out PyDict kwargs)
940968
{
941969
int arg_count;
@@ -946,22 +974,10 @@ private void GetArgs(object[] inargs, out PyTuple args, out PyDict kwargs)
946974
IntPtr argtuple = Runtime.PyTuple_New(arg_count);
947975
for (var i = 0; i < arg_count; i++)
948976
{
949-
IntPtr ptr;
950-
if (inargs[i] is PyObject)
951-
{
952-
ptr = ((PyObject)inargs[i]).Handle;
953-
Runtime.XIncref(ptr);
954-
}
955-
else
956-
{
957-
ptr = Converter.ToPython(inargs[i], inargs[i]?.GetType());
958-
}
959-
if (Runtime.PyTuple_SetItem(argtuple, i, ptr) < 0)
960-
{
961-
throw new PythonException();
962-
}
977+
AddArgument(argtuple, i, inargs[i]);
963978
}
964979
args = new PyTuple(argtuple);
980+
965981
kwargs = null;
966982
for (int i = arg_count; i < inargs.Length; i++)
967983
{
@@ -980,6 +996,32 @@ private void GetArgs(object[] inargs, out PyTuple args, out PyDict kwargs)
980996
}
981997
}
982998

999+
private static void AddArgument(IntPtr argtuple, int i, object target)
1000+
{
1001+
IntPtr ptr = GetPythonObject(target);
1002+
1003+
if (Runtime.PyTuple_SetItem(argtuple, i, ptr) < 0)
1004+
{
1005+
throw new PythonException();
1006+
}
1007+
}
1008+
1009+
private static IntPtr GetPythonObject(object target)
1010+
{
1011+
IntPtr ptr;
1012+
if (target is PyObject)
1013+
{
1014+
ptr = ((PyObject)target).Handle;
1015+
Runtime.XIncref(ptr);
1016+
}
1017+
else
1018+
{
1019+
ptr = Converter.ToPython(target, target?.GetType());
1020+
}
1021+
1022+
return ptr;
1023+
}
1024+
9831025
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
9841026
{
9851027
if (this.HasAttr(binder.Name) && this.GetAttr(binder.Name).IsCallable())
@@ -988,7 +1030,7 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o
9881030
PyDict kwargs = null;
9891031
try
9901032
{
991-
GetArgs(args, out pyargs, out kwargs);
1033+
GetArgs(args, binder.CallInfo, out pyargs, out kwargs);
9921034
result = CheckNone(InvokeMethod(binder.Name, pyargs, kwargs));
9931035
}
9941036
finally
@@ -1018,7 +1060,7 @@ public override bool TryInvoke(InvokeBinder binder, object[] args, out object re
10181060
PyDict kwargs = null;
10191061
try
10201062
{
1021-
GetArgs(args, out pyargs, out kwargs);
1063+
GetArgs(args, binder.CallInfo, out pyargs, out kwargs);
10221064
result = CheckNone(Invoke(pyargs, kwargs));
10231065
}
10241066
finally

src/runtime/pythonengine.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
@@ -652,5 +652,34 @@ public static void SetArgv(IEnumerable<string> argv)
652652
Runtime.CheckExceptionOccurred();
653653
}
654654
}
655+
656+
public static void With(PyObject obj, Action<dynamic> Body)
657+
{
658+
// Behavior described here:
659+
// https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers
660+
661+
IntPtr type = Runtime.PyNone;
662+
IntPtr val = Runtime.PyNone;
663+
IntPtr traceBack = Runtime.PyNone;
664+
PythonException ex = null;
665+
666+
try
667+
{
668+
PyObject enterResult = obj.InvokeMethod("__enter__");
669+
670+
Body(enterResult);
671+
}
672+
catch (PythonException e)
673+
{
674+
ex = e;
675+
type = ex.PyType;
676+
val = ex.PyValue;
677+
traceBack = ex.PyTB;
678+
}
679+
680+
var exitResult = obj.InvokeMethod("__exit__", new PyObject(type), new PyObject(val), new PyObject(traceBack));
681+
682+
if (ex != null && !exitResult.IsTrue()) throw ex;
683+
}
655684
}
656685
}

0 commit comments

Comments
 (0)