diff --git a/src/runtime/classderived.cs b/src/runtime/classderived.cs index ea1181731..1fd8e04d1 100644 --- a/src/runtime/classderived.cs +++ b/src/runtime/classderived.cs @@ -786,7 +786,7 @@ public static void InvokeCtor(IPythonDerivedType obj, string origCtorName, Objec PyObject[] pyargs = new PyObject[args.Length]; for (int i = 0; i < args.Length; ++i) { - pyargs[i] = new PyObject(Converter.ToPython(args[i], args[i].GetType())); + pyargs[i] = new PyObject(Converter.ToPython(args[i], args[i]?.GetType())); disposeList.Add(pyargs[i]); } diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index cdc610c2c..66ddfad5d 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -228,7 +228,7 @@ internal static IntPtr ToPython(Object value, Type type) { foreach (object o in (IEnumerable)value) { - using (var p = new PyObject(ToPython(o, o.GetType()))) + using (var p = new PyObject(ToPython(o, o?.GetType()))) resultlist.Append(p); } Runtime.Incref(resultlist.Handle); @@ -962,7 +962,7 @@ public static class ConverterExtension { public static PyObject ToPython(this object o) { - return new PyObject(Converter.ToPython(o, o.GetType())); + return new PyObject(Converter.ToPython(o, o?.GetType())); } } } \ No newline at end of file diff --git a/src/runtime/moduleobject.cs b/src/runtime/moduleobject.cs index 738985598..c2ddf564b 100644 --- a/src/runtime/moduleobject.cs +++ b/src/runtime/moduleobject.cs @@ -35,7 +35,10 @@ public ModuleObject(string name) : base() string docstring = "Namespace containing types from the following assemblies:\n\n"; foreach (Assembly a in AssemblyManager.GetAssemblies(name)) { - filename = a.Location; + if (!a.IsDynamic && a.Location != null) + { + filename = a.Location; + } docstring += "- " + a.FullName + "\n"; } diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index 42b8d71b4..308e71ffb 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -939,7 +939,7 @@ public override bool TryGetMember(GetMemberBinder binder, out object result) { if (this.HasAttr(binder.Name)) { - result = this.GetAttr(binder.Name); + result = CheckNone(this.GetAttr(binder.Name)); return true; } else @@ -972,7 +972,7 @@ private void GetArgs(object[] inargs, out PyTuple args, out PyDict kwargs) } else { - ptr = Converter.ToPython(inargs[i], inargs[i].GetType()); + ptr = Converter.ToPython(inargs[i], inargs[i]?.GetType()); } if (Runtime.PyTuple_SetItem(argtuple, i, ptr) < 0) throw new PythonException(); @@ -999,7 +999,7 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o try { GetArgs(args, out pyargs, out kwargs); - result = InvokeMethod(binder.Name, pyargs, kwargs); + result = CheckNone(InvokeMethod(binder.Name, pyargs, kwargs)); } finally { @@ -1023,7 +1023,7 @@ public override bool TryInvoke(InvokeBinder binder, object[] args, out object re try { GetArgs(args, out pyargs, out kwargs); - result = Invoke(pyargs, kwargs); + result = CheckNone(Invoke(pyargs, kwargs)); } finally { @@ -1133,10 +1133,25 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, Object arg result = null; return false; } - result = new PyObject(res); + result = CheckNone(new PyObject(res)); return true; } + // Workaround for https://bugzilla.xamarin.com/show_bug.cgi?id=41509 + // See https://github.com/pythonnet/pythonnet/pull/219 + private static object CheckNone(PyObject pyObj) + { + if (pyObj != null) + { + if (pyObj.obj == Runtime.PyNone) + { + return null; + } + } + + return pyObj; + } + public override bool TryUnaryOperation(UnaryOperationBinder binder, out Object result) { int r; @@ -1170,7 +1185,7 @@ public override bool TryUnaryOperation(UnaryOperationBinder binder, out Object r result = null; return false; } - result = new PyObject(res); + result = CheckNone(new PyObject(res)); return true; } } diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index 7e37d6211..cf9bfb9ee 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -487,7 +487,7 @@ public static KeywordArguments kw(params object[] kv) if (kv[i + 1] is PyObject) value = ((PyObject)kv[i + 1]).Handle; else - value = Converter.ToPython(kv[i + 1], kv[i + 1].GetType()); + value = Converter.ToPython(kv[i + 1], kv[i + 1]?.GetType()); if (Runtime.PyDict_SetItemString(dict.Handle, (string)kv[i], value) != 0) throw new ArgumentException(string.Format("Cannot add key '{0}' to dictionary.", (string)kv[i])); if (!(kv[i + 1] is PyObject)) diff --git a/src/testing/Python.Test.csproj b/src/testing/Python.Test.csproj index 354a6eae6..56efda8e3 100644 --- a/src/testing/Python.Test.csproj +++ b/src/testing/Python.Test.csproj @@ -108,6 +108,7 @@ + @@ -127,6 +128,7 @@ + 3.5 diff --git a/src/testing/callbacktest.cs b/src/testing/callbacktest.cs new file mode 100644 index 000000000..81389289d --- /dev/null +++ b/src/testing/callbacktest.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Python.Test +{ + //======================================================================== + // Tests callbacks into python code. + //======================================================================== + + public class CallbackTest + { + public string Call_simpleDefaultArg_WithNull(string moduleName) + { + using (Runtime.Py.GIL()) + { + dynamic module = Runtime.Py.Import(moduleName); + return module.simpleDefaultArg(null); + } + } + public string Call_simpleDefaultArg_WithEmptyArgs(string moduleName) + { + using (Runtime.Py.GIL()) + { + dynamic module = Runtime.Py.Import(moduleName); + return module.simpleDefaultArg(); + } + } + } +} diff --git a/src/tests/runtests.py b/src/tests/runtests.py index 3a7bde07b..660d3442d 100644 --- a/src/tests/runtests.py +++ b/src/tests/runtests.py @@ -18,6 +18,7 @@ # other test modules that import System.Windows.Forms # run first. They must not do module level import/AddReference() # of the System.Windows.Forms namespace. + 'test_suite', 'test_event', 'test_constructors', 'test_enum', diff --git a/src/tests/test_suite/__init__.py b/src/tests/test_suite/__init__.py new file mode 100644 index 000000000..1ca730d7b --- /dev/null +++ b/src/tests/test_suite/__init__.py @@ -0,0 +1,12 @@ +import unittest + +__all__ = ['test_suite'] + +from .test_import import test_suite as import_tests +from .test_callback import test_suite as callback_tests + +def test_suite(): + suite = unittest.TestSuite() + suite.addTests((import_tests(),)) + suite.addTests((callback_tests(),)) + return suite \ No newline at end of file diff --git a/src/tests/test_suite/_missing_import.py b/src/tests/test_suite/_missing_import.py new file mode 100644 index 000000000..629ff95be --- /dev/null +++ b/src/tests/test_suite/_missing_import.py @@ -0,0 +1,2 @@ + +import this_package_should_never_exist_ever \ No newline at end of file diff --git a/src/tests/test_suite/test_callback.py b/src/tests/test_suite/test_callback.py new file mode 100644 index 000000000..16c45914d --- /dev/null +++ b/src/tests/test_suite/test_callback.py @@ -0,0 +1,30 @@ +import unittest, sys +import clr + +this_module = sys.modules[__name__] +clr.AddReference("Python.Test") +import Python.Test as Test +from Python.Test import CallbackTest +test_instance = CallbackTest() + +def simpleDefaultArg(arg = 'test'): + return arg + +class CallbackTests(unittest.TestCase): + """Test that callbacks from C# into python work.""" + + def testDefaultForNull(self): + """Test that C# can use null for an optional python argument""" + retVal = test_instance.Call_simpleDefaultArg_WithNull(__name__) + pythonRetVal = simpleDefaultArg(None) + self.assertEquals(retVal, pythonRetVal) + + def testDefaultForNone(self): + """Test that C# can use no argument for an optional python argument""" + retVal = test_instance.Call_simpleDefaultArg_WithEmptyArgs(__name__) + pythonRetVal = simpleDefaultArg() + self.assertEquals(retVal, pythonRetVal) + +def test_suite(): + return unittest.makeSuite(CallbackTests) + diff --git a/src/tests/test_suite/test_import.py b/src/tests/test_suite/test_import.py new file mode 100644 index 000000000..b6d155af3 --- /dev/null +++ b/src/tests/test_suite/test_import.py @@ -0,0 +1,16 @@ +import unittest + +class ImportTests(unittest.TestCase): + """Test the import statement.""" + + def testRealtiveMissingImport(self): + """Test that a relative missing import doesn't crash. Some modules use this to check if a package is installed (realtive import in the site-packages folder""" + try: + from . import _missing_import + except ImportError: + pass + + +def test_suite(): + return unittest.makeSuite(ImportTests) +