diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj
index 56ef886ee..2edf4f515 100644
--- a/src/embed_tests/Python.EmbeddingTest.csproj
+++ b/src/embed_tests/Python.EmbeddingTest.csproj
@@ -1,4 +1,4 @@
-
+
Debug
@@ -97,6 +97,8 @@
+
+
@@ -117,4 +119,4 @@
-
+
\ No newline at end of file
diff --git a/src/embed_tests/TestNamedArguments.cs b/src/embed_tests/TestNamedArguments.cs
new file mode 100644
index 000000000..1d7076956
--- /dev/null
+++ b/src/embed_tests/TestNamedArguments.cs
@@ -0,0 +1,64 @@
+using System;
+using NUnit.Framework;
+using Python.Runtime;
+
+namespace Python.EmbeddingTest
+{
+ public class TestNamedArguments
+ {
+ [OneTimeSetUp]
+ public void SetUp()
+ {
+ PythonEngine.Initialize();
+ }
+
+ [OneTimeTearDown]
+ public void Dispose()
+ {
+ PythonEngine.Shutdown();
+ }
+
+ ///
+ /// Test named arguments support through Py.kw method
+ ///
+ [Test]
+ public void TestKeywordArgs()
+ {
+ dynamic a = CreateTestClass();
+ var result = (int)a.Test3(2, Py.kw("a4", 8));
+
+ Assert.AreEqual(12, result);
+ }
+
+
+ ///
+ /// Test keyword arguments with .net named arguments
+ ///
+ [Test]
+ public void TestNamedArgs()
+ {
+ dynamic a = CreateTestClass();
+ var result = (int)a.Test3(2, a4: 8);
+
+ Assert.AreEqual(12, result);
+ }
+
+
+
+ private static PyObject CreateTestClass()
+ {
+ var locals = new PyDict();
+
+ PythonEngine.Exec(@"
+class cmTest3:
+ def Test3(self, a1 = 1, a2 = 1, a3 = 1, a4 = 1):
+ return a1 + a2 + a3 + a4
+
+a = cmTest3()
+", null, locals.Handle);
+
+ return locals.GetItem("a");
+ }
+
+ }
+}
diff --git a/src/embed_tests/TestPyWith.cs b/src/embed_tests/TestPyWith.cs
new file mode 100644
index 000000000..fd3f8e662
--- /dev/null
+++ b/src/embed_tests/TestPyWith.cs
@@ -0,0 +1,88 @@
+using System;
+using NUnit.Framework;
+using Python.Runtime;
+
+namespace Python.EmbeddingTest
+{
+ public class TestPyWith
+ {
+ [OneTimeSetUp]
+ public void SetUp()
+ {
+ PythonEngine.Initialize();
+ }
+
+ [OneTimeTearDown]
+ public void Dispose()
+ {
+ PythonEngine.Shutdown();
+ }
+
+ ///
+ /// Test that exception is raised in context manager that ignores it.
+ ///
+ [Test]
+ public void TestWithPositive()
+ {
+ var locals = new PyDict();
+
+ PythonEngine.Exec(@"
+class CmTest:
+ def __enter__(self):
+ print('Enter')
+ return self
+ def __exit__(self, t, v, tb):
+ # Exception not handled, return will be False
+ print('Exit')
+ def fail(self):
+ return 5 / 0
+
+a = CmTest()
+", null, locals.Handle);
+
+ var a = locals.GetItem("a");
+
+ try
+ {
+ Py.With(a, cmTest =>
+ {
+ cmTest.fail();
+ });
+ }
+ catch (PythonException e)
+ {
+ Assert.IsTrue(e.Message.Contains("ZeroDivisionError"));
+ }
+ }
+
+
+ ///
+ /// Test that exception is not raised in context manager that handles it
+ ///
+ [Test]
+ public void TestWithNegative()
+ {
+ var locals = new PyDict();
+
+ PythonEngine.Exec(@"
+class CmTest:
+ def __enter__(self):
+ print('Enter')
+ return self
+ def __exit__(self, t, v, tb):
+ # Signal exception is handled by returning true
+ return True
+ def fail(self):
+ return 5 / 0
+
+a = CmTest()
+", null, locals.Handle);
+
+ var a = locals.GetItem("a");
+ Py.With(a, cmTest =>
+ {
+ cmTest.fail();
+ });
+ }
+ }
+}
diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs
index 47f413409..80704c59e 100644
--- a/src/runtime/pyobject.cs
+++ b/src/runtime/pyobject.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections;
using System.Dynamic;
using System.Linq.Expressions;
@@ -915,6 +915,34 @@ public override bool TrySetMember(SetMemberBinder binder, object value)
return true;
}
+ private void GetArgs(object[] inargs, CallInfo callInfo, out PyTuple args, out PyDict kwargs)
+ {
+ if (callInfo == null || callInfo.ArgumentNames.Count == 0)
+ {
+ GetArgs(inargs, out args, out kwargs);
+ return;
+ }
+
+ // Support for .net named arguments
+ var namedArgumentCount = callInfo.ArgumentNames.Count;
+ var regularArgumentCount = callInfo.ArgumentCount - namedArgumentCount;
+
+ var argTuple = Runtime.PyTuple_New(regularArgumentCount);
+ for (int i = 0; i < regularArgumentCount; ++i)
+ {
+ AddArgument(argTuple, i, inargs[i]);
+ }
+ args = new PyTuple(argTuple);
+
+ var namedArgs = new object[namedArgumentCount * 2];
+ for (int i = 0; i < namedArgumentCount; ++i)
+ {
+ namedArgs[i * 2] = callInfo.ArgumentNames[i];
+ namedArgs[i * 2 + 1] = inargs[regularArgumentCount + i];
+ }
+ kwargs = Py.kw(namedArgs);
+ }
+
private void GetArgs(object[] inargs, out PyTuple args, out PyDict kwargs)
{
int arg_count;
@@ -925,22 +953,10 @@ private void GetArgs(object[] inargs, out PyTuple args, out PyDict kwargs)
IntPtr argtuple = Runtime.PyTuple_New(arg_count);
for (var i = 0; i < arg_count; i++)
{
- IntPtr ptr;
- if (inargs[i] is PyObject)
- {
- ptr = ((PyObject)inargs[i]).Handle;
- Runtime.XIncref(ptr);
- }
- else
- {
- ptr = Converter.ToPython(inargs[i], inargs[i]?.GetType());
- }
- if (Runtime.PyTuple_SetItem(argtuple, i, ptr) < 0)
- {
- throw new PythonException();
- }
+ AddArgument(argtuple, i, inargs[i]);
}
args = new PyTuple(argtuple);
+
kwargs = null;
for (int i = arg_count; i < inargs.Length; i++)
{
@@ -959,6 +975,32 @@ private void GetArgs(object[] inargs, out PyTuple args, out PyDict kwargs)
}
}
+ private static void AddArgument(IntPtr argtuple, int i, object target)
+ {
+ IntPtr ptr = GetPythonObject(target);
+
+ if (Runtime.PyTuple_SetItem(argtuple, i, ptr) < 0)
+ {
+ throw new PythonException();
+ }
+ }
+
+ private static IntPtr GetPythonObject(object target)
+ {
+ IntPtr ptr;
+ if (target is PyObject)
+ {
+ ptr = ((PyObject)target).Handle;
+ Runtime.XIncref(ptr);
+ }
+ else
+ {
+ ptr = Converter.ToPython(target, target?.GetType());
+ }
+
+ return ptr;
+ }
+
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
if (this.HasAttr(binder.Name) && this.GetAttr(binder.Name).IsCallable())
@@ -967,7 +1009,7 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o
PyDict kwargs = null;
try
{
- GetArgs(args, out pyargs, out kwargs);
+ GetArgs(args, binder.CallInfo, out pyargs, out kwargs);
result = CheckNone(InvokeMethod(binder.Name, pyargs, kwargs));
}
finally
@@ -997,7 +1039,7 @@ public override bool TryInvoke(InvokeBinder binder, object[] args, out object re
PyDict kwargs = null;
try
{
- GetArgs(args, out pyargs, out kwargs);
+ GetArgs(args, binder.CallInfo, out pyargs, out kwargs);
result = CheckNone(Invoke(pyargs, kwargs));
}
finally
diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs
index 1fd3b239a..9ddd85da6 100644
--- a/src/runtime/pythonengine.cs
+++ b/src/runtime/pythonengine.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -632,5 +632,34 @@ public static void SetArgv(IEnumerable argv)
Runtime.CheckExceptionOccurred();
}
}
+
+ public static void With(PyObject obj, Action Body)
+ {
+ // Behavior described here:
+ // https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers
+
+ IntPtr type = Runtime.PyNone;
+ IntPtr val = Runtime.PyNone;
+ IntPtr traceBack = Runtime.PyNone;
+ PythonException ex = null;
+
+ try
+ {
+ PyObject enterResult = obj.InvokeMethod("__enter__");
+
+ Body(enterResult);
+ }
+ catch (PythonException e)
+ {
+ ex = e;
+ type = ex.PyType;
+ val = ex.PyValue;
+ traceBack = ex.PyTB;
+ }
+
+ var exitResult = obj.InvokeMethod("__exit__", new PyObject(type), new PyObject(val), new PyObject(traceBack));
+
+ if (ex != null && !exitResult.IsTrue()) throw ex;
+ }
}
}