From 73bf84ed7d37a748da1d81c057753ee6b0508863 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 3 May 2019 09:37:43 +0200 Subject: [PATCH 001/112] Back to dev version for 2.4.1 --- CHANGELOG.md | 8 ++++++++ setup.py | 2 +- src/SharedAssemblyInfo.cs | 2 +- src/clrmodule/ClrModule.cs | 2 +- src/clrmodule/clrmodule.15.csproj | 2 +- src/console/Console.15.csproj | 2 +- src/embed_tests/Python.EmbeddingTest.15.csproj | 2 +- src/runtime/Python.Runtime.15.csproj | 2 +- src/runtime/resources/clr.py | 2 +- src/testing/Python.Test.15.csproj | 2 +- 10 files changed, 17 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 289b864cb..f2d276e51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ This project adheres to [Semantic Versioning][]. This document follows the conventions laid out in [Keep a CHANGELOG][]. +## [unreleased][] + +### Added + +### Changed + +### Fixed + ## [2.4.0][] ### Added diff --git a/setup.py b/setup.py index 7b12997d9..beb930afb 100644 --- a/setup.py +++ b/setup.py @@ -622,7 +622,7 @@ def run(self): setup( name="pythonnet", - version="2.4.0", + version="2.4.1-dev", description=".Net and Mono integration for Python", url="https://pythonnet.github.io/", license="MIT", diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs index 669a43746..dc72b0bdf 100644 --- a/src/SharedAssemblyInfo.cs +++ b/src/SharedAssemblyInfo.cs @@ -25,4 +25,4 @@ // Version Information. Keeping it simple. May need to revisit for Nuget // See: https://codingforsmarties.wordpress.com/2016/01/21/how-to-version-assemblies-destined-for-nuget/ // AssemblyVersion can only be numeric -[assembly: AssemblyVersion("2.4.0")] +[assembly: AssemblyVersion("2.4.1")] diff --git a/src/clrmodule/ClrModule.cs b/src/clrmodule/ClrModule.cs index 3bb3a533b..7fc654fd6 100644 --- a/src/clrmodule/ClrModule.cs +++ b/src/clrmodule/ClrModule.cs @@ -53,7 +53,7 @@ public static void initclr() { #if USE_PYTHON_RUNTIME_VERSION // Has no effect until SNK works. Keep updated anyways. - Version = new Version("2.4.0"), + Version = new Version("2.4.1"), #endif CultureInfo = CultureInfo.InvariantCulture }; diff --git a/src/clrmodule/clrmodule.15.csproj b/src/clrmodule/clrmodule.15.csproj index 2585ffdd2..326620c00 100644 --- a/src/clrmodule/clrmodule.15.csproj +++ b/src/clrmodule/clrmodule.15.csproj @@ -9,7 +9,7 @@ clrmodule clrmodule clrmodule - 2.4.0 + 2.4.1 false false false diff --git a/src/console/Console.15.csproj b/src/console/Console.15.csproj index ec5008036..4e765fea4 100644 --- a/src/console/Console.15.csproj +++ b/src/console/Console.15.csproj @@ -8,7 +8,7 @@ nPython Python.Runtime nPython - 2.4.0 + 2.4.1 false false false diff --git a/src/embed_tests/Python.EmbeddingTest.15.csproj b/src/embed_tests/Python.EmbeddingTest.15.csproj index a741a589e..4f6b2de46 100644 --- a/src/embed_tests/Python.EmbeddingTest.15.csproj +++ b/src/embed_tests/Python.EmbeddingTest.15.csproj @@ -10,7 +10,7 @@ Python.EmbeddingTest Python.EmbeddingTest Python.EmbeddingTest - 2.4.0 + 2.4.1 false false false diff --git a/src/runtime/Python.Runtime.15.csproj b/src/runtime/Python.Runtime.15.csproj index 29177b78c..fb0020356 100644 --- a/src/runtime/Python.Runtime.15.csproj +++ b/src/runtime/Python.Runtime.15.csproj @@ -8,7 +8,7 @@ Python.Runtime Python.Runtime Python.Runtime - 2.4.0 + 2.4.1 false false false diff --git a/src/runtime/resources/clr.py b/src/runtime/resources/clr.py index ddb0d94e8..45265226a 100644 --- a/src/runtime/resources/clr.py +++ b/src/runtime/resources/clr.py @@ -2,7 +2,7 @@ Code in this module gets loaded into the main clr module. """ -__version__ = "2.4.0" +__version__ = "2.4.1" class clrproperty(object): diff --git a/src/testing/Python.Test.15.csproj b/src/testing/Python.Test.15.csproj index da20ed2ef..8c23fe4b5 100644 --- a/src/testing/Python.Test.15.csproj +++ b/src/testing/Python.Test.15.csproj @@ -7,7 +7,7 @@ Python.Test Python.Test Python.Test - 2.4.0 + 2.4.1 bin\ false $(OutputPath)\$(AssemblyName).xml From f544adc4076b1c5530896a19e2a3fbf7b238c754 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 4 May 2019 10:19:34 +0200 Subject: [PATCH 002/112] Drop official 3.4 compatibility Closes #817 --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index beb930afb..8528753b0 100644 --- a/setup.py +++ b/setup.py @@ -647,7 +647,6 @@ def run(self): "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", From af419b10e191f6c7673524b4a2dc5e8dab7643dc Mon Sep 17 00:00:00 2001 From: Marco Rossi Date: Sat, 1 Jun 2019 15:31:47 +0200 Subject: [PATCH 003/112] Add shield of conda-forge package to README (#877) --- README.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index e59ad94f6..5366649ae 100644 --- a/README.rst +++ b/README.rst @@ -5,7 +5,7 @@ pythonnet - Python for .NET |appveyor shield| |travis shield| |codecov shield| -|license shield| |pypi package version| |python supported shield| +|license shield| |pypi package version| |conda-forge version| |python supported shield| |stackexchange shield| Python for .NET is a package that gives Python programmers nearly @@ -111,3 +111,5 @@ https://github.com/pythonnet/pythonnet/wiki :target: https://pypi.python.org/pypi/pythonnet .. |stackexchange shield| image:: https://img.shields.io/badge/StackOverflow-python.net-blue.svg :target: http://stackoverflow.com/questions/tagged/python.net +.. |conda-forge version| image:: https://img.shields.io/conda/vn/conda-forge/pythonnet.svg + :target: https://anaconda.org/conda-forge/pythonnet From 0d6194dce15ae5444697d0be80243a9d5f699504 Mon Sep 17 00:00:00 2001 From: inna-w <40801200+inna-w@users.noreply.github.com> Date: Mon, 3 Jun 2019 22:16:51 +0300 Subject: [PATCH 004/112] Generate NuGet package during build (#875) * Generate NuGet package during build * Comment out PackageReleaseNotes field * Update CHANGELOG and AUTHORS --- AUTHORS.md | 5 +++-- CHANGELOG.md | 2 ++ appveyor.yml | 1 + setup.py | 1 + src/runtime/Python.Runtime.15.csproj | 21 ++++++++++++++------- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 27aae63f4..ba954b47d 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -27,9 +27,10 @@ - David Lechner ([@dlech](https://github.com/dlech)) - Dmitriy Se ([@dmitriyse](https://github.com/dmitriyse)) - He-chien Tsai ([@t3476](https://github.com/t3476)) --   Ivan Cronyn ([@cronan](https://github.com/cronan)) +- Inna Wiesel ([@inna-w](https://github.com/inna-w)) +- Ivan Cronyn ([@cronan](https://github.com/cronan)) - Jan Krivanek ([@jakrivan](https://github.com/jakrivan)) --   Jeff Reback ([@jreback](https://github.com/jreback)) +- Jeff Reback ([@jreback](https://github.com/jreback)) - Joe Frayne ([@jfrayne](https://github.com/jfrayne)) - John Burnett ([@johnburnett](https://github.com/johnburnett)) - John Wilkes ([@jbw3](https://github.com/jbw3)) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2d276e51..b5531bf47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Added +- Added automatic NuGet package generation in appveyor and local builds + ### Changed ### Fixed diff --git a/appveyor.yml b/appveyor.yml index 74b9a9c8e..445f9bb5a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -72,6 +72,7 @@ on_finish: artifacts: - path: dist\* + - path: '.\src\runtime\bin\*.nupkg' notifications: - provider: Slack diff --git a/setup.py b/setup.py index 8528753b0..53c7f3f67 100644 --- a/setup.py +++ b/setup.py @@ -334,6 +334,7 @@ def build_extension(self, ext): ), '/p:PythonBuildDir="{}"'.format(os.path.abspath(dest_dir)), '/p:PythonInteropFile="{}"'.format(os.path.basename(interop_file)), + "/p:PackageId=pythonnet_py{0}{1}_{2}".format(PY_MAJOR, PY_MINOR, ARCH), "/verbosity:{}".format(VERBOSITY), ] diff --git a/src/runtime/Python.Runtime.15.csproj b/src/runtime/Python.Runtime.15.csproj index fb0020356..a4d1773f7 100644 --- a/src/runtime/Python.Runtime.15.csproj +++ b/src/runtime/Python.Runtime.15.csproj @@ -7,14 +7,21 @@ net45 Python.Runtime Python.Runtime - Python.Runtime + pythonnet 2.4.1 - false - false - false - false - false - false + true + false + Python for .NET + Copyright (c) 2006-2019 the contributors of the 'Python for .NET' project + Python and CLR (.NET and Mono) cross-platform language interop + pythonnet + https://github.com/pythonnet/pythonnet/blob/master/LICENSE + https://github.com/pythonnet/pythonnet + git + + python interop dynamic dlr Mono pinvoke + https://raw.githubusercontent.com/pythonnet/pythonnet/master/src/console/python-clear.ico + https://pythonnet.github.io/ bin\ false $(OutputPath)\$(AssemblyName).xml From 43c972d50fa2526f5f9ad73c591f6170c0538854 Mon Sep 17 00:00:00 2001 From: Victor Date: Mon, 17 Jun 2019 12:31:11 -0700 Subject: [PATCH 005/112] Enable C# parameters of type `object` accept any argument, passed from Python (#853) * added a regression test for #881 https://github.com/pythonnet/pythonnet/issues/811 * when converting to object, wrap values of unknown type into PyObject instead of failing This enables overload resolution with object parameters to behave the same way PyObject parameters behave - e.g. allow any Python object to be passed as PyObject as fallback. Resolves #811 --- CHANGELOG.md | 1 + src/embed_tests/Python.EmbeddingTest.csproj | 1 + src/embed_tests/TestInstanceWrapping.cs | 58 +++++++++++++++++++++ src/runtime/converter.cs | 9 ++-- 4 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 src/embed_tests/TestInstanceWrapping.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index b5531bf47..d8683622f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Reattach python exception traceback information (#545) - PythonEngine.Intialize will now call `Py_InitializeEx` with a default value of 0, so signals will not be configured by default on embedding. This is different from the previous behaviour, where `Py_Initialize` was called instead, which sets initSigs to 1. ([#449][i449]) - Refactored MethodBinder.Bind in preparation to make it extensible (#829) +- When calling C# from Python, enable passing argument of any type to a parameter of C# type `object` by wrapping it into `PyObject` instance. ([#881][i881]) - Look for installed Windows 10 sdk's during installation instead of relying on specific versions. ### Fixed diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index faa55fa27..d351709a4 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -89,6 +89,7 @@ + diff --git a/src/embed_tests/TestInstanceWrapping.cs b/src/embed_tests/TestInstanceWrapping.cs new file mode 100644 index 000000000..ec275d67a --- /dev/null +++ b/src/embed_tests/TestInstanceWrapping.cs @@ -0,0 +1,58 @@ +using System; +using System.Globalization; +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest +{ + public class TestInstanceWrapping + { + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + // regression test for https://github.com/pythonnet/pythonnet/issues/811 + [Test] + public void OverloadResolution_UnknownToObject() + { + var overloaded = new Overloaded(); + using (Py.GIL()) + { + var o = overloaded.ToPython(); + + dynamic callWithSelf = PythonEngine.Eval("lambda o: o.ObjOrClass(KeyError())"); + callWithSelf(o); + Assert.AreEqual(Overloaded.Object, overloaded.Value); + } + } + + class Base {} + class Derived: Base { } + + class Overloaded: Derived + { + public int Value { get; set; } + public void IntOrStr(int arg) => this.Value = arg; + public void IntOrStr(string arg) => + this.Value = int.Parse(arg, NumberStyles.Integer, CultureInfo.InvariantCulture); + + public const int Object = 1; + public const int ConcreteClass = 2; + public void ObjOrClass(object _) => this.Value = Object; + public void ObjOrClass(Overloaded _) => this.Value = ConcreteClass; + + public const int Base = ConcreteClass + 1; + public const int Derived = Base + 1; + public void BaseOrDerived(Base _) => this.Value = Base; + public void BaseOrDerived(Derived _) => this.Value = Derived; + } + } +} diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index 11c67bf82..1883dc32b 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -382,12 +382,9 @@ internal static bool ToManagedValue(IntPtr value, Type obType, return ToArray(value, typeof(object[]), out result, setError); } - if (setError) - { - Exceptions.SetError(Exceptions.TypeError, "value cannot be converted to Object"); - } - - return false; + Runtime.XIncref(value); // PyObject() assumes ownership + result = new PyObject(value); + return true; } // Conversion to 'Type' is done using the same mappings as above for objects. From e4e16a422a466b8ed5524f568859c3aba1f9d2ff Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 20 Jun 2019 17:59:28 +0200 Subject: [PATCH 006/112] Revert "Enable C# parameters of type `object` accept any argument, passed from Python (#853)" (#882) This reverts commit 43c972d50fa2526f5f9ad73c591f6170c0538854. --- CHANGELOG.md | 1 - src/embed_tests/Python.EmbeddingTest.csproj | 1 - src/embed_tests/TestInstanceWrapping.cs | 58 --------------------- src/runtime/converter.cs | 9 ++-- 4 files changed, 6 insertions(+), 63 deletions(-) delete mode 100644 src/embed_tests/TestInstanceWrapping.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index d8683622f..b5531bf47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,7 +41,6 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Reattach python exception traceback information (#545) - PythonEngine.Intialize will now call `Py_InitializeEx` with a default value of 0, so signals will not be configured by default on embedding. This is different from the previous behaviour, where `Py_Initialize` was called instead, which sets initSigs to 1. ([#449][i449]) - Refactored MethodBinder.Bind in preparation to make it extensible (#829) -- When calling C# from Python, enable passing argument of any type to a parameter of C# type `object` by wrapping it into `PyObject` instance. ([#881][i881]) - Look for installed Windows 10 sdk's during installation instead of relying on specific versions. ### Fixed diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index d351709a4..faa55fa27 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -89,7 +89,6 @@ - diff --git a/src/embed_tests/TestInstanceWrapping.cs b/src/embed_tests/TestInstanceWrapping.cs deleted file mode 100644 index ec275d67a..000000000 --- a/src/embed_tests/TestInstanceWrapping.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Globalization; -using NUnit.Framework; -using Python.Runtime; - -namespace Python.EmbeddingTest -{ - public class TestInstanceWrapping - { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - - // regression test for https://github.com/pythonnet/pythonnet/issues/811 - [Test] - public void OverloadResolution_UnknownToObject() - { - var overloaded = new Overloaded(); - using (Py.GIL()) - { - var o = overloaded.ToPython(); - - dynamic callWithSelf = PythonEngine.Eval("lambda o: o.ObjOrClass(KeyError())"); - callWithSelf(o); - Assert.AreEqual(Overloaded.Object, overloaded.Value); - } - } - - class Base {} - class Derived: Base { } - - class Overloaded: Derived - { - public int Value { get; set; } - public void IntOrStr(int arg) => this.Value = arg; - public void IntOrStr(string arg) => - this.Value = int.Parse(arg, NumberStyles.Integer, CultureInfo.InvariantCulture); - - public const int Object = 1; - public const int ConcreteClass = 2; - public void ObjOrClass(object _) => this.Value = Object; - public void ObjOrClass(Overloaded _) => this.Value = ConcreteClass; - - public const int Base = ConcreteClass + 1; - public const int Derived = Base + 1; - public void BaseOrDerived(Base _) => this.Value = Base; - public void BaseOrDerived(Derived _) => this.Value = Derived; - } - } -} diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index 1883dc32b..11c67bf82 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -382,9 +382,12 @@ internal static bool ToManagedValue(IntPtr value, Type obType, return ToArray(value, typeof(object[]), out result, setError); } - Runtime.XIncref(value); // PyObject() assumes ownership - result = new PyObject(value); - return true; + if (setError) + { + Exceptions.SetError(Exceptions.TypeError, "value cannot be converted to Object"); + } + + return false; } // Conversion to 'Type' is done using the same mappings as above for objects. From 2bc514f6c5454bc9ff2b94709f5cda400c97442e Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 20 Jun 2019 19:29:20 +0200 Subject: [PATCH 007/112] Fix the failing test (#888) Warn instead of fail on issues with the garbage collector itself. --- src/embed_tests/TestFinalizer.cs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/embed_tests/TestFinalizer.cs b/src/embed_tests/TestFinalizer.cs index 53838f315..650ee5686 100644 --- a/src/embed_tests/TestFinalizer.cs +++ b/src/embed_tests/TestFinalizer.cs @@ -45,7 +45,7 @@ public void CollectBasicObject() called = true; }; - Assert.IsFalse(called); + Assert.IsFalse(called, "The event handler was called before it was installed"); Finalizer.Instance.CollectOnce += handler; WeakReference shortWeak; @@ -55,13 +55,25 @@ public void CollectBasicObject() } FullGCCollect(); // The object has been resurrected - Assert.IsFalse(shortWeak.IsAlive); - Assert.IsTrue(longWeak.IsAlive); + Warn.If( + shortWeak.IsAlive, + "The referenced object is alive although it should have been collected", + shortWeak + ); + Assert.IsTrue( + longWeak.IsAlive, + "The reference object is not alive although it should still be", + longWeak + ); { var garbage = Finalizer.Instance.GetCollectedObjects(); - Assert.NotZero(garbage.Count); - Assert.IsTrue(garbage.Any(T => ReferenceEquals(T.Target, longWeak.Target))); + Assert.NotZero(garbage.Count, "There should still be garbage around"); + Warn.Unless( + garbage.Any(T => ReferenceEquals(T.Target, longWeak.Target)), + $"The {nameof(longWeak)} reference doesn't show up in the garbage list", + garbage + ); } try { @@ -71,7 +83,7 @@ public void CollectBasicObject() { Finalizer.Instance.CollectOnce -= handler; } - Assert.IsTrue(called); + Assert.IsTrue(called, "The event handler was not called during finalization"); Assert.GreaterOrEqual(objectCount, 1); } From fc7d8a466885ced4690327081695f708190e998a Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 24 Jun 2019 18:07:55 +0200 Subject: [PATCH 008/112] Support ARM architectures again (#887) --- src/runtime/runtime.cs | 4 ++++ src/runtime/typemanager.cs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 7623200e0..294ecaf48 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -229,6 +229,8 @@ public enum MachineType { i386, x86_64, + armv7l, + armv8, Other }; @@ -247,6 +249,8 @@ public enum MachineType ["amd64"] = MachineType.x86_64, ["x64"] = MachineType.x86_64, ["em64t"] = MachineType.x86_64, + ["armv7l"] = MachineType.armv7l, + ["armv8"] = MachineType.armv8, }; /// diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index d19c8737f..a260e8dfa 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -510,6 +510,10 @@ public static NativeCode Active return I386; case Runtime.MachineType.x86_64: return X86_64; + case Runtime.MachineType.armv7l: + return Armv7l; + case Runtime.MachineType.armv8: + return Armv8; default: throw new NotImplementedException($"No support for {Runtime.MachineName}"); } @@ -546,6 +550,34 @@ public static NativeCode Active /// /// public static readonly NativeCode I386 = X86_64; + + public static readonly NativeCode Armv7l = new NativeCode() + { + Return0 = 0, + Return1 = 0x08, + Code = new byte[] + { + 0xe3, 0xa0, 0x00, 0x00, // mov r0, #0 + 0xe1, 0x2f, 0xff, 0x1e, // bx lr + + 0xe3, 0xa0, 0x00, 0x01, // mov r0, #1 + 0xe1, 0x2f, 0xff, 0x1e, // bx lr + } + }; + + public static readonly NativeCode Armv8 = new NativeCode() + { + Return0 = 0, + Return1 = 0x08, + Code = new byte[] + { + 0x52, 0x80, 0x00, 0x00, // mov w0, #0x0 + 0xd6, 0x5f, 0x03, 0xc0, // ret + + 0x52, 0x80, 0x00, 0x20, // mov w0, #0x1 + 0xd6, 0x5f, 0x03, 0xc0, // ret + } + }; } /// From 1dd2ee1b02449b85eeee6120c88a4092dc78851a Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 25 Jun 2019 23:00:34 +0200 Subject: [PATCH 009/112] Get the correct library loading functions at runtime --- src/embed_tests/TestRuntime.cs | 7 +- src/runtime/Python.Runtime.csproj | 2 + src/runtime/platform/LibraryLoader.cs | 153 ++++++++++++++++++++++++++ src/runtime/platform/Types.cs | 22 ++++ src/runtime/runtime.cs | 128 ++------------------- src/runtime/typemanager.cs | 22 ++-- 6 files changed, 203 insertions(+), 131 deletions(-) create mode 100644 src/runtime/platform/LibraryLoader.cs create mode 100644 src/runtime/platform/Types.cs diff --git a/src/embed_tests/TestRuntime.cs b/src/embed_tests/TestRuntime.cs index ac1fa1ac0..25b70fac5 100644 --- a/src/embed_tests/TestRuntime.cs +++ b/src/embed_tests/TestRuntime.cs @@ -1,6 +1,7 @@ using System; using NUnit.Framework; using Python.Runtime; +using Python.Runtime.Platform; namespace Python.EmbeddingTest { @@ -26,10 +27,10 @@ public static void PlatformCache() { Runtime.Runtime.Initialize(); - Assert.That(Runtime.Runtime.Machine, Is.Not.EqualTo(Runtime.Runtime.MachineType.Other)); + Assert.That(Runtime.Runtime.Machine, Is.Not.EqualTo(MachineType.Other)); Assert.That(!string.IsNullOrEmpty(Runtime.Runtime.MachineName)); - Assert.That(Runtime.Runtime.OperatingSystem, Is.Not.EqualTo(Runtime.Runtime.OperatingSystemType.Other)); + Assert.That(Runtime.Runtime.OperatingSystem, Is.Not.EqualTo(OperatingSystemType.Other)); Assert.That(!string.IsNullOrEmpty(Runtime.Runtime.OperatingSystemName)); // Don't shut down the runtime: if the python engine was initialized @@ -39,7 +40,7 @@ public static void PlatformCache() [Test] public static void Py_IsInitializedValue() { - Runtime.Runtime.Py_Finalize(); + Runtime.Runtime.Py_Finalize(); Assert.AreEqual(0, Runtime.Runtime.Py_IsInitialized()); Runtime.Runtime.Py_Initialize(); Assert.AreEqual(1, Runtime.Runtime.Py_IsInitialized()); diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 19f776c77..c4d63695f 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -140,6 +140,8 @@ + + diff --git a/src/runtime/platform/LibraryLoader.cs b/src/runtime/platform/LibraryLoader.cs new file mode 100644 index 000000000..c0157b04b --- /dev/null +++ b/src/runtime/platform/LibraryLoader.cs @@ -0,0 +1,153 @@ +using System; +using System.Runtime.InteropServices; + +namespace Python.Runtime.Platform +{ + interface ILibraryLoader + { + IntPtr Load(string dllToLoad); + + IntPtr GetFunction(IntPtr hModule, string procedureName); + + bool Free(IntPtr hModule); + } + + static class LibraryLoader + { + public static ILibraryLoader Get(OperatingSystemType os) + { + switch (os) + { + case OperatingSystemType.Windows: + return new WindowsLoader(); + case OperatingSystemType.Darwin: + return new DarwinLoader(); + case OperatingSystemType.Linux: + return new LinuxLoader(); + default: + throw new Exception($"This operating system ({os}) is not supported"); + } + } + } + + class LinuxLoader : ILibraryLoader + { + private static int RTLD_NOW = 0x2; + private static int RTLD_GLOBAL = 0x100; + private static IntPtr RTLD_DEFAULT = IntPtr.Zero; + private const string NativeDll = "libdl.so"; + + public IntPtr Load(string fileName) + { + return dlopen($"lib{fileName}.so", RTLD_NOW | RTLD_GLOBAL); + } + + public bool Free(IntPtr handle) + { + dlclose(handle); + return true; + } + + public IntPtr GetFunction(IntPtr dllHandle, string name) + { + // look in the exe if dllHandle is NULL + if (dllHandle == IntPtr.Zero) + { + dllHandle = RTLD_DEFAULT; + } + + // clear previous errors if any + dlerror(); + IntPtr res = dlsym(dllHandle, name); + IntPtr errPtr = dlerror(); + if (errPtr != IntPtr.Zero) + { + throw new Exception("dlsym: " + Marshal.PtrToStringAnsi(errPtr)); + } + return res; + } + + [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public static extern IntPtr dlopen(String fileName, int flags); + + [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + private static extern IntPtr dlsym(IntPtr handle, String symbol); + + [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl)] + private static extern int dlclose(IntPtr handle); + + [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr dlerror(); + } + + class DarwinLoader : ILibraryLoader + { + private static int RTLD_NOW = 0x2; + private static int RTLD_GLOBAL = 0x8; + private const string NativeDll = "/usr/lib/libSystem.dylib"; + private static IntPtr RTLD_DEFAULT = new IntPtr(-2); + + public IntPtr Load(string fileName) + { + return dlopen($"lib{fileName}.dylib", RTLD_NOW | RTLD_GLOBAL); + } + + public bool Free(IntPtr handle) + { + dlclose(handle); + return true; + } + + public IntPtr GetFunction(IntPtr dllHandle, string name) + { + // look in the exe if dllHandle is NULL + if (dllHandle == IntPtr.Zero) + { + dllHandle = RTLD_DEFAULT; + } + + // clear previous errors if any + dlerror(); + IntPtr res = dlsym(dllHandle, name); + IntPtr errPtr = dlerror(); + if (errPtr != IntPtr.Zero) + { + throw new Exception("dlsym: " + Marshal.PtrToStringAnsi(errPtr)); + } + return res; + } + + [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public static extern IntPtr dlopen(String fileName, int flags); + + [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + private static extern IntPtr dlsym(IntPtr handle, String symbol); + + [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl)] + private static extern int dlclose(IntPtr handle); + + [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr dlerror(); + } + + class WindowsLoader : ILibraryLoader + { + private const string NativeDll = "kernel32.dll"; + + [DllImport(NativeDll)] + static extern IntPtr LoadLibrary(string dllToLoad); + + public IntPtr Load(string dllToLoad) => WindowsLoader.LoadLibrary(dllToLoad); + + [DllImport(NativeDll)] + static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); + + public IntPtr GetFunction(IntPtr hModule, string procedureName) => WindowsLoader.GetProcAddress(hModule, procedureName); + + + [DllImport(NativeDll)] + static extern bool FreeLibrary(IntPtr hModule); + + public bool Free(IntPtr hModule) => WindowsLoader.FreeLibrary(hModule); + } +} diff --git a/src/runtime/platform/Types.cs b/src/runtime/platform/Types.cs new file mode 100644 index 000000000..bdc51af39 --- /dev/null +++ b/src/runtime/platform/Types.cs @@ -0,0 +1,22 @@ +namespace Python.Runtime.Platform +{ + public enum MachineType + { + i386, + x86_64, + armv7l, + armv8, + Other + }; + + /// + /// Operating system type as reported by Python. + /// + public enum OperatingSystemType + { + Windows, + Darwin, + Linux, + Other + } +} diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 294ecaf48..ec5bddfd0 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -7,96 +7,7 @@ namespace Python.Runtime { - [SuppressUnmanagedCodeSecurity] - internal static class NativeMethods - { -#if MONO_LINUX || MONO_OSX -#if NETSTANDARD - private static int RTLD_NOW = 0x2; -#if MONO_LINUX - private static int RTLD_GLOBAL = 0x100; - private static IntPtr RTLD_DEFAULT = IntPtr.Zero; - private const string NativeDll = "libdl.so"; - public static IntPtr LoadLibrary(string fileName) - { - return dlopen($"lib{fileName}.so", RTLD_NOW | RTLD_GLOBAL); - } -#elif MONO_OSX - private static int RTLD_GLOBAL = 0x8; - private const string NativeDll = "/usr/lib/libSystem.dylib"; - private static IntPtr RTLD_DEFAULT = new IntPtr(-2); - - public static IntPtr LoadLibrary(string fileName) - { - return dlopen($"lib{fileName}.dylib", RTLD_NOW | RTLD_GLOBAL); - } -#endif -#else - private static int RTLD_NOW = 0x2; - private static int RTLD_SHARED = 0x20; -#if MONO_OSX - private static IntPtr RTLD_DEFAULT = new IntPtr(-2); - private const string NativeDll = "__Internal"; -#elif MONO_LINUX - private static IntPtr RTLD_DEFAULT = IntPtr.Zero; - private const string NativeDll = "libdl.so"; -#endif - - public static IntPtr LoadLibrary(string fileName) - { - return dlopen(fileName, RTLD_NOW | RTLD_SHARED); - } -#endif - - - public static void FreeLibrary(IntPtr handle) - { - dlclose(handle); - } - - public static IntPtr GetProcAddress(IntPtr dllHandle, string name) - { - // look in the exe if dllHandle is NULL - if (dllHandle == IntPtr.Zero) - { - dllHandle = RTLD_DEFAULT; - } - - // clear previous errors if any - dlerror(); - IntPtr res = dlsym(dllHandle, name); - IntPtr errPtr = dlerror(); - if (errPtr != IntPtr.Zero) - { - throw new Exception("dlsym: " + Marshal.PtrToStringAnsi(errPtr)); - } - return res; - } - - [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr dlopen(String fileName, int flags); - - [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr dlsym(IntPtr handle, String symbol); - - [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl)] - private static extern int dlclose(IntPtr handle); - - [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl)] - private static extern IntPtr dlerror(); -#else // Windows - private const string NativeDll = "kernel32.dll"; - - [DllImport(NativeDll)] - public static extern IntPtr LoadLibrary(string dllToLoad); - - [DllImport(NativeDll)] - public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); - - [DllImport(NativeDll)] - public static extern bool FreeLibrary(IntPtr hModule); -#endif - } + using Python.Runtime.Platform; /// /// Encapsulates the low-level Python C API. Note that it is @@ -197,17 +108,6 @@ public class Runtime // .NET core: System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows) internal static bool IsWindows = Environment.OSVersion.Platform == PlatformID.Win32NT; - /// - /// Operating system type as reported by Python. - /// - public enum OperatingSystemType - { - Windows, - Darwin, - Linux, - Other - } - static readonly Dictionary OperatingSystemTypeMapping = new Dictionary() { { "Windows", OperatingSystemType.Windows }, @@ -225,14 +125,6 @@ public enum OperatingSystemType /// public static string OperatingSystemName { get; private set; } - public enum MachineType - { - i386, - x86_64, - armv7l, - armv8, - Other - }; /// /// Map lower-case version of the python machine name to the processor @@ -397,24 +289,24 @@ internal static void Initialize(bool initSigs = false) Error = new IntPtr(-1); + // Initialize data about the platform we're running on. We need + // this for the type manager and potentially other details. Must + // happen after caching the python types, above. + InitializePlatformData(); + IntPtr dllLocal = IntPtr.Zero; + var loader = LibraryLoader.Get(OperatingSystem); if (_PythonDll != "__Internal") { - dllLocal = NativeMethods.LoadLibrary(_PythonDll); + dllLocal = loader.Load(_PythonDll); } - _PyObject_NextNotImplemented = NativeMethods.GetProcAddress(dllLocal, "_PyObject_NextNotImplemented"); + _PyObject_NextNotImplemented = loader.GetFunction(dllLocal, "_PyObject_NextNotImplemented"); -#if !(MONO_LINUX || MONO_OSX) if (dllLocal != IntPtr.Zero) { - NativeMethods.FreeLibrary(dllLocal); + loader.Free(dllLocal); } -#endif - // Initialize data about the platform we're running on. We need - // this for the type manager and potentially other details. Must - // happen after caching the python types, above. - InitializePlatformData(); // Initialize modules that depend on the runtime class. AssemblyManager.Initialize(); diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index a260e8dfa..00a8f0a89 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -6,6 +6,8 @@ namespace Python.Runtime { + using Python.Runtime.Platform; + /// /// The TypeManager class is responsible for building binary-compatible /// Python type objects that are implemented in managed code. @@ -504,15 +506,15 @@ public static NativeCode Active { get { - switch(Runtime.Machine) + switch (Runtime.Machine) { - case Runtime.MachineType.i386: + case MachineType.i386: return I386; - case Runtime.MachineType.x86_64: + case MachineType.x86_64: return X86_64; - case Runtime.MachineType.armv7l: + case MachineType.armv7l: return Armv7l; - case Runtime.MachineType.armv8: + case MachineType.armv8: return Armv8; default: throw new NotImplementedException($"No support for {Runtime.MachineName}"); @@ -635,9 +637,9 @@ int MAP_ANONYMOUS { switch (Runtime.OperatingSystem) { - case Runtime.OperatingSystemType.Darwin: + case OperatingSystemType.Darwin: return 0x1000; - case Runtime.OperatingSystemType.Linux: + case OperatingSystemType.Linux: return 0x20; default: throw new NotImplementedException($"mmap is not supported on {Runtime.OperatingSystemName}"); @@ -668,10 +670,10 @@ internal static IMemoryMapper CreateMemoryMapper() { switch (Runtime.OperatingSystem) { - case Runtime.OperatingSystemType.Darwin: - case Runtime.OperatingSystemType.Linux: + case OperatingSystemType.Darwin: + case OperatingSystemType.Linux: return new UnixMemoryMapper(); - case Runtime.OperatingSystemType.Windows: + case OperatingSystemType.Windows: return new WindowsMemoryMapper(); default: throw new NotImplementedException($"No support for {Runtime.OperatingSystemName}"); From 537ee5fae147c78d0221133f4db4de3371ebc319 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 26 Jun 2019 08:04:00 +0200 Subject: [PATCH 010/112] Implement error handling, move using statements --- src/runtime/platform/LibraryLoader.cs | 115 +++++++++++++++++++------- src/runtime/runtime.cs | 2 +- src/runtime/typemanager.cs | 2 +- 3 files changed, 87 insertions(+), 32 deletions(-) diff --git a/src/runtime/platform/LibraryLoader.cs b/src/runtime/platform/LibraryLoader.cs index c0157b04b..a6d88cd19 100644 --- a/src/runtime/platform/LibraryLoader.cs +++ b/src/runtime/platform/LibraryLoader.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Runtime.InteropServices; namespace Python.Runtime.Platform @@ -9,7 +10,7 @@ interface ILibraryLoader IntPtr GetFunction(IntPtr hModule, string procedureName); - bool Free(IntPtr hModule); + void Free(IntPtr hModule); } static class LibraryLoader @@ -25,7 +26,7 @@ public static ILibraryLoader Get(OperatingSystemType os) case OperatingSystemType.Linux: return new LinuxLoader(); default: - throw new Exception($"This operating system ({os}) is not supported"); + throw new PlatformNotSupportedException($"This operating system ({os}) is not supported"); } } } @@ -37,15 +38,23 @@ class LinuxLoader : ILibraryLoader private static IntPtr RTLD_DEFAULT = IntPtr.Zero; private const string NativeDll = "libdl.so"; - public IntPtr Load(string fileName) + public IntPtr Load(string dllToLoad) { - return dlopen($"lib{fileName}.so", RTLD_NOW | RTLD_GLOBAL); + var filename = $"lib{dllToLoad}.so"; + ClearError(); + var res = dlopen(filename, RTLD_NOW | RTLD_GLOBAL); + if (res == IntPtr.Zero) + { + var err = GetError(); + throw new DllNotFoundException($"Could not load {filename} with flags RTLD_NOW | RTLD_GLOBAL: {err}"); + } + + return res; } - public bool Free(IntPtr handle) + public void Free(IntPtr handle) { dlclose(handle); - return true; } public IntPtr GetFunction(IntPtr dllHandle, string name) @@ -56,22 +65,35 @@ public IntPtr GetFunction(IntPtr dllHandle, string name) dllHandle = RTLD_DEFAULT; } - // clear previous errors if any - dlerror(); + ClearError(); IntPtr res = dlsym(dllHandle, name); - IntPtr errPtr = dlerror(); - if (errPtr != IntPtr.Zero) + if (res == IntPtr.Zero) { - throw new Exception("dlsym: " + Marshal.PtrToStringAnsi(errPtr)); + var err = GetError(); + throw new MissingMethodException($"Failed to load symbol {name}: {err}"); } return res; } + void ClearError() + { + dlerror(); + } + + string GetError() + { + var res = dlerror(); + if (res != IntPtr.Zero) + return Marshal.PtrToStringAnsi(res); + else + return null; + } + [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr dlopen(String fileName, int flags); + public static extern IntPtr dlopen(string fileName, int flags); [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr dlsym(IntPtr handle, String symbol); + private static extern IntPtr dlsym(IntPtr handle, string symbol); [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl)] private static extern int dlclose(IntPtr handle); @@ -87,15 +109,23 @@ class DarwinLoader : ILibraryLoader private const string NativeDll = "/usr/lib/libSystem.dylib"; private static IntPtr RTLD_DEFAULT = new IntPtr(-2); - public IntPtr Load(string fileName) + public IntPtr Load(string dllToLoad) { - return dlopen($"lib{fileName}.dylib", RTLD_NOW | RTLD_GLOBAL); + var filename = $"lib{dllToLoad}.dylib"; + ClearError(); + var res = dlopen(filename, RTLD_NOW | RTLD_GLOBAL); + if (res == IntPtr.Zero) + { + var err = GetError(); + throw new DllNotFoundException($"Could not load {filename} with flags RTLD_NOW | RTLD_GLOBAL: {err}"); + } + + return res; } - public bool Free(IntPtr handle) + public void Free(IntPtr handle) { dlclose(handle); - return true; } public IntPtr GetFunction(IntPtr dllHandle, string name) @@ -106,17 +136,30 @@ public IntPtr GetFunction(IntPtr dllHandle, string name) dllHandle = RTLD_DEFAULT; } - // clear previous errors if any - dlerror(); + ClearError(); IntPtr res = dlsym(dllHandle, name); - IntPtr errPtr = dlerror(); - if (errPtr != IntPtr.Zero) + if (res == IntPtr.Zero) { - throw new Exception("dlsym: " + Marshal.PtrToStringAnsi(errPtr)); + var err = GetError(); + throw new MissingMethodException($"Failed to load symbol {name}: {err}"); } return res; } + void ClearError() + { + dlerror(); + } + + string GetError() + { + var res = dlerror(); + if (res != IntPtr.Zero) + return Marshal.PtrToStringAnsi(res); + else + return null; + } + [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] public static extern IntPtr dlopen(String fileName, int flags); @@ -134,20 +177,32 @@ class WindowsLoader : ILibraryLoader { private const string NativeDll = "kernel32.dll"; - [DllImport(NativeDll)] - static extern IntPtr LoadLibrary(string dllToLoad); - public IntPtr Load(string dllToLoad) => WindowsLoader.LoadLibrary(dllToLoad); + public IntPtr Load(string dllToLoad) + { + var res = WindowsLoader.LoadLibrary(dllToLoad); + if (res == IntPtr.Zero) + throw new DllNotFoundException($"Could not load {dllToLoad}", new Win32Exception()); + return res; + } + + public IntPtr GetFunction(IntPtr hModule, string procedureName) + { + var res = WindowsLoader.GetProcAddress(hModule, procedureName); + if (res == IntPtr.Zero) + throw new MissingMethodException($"Failed to load symbol {procedureName}", new Win32Exception()); + return res; + } - [DllImport(NativeDll)] - static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); + public void Free(IntPtr hModule) => WindowsLoader.FreeLibrary(hModule); - public IntPtr GetFunction(IntPtr hModule, string procedureName) => WindowsLoader.GetProcAddress(hModule, procedureName); + [DllImport(NativeDll, SetLastError = true)] + static extern IntPtr LoadLibrary(string dllToLoad); + [DllImport(NativeDll, SetLastError = true)] + static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); [DllImport(NativeDll)] static extern bool FreeLibrary(IntPtr hModule); - - public bool Free(IntPtr hModule) => WindowsLoader.FreeLibrary(hModule); } } diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index ec5bddfd0..a347651d0 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -4,10 +4,10 @@ using System.Text; using System.Threading; using System.Collections.Generic; +using Python.Runtime.Platform; namespace Python.Runtime { - using Python.Runtime.Platform; /// /// Encapsulates the low-level Python C API. Note that it is diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index 00a8f0a89..127e82eaa 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices; +using Python.Runtime.Platform; namespace Python.Runtime { - using Python.Runtime.Platform; /// /// The TypeManager class is responsible for building binary-compatible From a8a94264164406e7166038d7fa8b03ac40eb8b71 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 26 Jun 2019 10:55:55 -0700 Subject: [PATCH 011/112] Bump C# language version to 7.3 (#896) * Bump C# language version to 7.3 #860 * Switch to .NET SDK 2.2 * Use xenial image for travis CI --- .travis.yml | 20 ++++++++++---------- src/runtime/Python.Runtime.15.csproj | 2 +- src/runtime/Python.Runtime.csproj | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1dadbad1d..46f47489d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -dist: trusty +dist: xenial sudo: false language: python @@ -12,16 +12,16 @@ matrix: addons: &xplat-addons apt: sources: - - sourceline: deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-trusty-prod trusty main + - sourceline: deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-xenial-prod xenial main key_url: https://packages.microsoft.com/keys/microsoft.asc - - sourceline: deb http://download.mono-project.com/repo/ubuntu trusty main + - sourceline: deb http://download.mono-project.com/repo/ubuntu xenial main key_url: http://keyserver.ubuntu.com/pks/lookup?op=get&search=0xA6A19B38D3D831EF packages: - mono-devel - ca-certificates-mono - - dotnet-hostfxr-2.0.0 - - dotnet-runtime-2.0.0 - - dotnet-sdk-2.0.0 + - dotnet-hostfxr-2.2 + - dotnet-runtime-2.2 + - dotnet-sdk-2.2 - python: 3.5 env: *xplat-env @@ -45,9 +45,9 @@ matrix: packages: - mono-devel - ca-certificates-mono - - dotnet-hostfxr-2.0.0 - - dotnet-runtime-2.0.0 - - dotnet-sdk-2.0.0 + - dotnet-hostfxr-2.2 + - dotnet-runtime-2.2 + - dotnet-sdk-2.2 # --------------------- Classic builds ------------------------ - python: 2.7 @@ -84,7 +84,7 @@ env: addons: apt: sources: - - sourceline: deb http://download.mono-project.com/repo/ubuntu trusty main + - sourceline: deb http://download.mono-project.com/repo/ubuntu xenial main key_url: http://keyserver.ubuntu.com/pks/lookup?op=get&search=0xA6A19B38D3D831EF packages: - mono-devel diff --git a/src/runtime/Python.Runtime.15.csproj b/src/runtime/Python.Runtime.15.csproj index a4d1773f7..122132513 100644 --- a/src/runtime/Python.Runtime.15.csproj +++ b/src/runtime/Python.Runtime.15.csproj @@ -30,7 +30,7 @@ ..\..\ $(SolutionDir)\bin\ $(PythonBuildDir)\$(TargetFramework)\ - 6 + 7.3 True ..\pythonnet.snk $(PYTHONNET_DEFINE_CONSTANTS) diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index c4d63695f..ac6b59150 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -15,7 +15,7 @@ ..\..\ $(SolutionDir)\bin\ Properties - 6 + 7.3 true false ..\pythonnet.snk From df0574db552020397728648ee3ec576aa3a1c2a8 Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 27 Jun 2019 22:36:30 -0700 Subject: [PATCH 012/112] Improve "No method matches given arguments" message with more details (#900) * generate more useful message, when a .NET overload can't be found, that matches Python parameter types * provide detailed error message, when an overload can't be found when calling C# from Python Related: #811, #265, #782 --- CHANGELOG.md | 2 ++ src/embed_tests/TestCallbacks.cs | 35 ++++++++++++++++++++++++++++++++ src/runtime/methodbinder.cs | 31 +++++++++++++++++++++++++--- 3 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 src/embed_tests/TestCallbacks.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index b5531bf47..e5a990922 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Changed +- Added argument types information to "No method matches given arguments" message + ### Fixed ## [2.4.0][] diff --git a/src/embed_tests/TestCallbacks.cs b/src/embed_tests/TestCallbacks.cs new file mode 100644 index 000000000..220b0a86a --- /dev/null +++ b/src/embed_tests/TestCallbacks.cs @@ -0,0 +1,35 @@ +using System; + +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest { + using Runtime = Python.Runtime.Runtime; + + public class TestCallbacks { + [OneTimeSetUp] + public void SetUp() { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() { + PythonEngine.Shutdown(); + } + + [Test] + public void TestNoOverloadException() { + int passed = 0; + var aFunctionThatCallsIntoPython = new Action(value => passed = value); + using (Py.GIL()) { + dynamic callWith42 = PythonEngine.Eval("lambda f: f([42])"); + var error = Assert.Throws(() => callWith42(aFunctionThatCallsIntoPython.ToPython())); + Assert.AreEqual("TypeError", error.PythonTypeName); + string expectedArgTypes = Runtime.IsPython2 + ? "()" + : "()"; + StringAssert.EndsWith(expectedArgTypes, error.Message); + } + } + } +} diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs index 7471d5d7c..95b953555 100644 --- a/src/runtime/methodbinder.cs +++ b/src/runtime/methodbinder.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Reflection; +using System.Text; namespace Python.Runtime { @@ -555,12 +556,36 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i if (binding == null) { - var value = "No method matches given arguments"; + var value = new StringBuilder("No method matches given arguments"); if (methodinfo != null && methodinfo.Length > 0) { - value += $" for {methodinfo[0].Name}"; + value.Append($" for {methodinfo[0].Name}"); } - Exceptions.SetError(Exceptions.TypeError, value); + + long argCount = Runtime.PyTuple_Size(args); + value.Append(": ("); + for(long argIndex = 0; argIndex < argCount; argIndex++) { + var arg = Runtime.PyTuple_GetItem(args, argIndex); + if (arg != IntPtr.Zero) { + var type = Runtime.PyObject_Type(arg); + if (type != IntPtr.Zero) { + try { + var description = Runtime.PyObject_Unicode(type); + if (description != IntPtr.Zero) { + value.Append(Runtime.GetManagedString(description)); + Runtime.XDecref(description); + } + } finally { + Runtime.XDecref(type); + } + } + } + + if (argIndex + 1 < argCount) + value.Append(", "); + } + value.Append(')'); + Exceptions.SetError(Exceptions.TypeError, value.ToString()); return IntPtr.Zero; } From 93968d25728b7282937067ebb966202da8c21a69 Mon Sep 17 00:00:00 2001 From: chrisjbremner Date: Wed, 3 Jul 2019 14:36:44 -0700 Subject: [PATCH 013/112] Safe wheel import (#905) Only allow wheel commands if wheel is installed --- AUTHORS.md | 1 + CHANGELOG.md | 1 + setup.py | 47 +++++++++++++++++++++++++++-------------------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index ba954b47d..a45cf6d78 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -19,6 +19,7 @@ - Callum Noble ([@callumnoble](https://github.com/callumnoble)) - Christian Heimes ([@tiran](https://github.com/tiran)) - Christoph Gohlke ([@cgohlke](https://github.com/cgohlke)) +- Christopher Bremner ([@chrisjbremner](https://github.com/chrisjbremner)) - Christopher Pow ([@christopherpow](https://github.com/christopherpow)) - Daniel Fernandez ([@fdanny](https://github.com/fdanny)) - Daniel Santana ([@dgsantana](https://github.com/dgsantana)) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5a990922..ada979147 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Changed - Added argument types information to "No method matches given arguments" message +- Moved wheel import in setup.py inside of a try/except to prevent pip collection failures ### Fixed diff --git a/setup.py b/setup.py index 53c7f3f67..c6e4007e6 100644 --- a/setup.py +++ b/setup.py @@ -15,10 +15,14 @@ import sysconfig from distutils import spawn from distutils.command import install, build, build_ext, install_data, install_lib -from wheel import bdist_wheel from setuptools import Extension, setup +try: + from wheel import bdist_wheel +except ImportError: + bdist_wheel = None + # Allow config/verbosity to be set from cli # http://stackoverflow.com/a/4792601/5208670 CONFIG = "Release" # Release or Debug @@ -594,21 +598,21 @@ def run(self): _update_xlat_devtools() return install.install.run(self) +if bdist_wheel: + class BDistWheelPythonnet(bdist_wheel.bdist_wheel): + user_options = bdist_wheel.bdist_wheel.user_options + [("xplat", None, None)] -class BDistWheelPythonnet(bdist_wheel.bdist_wheel): - user_options = bdist_wheel.bdist_wheel.user_options + [("xplat", None, None)] + def initialize_options(self): + bdist_wheel.bdist_wheel.initialize_options(self) + self.xplat = None - def initialize_options(self): - bdist_wheel.bdist_wheel.initialize_options(self) - self.xplat = None + def finalize_options(self): + bdist_wheel.bdist_wheel.finalize_options(self) - def finalize_options(self): - bdist_wheel.bdist_wheel.finalize_options(self) - - def run(self): - if self.xplat: - _update_xlat_devtools() - return bdist_wheel.bdist_wheel.run(self) + def run(self): + if self.xplat: + _update_xlat_devtools() + return bdist_wheel.bdist_wheel.run(self) ############################################################################### @@ -621,6 +625,15 @@ def run(self): if not os.path.exists(_get_interop_filename()): setup_requires.append("pycparser") +cmdclass={ + "install": InstallPythonnet, + "build_ext": BuildExtPythonnet, + "install_lib": InstallLibPythonnet, + "install_data": InstallDataPythonnet, +} +if bdist_wheel: + cmdclass["bdist_wheel"] = BDistWheelPythonnet + setup( name="pythonnet", version="2.4.1-dev", @@ -633,13 +646,7 @@ def run(self): long_description=_get_long_description(), ext_modules=[Extension("clr", sources=list(_get_source_files()))], data_files=[("{install_platlib}", ["{build_lib}/Python.Runtime.dll"])], - cmdclass={ - "install": InstallPythonnet, - "build_ext": BuildExtPythonnet, - "install_lib": InstallLibPythonnet, - "install_data": InstallDataPythonnet, - "bdist_wheel": BDistWheelPythonnet, - }, + cmdclass=cmdclass, classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", From 6f635a42a97400bf5e284c4d821a75708393ac70 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 1 Aug 2019 17:04:07 +0200 Subject: [PATCH 014/112] Simplify travis config and pin mono to 5.20 (#927) --- .travis.yml | 94 +++++++++-------------------------------------------- 1 file changed, 16 insertions(+), 78 deletions(-) diff --git a/.travis.yml b/.travis.yml index 46f47489d..9689c0422 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,80 +1,17 @@ dist: xenial sudo: false language: python - -matrix: - include: -# --------------------- XPLAT builds ------------------------ - - python: 2.7 - env: &xplat-env - - BUILD_OPTS=--xplat - - NUNIT_PATH=~/.nuget/packages/nunit.consolerunner/3.*/tools/nunit3-console.exe - addons: &xplat-addons - apt: - sources: - - sourceline: deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-xenial-prod xenial main - key_url: https://packages.microsoft.com/keys/microsoft.asc - - sourceline: deb http://download.mono-project.com/repo/ubuntu xenial main - key_url: http://keyserver.ubuntu.com/pks/lookup?op=get&search=0xA6A19B38D3D831EF - packages: - - mono-devel - - ca-certificates-mono - - dotnet-hostfxr-2.2 - - dotnet-runtime-2.2 - - dotnet-sdk-2.2 - - - python: 3.5 - env: *xplat-env - addons: *xplat-addons - - - python: 3.6 - env: *xplat-env - addons: *xplat-addons - - - python: 3.7 - env: *xplat-env - dist: xenial - sudo: true - addons: &xplat-addons-xenial - apt: - sources: - - sourceline: deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-xenial-prod xenial main - key_url: https://packages.microsoft.com/keys/microsoft.asc - - sourceline: deb https://download.mono-project.com/repo/ubuntu stable-xenial main - key_url: http://keyserver.ubuntu.com/pks/lookup?op=get&search=0xA6A19B38D3D831EF - packages: - - mono-devel - - ca-certificates-mono - - dotnet-hostfxr-2.2 - - dotnet-runtime-2.2 - - dotnet-sdk-2.2 - -# --------------------- Classic builds ------------------------ - - python: 2.7 - env: &classic-env - - BUILD_OPTS= - - NUNIT_PATH=./packages/NUnit.*/tools/nunit3-console.exe - - - python: 3.5 - env: *classic-env - - - python: 3.6 - env: *classic-env - - - python: 3.7 - env: *classic-env - dist: xenial - sudo: true - addons: - apt: - sources: - - sourceline: deb http://download.mono-project.com/repo/ubuntu xenial main - key_url: http://keyserver.ubuntu.com/pks/lookup?op=get&search=0xA6A19B38D3D831EF - packages: - - mono-devel - - ca-certificates-mono +python: + - 2.7 + - 3.5 + - 3.6 + - 3.7 env: + matrix: + - BUILD_OPTS=--xplat NUNIT_PATH="~/.nuget/packages/nunit.consolerunner/3.*/tools/nunit3-console.exe" RUN_TESTS=dotnet EMBED_TESTS_PATH=netcoreapp2.0_publish/ + - BUILD_OPTS="" NUNIT_PATH="./packages/NUnit.*/tools/nunit3-console.exe" RUN_TESTS="mono $NUNIT_PATH" EMBED_TESTS_PATH="" + global: - LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so - SEGFAULT_SIGNALS=all @@ -84,11 +21,16 @@ env: addons: apt: sources: - - sourceline: deb http://download.mono-project.com/repo/ubuntu xenial main + - sourceline: deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-xenial-prod xenial main + key_url: https://packages.microsoft.com/keys/microsoft.asc + - sourceline: deb http://download.mono-project.com/repo/ubuntu stable-xenial/snapshots/5.20 main key_url: http://keyserver.ubuntu.com/pks/lookup?op=get&search=0xA6A19B38D3D831EF packages: - mono-devel - ca-certificates-mono + - dotnet-hostfxr-2.2 + - dotnet-runtime-2.2 + - dotnet-sdk-2.2 before_install: # Set-up dll path for embedded tests @@ -102,13 +44,9 @@ install: script: - python -m pytest - - mono $NUNIT_PATH src/embed_tests/bin/Python.EmbeddingTest.dll - - if [[ $BUILD_OPTS == --xplat ]]; then dotnet src/embed_tests/bin/netcoreapp2.0_publish/Python.EmbeddingTest.dll; fi + - $RUN_TESTS src/embed_tests/bin/$EMBED_TESTS_PATH/Python.EmbeddingTest.dll after_script: - # Uncomment if need to geninterop, ie. py37 final - # - python tools/geninterop/geninterop.py - # Waiting on mono-coverage, SharpCover or xr.Baboon - coverage xml -i - codecov --file coverage.xml --flags setup_linux From 1ce630e5b0c2fce208267591d6502a5d4d0e2a0a Mon Sep 17 00:00:00 2001 From: Ivan Cronyn Date: Fri, 2 Aug 2019 11:19:10 +0100 Subject: [PATCH 015/112] Removes imports deprecated in Python3 (#925) --- CHANGELOG.md | 1 + src/runtime/runtime.cs | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ada979147..b0d525b36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added argument types information to "No method matches given arguments" message - Moved wheel import in setup.py inside of a try/except to prevent pip collection failures +- Removes PyLong_GetMax and PyClass_New when targetting Python3 ### Fixed diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index a347651d0..a6bfca431 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -769,8 +769,10 @@ public static extern int Py_Main( [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyCFunction_Call(IntPtr func, IntPtr args, IntPtr kw); +#if PYTHON2 [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyClass_New(IntPtr bases, IntPtr dict, IntPtr name); +#endif [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyInstance_New(IntPtr cls, IntPtr args, IntPtr kw); @@ -1012,10 +1014,6 @@ internal static IntPtr PyInt_FromInt64(long value) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PyLong_FromString")] internal static extern IntPtr PyInt_FromString(string value, IntPtr end, int radix); - - [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, - EntryPoint = "PyLong_GetMax")] - internal static extern int PyInt_GetMax(); #elif PYTHON2 [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr PyInt_FromLong(IntPtr value); From f20bcf6498f336b684b8b5d515aaee5e3ebb24f4 Mon Sep 17 00:00:00 2001 From: ftreurni Date: Fri, 2 Aug 2019 12:21:48 +0200 Subject: [PATCH 016/112] Fix so that Runtime.PyModuleType is retrieved via Python.dll (#904) (#929) --- AUTHORS.md | 1 + CHANGELOG.md | 5 ++++- src/runtime/runtime.cs | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index a45cf6d78..6c2817aeb 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -27,6 +27,7 @@ - David Lassonde ([@lassond](https://github.com/lassond)) - David Lechner ([@dlech](https://github.com/dlech)) - Dmitriy Se ([@dmitriyse](https://github.com/dmitriyse)) +- Florian Treurniet ([@ftreurni](https://github.com/ftreurni)) - He-chien Tsai ([@t3476](https://github.com/t3476)) - Inna Wiesel ([@inna-w](https://github.com/inna-w)) - Ivan Cronyn ([@cronan](https://github.com/cronan)) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0d525b36..705b33b7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Fixed +- Fixed runtime that fails loading when using pythonnet in an environment + together with Nuitka + ## [2.4.0][] ### Added @@ -61,7 +64,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Fixed conversion of 'float' and 'double' values ([#486][i486]) - Fixed 'clrmethod' for python 2 ([#492][i492]) - Fixed double calling of constructor when deriving from .NET class ([#495][i495]) -- Fixed `clr.GetClrType` when iterating over `System` members ([#607][p607]) +- Fixed `clr.GetClrType` when iterating over `System` members ([#607][p607]) - Fixed `LockRecursionException` when loading assemblies ([#627][i627]) - Fixed errors breaking .NET Remoting on method invoke ([#276][i276]) - Fixed PyObject.GetHashCode ([#676][i676]) diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index a6bfca431..75f11492f 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -205,7 +205,6 @@ internal static void Initialize(bool initSigs = false) PyNotImplemented = PyObject_GetAttrString(op, "NotImplemented"); PyBaseObjectType = PyObject_GetAttrString(op, "object"); - PyModuleType = PyObject_Type(op); PyNone = PyObject_GetAttrString(op, "None"); PyTrue = PyObject_GetAttrString(op, "True"); PyFalse = PyObject_GetAttrString(op, "False"); @@ -302,6 +301,7 @@ internal static void Initialize(bool initSigs = false) dllLocal = loader.Load(_PythonDll); } _PyObject_NextNotImplemented = loader.GetFunction(dllLocal, "_PyObject_NextNotImplemented"); + PyModuleType = loader.GetFunction(dllLocal, "PyModule_Type"); if (dllLocal != IntPtr.Zero) { From c97a380bd38c28a055b7228028f01f5e8d1e5d8f Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 2 Aug 2019 14:10:09 +0200 Subject: [PATCH 017/112] Readd .NET implementations of GC slots again (#913) Since I failed to provide properly working implementations of Return0 and Return1 for ARM, I'll just add the old behaviour back for all platforms but x86 and amd64. --- src/runtime/typemanager.cs | 75 ++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 44 deletions(-) diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index 127e82eaa..9a98e9ebb 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -512,12 +512,8 @@ public static NativeCode Active return I386; case MachineType.x86_64: return X86_64; - case MachineType.armv7l: - return Armv7l; - case MachineType.armv8: - return Armv8; default: - throw new NotImplementedException($"No support for {Runtime.MachineName}"); + return null; } } } @@ -552,34 +548,6 @@ public static NativeCode Active /// /// public static readonly NativeCode I386 = X86_64; - - public static readonly NativeCode Armv7l = new NativeCode() - { - Return0 = 0, - Return1 = 0x08, - Code = new byte[] - { - 0xe3, 0xa0, 0x00, 0x00, // mov r0, #0 - 0xe1, 0x2f, 0xff, 0x1e, // bx lr - - 0xe3, 0xa0, 0x00, 0x01, // mov r0, #1 - 0xe1, 0x2f, 0xff, 0x1e, // bx lr - } - }; - - public static readonly NativeCode Armv8 = new NativeCode() - { - Return0 = 0, - Return1 = 0x08, - Code = new byte[] - { - 0x52, 0x80, 0x00, 0x00, // mov w0, #0x0 - 0xd6, 0x5f, 0x03, 0xc0, // ret - - 0x52, 0x80, 0x00, 0x20, // mov w0, #0x1 - 0xd6, 0x5f, 0x03, 0xc0, // ret - } - }; } /// @@ -702,7 +670,7 @@ internal static void InitializeNativeCodePage() Marshal.Copy(NativeCode.Active.Code, 0, NativeCodePage, codeLength); mapper.SetReadExec(NativeCodePage, codeLength); } -#endregion + #endregion /// /// Given a newly allocated Python type object and a managed Type that @@ -745,21 +713,40 @@ internal static void InitializeSlots(IntPtr type, Type impl) impl = impl.BaseType; } - // See the TestDomainReload test: there was a crash related to - // the gc-related slots. They always return 0 or 1 because we don't - // really support gc: + var native = NativeCode.Active; + + // The garbage collection related slots always have to return 1 or 0 + // since .NET objects don't take part in Python's gc: // tp_traverse (returns 0) // tp_clear (returns 0) // tp_is_gc (returns 1) - // We can't do without: python really wants those slots to exist. - // We can't implement those in C# because the application domain - // can be shut down and the memory released. - InitializeNativeCodePage(); - InitializeSlot(type, NativeCodePage + NativeCode.Active.Return0, "tp_traverse"); - InitializeSlot(type, NativeCodePage + NativeCode.Active.Return0, "tp_clear"); - InitializeSlot(type, NativeCodePage + NativeCode.Active.Return1, "tp_is_gc"); + // These have to be defined, though, so by default we fill these with + // static C# functions from this class. + + var ret0 = Interop.GetThunk(((Func)Return0).Method); + var ret1 = Interop.GetThunk(((Func)Return1).Method); + + if (native != null) + { + // If we want to support domain reload, the C# implementation + // cannot be used as the assembly may get released before + // CPython calls these functions. Instead, for amd64 and x86 we + // load them into a separate code page that is leaked + // intentionally. + InitializeNativeCodePage(); + ret1 = NativeCodePage + native.Return1; + ret0 = NativeCodePage + native.Return0; + } + + InitializeSlot(type, ret0, "tp_traverse"); + InitializeSlot(type, ret0, "tp_clear"); + InitializeSlot(type, ret1, "tp_is_gc"); } + static int Return1(IntPtr _) => 1; + + static int Return0(IntPtr _) => 0; + /// /// Helper for InitializeSlots. /// From 51a186858096577aef6a860656523d3194eee9a8 Mon Sep 17 00:00:00 2001 From: jmlidbetter <53430310+jmlidbetter@users.noreply.github.com> Date: Mon, 5 Aug 2019 17:25:37 +0100 Subject: [PATCH 018/112] Adds support to convert iterators to arrays (#928) --- AUTHORS.md | 1 + CHANGELOG.md | 1 + src/runtime/converter.cs | 37 ++++++++++++++++++++----------------- src/tests/test_array.py | 31 +++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 6c2817aeb..39c2eb180 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -34,6 +34,7 @@ - Jan Krivanek ([@jakrivan](https://github.com/jakrivan)) - Jeff Reback ([@jreback](https://github.com/jreback)) - Joe Frayne ([@jfrayne](https://github.com/jfrayne)) +- Joe Lidbetter ([@jmlidbetter](https://github.com/jmlidbetter)) - John Burnett ([@johnburnett](https://github.com/johnburnett)) - John Wilkes ([@jbw3](https://github.com/jbw3)) - Luke Stratman ([@lstratman](https://github.com/lstratman)) diff --git a/CHANGELOG.md b/CHANGELOG.md index 705b33b7d..941045aef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added argument types information to "No method matches given arguments" message - Moved wheel import in setup.py inside of a try/except to prevent pip collection failures - Removes PyLong_GetMax and PyClass_New when targetting Python3 +- Added support for converting python iterators to C# arrays ### Fixed diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index 11c67bf82..5d8769a73 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -837,17 +837,20 @@ private static void SetConversionError(IntPtr value, Type target) /// /// Convert a Python value to a correctly typed managed array instance. - /// The Python value must support the Python sequence protocol and the + /// The Python value must support the Python iterator protocol or and the /// items in the sequence must be convertible to the target array type. /// private static bool ToArray(IntPtr value, Type obType, out object result, bool setError) { Type elementType = obType.GetElementType(); - var size = Runtime.PySequence_Size(value); result = null; - if (size < 0) - { + bool IsSeqObj = Runtime.PySequence_Check(value); + var len = IsSeqObj ? Runtime.PySequence_Size(value) : -1; + + IntPtr IterObject = Runtime.PyObject_GetIter(value); + + if(IterObject==IntPtr.Zero) { if (setError) { SetConversionError(value, obType); @@ -855,21 +858,17 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s return false; } - Array items = Array.CreateInstance(elementType, size); + Array items; + + var listType = typeof(List<>); + var constructedListType = listType.MakeGenericType(elementType); + IList list = IsSeqObj ? (IList) Activator.CreateInstance(constructedListType, new Object[] {(int) len}) : + (IList) Activator.CreateInstance(constructedListType); + IntPtr item; - // XXX - is there a better way to unwrap this if it is a real array? - for (var i = 0; i < size; i++) + while ((item = Runtime.PyIter_Next(IterObject)) != IntPtr.Zero) { object obj = null; - IntPtr item = Runtime.PySequence_GetItem(value, i); - if (item == IntPtr.Zero) - { - if (setError) - { - SetConversionError(value, obType); - return false; - } - } if (!Converter.ToManaged(item, elementType, out obj, true)) { @@ -877,10 +876,14 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s return false; } - items.SetValue(obj, i); + list.Add(obj); Runtime.XDecref(item); } + Runtime.XDecref(IterObject); + items = Array.CreateInstance(elementType, list.Count); + list.CopyTo(items, 0); + result = items; return true; } diff --git a/src/tests/test_array.py b/src/tests/test_array.py index 7ccadddff..b492a66d3 100644 --- a/src/tests/test_array.py +++ b/src/tests/test_array.py @@ -1337,3 +1337,34 @@ def test_array_abuse(): with pytest.raises(TypeError): desc = Test.PublicArrayTest.__dict__['__setitem__'] desc(0, 0, 0) + + +@pytest.mark.skipif(PY2, reason="Only applies in Python 3") +def test_iterator_to_array(): + from System import Array, String + + d = {"a": 1, "b": 2, "c": 3} + keys_iterator = iter(d.keys()) + arr = Array[String](keys_iterator) + + Array.Sort(arr) + + assert arr[0] == "a" + assert arr[1] == "b" + assert arr[2] == "c" + + +@pytest.mark.skipif(PY2, reason="Only applies in Python 3") +def test_dict_keys_to_array(): + from System import Array, String + + d = {"a": 1, "b": 2, "c": 3} + d_keys = d.keys() + arr = Array[String](d_keys) + + Array.Sort(arr) + + assert arr[0] == "a" + assert arr[1] == "b" + assert arr[2] == "c" + From f1da55e5d13f7c82d2eb62e211afd93d574d5fe8 Mon Sep 17 00:00:00 2001 From: jmlidbetter <53430310+jmlidbetter@users.noreply.github.com> Date: Mon, 26 Aug 2019 13:13:38 +0100 Subject: [PATCH 019/112] Fixes bug where there is casting on delegates -- this is explicitly in the NET standard documentation for GetDelegateForFunctionPointer (#936) --- CHANGELOG.md | 2 ++ src/runtime/nativecall.cs | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 941045aef..a545f335c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,11 +17,13 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Moved wheel import in setup.py inside of a try/except to prevent pip collection failures - Removes PyLong_GetMax and PyClass_New when targetting Python3 - Added support for converting python iterators to C# arrays +- Changed usage of obselete function GetDelegateForFunctionPointer(IntPtr, Type) to GetDelegateForFunctionPointer(IntPtr) ### Fixed - Fixed runtime that fails loading when using pythonnet in an environment together with Nuitka +- Fixes bug where delegates get casts (dotnetcore) ## [2.4.0][] diff --git a/src/runtime/nativecall.cs b/src/runtime/nativecall.cs index b5bf25dd7..4a7bf05c8 100644 --- a/src/runtime/nativecall.cs +++ b/src/runtime/nativecall.cs @@ -32,19 +32,21 @@ internal class NativeCall public static void Void_Call_1(IntPtr fp, IntPtr a1) { - ((Void_1_Delegate)Marshal.GetDelegateForFunctionPointer(fp, typeof(Void_1_Delegate)))(a1); + var d = Marshal.GetDelegateForFunctionPointer(fp); + d(a1); } public static IntPtr Call_3(IntPtr fp, IntPtr a1, IntPtr a2, IntPtr a3) { - var d = (Interop.TernaryFunc)Marshal.GetDelegateForFunctionPointer(fp, typeof(Interop.TernaryFunc)); + var d = Marshal.GetDelegateForFunctionPointer(fp); return d(a1, a2, a3); } public static int Int_Call_3(IntPtr fp, IntPtr a1, IntPtr a2, IntPtr a3) { - return ((Int_3_Delegate)Marshal.GetDelegateForFunctionPointer(fp, typeof(Int_3_Delegate)))(a1, a2, a3); + var d = Marshal.GetDelegateForFunctionPointer(fp); + return d(a1, a2, a3); } #else private static AssemblyBuilder aBuilder; From 1bcbeb5d0e9bcc7f0304994a44a7485d6126873b Mon Sep 17 00:00:00 2001 From: Joe Savage Date: Thu, 12 Sep 2019 03:26:06 -0500 Subject: [PATCH 020/112] Feature/named arg support (#953) * Add support for named arguments (#849) * Remove kwarg check since it breaks the python-derived CLR class use-case * Add named parameter test cases * Update changelog and authors * Add default params tests --- AUTHORS.md | 1 + CHANGELOG.md | 1 + src/runtime/methodbinder.cs | 118 +++++++++++++++++++++----- src/testing/methodtest.cs | 33 ++++++++ src/tests/test_method.py | 162 ++++++++++++++++++++++++++++++++++++ 5 files changed, 296 insertions(+), 19 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 39c2eb180..9e13ca569 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -35,6 +35,7 @@ - Jeff Reback ([@jreback](https://github.com/jreback)) - Joe Frayne ([@jfrayne](https://github.com/jfrayne)) - Joe Lidbetter ([@jmlidbetter](https://github.com/jmlidbetter)) +- Joe Savage ([@s4v4g3](https://github.com/s4v4g3)) - John Burnett ([@johnburnett](https://github.com/johnburnett)) - John Wilkes ([@jbw3](https://github.com/jbw3)) - Luke Stratman ([@lstratman](https://github.com/lstratman)) diff --git a/CHANGELOG.md b/CHANGELOG.md index a545f335c..5cb0ea96c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Removes PyLong_GetMax and PyClass_New when targetting Python3 - Added support for converting python iterators to C# arrays - Changed usage of obselete function GetDelegateForFunctionPointer(IntPtr, Type) to GetDelegateForFunctionPointer(IntPtr) +- Added support for kwarg parameters when calling .NET methods from Python ### Fixed diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs index 95b953555..8a7fc1930 100644 --- a/src/runtime/methodbinder.cs +++ b/src/runtime/methodbinder.cs @@ -2,6 +2,8 @@ using System.Collections; using System.Reflection; using System.Text; +using System.Collections.Generic; +using System.Linq; namespace Python.Runtime { @@ -280,6 +282,22 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth { // loop to find match, return invoker w/ or /wo error MethodBase[] _methods = null; + + var kwargDict = new Dictionary(); + if (kw != IntPtr.Zero) + { + var pynkwargs = (int)Runtime.PyDict_Size(kw); + IntPtr keylist = Runtime.PyDict_Keys(kw); + IntPtr valueList = Runtime.PyDict_Values(kw); + for (int i = 0; i < pynkwargs; ++i) + { + var keyStr = Runtime.GetManagedString(Runtime.PyList_GetItem(keylist, i)); + kwargDict[keyStr] = Runtime.PyList_GetItem(valueList, i); + } + Runtime.XDecref(keylist); + Runtime.XDecref(valueList); + } + var pynargs = (int)Runtime.PyTuple_Size(args); var isGeneric = false; if (info != null) @@ -303,11 +321,12 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth ArrayList defaultArgList; bool paramsArray; - if (!MatchesArgumentCount(pynargs, pi, out paramsArray, out defaultArgList)) { + if (!MatchesArgumentCount(pynargs, pi, kwargDict, out paramsArray, out defaultArgList)) + { continue; } var outs = 0; - var margs = TryConvertArguments(pi, paramsArray, args, pynargs, defaultArgList, + var margs = TryConvertArguments(pi, paramsArray, args, pynargs, kwargDict, defaultArgList, needsResolution: _methods.Length > 1, outs: out outs); @@ -351,19 +370,21 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth } /// - /// Attempts to convert Python argument tuple into an array of managed objects, - /// that can be passed to a method. + /// Attempts to convert Python positional argument tuple and keyword argument table + /// into an array of managed objects, that can be passed to a method. /// /// Information about expected parameters /// true, if the last parameter is a params array. /// A pointer to the Python argument tuple /// Number of arguments, passed by Python + /// Dictionary of keyword argument name to python object pointer /// A list of default values for omitted parameters /// true, if overloading resolution is required /// Returns number of output parameters /// An array of .NET arguments, that can be passed to a method. static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, IntPtr args, int pyArgCount, + Dictionary kwargDict, ArrayList defaultArgList, bool needsResolution, out int outs) @@ -374,7 +395,10 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, for (int paramIndex = 0; paramIndex < pi.Length; paramIndex++) { - if (paramIndex >= pyArgCount) + var parameter = pi[paramIndex]; + bool hasNamedParam = kwargDict.ContainsKey(parameter.Name); + + if (paramIndex >= pyArgCount && !hasNamedParam) { if (defaultArgList != null) { @@ -384,12 +408,19 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, continue; } - var parameter = pi[paramIndex]; - IntPtr op = (arrayStart == paramIndex) - // map remaining Python arguments to a tuple since - // the managed function accepts it - hopefully :] - ? Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount) - : Runtime.PyTuple_GetItem(args, paramIndex); + IntPtr op; + if (hasNamedParam) + { + op = kwargDict[parameter.Name]; + } + else + { + op = (arrayStart == paramIndex) + // map remaining Python arguments to a tuple since + // the managed function accepts it - hopefully :] + ? Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount) + : Runtime.PyTuple_GetItem(args, paramIndex); + } bool isOut; if (!TryConvertArgument(op, parameter.ParameterType, needsResolution, out margs[paramIndex], out isOut)) @@ -505,7 +536,8 @@ static Type TryComputeClrArgumentType(Type parameterType, IntPtr argument, bool return clrtype; } - static bool MatchesArgumentCount(int argumentCount, ParameterInfo[] parameters, + static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] parameters, + Dictionary kwargDict, out bool paramsArray, out ArrayList defaultArgList) { @@ -513,21 +545,40 @@ static bool MatchesArgumentCount(int argumentCount, ParameterInfo[] parameters, var match = false; paramsArray = false; - if (argumentCount == parameters.Length) + if (positionalArgumentCount == parameters.Length) { match = true; - } else if (argumentCount < parameters.Length) + } + else if (positionalArgumentCount < parameters.Length) { + // every parameter past 'positionalArgumentCount' must have either + // a corresponding keyword argument or a default parameter match = true; defaultArgList = new ArrayList(); - for (var v = argumentCount; v < parameters.Length; v++) { - if (parameters[v].DefaultValue == DBNull.Value) { + for (var v = positionalArgumentCount; v < parameters.Length; v++) + { + if (kwargDict.ContainsKey(parameters[v].Name)) + { + // we have a keyword argument for this parameter, + // no need to check for a default parameter, but put a null + // placeholder in defaultArgList + defaultArgList.Add(null); + } + else if (parameters[v].IsOptional) + { + // IsOptional will be true if the parameter has a default value, + // or if the parameter has the [Optional] attribute specified. + // The GetDefaultValue() extension method will return the value + // to be passed in as the parameter value + defaultArgList.Add(parameters[v].GetDefaultValue()); + } + else + { match = false; - } else { - defaultArgList.Add(parameters[v].DefaultValue); } } - } else if (argumentCount > parameters.Length && parameters.Length > 0 && + } + else if (positionalArgumentCount > parameters.Length && parameters.Length > 0 && Attribute.IsDefined(parameters[parameters.Length - 1], typeof(ParamArrayAttribute))) { // This is a `foo(params object[] bar)` style method @@ -722,4 +773,33 @@ internal Binding(MethodBase info, object inst, object[] args, int outs) this.outs = outs; } } + + + static internal class ParameterInfoExtensions + { + public static object GetDefaultValue(this ParameterInfo parameterInfo) + { + // parameterInfo.HasDefaultValue is preferable but doesn't exist in .NET 4.0 + bool hasDefaultValue = (parameterInfo.Attributes & ParameterAttributes.HasDefault) == + ParameterAttributes.HasDefault; + + if (hasDefaultValue) + { + return parameterInfo.DefaultValue; + } + else + { + // [OptionalAttribute] was specified for the parameter. + // See https://stackoverflow.com/questions/3416216/optionalattribute-parameters-default-value + // for rules on determining the value to pass to the parameter + var type = parameterInfo.ParameterType; + if (type == typeof(object)) + return Type.Missing; + else if (type.IsValueType) + return Activator.CreateInstance(type); + else + return null; + } + } + } } diff --git a/src/testing/methodtest.cs b/src/testing/methodtest.cs index cf653f9f9..91836b727 100644 --- a/src/testing/methodtest.cs +++ b/src/testing/methodtest.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Runtime.InteropServices; namespace Python.Test { @@ -651,6 +652,38 @@ public static string Casesensitive() { return "Casesensitive"; } + + public static string DefaultParams(int a=0, int b=0, int c=0, int d=0) + { + return string.Format("{0}{1}{2}{3}", a, b, c, d); + } + + public static string OptionalParams([Optional]int a, [Optional]int b, [Optional]int c, [Optional] int d) + { + return string.Format("{0}{1}{2}{3}", a, b, c, d); + } + + public static bool OptionalParams_TestMissing([Optional]object a) + { + return a == Type.Missing; + } + + public static bool OptionalParams_TestReferenceType([Optional]string a) + { + return a == null; + } + + public static string OptionalAndDefaultParams([Optional]int a, [Optional]int b, int c=0, int d=0) + { + return string.Format("{0}{1}{2}{3}", a, b, c, d); + } + + public static string OptionalAndDefaultParams2([Optional]int a, [Optional]int b, [Optional, DefaultParameterValue(1)]int c, int d = 2) + { + return string.Format("{0}{1}{2}{3}", a, b, c, d); + } + + } diff --git a/src/tests/test_method.py b/src/tests/test_method.py index ad678611b..34f460d59 100644 --- a/src/tests/test_method.py +++ b/src/tests/test_method.py @@ -776,6 +776,9 @@ def test_no_object_in_param(): res = MethodTest.TestOverloadedNoObject(5) assert res == "Got int" + + res = MethodTest.TestOverloadedNoObject(i=7) + assert res == "Got int" with pytest.raises(TypeError): MethodTest.TestOverloadedNoObject("test") @@ -787,9 +790,15 @@ def test_object_in_param(): res = MethodTest.TestOverloadedObject(5) assert res == "Got int" + + res = MethodTest.TestOverloadedObject(i=7) + assert res == "Got int" res = MethodTest.TestOverloadedObject("test") assert res == "Got object" + + res = MethodTest.TestOverloadedObject(o="test") + assert res == "Got object" def test_object_in_multiparam(): @@ -813,6 +822,42 @@ def test_object_in_multiparam(): res = MethodTest.TestOverloadedObjectTwo(7.24, 7.24) assert res == "Got object-object" + res = MethodTest.TestOverloadedObjectTwo(a=5, b=5) + assert res == "Got int-int" + + res = MethodTest.TestOverloadedObjectTwo(5, b=5) + assert res == "Got int-int" + + res = MethodTest.TestOverloadedObjectTwo(a=5, b="foo") + assert res == "Got int-string" + + res = MethodTest.TestOverloadedObjectTwo(5, b="foo") + assert res == "Got int-string" + + res = MethodTest.TestOverloadedObjectTwo(a="foo", b=7.24) + assert res == "Got string-object" + + res = MethodTest.TestOverloadedObjectTwo("foo", b=7.24) + assert res == "Got string-object" + + res = MethodTest.TestOverloadedObjectTwo(a="foo", b="bar") + assert res == "Got string-string" + + res = MethodTest.TestOverloadedObjectTwo("foo", b="bar") + assert res == "Got string-string" + + res = MethodTest.TestOverloadedObjectTwo(a="foo", b=5) + assert res == "Got string-int" + + res = MethodTest.TestOverloadedObjectTwo("foo", b=5) + assert res == "Got string-int" + + res = MethodTest.TestOverloadedObjectTwo(a=7.24, b=7.24) + assert res == "Got object-object" + + res = MethodTest.TestOverloadedObjectTwo(7.24, b=7.24) + assert res == "Got object-object" + def test_object_in_multiparam_exception(): """Test method with object multiparams behaves""" @@ -966,3 +1011,120 @@ def test_getting_overloaded_constructor_binding_does_not_leak_ref_count(): # simple test refCount = sys.getrefcount(PlainOldClass.Overloads[int]) assert refCount == 1 + + +def test_default_params(): + # all positional parameters + res = MethodTest.DefaultParams(1,2,3,4) + assert res == "1234" + + res = MethodTest.DefaultParams(1, 2, 3) + assert res == "1230" + + res = MethodTest.DefaultParams(1, 2) + assert res == "1200" + + res = MethodTest.DefaultParams(1) + assert res == "1000" + + res = MethodTest.DefaultParams(a=2) + assert res == "2000" + + res = MethodTest.DefaultParams(b=3) + assert res == "0300" + + res = MethodTest.DefaultParams(c=4) + assert res == "0040" + + res = MethodTest.DefaultParams(d=7) + assert res == "0007" + + res = MethodTest.DefaultParams(a=2, c=5) + assert res == "2050" + + res = MethodTest.DefaultParams(1, d=7, c=3) + assert res == "1037" + + with pytest.raises(TypeError): + MethodTest.DefaultParams(1,2,3,4,5) + +def test_optional_params(): + res = MethodTest.OptionalParams(1, 2, 3, 4) + assert res == "1234" + + res = MethodTest.OptionalParams(1, 2, 3) + assert res == "1230" + + res = MethodTest.OptionalParams(1, 2) + assert res == "1200" + + res = MethodTest.OptionalParams(1) + assert res == "1000" + + res = MethodTest.OptionalParams(a=2) + assert res == "2000" + + res = MethodTest.OptionalParams(b=3) + assert res == "0300" + + res = MethodTest.OptionalParams(c=4) + assert res == "0040" + + res = MethodTest.OptionalParams(d=7) + assert res == "0007" + + res = MethodTest.OptionalParams(a=2, c=5) + assert res == "2050" + + res = MethodTest.OptionalParams(1, d=7, c=3) + assert res == "1037" + + res = MethodTest.OptionalParams_TestMissing() + assert res == True + + res = MethodTest.OptionalParams_TestMissing(None) + assert res == False + + res = MethodTest.OptionalParams_TestMissing(a = None) + assert res == False + + res = MethodTest.OptionalParams_TestMissing(a='hi') + assert res == False + + res = MethodTest.OptionalParams_TestReferenceType() + assert res == True + + res = MethodTest.OptionalParams_TestReferenceType(None) + assert res == True + + res = MethodTest.OptionalParams_TestReferenceType(a=None) + assert res == True + + res = MethodTest.OptionalParams_TestReferenceType('hi') + assert res == False + + res = MethodTest.OptionalParams_TestReferenceType(a='hi') + assert res == False + +def test_optional_and_default_params(): + + res = MethodTest.OptionalAndDefaultParams() + assert res == "0000" + + res = MethodTest.OptionalAndDefaultParams(1) + assert res == "1000" + + res = MethodTest.OptionalAndDefaultParams(1, c=4) + assert res == "1040" + + res = MethodTest.OptionalAndDefaultParams(b=4, c=7) + assert res == "0470" + + res = MethodTest.OptionalAndDefaultParams2() + assert res == "0012" + + res = MethodTest.OptionalAndDefaultParams2(a=1,b=2,c=3,d=4) + assert res == "1234" + + res = MethodTest.OptionalAndDefaultParams2(b=2, c=3) + assert res == "0232" From 46c7597eb18f3586de76a66c3a5ec3d185b64010 Mon Sep 17 00:00:00 2001 From: Mark Visser Date: Fri, 20 Sep 2019 10:01:24 -0400 Subject: [PATCH 021/112] Update readme with resources (#955) Added resources section with mailing list and chat --- README.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.rst b/README.rst index 5366649ae..84bf93d84 100644 --- a/README.rst +++ b/README.rst @@ -113,3 +113,8 @@ https://github.com/pythonnet/pythonnet/wiki :target: http://stackoverflow.com/questions/tagged/python.net .. |conda-forge version| image:: https://img.shields.io/conda/vn/conda-forge/pythonnet.svg :target: https://anaconda.org/conda-forge/pythonnet + +Resources +--------- +Mailing list: https://mail.python.org/mailman/listinfo/pythondotnet +Chat: https://gitter.im/pythonnet/pythonnet From 60e6045f6873495170502f0247205a9c923fc1fe Mon Sep 17 00:00:00 2001 From: jmlidbetter <53430310+jmlidbetter@users.noreply.github.com> Date: Tue, 1 Oct 2019 16:13:34 +0100 Subject: [PATCH 022/112] Detect py arch (#961) * Gets size of C long from Is32Bit and IsWindows --- CHANGELOG.md | 1 + src/runtime/converter.cs | 15 ++++++++++++++- src/runtime/runtime.cs | 34 ++++++++++++++++++++++++++++++---- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cb0ea96c..b5b11cd77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Fixed runtime that fails loading when using pythonnet in an environment together with Nuitka - Fixes bug where delegates get casts (dotnetcore) +- Determine size of interpreter longs at runtime ## [2.4.0][] diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index 5d8769a73..e7e047419 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -728,7 +728,20 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo } goto type_error; } - uint ui = (uint)Runtime.PyLong_AsUnsignedLong(op); + + uint ui; + try + { + ui = Convert.ToUInt32(Runtime.PyLong_AsUnsignedLong(op)); + } catch (OverflowException) + { + // Probably wasn't an overflow in python but was in C# (e.g. if cpython + // longs are 64 bit then 0xFFFFFFFF + 1 will not overflow in + // PyLong_AsUnsignedLong) + Runtime.XDecref(op); + goto overflow; + } + if (Exceptions.ErrorOccurred()) { diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 75f11492f..4985a57f5 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1036,8 +1036,21 @@ internal static bool PyLong_Check(IntPtr ob) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyLong_FromLong(long value); - [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr PyLong_FromUnsignedLong(uint value); + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyLong_FromUnsignedLong")] + internal static extern IntPtr PyLong_FromUnsignedLong32(uint value); + + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyLong_FromUnsignedLong")] + internal static extern IntPtr PyLong_FromUnsignedLong64(ulong value); + + internal static IntPtr PyLong_FromUnsignedLong(object value) + { + if(Is32Bit || IsWindows) + return PyLong_FromUnsignedLong32(Convert.ToUInt32(value)); + else + return PyLong_FromUnsignedLong64(Convert.ToUInt64(value)); + } [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyLong_FromDouble(double value); @@ -1054,8 +1067,21 @@ internal static bool PyLong_Check(IntPtr ob) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyLong_AsLong(IntPtr value); - [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint PyLong_AsUnsignedLong(IntPtr value); + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyLong_AsUnsignedLong")] + internal static extern uint PyLong_AsUnsignedLong32(IntPtr value); + + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyLong_AsUnsignedLong")] + internal static extern ulong PyLong_AsUnsignedLong64(IntPtr value); + + internal static object PyLong_AsUnsignedLong(IntPtr value) + { + if (Is32Bit || IsWindows) + return PyLong_AsUnsignedLong32(value); + else + return PyLong_AsUnsignedLong64(value); + } [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern long PyLong_AsLongLong(IntPtr value); From 4a9457fed54d2eeee3860a10f2b8c59f48aef043 Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Fri, 18 Oct 2019 03:07:57 -0500 Subject: [PATCH 023/112] Provide hook to implement __repr__ (#808) Provide hook to implement __repr__ --- AUTHORS.md | 1 + CHANGELOG.md | 1 + src/runtime/classbase.cs | 38 ++++++++++++ src/runtime/exceptions.cs | 23 +++++++ src/testing/Python.Test.csproj | 3 +- src/testing/ReprTest.cs | 108 +++++++++++++++++++++++++++++++++ src/tests/test_exceptions.py | 7 +-- src/tests/test_repr.py | 68 +++++++++++++++++++++ src/tests/tests.pyproj | 1 + 9 files changed, 244 insertions(+), 6 deletions(-) create mode 100644 src/testing/ReprTest.cs create mode 100644 src/tests/test_repr.py diff --git a/AUTHORS.md b/AUTHORS.md index 9e13ca569..9253c7e55 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -41,6 +41,7 @@ - Luke Stratman ([@lstratman](https://github.com/lstratman)) - Konstantin Posudevskiy ([@konstantin-posudevskiy](https://github.com/konstantin-posudevskiy)) - Matthias Dittrich ([@matthid](https://github.com/matthid)) +- Mohamed Koubaa ([@koubaa](https://github.com/koubaa)) - Patrick Stewart ([@patstew](https://github.com/patstew)) - Raphael Nestler ([@rnestler](https://github.com/rnestler)) - Rickard Holmberg ([@rickardraysearch](https://github.com/rickardraysearch)) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5b11cd77..e24a46904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added PyObject finalizer support, Python objects referred by C# can be auto collect now ([#692][p692]). - Added detailed comments about aproaches and dangers to handle multi-app-domains ([#625][p625]) - Python 3.7 support, builds and testing added. Defaults changed from Python 3.6 to 3.7 ([#698][p698]) +- Added support for C# types to provide `__repr__` ([#680][p680]) ### Changed diff --git a/src/runtime/classbase.cs b/src/runtime/classbase.cs index 5846fa82a..41636c404 100644 --- a/src/runtime/classbase.cs +++ b/src/runtime/classbase.cs @@ -246,6 +246,44 @@ public static IntPtr tp_str(IntPtr ob) } } + public static IntPtr tp_repr(IntPtr ob) + { + var co = GetManagedObject(ob) as CLRObject; + if (co == null) + { + return Exceptions.RaiseTypeError("invalid object"); + } + try + { + //if __repr__ is defined, use it + var instType = co.inst.GetType(); + System.Reflection.MethodInfo methodInfo = instType.GetMethod("__repr__"); + if (methodInfo != null && methodInfo.IsPublic) + { + var reprString = methodInfo.Invoke(co.inst, null) as string; + return Runtime.PyString_FromString(reprString); + } + + //otherwise use the standard object.__repr__(inst) + IntPtr args = Runtime.PyTuple_New(1); + Runtime.PyTuple_SetItem(args, 0, ob); + IntPtr reprFunc = Runtime.PyObject_GetAttrString(Runtime.PyBaseObjectType, "__repr__"); + var output = Runtime.PyObject_Call(reprFunc, args, IntPtr.Zero); + Runtime.XDecref(args); + Runtime.XDecref(reprFunc); + return output; + } + catch (Exception e) + { + if (e.InnerException != null) + { + e = e.InnerException; + } + Exceptions.SetError(e); + return IntPtr.Zero; + } + } + /// /// Standard dealloc implementation for instances of reflected types. diff --git a/src/runtime/exceptions.cs b/src/runtime/exceptions.cs index 8bed0abfd..31c367eb2 100644 --- a/src/runtime/exceptions.cs +++ b/src/runtime/exceptions.cs @@ -36,6 +36,29 @@ internal static Exception ToException(IntPtr ob) return e; } + /// + /// Exception __repr__ implementation + /// + public new static IntPtr tp_repr(IntPtr ob) + { + Exception e = ToException(ob); + if (e == null) + { + return Exceptions.RaiseTypeError("invalid object"); + } + string name = e.GetType().Name; + string message; + if (e.Message != String.Empty) + { + message = String.Format("{0}('{1}')", name, e.Message); + } + else + { + message = String.Format("{0}()", name); + } + return Runtime.PyUnicode_FromString(message); + } + /// /// Exception __str__ implementation /// diff --git a/src/testing/Python.Test.csproj b/src/testing/Python.Test.csproj index 27639ed5a..6bf5c2d22 100644 --- a/src/testing/Python.Test.csproj +++ b/src/testing/Python.Test.csproj @@ -91,6 +91,7 @@ + @@ -111,4 +112,4 @@ - \ No newline at end of file + diff --git a/src/testing/ReprTest.cs b/src/testing/ReprTest.cs new file mode 100644 index 000000000..48e93683a --- /dev/null +++ b/src/testing/ReprTest.cs @@ -0,0 +1,108 @@ +using System; +using System.Text; + +namespace Python.Test +{ + /// + /// Supports repr unit tests. + /// + public class ReprTest + { + public class Point + { + public Point(double x, double y) + { + X = x; + Y = y; + } + + public double X { get; set; } + public double Y { get; set; } + + public override string ToString() + { + return base.ToString() + ": X=" + X.ToString() + ", Y=" + Y.ToString(); + } + + public string __repr__() + { + return "Point(" + X.ToString() + "," + Y.ToString() + ")"; + } + } + + public class Foo + { + public string __repr__() + { + return "I implement __repr__() but not ToString()!"; + } + } + + public class Bar + { + public override string ToString() + { + return "I implement ToString() but not __repr__()!"; + } + } + + public class BazBase + { + public override string ToString() + { + return "Base class implementing ToString()!"; + } + } + + public class BazMiddle : BazBase + { + public override string ToString() + { + return "Middle class implementing ToString()!"; + } + } + + //implements ToString via BazMiddle + public class Baz : BazMiddle + { + + } + + public class Quux + { + public string ToString(string format) + { + return "I implement ToString() with an argument!"; + } + } + + public class QuuzBase + { + protected string __repr__() + { + return "I implement __repr__ but it isn't public!"; + } + } + + public class Quuz : QuuzBase + { + + } + + public class Corge + { + public string __repr__(int i) + { + return "__repr__ implemention with input parameter!"; + } + } + + public class Grault + { + public int __repr__() + { + return "__repr__ implemention with wrong return type!".Length; + } + } + } +} diff --git a/src/tests/test_exceptions.py b/src/tests/test_exceptions.py index a10d9a183..c2f18d443 100644 --- a/src/tests/test_exceptions.py +++ b/src/tests/test_exceptions.py @@ -282,11 +282,8 @@ def test_python_compat_of_managed_exceptions(): assert e.args == (msg,) assert isinstance(e.args, tuple) - if PY3: - strexp = "OverflowException('Simple message" - assert repr(e)[:len(strexp)] == strexp - elif PY2: - assert repr(e) == "OverflowException(u'Simple message',)" + strexp = "OverflowException('Simple message" + assert repr(e)[:len(strexp)] == strexp def test_exception_is_instance_of_system_object(): diff --git a/src/tests/test_repr.py b/src/tests/test_repr.py new file mode 100644 index 000000000..d120b0c4c --- /dev/null +++ b/src/tests/test_repr.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +"""Test __repr__ output""" + +import System +import pytest +from Python.Test import ReprTest + +def test_basic(): + """Test Point class which implements both ToString and __repr__ without inheritance""" + ob = ReprTest.Point(1,2) + # point implements ToString() and __repr__() + assert ob.__repr__() == "Point(1,2)" + assert str(ob) == "Python.Test.ReprTest+Point: X=1, Y=2" + +def test_system_string(): + """Test system string""" + ob = System.String("hello") + assert str(ob) == "hello" + assert " + From f2e6f6f89dde86b2043d6945fdffc2858eed2348 Mon Sep 17 00:00:00 2001 From: Alex Helms Date: Wed, 23 Oct 2019 01:32:44 -0700 Subject: [PATCH 024/112] Add function to set Py_NoSiteFlag global variable to 1 (#971) * Add function to set Py_NoSiteFlag global variable to 1. * Add myself to authors, update changelog. --- AUTHORS.md | 1 + CHANGELOG.md | 1 + src/runtime/pythonengine.cs | 10 ++++++++++ src/runtime/runtime.cs | 26 ++++++++++++++++++++++++++ 4 files changed, 38 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index 9253c7e55..efa04c8f0 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -12,6 +12,7 @@ ## Contributors +- Alex Helms ([@alexhelms](https://github.com/alexhelms)) - Alexandre Catarino([@AlexCatarino](https://github.com/AlexCatarino)) - Arvid JB ([@ArvidJB](https://github.com/ArvidJB)) - Benoît Hudson ([@benoithudson](https://github.com/benoithudson)) diff --git a/CHANGELOG.md b/CHANGELOG.md index e24a46904..c7cad9567 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Added - Added automatic NuGet package generation in appveyor and local builds +- Added function that sets Py_NoSiteFlag to 1. ### Changed diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index c1b663d22..700543839 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -130,6 +130,16 @@ public static string Compiler get { return Marshal.PtrToStringAnsi(Runtime.Py_GetCompiler()); } } + /// + /// Set the NoSiteFlag to disable loading the site module. + /// Must be called before Initialize. + /// https://docs.python.org/3/c-api/init.html#c.Py_NoSiteFlag + /// + public static void SetNoSiteFlag() + { + Runtime.SetNoSiteFlag(); + } + public static int RunSimpleString(string code) { return Runtime.PyRun_SimpleString(code); diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 4985a57f5..a1f9a38aa 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -414,6 +414,8 @@ internal static int AtExit() internal static IntPtr PyNoneType; internal static IntPtr PyTypeType; + internal static IntPtr Py_NoSiteFlag; + #if PYTHON3 internal static IntPtr PyBytesType; #endif @@ -1884,5 +1886,29 @@ internal static IntPtr PyMem_Realloc(IntPtr ptr, long size) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int Py_MakePendingCalls(); + + internal static void SetNoSiteFlag() + { + var loader = LibraryLoader.Get(OperatingSystem); + + IntPtr dllLocal; + if (_PythonDll != "__Internal") + { + dllLocal = loader.Load(_PythonDll); + } + + try + { + Py_NoSiteFlag = loader.GetFunction(dllLocal, "Py_NoSiteFlag"); + Marshal.WriteInt32(Py_NoSiteFlag, 1); + } + finally + { + if (dllLocal != IntPtr.Zero) + { + loader.Free(dllLocal); + } + } + } } } From 146353855b6ed2414f3c5fd1b4bf231c2a985da8 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 23 Oct 2019 06:05:17 -0700 Subject: [PATCH 025/112] Adds performance tests, that compare to published NuGet (#975) Add performance tests that compare to published NuGet --- .editorconfig | 11 + pythonnet.15.sln | 191 +++++++++++++++++- .../BaselineComparisonBenchmarkBase.cs | 69 +++++++ src/perf_tests/BaselineComparisonConfig.cs | 47 +++++ src/perf_tests/BenchmarkTests.cs | 63 ++++++ src/perf_tests/Python.PerformanceTests.csproj | 34 ++++ src/perf_tests/PythonCallingNetBenchmark.cs | 46 +++++ 7 files changed, 454 insertions(+), 7 deletions(-) create mode 100644 src/perf_tests/BaselineComparisonBenchmarkBase.cs create mode 100644 src/perf_tests/BaselineComparisonConfig.cs create mode 100644 src/perf_tests/BenchmarkTests.cs create mode 100644 src/perf_tests/Python.PerformanceTests.csproj create mode 100644 src/perf_tests/PythonCallingNetBenchmark.cs diff --git a/.editorconfig b/.editorconfig index 2e7c58ffe..9e10931d0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -19,6 +19,17 @@ indent_size = 2 [*.{csproj,pyproj,config}] indent_size = 2 +# .NET formatting settings +[*.{cs,vb}] +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = true + +[*.cs] +csharp_new_line_before_open_brace = true +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true + # Solution [*.sln] indent_style = tab diff --git a/pythonnet.15.sln b/pythonnet.15.sln index f2015e480..096dfbe9a 100644 --- a/pythonnet.15.sln +++ b/pythonnet.15.sln @@ -1,189 +1,366 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.3 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29102.190 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.Runtime.15", "src/runtime/Python.Runtime.15.csproj", "{2759F4FF-716B-4828-916F-50FA86613DFC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.Runtime.15", "src\runtime\Python.Runtime.15.csproj", "{2759F4FF-716B-4828-916F-50FA86613DFC}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.EmbeddingTest.15", "src/embed_tests/Python.EmbeddingTest.15.csproj", "{66B8D01A-9906-452A-B09E-BF75EA76468F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.EmbeddingTest.15", "src\embed_tests\Python.EmbeddingTest.15.csproj", "{66B8D01A-9906-452A-B09E-BF75EA76468F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "clrmodule.15", "src/clrmodule/clrmodule.15.csproj", "{E08678D4-9A52-4AD5-B63D-8EBC7399981B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "clrmodule.15", "src\clrmodule\clrmodule.15.csproj", "{E08678D4-9A52-4AD5-B63D-8EBC7399981B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Console.15", "src/console/Console.15.csproj", "{CDAD305F-8E72-492C-A314-64CF58D472A0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Console.15", "src\console\Console.15.csproj", "{CDAD305F-8E72-492C-A314-64CF58D472A0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.Test.15", "src/testing/Python.Test.15.csproj", "{F94B547A-E97E-4500-8D53-B4D64D076E5F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.Test.15", "src\testing\Python.Test.15.csproj", "{F94B547A-E97E-4500-8D53-B4D64D076E5F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.PerformanceTests", "src\perf_tests\Python.PerformanceTests.csproj", "{6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repo", "Repo", "{441A0123-F4C6-4EE4-9AEE-315FD79BE2D5}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + DebugMono|Any CPU = DebugMono|Any CPU DebugMono|x64 = DebugMono|x64 DebugMono|x86 = DebugMono|x86 + DebugMonoPY3|Any CPU = DebugMonoPY3|Any CPU DebugMonoPY3|x64 = DebugMonoPY3|x64 DebugMonoPY3|x86 = DebugMonoPY3|x86 + DebugWin|Any CPU = DebugWin|Any CPU DebugWin|x64 = DebugWin|x64 DebugWin|x86 = DebugWin|x86 + DebugWinPY3|Any CPU = DebugWinPY3|Any CPU DebugWinPY3|x64 = DebugWinPY3|x64 DebugWinPY3|x86 = DebugWinPY3|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + ReleaseMono|Any CPU = ReleaseMono|Any CPU ReleaseMono|x64 = ReleaseMono|x64 ReleaseMono|x86 = ReleaseMono|x86 + ReleaseMonoPY3|Any CPU = ReleaseMonoPY3|Any CPU ReleaseMonoPY3|x64 = ReleaseMonoPY3|x64 ReleaseMonoPY3|x86 = ReleaseMonoPY3|x86 + ReleaseWin|Any CPU = ReleaseWin|Any CPU ReleaseWin|x64 = ReleaseWin|x64 ReleaseWin|x86 = ReleaseWin|x86 + ReleaseWinPY3|Any CPU = ReleaseWinPY3|Any CPU ReleaseWinPY3|x64 = ReleaseWinPY3|x64 ReleaseWinPY3|x86 = ReleaseWinPY3|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2759F4FF-716B-4828-916F-50FA86613DFC}.Debug|Any CPU.ActiveCfg = DebugWinPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.Debug|Any CPU.Build.0 = DebugWinPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.Debug|x64.ActiveCfg = DebugWinPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.Debug|x64.Build.0 = DebugWinPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.Debug|x86.ActiveCfg = DebugWinPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.Debug|x86.Build.0 = DebugWinPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugMono|Any CPU.ActiveCfg = DebugMono|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugMono|Any CPU.Build.0 = DebugMono|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugMono|x64.ActiveCfg = DebugMono|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugMono|x64.Build.0 = DebugMono|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugMono|x86.ActiveCfg = DebugMono|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugMono|x86.Build.0 = DebugMono|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugMonoPY3|Any CPU.ActiveCfg = DebugMonoPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugMonoPY3|Any CPU.Build.0 = DebugMonoPY3|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugMonoPY3|x64.ActiveCfg = DebugMonoPY3|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugMonoPY3|x64.Build.0 = DebugMonoPY3|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugMonoPY3|x86.ActiveCfg = DebugMonoPY3|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugMonoPY3|x86.Build.0 = DebugMonoPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugWin|Any CPU.ActiveCfg = DebugWin|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugWin|Any CPU.Build.0 = DebugWin|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugWin|x64.ActiveCfg = DebugWin|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugWin|x64.Build.0 = DebugWin|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugWin|x86.ActiveCfg = DebugWin|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugWin|x86.Build.0 = DebugWin|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugWinPY3|Any CPU.ActiveCfg = DebugWinPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugWinPY3|Any CPU.Build.0 = DebugWinPY3|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugWinPY3|x64.ActiveCfg = DebugWinPY3|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugWinPY3|x64.Build.0 = DebugWinPY3|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugWinPY3|x86.ActiveCfg = DebugWinPY3|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.DebugWinPY3|x86.Build.0 = DebugWinPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.Release|Any CPU.ActiveCfg = ReleaseWinPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.Release|Any CPU.Build.0 = ReleaseWinPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.Release|x64.ActiveCfg = ReleaseWinPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.Release|x64.Build.0 = ReleaseWinPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.Release|x86.ActiveCfg = ReleaseWinPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.Release|x86.Build.0 = ReleaseWinPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseMono|Any CPU.ActiveCfg = ReleaseMono|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseMono|Any CPU.Build.0 = ReleaseMono|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseMono|x64.ActiveCfg = ReleaseMono|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseMono|x64.Build.0 = ReleaseMono|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseMono|x86.ActiveCfg = ReleaseMono|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseMono|x86.Build.0 = ReleaseMono|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseMonoPY3|Any CPU.ActiveCfg = ReleaseMonoPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseMonoPY3|Any CPU.Build.0 = ReleaseMonoPY3|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseMonoPY3|x64.ActiveCfg = ReleaseMonoPY3|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseMonoPY3|x64.Build.0 = ReleaseMonoPY3|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseMonoPY3|x86.ActiveCfg = ReleaseMonoPY3|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseMonoPY3|x86.Build.0 = ReleaseMonoPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseWin|Any CPU.ActiveCfg = ReleaseWin|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseWin|Any CPU.Build.0 = ReleaseWin|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseWin|x64.ActiveCfg = ReleaseWin|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseWin|x64.Build.0 = ReleaseWin|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseWin|x86.ActiveCfg = ReleaseWin|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseWin|x86.Build.0 = ReleaseWin|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseWinPY3|Any CPU.ActiveCfg = ReleaseWinPY3|Any CPU + {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseWinPY3|Any CPU.Build.0 = ReleaseWinPY3|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseWinPY3|x64.ActiveCfg = ReleaseWinPY3|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseWinPY3|x64.Build.0 = ReleaseWinPY3|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseWinPY3|x86.ActiveCfg = ReleaseWinPY3|Any CPU {2759F4FF-716B-4828-916F-50FA86613DFC}.ReleaseWinPY3|x86.Build.0 = ReleaseWinPY3|Any CPU + {66B8D01A-9906-452A-B09E-BF75EA76468F}.Debug|Any CPU.ActiveCfg = ReleaseWinPY3|x86 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.Debug|Any CPU.Build.0 = ReleaseWinPY3|x86 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.Debug|x64.ActiveCfg = DebugWinPY3|x64 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.Debug|x64.Build.0 = DebugWinPY3|x64 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.Debug|x86.ActiveCfg = DebugWinPY3|x86 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.Debug|x86.Build.0 = DebugWinPY3|x86 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugMono|Any CPU.ActiveCfg = DebugMono|x86 {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugMono|x64.ActiveCfg = DebugMono|x64 {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugMono|x64.Build.0 = DebugMono|x64 {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugMono|x86.ActiveCfg = DebugMono|x86 {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugMono|x86.Build.0 = DebugMono|x86 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugMonoPY3|Any CPU.ActiveCfg = DebugMonoPY3|x86 {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugMonoPY3|x64.ActiveCfg = DebugMonoPY3|x64 {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugMonoPY3|x64.Build.0 = DebugMonoPY3|x64 {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugMonoPY3|x86.ActiveCfg = DebugMonoPY3|x86 {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugMonoPY3|x86.Build.0 = DebugMonoPY3|x86 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugWin|Any CPU.ActiveCfg = DebugWin|x86 {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugWin|x64.ActiveCfg = DebugWin|x64 {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugWin|x64.Build.0 = DebugWin|x64 {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugWin|x86.ActiveCfg = DebugWin|x86 {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugWin|x86.Build.0 = DebugWin|x86 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugWinPY3|Any CPU.ActiveCfg = DebugWinPY3|x86 {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugWinPY3|x64.ActiveCfg = DebugWinPY3|x64 {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugWinPY3|x64.Build.0 = DebugWinPY3|x64 {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugWinPY3|x86.ActiveCfg = DebugWinPY3|x86 {66B8D01A-9906-452A-B09E-BF75EA76468F}.DebugWinPY3|x86.Build.0 = DebugWinPY3|x86 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.Release|Any CPU.ActiveCfg = ReleaseWinPY3|x86 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.Release|Any CPU.Build.0 = ReleaseWinPY3|x86 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.Release|x64.ActiveCfg = ReleaseWinPY3|x64 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.Release|x64.Build.0 = ReleaseWinPY3|x64 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.Release|x86.ActiveCfg = ReleaseWinPY3|x86 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.Release|x86.Build.0 = ReleaseWinPY3|x86 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseMono|Any CPU.ActiveCfg = ReleaseMono|x86 {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseMono|x64.ActiveCfg = ReleaseMono|x64 {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseMono|x64.Build.0 = ReleaseMono|x64 {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseMono|x86.ActiveCfg = ReleaseMono|x86 {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseMono|x86.Build.0 = ReleaseMono|x86 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseMonoPY3|Any CPU.ActiveCfg = ReleaseMonoPY3|x86 {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseMonoPY3|x64.ActiveCfg = ReleaseMonoPY3|x64 {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseMonoPY3|x64.Build.0 = ReleaseMonoPY3|x64 {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseMonoPY3|x86.ActiveCfg = ReleaseMonoPY3|x86 {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseMonoPY3|x86.Build.0 = ReleaseMonoPY3|x86 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseWin|Any CPU.ActiveCfg = ReleaseWin|x86 {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseWin|x64.ActiveCfg = ReleaseWin|x64 {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseWin|x64.Build.0 = ReleaseWin|x64 {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseWin|x86.ActiveCfg = ReleaseWin|x86 {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseWin|x86.Build.0 = ReleaseWin|x86 + {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseWinPY3|Any CPU.ActiveCfg = ReleaseWinPY3|x86 {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseWinPY3|x64.ActiveCfg = ReleaseWinPY3|x64 {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseWinPY3|x64.Build.0 = ReleaseWinPY3|x64 {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseWinPY3|x86.ActiveCfg = ReleaseWinPY3|x86 {66B8D01A-9906-452A-B09E-BF75EA76468F}.ReleaseWinPY3|x86.Build.0 = ReleaseWinPY3|x86 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.Debug|Any CPU.ActiveCfg = ReleaseWinPY3|x86 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.Debug|Any CPU.Build.0 = ReleaseWinPY3|x86 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.Debug|x64.ActiveCfg = DebugWinPY3|x64 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.Debug|x64.Build.0 = DebugWinPY3|x64 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.Debug|x86.ActiveCfg = DebugWinPY3|x86 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.Debug|x86.Build.0 = DebugWinPY3|x86 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.DebugMono|Any CPU.ActiveCfg = DebugMono|x86 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.DebugMono|x64.ActiveCfg = DebugMono|x64 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.DebugMono|x86.ActiveCfg = DebugMono|x86 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.DebugMonoPY3|Any CPU.ActiveCfg = DebugMonoPY3|x86 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.DebugMonoPY3|x64.ActiveCfg = DebugMonoPY3|x64 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.DebugMonoPY3|x86.ActiveCfg = DebugMonoPY3|x86 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.DebugWin|Any CPU.ActiveCfg = DebugWin|x86 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.DebugWin|x64.ActiveCfg = DebugWin|x64 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.DebugWin|x64.Build.0 = DebugWin|x64 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.DebugWin|x86.ActiveCfg = DebugWin|x86 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.DebugWin|x86.Build.0 = DebugWin|x86 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.DebugWinPY3|Any CPU.ActiveCfg = DebugWinPY3|x86 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.DebugWinPY3|x64.ActiveCfg = DebugWinPY3|x64 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.DebugWinPY3|x64.Build.0 = DebugWinPY3|x64 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.DebugWinPY3|x86.ActiveCfg = DebugWinPY3|x86 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.DebugWinPY3|x86.Build.0 = DebugWinPY3|x86 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.Release|Any CPU.ActiveCfg = ReleaseWinPY3|x86 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.Release|Any CPU.Build.0 = ReleaseWinPY3|x86 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.Release|x64.ActiveCfg = ReleaseWinPY3|x64 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.Release|x64.Build.0 = ReleaseWinPY3|x64 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.Release|x86.ActiveCfg = ReleaseWinPY3|x86 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.Release|x86.Build.0 = ReleaseWinPY3|x86 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.ReleaseMono|Any CPU.ActiveCfg = ReleaseMono|x86 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.ReleaseMono|x64.ActiveCfg = ReleaseMono|x64 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.ReleaseMono|x86.ActiveCfg = ReleaseMono|x86 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.ReleaseMonoPY3|Any CPU.ActiveCfg = ReleaseMonoPY3|x86 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.ReleaseMonoPY3|x64.ActiveCfg = ReleaseMonoPY3|x64 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.ReleaseMonoPY3|x86.ActiveCfg = ReleaseMonoPY3|x86 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.ReleaseWin|Any CPU.ActiveCfg = ReleaseWin|x86 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.ReleaseWin|x64.ActiveCfg = ReleaseWin|x64 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.ReleaseWin|x64.Build.0 = ReleaseWin|x64 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.ReleaseWin|x86.ActiveCfg = ReleaseWin|x86 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.ReleaseWin|x86.Build.0 = ReleaseWin|x86 + {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.ReleaseWinPY3|Any CPU.ActiveCfg = ReleaseWinPY3|x86 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.ReleaseWinPY3|x64.ActiveCfg = ReleaseWinPY3|x64 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.ReleaseWinPY3|x64.Build.0 = ReleaseWinPY3|x64 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.ReleaseWinPY3|x86.ActiveCfg = ReleaseWinPY3|x86 {E08678D4-9A52-4AD5-B63D-8EBC7399981B}.ReleaseWinPY3|x86.Build.0 = ReleaseWinPY3|x86 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.Debug|Any CPU.ActiveCfg = ReleaseWinPY3|x86 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.Debug|Any CPU.Build.0 = ReleaseWinPY3|x86 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.Debug|x64.ActiveCfg = DebugWinPY3|x64 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.Debug|x64.Build.0 = DebugWinPY3|x64 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.Debug|x86.ActiveCfg = DebugWinPY3|x86 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.Debug|x86.Build.0 = DebugWinPY3|x86 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugMono|Any CPU.ActiveCfg = DebugMono|x86 {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugMono|x64.ActiveCfg = DebugMono|x64 {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugMono|x64.Build.0 = DebugMono|x64 {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugMono|x86.ActiveCfg = DebugMono|x86 {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugMono|x86.Build.0 = DebugMono|x86 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugMonoPY3|Any CPU.ActiveCfg = DebugMonoPY3|x86 {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugMonoPY3|x64.ActiveCfg = DebugMonoPY3|x64 {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugMonoPY3|x64.Build.0 = DebugMonoPY3|x64 {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugMonoPY3|x86.ActiveCfg = DebugMonoPY3|x86 {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugMonoPY3|x86.Build.0 = DebugMonoPY3|x86 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugWin|Any CPU.ActiveCfg = DebugWin|x86 {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugWin|x64.ActiveCfg = DebugWin|x64 {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugWin|x64.Build.0 = DebugWin|x64 {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugWin|x86.ActiveCfg = DebugWin|x86 {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugWin|x86.Build.0 = DebugWin|x86 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugWinPY3|Any CPU.ActiveCfg = DebugWinPY3|x86 {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugWinPY3|x64.ActiveCfg = DebugWinPY3|x64 {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugWinPY3|x64.Build.0 = DebugWinPY3|x64 {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugWinPY3|x86.ActiveCfg = DebugWinPY3|x86 {CDAD305F-8E72-492C-A314-64CF58D472A0}.DebugWinPY3|x86.Build.0 = DebugWinPY3|x86 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.Release|Any CPU.ActiveCfg = ReleaseWinPY3|x86 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.Release|Any CPU.Build.0 = ReleaseWinPY3|x86 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.Release|x64.ActiveCfg = ReleaseWinPY3|x64 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.Release|x64.Build.0 = ReleaseWinPY3|x64 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.Release|x86.ActiveCfg = ReleaseWinPY3|x86 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.Release|x86.Build.0 = ReleaseWinPY3|x86 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseMono|Any CPU.ActiveCfg = ReleaseMono|x86 {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseMono|x64.ActiveCfg = ReleaseMono|x64 {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseMono|x64.Build.0 = ReleaseMono|x64 {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseMono|x86.ActiveCfg = ReleaseMono|x86 {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseMono|x86.Build.0 = ReleaseMono|x86 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseMonoPY3|Any CPU.ActiveCfg = ReleaseMonoPY3|x86 {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseMonoPY3|x64.ActiveCfg = ReleaseMonoPY3|x64 {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseMonoPY3|x64.Build.0 = ReleaseMonoPY3|x64 {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseMonoPY3|x86.ActiveCfg = ReleaseMonoPY3|x86 {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseMonoPY3|x86.Build.0 = ReleaseMonoPY3|x86 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseWin|Any CPU.ActiveCfg = ReleaseWin|x86 {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseWin|x64.ActiveCfg = ReleaseWin|x64 {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseWin|x64.Build.0 = ReleaseWin|x64 {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseWin|x86.ActiveCfg = ReleaseWin|x86 {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseWin|x86.Build.0 = ReleaseWin|x86 + {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseWinPY3|Any CPU.ActiveCfg = ReleaseWinPY3|x86 {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseWinPY3|x64.ActiveCfg = ReleaseWinPY3|x64 {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseWinPY3|x64.Build.0 = ReleaseWinPY3|x64 {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseWinPY3|x86.ActiveCfg = ReleaseWinPY3|x86 {CDAD305F-8E72-492C-A314-64CF58D472A0}.ReleaseWinPY3|x86.Build.0 = ReleaseWinPY3|x86 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.Debug|Any CPU.ActiveCfg = ReleaseWinPY3|x86 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.Debug|Any CPU.Build.0 = ReleaseWinPY3|x86 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.Debug|x64.ActiveCfg = DebugWinPY3|x64 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.Debug|x64.Build.0 = DebugWinPY3|x64 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.Debug|x86.ActiveCfg = DebugWinPY3|x86 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.Debug|x86.Build.0 = DebugWinPY3|x86 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugMono|Any CPU.ActiveCfg = DebugMono|x86 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugMono|x64.ActiveCfg = DebugMono|x64 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugMono|x64.Build.0 = DebugMono|x64 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugMono|x86.ActiveCfg = DebugMono|x86 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugMono|x86.Build.0 = DebugMono|x86 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugMonoPY3|Any CPU.ActiveCfg = DebugMonoPY3|x86 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugMonoPY3|x64.ActiveCfg = DebugMonoPY3|x64 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugMonoPY3|x64.Build.0 = DebugMonoPY3|x64 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugMonoPY3|x86.ActiveCfg = DebugMonoPY3|x86 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugMonoPY3|x86.Build.0 = DebugMonoPY3|x86 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugWin|Any CPU.ActiveCfg = DebugWin|x86 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugWin|x64.ActiveCfg = DebugWin|x64 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugWin|x64.Build.0 = DebugWin|x64 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugWin|x86.ActiveCfg = DebugWin|x86 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugWin|x86.Build.0 = DebugWin|x86 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugWinPY3|Any CPU.ActiveCfg = DebugWinPY3|x86 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugWinPY3|x64.ActiveCfg = DebugWinPY3|x64 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugWinPY3|x64.Build.0 = DebugWinPY3|x64 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugWinPY3|x86.ActiveCfg = DebugWinPY3|x86 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.DebugWinPY3|x86.Build.0 = DebugWinPY3|x86 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.Release|Any CPU.ActiveCfg = ReleaseWinPY3|x86 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.Release|Any CPU.Build.0 = ReleaseWinPY3|x86 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.Release|x64.ActiveCfg = ReleaseWinPY3|x64 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.Release|x64.Build.0 = ReleaseWinPY3|x64 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.Release|x86.ActiveCfg = ReleaseWinPY3|x86 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.Release|x86.Build.0 = ReleaseWinPY3|x86 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseMono|Any CPU.ActiveCfg = ReleaseMono|x86 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseMono|x64.ActiveCfg = ReleaseMono|x64 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseMono|x64.Build.0 = ReleaseMono|x64 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseMono|x86.ActiveCfg = ReleaseMono|x86 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseMono|x86.Build.0 = ReleaseMono|x86 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseMonoPY3|Any CPU.ActiveCfg = ReleaseMonoPY3|x86 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseMonoPY3|x64.ActiveCfg = ReleaseMonoPY3|x64 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseMonoPY3|x64.Build.0 = ReleaseMonoPY3|x64 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseMonoPY3|x86.ActiveCfg = ReleaseMonoPY3|x86 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseMonoPY3|x86.Build.0 = ReleaseMonoPY3|x86 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseWin|Any CPU.ActiveCfg = ReleaseWin|x86 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseWin|x64.ActiveCfg = ReleaseWin|x64 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseWin|x64.Build.0 = ReleaseWin|x64 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseWin|x86.ActiveCfg = ReleaseWin|x86 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseWin|x86.Build.0 = ReleaseWin|x86 + {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseWinPY3|Any CPU.ActiveCfg = ReleaseWinPY3|x86 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseWinPY3|x64.ActiveCfg = ReleaseWinPY3|x64 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseWinPY3|x64.Build.0 = ReleaseWinPY3|x64 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseWinPY3|x86.ActiveCfg = ReleaseWinPY3|x86 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseWinPY3|x86.Build.0 = ReleaseWinPY3|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|Any CPU.ActiveCfg = DebugMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|Any CPU.Build.0 = DebugMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|x64.ActiveCfg = DebugMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|x64.Build.0 = DebugMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|x86.ActiveCfg = DebugMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|x86.Build.0 = DebugMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMono|Any CPU.ActiveCfg = DebugMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMono|x64.ActiveCfg = DebugMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMono|x86.ActiveCfg = DebugMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMonoPY3|Any CPU.ActiveCfg = DebugMonoPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMonoPY3|x64.ActiveCfg = DebugMonoPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMonoPY3|x86.ActiveCfg = DebugMonoPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|Any CPU.ActiveCfg = DebugWin|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|Any CPU.Build.0 = DebugWin|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|x64.ActiveCfg = DebugWin|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|x64.Build.0 = DebugWin|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|x86.ActiveCfg = DebugWin|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|x86.Build.0 = DebugWin|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWinPY3|Any CPU.ActiveCfg = DebugWinPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWinPY3|Any CPU.Build.0 = DebugWinPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWinPY3|x64.ActiveCfg = DebugWinPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWinPY3|x64.Build.0 = DebugWinPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWinPY3|x86.ActiveCfg = DebugWinPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWinPY3|x86.Build.0 = DebugWinPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|Any CPU.ActiveCfg = ReleaseMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|Any CPU.Build.0 = ReleaseMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|x64.ActiveCfg = ReleaseMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|x64.Build.0 = ReleaseMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|x86.ActiveCfg = ReleaseMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|x86.Build.0 = ReleaseMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMono|Any CPU.ActiveCfg = ReleaseMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMono|x64.ActiveCfg = ReleaseMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMono|x86.ActiveCfg = ReleaseMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMonoPY3|Any CPU.ActiveCfg = ReleaseMonoPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMonoPY3|x64.ActiveCfg = ReleaseMonoPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMonoPY3|x86.ActiveCfg = ReleaseMonoPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|Any CPU.ActiveCfg = ReleaseWin|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|Any CPU.Build.0 = ReleaseWin|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|x64.ActiveCfg = ReleaseWin|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|x64.Build.0 = ReleaseWin|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|x86.ActiveCfg = ReleaseWin|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|x86.Build.0 = ReleaseWin|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|Any CPU.ActiveCfg = ReleaseWinPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|Any CPU.Build.0 = ReleaseWinPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x64.ActiveCfg = ReleaseWinPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x64.Build.0 = ReleaseWinPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x86.ActiveCfg = ReleaseWinPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x86.Build.0 = ReleaseWinPY3|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/perf_tests/BaselineComparisonBenchmarkBase.cs b/src/perf_tests/BaselineComparisonBenchmarkBase.cs new file mode 100644 index 000000000..2388e3982 --- /dev/null +++ b/src/perf_tests/BaselineComparisonBenchmarkBase.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +using Python.Runtime; + +namespace Python.PerformanceTests +{ + public class BaselineComparisonBenchmarkBase + { + public BaselineComparisonBenchmarkBase() + { + Console.WriteLine($"CWD: {Environment.CurrentDirectory}"); + Console.WriteLine($"Using Python.Runtime from {typeof(PythonEngine).Assembly.Location} {typeof(PythonEngine).Assembly.GetName()}"); + + try { + PythonEngine.Initialize(); + Console.WriteLine("Python Initialized"); + if (PythonEngine.BeginAllowThreads() == IntPtr.Zero) + throw new PythonException(); + Console.WriteLine("Threading enabled"); + } + catch (Exception e) { + Console.WriteLine(e); + throw; + } + } + + static BaselineComparisonBenchmarkBase() + { + string pythonRuntimeDll = Environment.GetEnvironmentVariable(BaselineComparisonConfig.EnvironmentVariableName); + if (string.IsNullOrEmpty(pythonRuntimeDll)) + { + throw new ArgumentException( + "Required environment variable is missing", + BaselineComparisonConfig.EnvironmentVariableName); + } + + Console.WriteLine("Preloading " + pythonRuntimeDll); + Assembly.LoadFrom(pythonRuntimeDll); + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { + if (assembly.FullName.StartsWith("Python.Runtime")) + Console.WriteLine(assembly.Location); + foreach(var dependency in assembly.GetReferencedAssemblies()) + if (dependency.FullName.Contains("Python.Runtime")) { + Console.WriteLine($"{assembly} -> {dependency}"); + } + } + + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve; + } + + static Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args) { + if (!args.Name.StartsWith("Python.Runtime")) + return null; + + var preloaded = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => a.GetName().Name == "Python.Runtime"); + if (preloaded != null) return preloaded; + + string pythonRuntimeDll = Environment.GetEnvironmentVariable(BaselineComparisonConfig.EnvironmentVariableName); + if (string.IsNullOrEmpty(pythonRuntimeDll)) + return null; + + return Assembly.LoadFrom(pythonRuntimeDll); + } + } +} diff --git a/src/perf_tests/BaselineComparisonConfig.cs b/src/perf_tests/BaselineComparisonConfig.cs new file mode 100644 index 000000000..06d529ff9 --- /dev/null +++ b/src/perf_tests/BaselineComparisonConfig.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Jobs; + +namespace Python.PerformanceTests +{ + public class BaselineComparisonConfig : ManualConfig + { + public const string EnvironmentVariableName = "PythonRuntimeDLL"; + + public BaselineComparisonConfig() + { + this.Options |= ConfigOptions.DisableOptimizationsValidator; + + string deploymentRoot = BenchmarkTests.DeploymentRoot; + + var baseJob = Job.Default; + this.Add(baseJob + .WithId("baseline") + .WithEnvironmentVariable(EnvironmentVariableName, + Path.Combine(deploymentRoot, "baseline", "Python.Runtime.dll")) + .WithBaseline(true)); + this.Add(baseJob + .WithId("new") + .WithEnvironmentVariable(EnvironmentVariableName, + Path.Combine(deploymentRoot, "new", "Python.Runtime.dll"))); + } + + static BaselineComparisonConfig() { + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve; + } + + static Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args) { + Console.WriteLine(args.Name); + if (!args.Name.StartsWith("Python.Runtime")) + return null; + string pythonRuntimeDll = Environment.GetEnvironmentVariable(EnvironmentVariableName); + if (string.IsNullOrEmpty(pythonRuntimeDll)) + pythonRuntimeDll = Path.Combine(BenchmarkTests.DeploymentRoot, "baseline", "Python.Runtime.dll"); + return Assembly.LoadFrom(pythonRuntimeDll); + } + } +} diff --git a/src/perf_tests/BenchmarkTests.cs b/src/perf_tests/BenchmarkTests.cs new file mode 100644 index 000000000..12ba6c900 --- /dev/null +++ b/src/perf_tests/BenchmarkTests.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Reflection; + +using BenchmarkDotNet.Reports; +using BenchmarkDotNet.Running; +using NUnit.Framework; + +namespace Python.PerformanceTests +{ + public class BenchmarkTests + { + Summary summary; + + [OneTimeSetUp] + public void SetUp() + { + Environment.CurrentDirectory = Path.Combine(DeploymentRoot, "new"); + this.summary = BenchmarkRunner.Run(); + Assert.IsNotEmpty(this.summary.Reports); + Assert.IsTrue(this.summary.Reports.All(r => r.Success)); + } + + [Test] + public void ReadInt64Property() + { + double optimisticPerfRatio = GetOptimisticPerfRatio(this.summary.Reports); + Assert.LessOrEqual(optimisticPerfRatio, 0.68); + } + + [Test] + public void WriteInt64Property() + { + double optimisticPerfRatio = GetOptimisticPerfRatio(this.summary.Reports); + Assert.LessOrEqual(optimisticPerfRatio, 0.66); + } + + static double GetOptimisticPerfRatio( + IReadOnlyList reports, + [CallerMemberName] string methodName = null) + { + reports = reports.Where(r => r.BenchmarkCase.Descriptor.WorkloadMethod.Name == methodName).ToArray(); + if (reports.Count == 0) + throw new ArgumentException( + message: $"No reports found for {methodName}. " + + "You have to match test method name to benchmark method name or " + + "pass benchmark method name explicitly", + paramName: nameof(methodName)); + + var baseline = reports.Single(r => r.BenchmarkCase.Job.ResolvedId == "baseline").ResultStatistics; + var @new = reports.Single(r => r.BenchmarkCase.Job.ResolvedId != "baseline").ResultStatistics; + + double newTimeOptimistic = @new.Mean - (@new.StandardDeviation + baseline.StandardDeviation) * 0.5; + + return newTimeOptimistic / baseline.Mean; + } + + public static string DeploymentRoot => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + } +} diff --git a/src/perf_tests/Python.PerformanceTests.csproj b/src/perf_tests/Python.PerformanceTests.csproj new file mode 100644 index 000000000..33949fdc1 --- /dev/null +++ b/src/perf_tests/Python.PerformanceTests.csproj @@ -0,0 +1,34 @@ + + + + net461 + DebugMono;DebugMonoPY3;ReleaseMono;ReleaseMonoPY3;DebugWin;DebugWinPY3;ReleaseWin;ReleaseWinPY3 + + false + + + + + + + + + compile + + + + + + + + + + + + + + + + + + diff --git a/src/perf_tests/PythonCallingNetBenchmark.cs b/src/perf_tests/PythonCallingNetBenchmark.cs new file mode 100644 index 000000000..4e9461d2e --- /dev/null +++ b/src/perf_tests/PythonCallingNetBenchmark.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using BenchmarkDotNet.Attributes; +using Python.Runtime; + +namespace Python.PerformanceTests +{ + [Config(typeof(BaselineComparisonConfig))] + public class PythonCallingNetBenchmark: BaselineComparisonBenchmarkBase + { + [Benchmark] + public void ReadInt64Property() + { + using (Py.GIL()) + { + var locals = new PyDict(); + locals.SetItem("a", new NetObject().ToPython()); + PythonEngine.Exec($@" +s = 0 +for i in range(300000): + s += a.{nameof(NetObject.LongProperty)} +", locals: locals.Handle); + } + } + + [Benchmark] + public void WriteInt64Property() { + using (Py.GIL()) { + var locals = new PyDict(); + locals.SetItem("a", new NetObject().ToPython()); + PythonEngine.Exec($@" +s = 0 +for i in range(300000): + a.{nameof(NetObject.LongProperty)} += i +", locals: locals.Handle); + } + } + } + + class NetObject + { + public long LongProperty { get; set; } = 42; + } +} From e1931262c8d0f18e6bd55cb122510be417733794 Mon Sep 17 00:00:00 2001 From: Ivan Cronyn Date: Wed, 13 Nov 2019 13:46:22 +0000 Subject: [PATCH 026/112] Adds support for the Jetson Nano (#986) --- CHANGELOG.md | 1 + src/runtime/platform/Types.cs | 1 + src/runtime/runtime.cs | 1 + 3 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7cad9567..5c999d668 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added automatic NuGet package generation in appveyor and local builds - Added function that sets Py_NoSiteFlag to 1. +- Added support for Jetson Nano. ### Changed diff --git a/src/runtime/platform/Types.cs b/src/runtime/platform/Types.cs index bdc51af39..62be0e421 100644 --- a/src/runtime/platform/Types.cs +++ b/src/runtime/platform/Types.cs @@ -6,6 +6,7 @@ public enum MachineType x86_64, armv7l, armv8, + aarch64, Other }; diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index a1f9a38aa..f97821d13 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -143,6 +143,7 @@ public class Runtime ["em64t"] = MachineType.x86_64, ["armv7l"] = MachineType.armv7l, ["armv8"] = MachineType.armv8, + ["aarch64"] = MachineType.aarch64, }; /// From 2736094ab87dcde26e63e1fddbb30a7a2453c961 Mon Sep 17 00:00:00 2001 From: matham Date: Tue, 19 Nov 2019 01:19:29 -0500 Subject: [PATCH 027/112] Add CI support for py3.8. (#988) * Add CI support for py3.8 * Add interop38.cs * Add PYTHON38 * Add support for 3.8 * Bump 3.7 to 3.8 * Allow failures for py3.8 because it's a Python 3.8.0 bug * Add note about py3.8.0 to readme --- README.rst | 8 ++ appveyor.yml | 6 ++ src/runtime/Python.Runtime.15.csproj | 2 +- src/runtime/Python.Runtime.csproj | 27 ++--- src/runtime/interop38.cs | 152 +++++++++++++++++++++++++++ src/runtime/runtime.cs | 5 +- 6 files changed, 185 insertions(+), 15 deletions(-) create mode 100644 src/runtime/interop38.cs diff --git a/README.rst b/README.rst index 84bf93d84..ee6573d84 100644 --- a/README.rst +++ b/README.rst @@ -95,6 +95,14 @@ projects using pythonnet can be found in the Wiki: https://github.com/pythonnet/pythonnet/wiki +Python 3.8.0 support +-------------------- + +Some features are disabled in Python 3.8.0 because of +`this bug in Python `_. The error is +``System.EntryPointNotFoundException : Unable to find an entry point named +'Py_CompileString' in DLL 'python38'``. This will be fixed in Python 3.8.1. + .. |Join the chat at https://gitter.im/pythonnet/pythonnet| image:: https://badges.gitter.im/pythonnet/pythonnet.svg :target: https://gitter.im/pythonnet/pythonnet?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge .. |appveyor shield| image:: https://img.shields.io/appveyor/ci/pythonnet/pythonnet/master.svg?label=AppVeyor diff --git a/appveyor.yml b/appveyor.yml index 445f9bb5a..20d8ed991 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,16 +23,22 @@ environment: BUILD_OPTS: --xplat - PYTHON_VERSION: 3.7 BUILD_OPTS: --xplat + - PYTHON_VERSION: 3.8 + BUILD_OPTS: --xplat - PYTHON_VERSION: 2.7 - PYTHON_VERSION: 3.5 - PYTHON_VERSION: 3.6 - PYTHON_VERSION: 3.7 + - PYTHON_VERSION: 3.8 matrix: allow_failures: - PYTHON_VERSION: 3.4 BUILD_OPTS: --xplat - PYTHON_VERSION: 3.4 + - PYTHON_VERSION: 3.8 + BUILD_OPTS: --xplat + - PYTHON_VERSION: 3.8 init: # Update Environment Variables based on matrix/platform diff --git a/src/runtime/Python.Runtime.15.csproj b/src/runtime/Python.Runtime.15.csproj index 122132513..c31d4bf91 100644 --- a/src/runtime/Python.Runtime.15.csproj +++ b/src/runtime/Python.Runtime.15.csproj @@ -42,7 +42,7 @@ $(PYTHONNET_PY2_VERSION) PYTHON27 $(PYTHONNET_PY3_VERSION) - PYTHON37 + PYTHON38 $(PYTHONNET_WIN_DEFINE_CONSTANTS) UCS2 $(PYTHONNET_MONO_DEFINE_CONSTANTS) diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index ac6b59150..02656e51e 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -22,11 +22,11 @@ - PYTHON2;PYTHON27;UCS4 @@ -34,7 +34,7 @@ pdbonly - PYTHON3;PYTHON37;UCS4 + PYTHON3;PYTHON38;UCS4 true pdbonly @@ -46,7 +46,7 @@ true - PYTHON3;PYTHON37;UCS4;TRACE;DEBUG + PYTHON3;PYTHON38;UCS4;TRACE;DEBUG false full @@ -56,7 +56,7 @@ pdbonly - PYTHON3;PYTHON37;UCS2 + PYTHON3;PYTHON38;UCS2 true pdbonly @@ -68,7 +68,7 @@ true - PYTHON3;PYTHON37;UCS2;TRACE;DEBUG + PYTHON3;PYTHON38;UCS2;TRACE;DEBUG false full @@ -140,8 +140,8 @@ - - + + @@ -151,7 +151,8 @@ - + + @@ -170,4 +171,4 @@ - + diff --git a/src/runtime/interop38.cs b/src/runtime/interop38.cs new file mode 100644 index 000000000..8f2e32afe --- /dev/null +++ b/src/runtime/interop38.cs @@ -0,0 +1,152 @@ + +// Auto-generated by geninterop.py. +// DO NOT MODIFIY BY HAND. + + +#if PYTHON38 +using System; +using System.Collections; +using System.Collections.Specialized; +using System.Runtime.InteropServices; +using System.Reflection; +using System.Text; + +namespace Python.Runtime +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + internal class TypeOffset + { + static TypeOffset() + { + Type type = typeof(TypeOffset); + FieldInfo[] fi = type.GetFields(); + int size = IntPtr.Size; + for (int i = 0; i < fi.Length; i++) + { + fi[i].SetValue(null, i * size); + } + } + + public static int magic() + { + return ob_size; + } + + // Auto-generated from PyHeapTypeObject in Python.h + public static int ob_refcnt = 0; + public static int ob_type = 0; + public static int ob_size = 0; + public static int tp_name = 0; + public static int tp_basicsize = 0; + public static int tp_itemsize = 0; + public static int tp_dealloc = 0; + public static int tp_vectorcall_offset = 0; + public static int tp_getattr = 0; + public static int tp_setattr = 0; + public static int tp_as_async = 0; + public static int tp_repr = 0; + public static int tp_as_number = 0; + public static int tp_as_sequence = 0; + public static int tp_as_mapping = 0; + public static int tp_hash = 0; + public static int tp_call = 0; + public static int tp_str = 0; + public static int tp_getattro = 0; + public static int tp_setattro = 0; + public static int tp_as_buffer = 0; + public static int tp_flags = 0; + public static int tp_doc = 0; + public static int tp_traverse = 0; + public static int tp_clear = 0; + public static int tp_richcompare = 0; + public static int tp_weaklistoffset = 0; + public static int tp_iter = 0; + public static int tp_iternext = 0; + public static int tp_methods = 0; + public static int tp_members = 0; + public static int tp_getset = 0; + public static int tp_base = 0; + public static int tp_dict = 0; + public static int tp_descr_get = 0; + public static int tp_descr_set = 0; + public static int tp_dictoffset = 0; + public static int tp_init = 0; + public static int tp_alloc = 0; + public static int tp_new = 0; + public static int tp_free = 0; + public static int tp_is_gc = 0; + public static int tp_bases = 0; + public static int tp_mro = 0; + public static int tp_cache = 0; + public static int tp_subclasses = 0; + public static int tp_weaklist = 0; + public static int tp_del = 0; + public static int tp_version_tag = 0; + public static int tp_finalize = 0; + public static int tp_vectorcall = 0; + public static int am_await = 0; + public static int am_aiter = 0; + public static int am_anext = 0; + public static int nb_add = 0; + public static int nb_subtract = 0; + public static int nb_multiply = 0; + public static int nb_remainder = 0; + public static int nb_divmod = 0; + public static int nb_power = 0; + public static int nb_negative = 0; + public static int nb_positive = 0; + public static int nb_absolute = 0; + public static int nb_bool = 0; + public static int nb_invert = 0; + public static int nb_lshift = 0; + public static int nb_rshift = 0; + public static int nb_and = 0; + public static int nb_xor = 0; + public static int nb_or = 0; + public static int nb_int = 0; + public static int nb_reserved = 0; + public static int nb_float = 0; + public static int nb_inplace_add = 0; + public static int nb_inplace_subtract = 0; + public static int nb_inplace_multiply = 0; + public static int nb_inplace_remainder = 0; + public static int nb_inplace_power = 0; + public static int nb_inplace_lshift = 0; + public static int nb_inplace_rshift = 0; + public static int nb_inplace_and = 0; + public static int nb_inplace_xor = 0; + public static int nb_inplace_or = 0; + public static int nb_floor_divide = 0; + public static int nb_true_divide = 0; + public static int nb_inplace_floor_divide = 0; + public static int nb_inplace_true_divide = 0; + public static int nb_index = 0; + public static int nb_matrix_multiply = 0; + public static int nb_inplace_matrix_multiply = 0; + public static int mp_length = 0; + public static int mp_subscript = 0; + public static int mp_ass_subscript = 0; + public static int sq_length = 0; + public static int sq_concat = 0; + public static int sq_repeat = 0; + public static int sq_item = 0; + public static int was_sq_slice = 0; + public static int sq_ass_item = 0; + public static int was_sq_ass_slice = 0; + public static int sq_contains = 0; + public static int sq_inplace_concat = 0; + public static int sq_inplace_repeat = 0; + public static int bf_getbuffer = 0; + public static int bf_releasebuffer = 0; + public static int name = 0; + public static int ht_slots = 0; + public static int qualname = 0; + public static int ht_cached_keys = 0; + + /* here are optional user slots, followed by the members. */ + public static int members = 0; + } +} + +#endif + diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index f97821d13..7a78cd6e1 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -64,8 +64,11 @@ public class Runtime #elif PYTHON37 internal const string _pyversion = "3.7"; internal const string _pyver = "37"; +#elif PYTHON38 + internal const string _pyversion = "3.8"; + internal const string _pyver = "38"; #else -#error You must define one of PYTHON34 to PYTHON37 or PYTHON27 +#error You must define one of PYTHON34 to PYTHON38 or PYTHON27 #endif #if MONO_LINUX || MONO_OSX // Linux/macOS use dotted version string From 5f2e2e2f81d0d4439f04d6ee322bae304d382f21 Mon Sep 17 00:00:00 2001 From: Benoit Hudson Date: Thu, 21 Nov 2019 04:10:58 -0500 Subject: [PATCH 028/112] Split from PR 958: restoring the __import__ after shutdown. (#993) When C# shuts down we should restore Python to its original state. --- src/runtime/importhook.cs | 67 +++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/src/runtime/importhook.cs b/src/runtime/importhook.cs index 7e4a208f5..06ba7a56d 100644 --- a/src/runtime/importhook.cs +++ b/src/runtime/importhook.cs @@ -26,25 +26,64 @@ internal static void InitializeModuleDef() #endif /// - /// Initialization performed on startup of the Python runtime. + /// Get a New reference to the builtins module. /// - internal static void Initialize() + static IntPtr GetNewRefToBuiltins() { - // Initialize the Python <--> CLR module hook. We replace the - // built-in Python __import__ with our own. This isn't ideal, - // but it provides the most "Pythonic" way of dealing with CLR - // modules (Python doesn't provide a way to emulate packages). - IntPtr dict = Runtime.PyImport_GetModuleDict(); + if (Runtime.IsPython3) + { + return Runtime.PyImport_ImportModule("builtins"); + } + else + { + // dict is a borrowed ref, no need to decref + IntPtr dict = Runtime.PyImport_GetModuleDict(); - IntPtr mod = Runtime.IsPython3 - ? Runtime.PyImport_ImportModule("builtins") - : Runtime.PyDict_GetItemString(dict, "__builtin__"); + // GetItemString is a borrowed ref; incref to get a new ref + IntPtr builtins = Runtime.PyDict_GetItemString(dict, "__builtin__"); + Runtime.XIncref(builtins); + return builtins; + } + } - py_import = Runtime.PyObject_GetAttrString(mod, "__import__"); + /// + /// Initialize just the __import__ hook itself. + /// + static void InitImport() + { + // We replace the built-in Python __import__ with our own: first + // look in CLR modules, then if we don't find any call the default + // Python __import__. + IntPtr builtins = GetNewRefToBuiltins(); + py_import = Runtime.PyObject_GetAttrString(builtins, "__import__"); hook = new MethodWrapper(typeof(ImportHook), "__import__", "TernaryFunc"); - Runtime.PyObject_SetAttrString(mod, "__import__", hook.ptr); + Runtime.PyObject_SetAttrString(builtins, "__import__", hook.ptr); Runtime.XDecref(hook.ptr); + Runtime.XDecref(builtins); + } + + /// + /// Restore the __import__ hook. + /// + static void RestoreImport() + { + IntPtr builtins = GetNewRefToBuiltins(); + + Runtime.PyObject_SetAttrString(builtins, "__import__", py_import); + Runtime.XDecref(py_import); + py_import = IntPtr.Zero; + + Runtime.XDecref(builtins); + } + + /// + /// Initialization performed on startup of the Python runtime. + /// + internal static void Initialize() + { + InitImport(); + // Initialize the clr module and tell Python about it. root = new CLRModule(); #if PYTHON3 @@ -62,6 +101,7 @@ internal static void Initialize() Runtime.XIncref(root.pyHandle); // we are using the module two times py_clr_module = root.pyHandle; // Alias handle for PY2/PY3 #endif + IntPtr dict = Runtime.PyImport_GetModuleDict(); Runtime.PyDict_SetItemString(dict, "CLR", py_clr_module); Runtime.PyDict_SetItemString(dict, "clr", py_clr_module); } @@ -74,9 +114,10 @@ internal static void Shutdown() { if (Runtime.Py_IsInitialized() != 0) { + RestoreImport(); + Runtime.XDecref(py_clr_module); Runtime.XDecref(root.pyHandle); - Runtime.XDecref(py_import); } } From 627cac0cb91eea4bfa6a36b25e523500a792c88c Mon Sep 17 00:00:00 2001 From: Jeff17Robbins Date: Wed, 27 Nov 2019 20:57:19 -0500 Subject: [PATCH 029/112] Capture function pointer declarations in structs - Update geninterop.py to visit function pointers - Support running on Windows 10 - Update interop38.cs --- AUTHORS.md | 1 + src/runtime/interop38.cs | 4 ++-- tools/geninterop/geninterop.py | 14 +++++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index efa04c8f0..e42a456ae 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -34,6 +34,7 @@ - Ivan Cronyn ([@cronan](https://github.com/cronan)) - Jan Krivanek ([@jakrivan](https://github.com/jakrivan)) - Jeff Reback ([@jreback](https://github.com/jreback)) +- Jeff Robbins ([@jeff17robbins](https://github.com/jeff17robbins)) - Joe Frayne ([@jfrayne](https://github.com/jfrayne)) - Joe Lidbetter ([@jmlidbetter](https://github.com/jmlidbetter)) - Joe Savage ([@s4v4g3](https://github.com/s4v4g3)) diff --git a/src/runtime/interop38.cs b/src/runtime/interop38.cs index 8f2e32afe..9126bca6a 100644 --- a/src/runtime/interop38.cs +++ b/src/runtime/interop38.cs @@ -1,6 +1,6 @@ // Auto-generated by geninterop.py. -// DO NOT MODIFIY BY HAND. +// DO NOT MODIFY BY HAND. #if PYTHON38 @@ -84,6 +84,7 @@ public static int magic() public static int tp_version_tag = 0; public static int tp_finalize = 0; public static int tp_vectorcall = 0; + public static int tp_print = 0; public static int am_await = 0; public static int am_aiter = 0; public static int am_anext = 0; @@ -149,4 +150,3 @@ public static int magic() } #endif - diff --git a/tools/geninterop/geninterop.py b/tools/geninterop/geninterop.py index f8ef8e561..1f4751939 100644 --- a/tools/geninterop/geninterop.py +++ b/tools/geninterop/geninterop.py @@ -76,6 +76,8 @@ def visit(self, node): self.visit_struct(node) elif isinstance(node, c_ast.Decl): self.visit_decl(node) + elif isinstance(node, c_ast.FuncDecl): + self.visit_funcdecl(node) elif isinstance(node, c_ast.PtrDecl): self.visit_ptrdecl(node) elif isinstance(node, c_ast.IdentifierType): @@ -110,6 +112,9 @@ def visit_struct(self, struct): def visit_decl(self, decl): self.visit(decl.type) + def visit_funcdecl(self, funcdecl): + self.visit(funcdecl.type) + def visit_ptrdecl(self, ptrdecl): self.__ptr_decl_depth += 1 self.visit(ptrdecl.type) @@ -177,6 +182,13 @@ def preprocess_python_headers(): "-D", "_POSIX_THREADS" ] + if os.name == 'nt': + defines.extend([ + "-D", "__inline=inline", + "-D", "__ptr32=", + "-D", "__declspec(x)=", + ]) + if hasattr(sys, "abiflags"): if "d" in sys.abiflags: defines.extend(("-D", "PYTHON_WITH_PYDEBUG")) @@ -216,7 +228,7 @@ def gen_interop_code(members): defines_str = " && ".join(defines) class_definition = """ // Auto-generated by %s. -// DO NOT MODIFIY BY HAND. +// DO NOT MODIFY BY HAND. #if %s From abbe870db593ba63efc5e9999b2474169d87e141 Mon Sep 17 00:00:00 2001 From: amos402 Date: Mon, 2 Dec 2019 19:53:15 +0800 Subject: [PATCH 030/112] Internal implement Py_CompileString for compat with CPython 3.8.0 (#1000) --- src/runtime/pythonengine.cs | 6 +++--- src/runtime/runtime.cs | 30 +++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index 700543839..5073067d3 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -503,7 +503,7 @@ public static PyObject ReloadModule(PyObject module) /// public static PyObject ModuleFromString(string name, string code) { - IntPtr c = Runtime.Py_CompileString(code, "none", (IntPtr)257); + IntPtr c = Runtime.Py_CompileString(code, "none", (int)RunFlagType.File); Runtime.CheckExceptionOccurred(); IntPtr m = Runtime.PyImport_ExecCodeModule(name, c); Runtime.CheckExceptionOccurred(); @@ -512,7 +512,7 @@ public static PyObject ModuleFromString(string name, string code) public static PyObject Compile(string code, string filename = "", RunFlagType mode = RunFlagType.File) { - var flag = (IntPtr)mode; + var flag = (int)mode; IntPtr ptr = Runtime.Py_CompileString(code, filename, flag); Runtime.CheckExceptionOccurred(); return new PyObject(ptr); @@ -610,7 +610,7 @@ internal static PyObject RunString(string code, IntPtr? globals, IntPtr? locals, } } - public enum RunFlagType + public enum RunFlagType : int { Single = 256, File = 257, /* Py_file_input */ diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 7a78cd6e1..66f5ed123 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -763,8 +763,36 @@ public static extern int Py_Main( [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyEval_EvalCode(IntPtr co, IntPtr globals, IntPtr locals); +#if PYTHON2 + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr Py_CompileString(string code, string file, int start); +#else + /// + /// Return value: New reference. + /// This is a simplified interface to Py_CompileStringFlags() below, leaving flags set to NULL. + /// + internal static IntPtr Py_CompileString(string str, string file, int start) + { + return Py_CompileStringFlags(str, file, start, IntPtr.Zero); + } + + /// + /// Return value: New reference. + /// This is a simplified interface to Py_CompileStringExFlags() below, with optimize set to -1. + /// + internal static IntPtr Py_CompileStringFlags(string str, string file, int start, IntPtr flags) + { + return Py_CompileStringExFlags(str, file, start, flags, -1); + } + + /// + /// Return value: New reference. + /// Like Py_CompileStringObject(), but filename is a byte string decoded from the filesystem encoding(os.fsdecode()). + /// + /// [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr Py_CompileString(string code, string file, IntPtr tok); + internal static extern IntPtr Py_CompileStringExFlags(string str, string file, int start, IntPtr flags, int optimize); +#endif [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyImport_ExecCodeModule(string name, IntPtr code); From d1044c3a54f1a359785251836868d835dbf97c6b Mon Sep 17 00:00:00 2001 From: amos402 Date: Mon, 2 Dec 2019 20:23:47 +0800 Subject: [PATCH 031/112] Remove unnecessary `CopySlot` calls (#1004) --- src/runtime/typemanager.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index 9a98e9ebb..e9a8818f0 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -309,17 +309,11 @@ internal static IntPtr CreateMetaType(Type impl) Marshal.WriteIntPtr(type, TypeOffset.tp_base, py_type); Runtime.XIncref(py_type); - // Copy gc and other type slots from the base Python metatype. - - CopySlot(py_type, type, TypeOffset.tp_basicsize); - CopySlot(py_type, type, TypeOffset.tp_itemsize); - - CopySlot(py_type, type, TypeOffset.tp_dictoffset); - CopySlot(py_type, type, TypeOffset.tp_weaklistoffset); - - CopySlot(py_type, type, TypeOffset.tp_traverse); - CopySlot(py_type, type, TypeOffset.tp_clear); - CopySlot(py_type, type, TypeOffset.tp_is_gc); + // Slots will inherit from TypeType, it's not neccesary for setting them. + // Inheried slots: + // tp_basicsize, tp_itemsize, + // tp_dictoffset, tp_weaklistoffset, + // tp_traverse, tp_clear, tp_is_gc, etc. // Override type slots with those of the managed implementation. From 5f56ebc8f0785f6581c10d5beb9c1a8fffe6c650 Mon Sep 17 00:00:00 2001 From: amos402 Date: Mon, 2 Dec 2019 20:31:05 +0800 Subject: [PATCH 032/112] Fix refcnt errors (split from #958) (#1001) * Add exception helper * Fixed refcnt error in ExtensionType.FinalizeObject * Fixed typename leaking * Fix refcnt error by using `using` --- .editorconfig | 2 +- src/runtime/extensiontype.cs | 3 ++- src/runtime/metatype.cs | 1 + src/runtime/pythonexception.cs | 17 +++++++++++++++++ src/runtime/runtime.cs | 4 ++++ src/runtime/typemanager.cs | 8 ++------ 6 files changed, 27 insertions(+), 8 deletions(-) diff --git a/.editorconfig b/.editorconfig index 9e10931d0..d64f74bc1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -25,7 +25,7 @@ dotnet_sort_system_directives_first = true dotnet_separate_import_directive_groups = true [*.cs] -csharp_new_line_before_open_brace = true +csharp_new_line_before_open_brace = all csharp_new_line_before_else = true csharp_new_line_before_catch = true csharp_new_line_before_finally = true diff --git a/src/runtime/extensiontype.cs b/src/runtime/extensiontype.cs index 693a46f42..6585180c1 100644 --- a/src/runtime/extensiontype.cs +++ b/src/runtime/extensiontype.cs @@ -38,6 +38,7 @@ public ExtensionType() Runtime.PyObject_GC_UnTrack(py); + // Steals a ref to tpHandle. tpHandle = tp; pyHandle = py; gcHandle = gc; @@ -50,7 +51,7 @@ public ExtensionType() public static void FinalizeObject(ManagedType self) { Runtime.PyObject_GC_Del(self.pyHandle); - Runtime.XDecref(self.tpHandle); + // Not necessary for decref of `tpHandle`. self.gcHandle.Free(); } diff --git a/src/runtime/metatype.cs b/src/runtime/metatype.cs index 8853c2d5e..5af2e1a7e 100644 --- a/src/runtime/metatype.cs +++ b/src/runtime/metatype.cs @@ -266,6 +266,7 @@ private static IntPtr DoInstanceCheck(IntPtr tp, IntPtr args, bool checkType) return Runtime.PyFalse; } + Runtime.XIncref(args); using (var argsObj = new PyList(args)) { if (argsObj.Length() != 1) diff --git a/src/runtime/pythonexception.cs b/src/runtime/pythonexception.cs index 295a63b3d..8a6a24799 100644 --- a/src/runtime/pythonexception.cs +++ b/src/runtime/pythonexception.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; namespace Python.Runtime { @@ -190,5 +191,21 @@ public static bool Matches(IntPtr ob) { return Runtime.PyErr_ExceptionMatches(ob) != 0; } + + public static void ThrowIfIsNull(IntPtr ob) + { + if (ob == IntPtr.Zero) + { + throw new PythonException(); + } + } + + public static void ThrowIfIsNotZero(int value) + { + if (value != 0) + { + throw new PythonException(); + } + } } } diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 66f5ed123..449d22435 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1368,6 +1368,10 @@ internal static IntPtr PyUnicode_FromStringAndSize(IntPtr value, long size) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr PyUnicode_FromStringAndSize(IntPtr value, IntPtr size); + + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr PyUnicode_AsUTF8(IntPtr unicode); + #elif PYTHON2 internal static IntPtr PyString_FromStringAndSize(string value, long size) { diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index e9a8818f0..4427305e6 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -409,12 +409,8 @@ internal static IntPtr AllocateTypeObject(string name) // the Python version of the type name - otherwise we'd have to // allocate the tp_name and would have no way to free it. #if PYTHON3 - // For python3 we leak two objects. One for the ASCII representation - // required for tp_name, and another for the Unicode representation - // for ht_name. - IntPtr temp = Runtime.PyBytes_FromString(name); - IntPtr raw = Runtime.PyBytes_AS_STRING(temp); - temp = Runtime.PyUnicode_FromString(name); + IntPtr temp = Runtime.PyUnicode_FromString(name); + IntPtr raw = Runtime.PyUnicode_AsUTF8(temp); #elif PYTHON2 IntPtr temp = Runtime.PyString_FromString(name); IntPtr raw = Runtime.PyString_AsString(temp); From ba5127aaf687d8bb816423cd31a4bc7698b1ebc7 Mon Sep 17 00:00:00 2001 From: Alex Earl Date: Mon, 16 Dec 2019 14:33:00 -0700 Subject: [PATCH 033/112] Add mp_length slot for .NET classes implementing ICollection/ICollection (#994) - Add mp_length slot implementation for .NET types - Check if the object implement ICollection or ICollection - Add tests for explicit and non-explicit interface implementation --- AUTHORS.md | 1 + CHANGELOG.md | 1 + src/runtime/Python.Runtime.csproj | 1 + src/runtime/arrayobject.cs | 11 -- src/runtime/slots/mp_length.cs | 50 +++++++++ src/runtime/typemanager.cs | 15 +++ src/testing/Python.Test.csproj | 1 + src/testing/mp_lengthtest.cs | 171 ++++++++++++++++++++++++++++++ src/tests/test_mp_length.py | 49 +++++++++ 9 files changed, 289 insertions(+), 11 deletions(-) create mode 100644 src/runtime/slots/mp_length.cs create mode 100644 src/testing/mp_lengthtest.cs create mode 100644 src/tests/test_mp_length.py diff --git a/AUTHORS.md b/AUTHORS.md index e42a456ae..26285bf6a 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -12,6 +12,7 @@ ## Contributors +- Alex Earl ([@slide](https://github.com/slide)) - Alex Helms ([@alexhelms](https://github.com/alexhelms)) - Alexandre Catarino([@AlexCatarino](https://github.com/AlexCatarino)) - Arvid JB ([@ArvidJB](https://github.com/ArvidJB)) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c999d668..1fd2b1dcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added automatic NuGet package generation in appveyor and local builds - Added function that sets Py_NoSiteFlag to 1. - Added support for Jetson Nano. +- Added support for __len__ for .NET classes that implement ICollection ### Changed diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 02656e51e..0c2f912de 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -142,6 +142,7 @@ + diff --git a/src/runtime/arrayobject.cs b/src/runtime/arrayobject.cs index c37295704..1ef318473 100644 --- a/src/runtime/arrayobject.cs +++ b/src/runtime/arrayobject.cs @@ -244,16 +244,5 @@ public static int sq_contains(IntPtr ob, IntPtr v) return 0; } - - - /// - /// Implements __len__ for array types. - /// - public static int mp_length(IntPtr ob) - { - var self = (CLRObject)GetManagedObject(ob); - var items = self.inst as Array; - return items.Length; - } } } diff --git a/src/runtime/slots/mp_length.cs b/src/runtime/slots/mp_length.cs new file mode 100644 index 000000000..b0a2e8d79 --- /dev/null +++ b/src/runtime/slots/mp_length.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Python.Runtime.Slots +{ + internal static class mp_length_slot + { + /// + /// Implements __len__ for classes that implement ICollection + /// (this includes any IList implementer or Array subclass) + /// + public static int mp_length(IntPtr ob) + { + var co = ManagedType.GetManagedObject(ob) as CLRObject; + if (co == null) + { + Exceptions.RaiseTypeError("invalid object"); + } + + // first look for ICollection implementation directly + if (co.inst is ICollection c) + { + return c.Count; + } + + Type clrType = co.inst.GetType(); + + // now look for things that implement ICollection directly (non-explicitly) + PropertyInfo p = clrType.GetProperty("Count"); + if (p != null && clrType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>))) + { + return (int)p.GetValue(co.inst, null); + } + + // finally look for things that implement the interface explicitly + var iface = clrType.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>)); + if (iface != null) + { + p = iface.GetProperty(nameof(ICollection.Count)); + return (int)p.GetValue(co.inst, null); + } + + Exceptions.SetError(Exceptions.TypeError, $"object of type '{clrType.Name}' has no len()"); + return -1; + } + } +} diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index 4427305e6..97e6032cd 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -1,9 +1,11 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using Python.Runtime.Platform; +using Python.Runtime.Slots; namespace Python.Runtime { @@ -153,6 +155,13 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType) Marshal.WriteIntPtr(type, TypeOffset.tp_itemsize, IntPtr.Zero); Marshal.WriteIntPtr(type, TypeOffset.tp_dictoffset, (IntPtr)tp_dictoffset); + // add a __len__ slot for inheritors of ICollection and ICollection<> + if (typeof(ICollection).IsAssignableFrom(clrType) || clrType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>))) + { + InitializeSlot(type, TypeOffset.mp_length, typeof(mp_length_slot).GetMethod(nameof(mp_length_slot.mp_length))); + } + + // we want to do this after the slot stuff above in case the class itself implements a slot method InitializeSlots(type, impl.GetType()); if (base_ != IntPtr.Zero) @@ -193,6 +202,12 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType) return type; } + static void InitializeSlot(IntPtr type, int slotOffset, MethodInfo method) + { + IntPtr thunk = Interop.GetThunk(method); + Marshal.WriteIntPtr(type, slotOffset, thunk); + } + internal static IntPtr CreateSubType(IntPtr py_name, IntPtr py_base_type, IntPtr py_dict) { // Utility to create a subtype of a managed type with the ability for the diff --git a/src/testing/Python.Test.csproj b/src/testing/Python.Test.csproj index 6bf5c2d22..515fd928c 100644 --- a/src/testing/Python.Test.csproj +++ b/src/testing/Python.Test.csproj @@ -92,6 +92,7 @@ + diff --git a/src/testing/mp_lengthtest.cs b/src/testing/mp_lengthtest.cs new file mode 100644 index 000000000..a4f3e8c25 --- /dev/null +++ b/src/testing/mp_lengthtest.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Python.Test +{ + public class MpLengthCollectionTest : ICollection + { + private readonly List items; + + public MpLengthCollectionTest() + { + SyncRoot = new object(); + items = new List + { + 1, + 2, + 3 + }; + } + + public int Count => items.Count; + + public object SyncRoot { get; private set; } + + public bool IsSynchronized => false; + + public void CopyTo(Array array, int index) + { + throw new NotImplementedException(); + } + + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + } + + public class MpLengthExplicitCollectionTest : ICollection + { + private readonly List items; + private readonly object syncRoot; + + public MpLengthExplicitCollectionTest() + { + syncRoot = new object(); + items = new List + { + 9, + 10 + }; + } + int ICollection.Count => items.Count; + + object ICollection.SyncRoot => syncRoot; + + bool ICollection.IsSynchronized => false; + + void ICollection.CopyTo(Array array, int index) + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } + + public class MpLengthGenericCollectionTest : ICollection + { + private readonly List items; + + public MpLengthGenericCollectionTest() { + SyncRoot = new object(); + items = new List(); + } + + public int Count => items.Count; + + public object SyncRoot { get; private set; } + + public bool IsSynchronized => false; + + public bool IsReadOnly => false; + + public void Add(T item) + { + items.Add(item); + } + + public void Clear() + { + items.Clear(); + } + + public bool Contains(T item) + { + return items.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + items.CopyTo(array, arrayIndex); + } + + public IEnumerator GetEnumerator() + { + return ((IEnumerable)items).GetEnumerator(); + } + + public bool Remove(T item) + { + return items.Remove(item); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return items.GetEnumerator(); + } + } + + public class MpLengthExplicitGenericCollectionTest : ICollection + { + private readonly List items; + + public MpLengthExplicitGenericCollectionTest() + { + items = new List(); + } + + int ICollection.Count => items.Count; + + bool ICollection.IsReadOnly => false; + + public void Add(T item) + { + items.Add(item); + } + + void ICollection.Clear() + { + items.Clear(); + } + + bool ICollection.Contains(T item) + { + return items.Contains(item); + } + + void ICollection.CopyTo(T[] array, int arrayIndex) + { + items.CopyTo(array, arrayIndex); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return items.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)items).GetEnumerator(); + } + + bool ICollection.Remove(T item) + { + return items.Remove(item); + } + } +} diff --git a/src/tests/test_mp_length.py b/src/tests/test_mp_length.py new file mode 100644 index 000000000..c96ac77d1 --- /dev/null +++ b/src/tests/test_mp_length.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +"""Test __len__ for .NET classes implementing ICollection/ICollection.""" + +import System +import pytest +from Python.Test import MpLengthCollectionTest, MpLengthExplicitCollectionTest, MpLengthGenericCollectionTest, MpLengthExplicitGenericCollectionTest + +def test_simple___len__(): + """Test __len__ for simple ICollection implementers""" + import System + import System.Collections.Generic + l = System.Collections.Generic.List[int]() + assert len(l) == 0 + l.Add(5) + l.Add(6) + assert len(l) == 2 + + d = System.Collections.Generic.Dictionary[int, int]() + assert len(d) == 0 + d.Add(4, 5) + assert len(d) == 1 + + a = System.Array[int]([0,1,2,3]) + assert len(a) == 4 + +def test_custom_collection___len__(): + """Test __len__ for custom collection class""" + s = MpLengthCollectionTest() + assert len(s) == 3 + +def test_custom_collection_explicit___len__(): + """Test __len__ for custom collection class that explicitly implements ICollection""" + s = MpLengthExplicitCollectionTest() + assert len(s) == 2 + +def test_custom_generic_collection___len__(): + """Test __len__ for custom generic collection class""" + s = MpLengthGenericCollectionTest[int]() + s.Add(1) + s.Add(2) + assert len(s) == 2 + +def test_custom_generic_collection_explicit___len__(): + """Test __len__ for custom generic collection that explicity implements ICollection""" + s = MpLengthExplicitGenericCollectionTest[int]() + s.Add(1) + s.Add(10) + assert len(s) == 2 From c1190f4397f365667d8246503ac936d3945bdc2b Mon Sep 17 00:00:00 2001 From: amos402 Date: Wed, 18 Dec 2019 16:03:28 +0800 Subject: [PATCH 034/112] Release method wrapper(split from #958) (#1002) * Add exception helper * Release memory on ImportHook.Shutdown * Release ModuleDef when py_clr_module released * Completely ModuleDef initialization --- src/runtime/importhook.cs | 44 ++++++++++++++++++++++++++++++------ src/runtime/interop.cs | 2 +- src/runtime/methodwrapper.cs | 17 ++++++++++++++ src/runtime/moduleobject.cs | 5 ++++ src/runtime/runtime.cs | 1 - 5 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/runtime/importhook.cs b/src/runtime/importhook.cs index 06ba7a56d..94f0f7c94 100644 --- a/src/runtime/importhook.cs +++ b/src/runtime/importhook.cs @@ -23,6 +23,16 @@ internal static void InitializeModuleDef() module_def = ModuleDefOffset.AllocModuleDef("clr"); } } + + internal static void ReleaseModuleDef() + { + if (module_def == IntPtr.Zero) + { + return; + } + ModuleDefOffset.FreeModuleDef(module_def); + module_def = IntPtr.Zero; + } #endif /// @@ -56,9 +66,12 @@ static void InitImport() // Python __import__. IntPtr builtins = GetNewRefToBuiltins(); py_import = Runtime.PyObject_GetAttrString(builtins, "__import__"); + PythonException.ThrowIfIsNull(py_import); + hook = new MethodWrapper(typeof(ImportHook), "__import__", "TernaryFunc"); - Runtime.PyObject_SetAttrString(builtins, "__import__", hook.ptr); - Runtime.XDecref(hook.ptr); + int res = Runtime.PyObject_SetAttrString(builtins, "__import__", hook.ptr); + PythonException.ThrowIfIsNotZero(res); + Runtime.XDecref(builtins); } @@ -69,10 +82,14 @@ static void RestoreImport() { IntPtr builtins = GetNewRefToBuiltins(); - Runtime.PyObject_SetAttrString(builtins, "__import__", py_import); + int res = Runtime.PyObject_SetAttrString(builtins, "__import__", py_import); + PythonException.ThrowIfIsNotZero(res); Runtime.XDecref(py_import); py_import = IntPtr.Zero; + hook.Release(); + hook = null; + Runtime.XDecref(builtins); } @@ -112,13 +129,26 @@ internal static void Initialize() /// internal static void Shutdown() { - if (Runtime.Py_IsInitialized() != 0) + if (Runtime.Py_IsInitialized() == 0) { - RestoreImport(); + return; + } - Runtime.XDecref(py_clr_module); - Runtime.XDecref(root.pyHandle); + RestoreImport(); + + bool shouldFreeDef = Runtime.Refcount(py_clr_module) == 1; + Runtime.XDecref(py_clr_module); + py_clr_module = IntPtr.Zero; +#if PYTHON3 + if (shouldFreeDef) + { + ReleaseModuleDef(); } +#endif + + Runtime.XDecref(root.pyHandle); + root = null; + CLRModule.Reset(); } /// diff --git a/src/runtime/interop.cs b/src/runtime/interop.cs index 4ae4b61e0..2e29601fd 100644 --- a/src/runtime/interop.cs +++ b/src/runtime/interop.cs @@ -227,7 +227,7 @@ public static IntPtr AllocModuleDef(string modulename) byte[] ascii = Encoding.ASCII.GetBytes(modulename); int size = name + ascii.Length + 1; IntPtr ptr = Marshal.AllocHGlobal(size); - for (int i = 0; i < m_free; i += IntPtr.Size) + for (int i = 0; i <= m_free; i += IntPtr.Size) Marshal.WriteIntPtr(ptr, i, IntPtr.Zero); Marshal.Copy(ascii, 0, (IntPtr)(ptr + name), ascii.Length); Marshal.WriteIntPtr(ptr, m_name, (IntPtr)(ptr + name)); diff --git a/src/runtime/methodwrapper.cs b/src/runtime/methodwrapper.cs index 2f3ce3ef2..ba92e99d4 100644 --- a/src/runtime/methodwrapper.cs +++ b/src/runtime/methodwrapper.cs @@ -12,6 +12,7 @@ internal class MethodWrapper { public IntPtr mdef; public IntPtr ptr; + private bool _disposed = false; public MethodWrapper(Type type, string name, string funcType = null) { @@ -31,5 +32,21 @@ public IntPtr Call(IntPtr args, IntPtr kw) { return Runtime.PyCFunction_Call(ptr, args, kw); } + + public void Release() + { + if (_disposed) + { + return; + } + _disposed = true; + bool freeDef = Runtime.Refcount(ptr) == 1; + Runtime.XDecref(ptr); + if (freeDef && mdef != IntPtr.Zero) + { + Runtime.PyMem_Free(mdef); + mdef = IntPtr.Zero; + } + } } } diff --git a/src/runtime/moduleobject.cs b/src/runtime/moduleobject.cs index 7a45c6c81..544f69c81 100644 --- a/src/runtime/moduleobject.cs +++ b/src/runtime/moduleobject.cs @@ -316,6 +316,11 @@ internal class CLRModule : ModuleObject internal static bool _SuppressDocs = false; internal static bool _SuppressOverloads = false; + static CLRModule() + { + Reset(); + } + public CLRModule() : base("clr") { _namespace = string.Empty; diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 449d22435..130d90c0a 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -187,7 +187,6 @@ internal static void Initialize(bool initSigs = false) IsFinalizing = false; - CLRModule.Reset(); GenericUtil.Reset(); PyScopeManager.Reset(); ClassManager.Reset(); From e896aa6714b9d736bea58fae7c9cbf0fd01337a2 Mon Sep 17 00:00:00 2001 From: amos402 Date: Thu, 19 Dec 2019 02:49:46 +0800 Subject: [PATCH 035/112] * Decref the members of Runtime * Unified GetBuiltins method --- src/runtime/importhook.cs | 25 +----- src/runtime/runtime.cs | 170 ++++++++++++++++++++++++++------------ 2 files changed, 120 insertions(+), 75 deletions(-) diff --git a/src/runtime/importhook.cs b/src/runtime/importhook.cs index 94f0f7c94..aa3bbab6d 100644 --- a/src/runtime/importhook.cs +++ b/src/runtime/importhook.cs @@ -35,27 +35,6 @@ internal static void ReleaseModuleDef() } #endif - /// - /// Get a New reference to the builtins module. - /// - static IntPtr GetNewRefToBuiltins() - { - if (Runtime.IsPython3) - { - return Runtime.PyImport_ImportModule("builtins"); - } - else - { - // dict is a borrowed ref, no need to decref - IntPtr dict = Runtime.PyImport_GetModuleDict(); - - // GetItemString is a borrowed ref; incref to get a new ref - IntPtr builtins = Runtime.PyDict_GetItemString(dict, "__builtin__"); - Runtime.XIncref(builtins); - return builtins; - } - } - /// /// Initialize just the __import__ hook itself. /// @@ -64,7 +43,7 @@ static void InitImport() // We replace the built-in Python __import__ with our own: first // look in CLR modules, then if we don't find any call the default // Python __import__. - IntPtr builtins = GetNewRefToBuiltins(); + IntPtr builtins = Runtime.GetBuiltins(); py_import = Runtime.PyObject_GetAttrString(builtins, "__import__"); PythonException.ThrowIfIsNull(py_import); @@ -80,7 +59,7 @@ static void InitImport() /// static void RestoreImport() { - IntPtr builtins = GetNewRefToBuiltins(); + IntPtr builtins = Runtime.GetBuiltins(); int res = Runtime.PyObject_SetAttrString(builtins, "__import__", py_import); PythonException.ThrowIfIsNotZero(res); diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 130d90c0a..2443e3edc 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -169,6 +169,8 @@ public class Runtime /// internal static readonly Encoding PyEncoding = _UCS == 2 ? Encoding.Unicode : Encoding.UTF32; + private static PyReferenceCollection _pyRefs = new PyReferenceCollection(); + /// /// Initialize the runtime... /// @@ -194,99 +196,94 @@ internal static void Initialize(bool initSigs = false) TypeManager.Reset(); IntPtr op; - IntPtr dict; - if (IsPython3) - { - op = PyImport_ImportModule("builtins"); - dict = PyObject_GetAttrString(op, "__dict__"); - } - else // Python2 { - dict = PyImport_GetModuleDict(); - op = PyDict_GetItemString(dict, "__builtin__"); - } - PyNotImplemented = PyObject_GetAttrString(op, "NotImplemented"); - PyBaseObjectType = PyObject_GetAttrString(op, "object"); + var builtins = GetBuiltins(); + SetPyMember(ref PyNotImplemented, PyObject_GetAttrString(builtins, "NotImplemented")); - PyNone = PyObject_GetAttrString(op, "None"); - PyTrue = PyObject_GetAttrString(op, "True"); - PyFalse = PyObject_GetAttrString(op, "False"); + SetPyMember(ref PyBaseObjectType, PyObject_GetAttrString(builtins, "object")); - PyBoolType = PyObject_Type(PyTrue); - PyNoneType = PyObject_Type(PyNone); - PyTypeType = PyObject_Type(PyNoneType); + SetPyMember(ref PyNone, PyObject_GetAttrString(builtins, "None")); + SetPyMember(ref PyTrue, PyObject_GetAttrString(builtins, "True")); + SetPyMember(ref PyFalse, PyObject_GetAttrString(builtins, "False")); - op = PyObject_GetAttrString(dict, "keys"); - PyMethodType = PyObject_Type(op); - XDecref(op); + SetPyMember(ref PyBoolType, PyObject_Type(PyTrue)); + SetPyMember(ref PyNoneType, PyObject_Type(PyNone)); + SetPyMember(ref PyTypeType, PyObject_Type(PyNoneType)); - // For some arcane reason, builtins.__dict__.__setitem__ is *not* - // a wrapper_descriptor, even though dict.__setitem__ is. - // - // object.__init__ seems safe, though. - op = PyObject_GetAttrString(PyBaseObjectType, "__init__"); - PyWrapperDescriptorType = PyObject_Type(op); - XDecref(op); + op = PyObject_GetAttrString(builtins, "len"); + SetPyMember(ref PyMethodType, PyObject_Type(op)); + XDecref(op); -#if PYTHON3 - XDecref(dict); -#endif + // For some arcane reason, builtins.__dict__.__setitem__ is *not* + // a wrapper_descriptor, even though dict.__setitem__ is. + // + // object.__init__ seems safe, though. + op = PyObject_GetAttrString(PyBaseObjectType, "__init__"); + SetPyMember(ref PyWrapperDescriptorType, PyObject_Type(op)); + XDecref(op); + + SetPyMember(ref PySuper_Type, PyObject_GetAttrString(builtins, "super")); + + XDecref(builtins); + } op = PyString_FromString("string"); - PyStringType = PyObject_Type(op); + SetPyMember(ref PyStringType, PyObject_Type(op)); XDecref(op); op = PyUnicode_FromString("unicode"); - PyUnicodeType = PyObject_Type(op); + SetPyMember(ref PyUnicodeType, PyObject_Type(op)); XDecref(op); #if PYTHON3 op = PyBytes_FromString("bytes"); - PyBytesType = PyObject_Type(op); + SetPyMember(ref PyBytesType, PyObject_Type(op)); XDecref(op); #endif op = PyTuple_New(0); - PyTupleType = PyObject_Type(op); + SetPyMember(ref PyTupleType, PyObject_Type(op)); XDecref(op); op = PyList_New(0); - PyListType = PyObject_Type(op); + SetPyMember(ref PyListType, PyObject_Type(op)); XDecref(op); op = PyDict_New(); - PyDictType = PyObject_Type(op); + SetPyMember(ref PyDictType, PyObject_Type(op)); XDecref(op); op = PyInt_FromInt32(0); - PyIntType = PyObject_Type(op); + SetPyMember(ref PyIntType, PyObject_Type(op)); XDecref(op); op = PyLong_FromLong(0); - PyLongType = PyObject_Type(op); + SetPyMember(ref PyLongType, PyObject_Type(op)); XDecref(op); op = PyFloat_FromDouble(0); - PyFloatType = PyObject_Type(op); + SetPyMember(ref PyFloatType, PyObject_Type(op)); XDecref(op); -#if PYTHON3 +#if !PYTHON2 PyClassType = IntPtr.Zero; PyInstanceType = IntPtr.Zero; -#elif PYTHON2 - IntPtr s = PyString_FromString("_temp"); - IntPtr d = PyDict_New(); +#else + { + IntPtr s = PyString_FromString("_temp"); + IntPtr d = PyDict_New(); - IntPtr c = PyClass_New(IntPtr.Zero, d, s); - PyClassType = PyObject_Type(c); + IntPtr c = PyClass_New(IntPtr.Zero, d, s); + SetPyMember(ref PyClassType, PyObject_Type(c)); - IntPtr i = PyInstance_New(c, IntPtr.Zero, IntPtr.Zero); - PyInstanceType = PyObject_Type(i); + IntPtr i = PyInstance_New(c, IntPtr.Zero, IntPtr.Zero); + SetPyMember(ref PyInstanceType, PyObject_Type(i)); - XDecref(s); - XDecref(i); - XDecref(c); - XDecref(d); + XDecref(s); + XDecref(i); + XDecref(c); + XDecref(d); + } #endif Error = new IntPtr(-1); @@ -380,6 +377,9 @@ internal static void Shutdown() Exceptions.Shutdown(); ImportHook.Shutdown(); Finalizer.Shutdown(); + // TOOD: PyCLRMetaType's release operation still in #958 + PyCLRMetaType = IntPtr.Zero; + ResetPyMembers(); Py_Finalize(); } @@ -393,6 +393,19 @@ internal static int AtExit() return 0; } + private static void SetPyMember(ref IntPtr obj, IntPtr value) + { + // XXX: For current usages, value should not be null. + PythonException.ThrowIfIsNull(value); + obj = value; + _pyRefs.Add(ref obj); + } + + private static void ResetPyMembers() + { + _pyRefs.Release(); + } + internal static IntPtr Py_single_input = (IntPtr)256; internal static IntPtr Py_file_input = (IntPtr)257; internal static IntPtr Py_eval_input = (IntPtr)258; @@ -401,6 +414,7 @@ internal static int AtExit() internal static IntPtr PyModuleType; internal static IntPtr PyClassType; internal static IntPtr PyInstanceType; + internal static IntPtr PySuper_Type; internal static IntPtr PyCLRMetaType; internal static IntPtr PyMethodType; internal static IntPtr PyWrapperDescriptorType; @@ -1746,6 +1760,9 @@ internal static bool PyIter_Check(IntPtr pointer) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyImport_Import(IntPtr name); + /// + /// Return value: New reference. + /// [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyImport_ImportModule(string name); @@ -1945,5 +1962,54 @@ internal static void SetNoSiteFlag() } } } + + /// + /// Return value: New reference. + /// + internal static IntPtr GetBuiltins() + { + return IsPython3 ? PyImport_ImportModule("builtins") + : PyImport_ImportModule("__builtin__"); + } + } + + + class PyReferenceCollection + { + public List _objects { get; private set; } + + public PyReferenceCollection() + { + _objects = new List(); + } + + /// + /// Record obj's address to release the obj in the future, + /// obj must alive before calling Release. + /// + public void Add(ref IntPtr obj) + { + unsafe + { + fixed (void* p = &obj) + { + _objects.Add((IntPtr)p); + } + } + } + + public void Release() + { + foreach (var objRef in _objects) + { + unsafe + { + var p = (void**)objRef; + Runtime.XDecref((IntPtr)(*p)); + *p = null; + } + } + _objects.Clear(); + } } } From f0e9c380687663cbc9fa5dbd52f0e461416cce0f Mon Sep 17 00:00:00 2001 From: amos402 Date: Thu, 19 Dec 2019 07:00:18 +0800 Subject: [PATCH 036/112] Transfer the ownership of method's thunk to caller(split from #958) (#1003) * Add exception helper * Make the caller of `Interop.GetThunk` handle thunk's lifecycle(unfinished) * Use Marshal.GetFunctionPointerForDelegate instead of Marshal + Thunk --- src/runtime/interop.cs | 43 ++++++++++++++++++++++++------------ src/runtime/methodwrapper.cs | 6 +++-- src/runtime/typemanager.cs | 17 ++++++++------ 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/src/runtime/interop.cs b/src/runtime/interop.cs index 2e29601fd..ca3c35bfd 100644 --- a/src/runtime/interop.cs +++ b/src/runtime/interop.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using System.Reflection; using System.Text; +using System.Collections.Generic; namespace Python.Runtime { @@ -334,7 +335,7 @@ internal class TypeFlags internal class Interop { - private static ArrayList keepAlive; + private static List keepAlive; private static Hashtable pmap; static Interop() @@ -351,8 +352,7 @@ static Interop() p[item.Name] = item; } - keepAlive = new ArrayList(); - Marshal.AllocHGlobal(IntPtr.Size); + keepAlive = new List(); pmap = new Hashtable(); pmap["tp_dealloc"] = p["DestructorFunc"]; @@ -449,7 +449,7 @@ internal static Type GetPrototype(string name) return pmap[name] as Type; } - internal static IntPtr GetThunk(MethodInfo method, string funcType = null) + internal static ThunkInfo GetThunk(MethodInfo method, string funcType = null) { Type dt; if (funcType != null) @@ -457,18 +457,15 @@ internal static IntPtr GetThunk(MethodInfo method, string funcType = null) else dt = GetPrototype(method.Name); - if (dt != null) + if (dt == null) { - IntPtr tmp = Marshal.AllocHGlobal(IntPtr.Size); - Delegate d = Delegate.CreateDelegate(dt, method); - Thunk cb = new Thunk(d); - Marshal.StructureToPtr(cb, tmp, false); - IntPtr fp = Marshal.ReadIntPtr(tmp, 0); - Marshal.FreeHGlobal(tmp); - keepAlive.Add(d); - return fp; + return ThunkInfo.Empty; } - return IntPtr.Zero; + Delegate d = Delegate.CreateDelegate(dt, method); + var info = new ThunkInfo(d); + // TODO: remove keepAlive when #958 merged, let the lifecycle of ThunkInfo transfer to caller. + keepAlive.Add(info); + return info; } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -522,4 +519,22 @@ public Thunk(Delegate d) fn = d; } } + + internal class ThunkInfo + { + public readonly Delegate Target; + public readonly IntPtr Address; + + public static readonly ThunkInfo Empty = new ThunkInfo(null); + + public ThunkInfo(Delegate target) + { + if (target == null) + { + return; + } + Target = target; + Address = Marshal.GetFunctionPointerForDelegate(target); + } + } } diff --git a/src/runtime/methodwrapper.cs b/src/runtime/methodwrapper.cs index ba92e99d4..bc7500dab 100644 --- a/src/runtime/methodwrapper.cs +++ b/src/runtime/methodwrapper.cs @@ -14,17 +14,19 @@ internal class MethodWrapper public IntPtr ptr; private bool _disposed = false; + private ThunkInfo _thunk; + public MethodWrapper(Type type, string name, string funcType = null) { // Turn the managed method into a function pointer - IntPtr fp = Interop.GetThunk(type.GetMethod(name), funcType); + _thunk = Interop.GetThunk(type.GetMethod(name), funcType); // Allocate and initialize a PyMethodDef structure to represent // the managed method, then create a PyCFunction. mdef = Runtime.PyMem_Malloc(4 * IntPtr.Size); - TypeManager.WriteMethodDef(mdef, name, fp, 0x0003); + TypeManager.WriteMethodDef(mdef, name, _thunk.Address, 0x0003); ptr = Runtime.PyCFunction_NewEx(mdef, IntPtr.Zero, IntPtr.Zero); } diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index 97e6032cd..bb920b74f 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -204,8 +204,8 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType) static void InitializeSlot(IntPtr type, int slotOffset, MethodInfo method) { - IntPtr thunk = Interop.GetThunk(method); - Marshal.WriteIntPtr(type, slotOffset, thunk); + var thunk = Interop.GetThunk(method); + Marshal.WriteIntPtr(type, slotOffset, thunk.Address); } internal static IntPtr CreateSubType(IntPtr py_name, IntPtr py_base_type, IntPtr py_dict) @@ -344,16 +344,18 @@ internal static IntPtr CreateMetaType(Type impl) // 4 int-ptrs in size. IntPtr mdef = Runtime.PyMem_Malloc(3 * 4 * IntPtr.Size); IntPtr mdefStart = mdef; + ThunkInfo thunkInfo = Interop.GetThunk(typeof(MetaType).GetMethod("__instancecheck__"), "BinaryFunc"); mdef = WriteMethodDef( mdef, "__instancecheck__", - Interop.GetThunk(typeof(MetaType).GetMethod("__instancecheck__"), "BinaryFunc") + thunkInfo.Address ); + thunkInfo = Interop.GetThunk(typeof(MetaType).GetMethod("__subclasscheck__"), "BinaryFunc"); mdef = WriteMethodDef( mdef, "__subclasscheck__", - Interop.GetThunk(typeof(MetaType).GetMethod("__subclasscheck__"), "BinaryFunc") + thunkInfo.Address ); // FIXME: mdef is not used @@ -710,7 +712,8 @@ internal static void InitializeSlots(IntPtr type, Type impl) continue; } - InitializeSlot(type, Interop.GetThunk(method), name); + var thunkInfo = Interop.GetThunk(method); + InitializeSlot(type, thunkInfo.Address, name); seen.Add(name); } @@ -728,8 +731,8 @@ internal static void InitializeSlots(IntPtr type, Type impl) // These have to be defined, though, so by default we fill these with // static C# functions from this class. - var ret0 = Interop.GetThunk(((Func)Return0).Method); - var ret1 = Interop.GetThunk(((Func)Return1).Method); + var ret0 = Interop.GetThunk(((Func)Return0).Method).Address; + var ret1 = Interop.GetThunk(((Func)Return1).Method).Address; if (native != null) { From ab0cb02fb8306f15beb9b734a8fbf56b2708ad26 Mon Sep 17 00:00:00 2001 From: amos402 Date: Sat, 21 Dec 2019 11:56:30 +0800 Subject: [PATCH 037/112] Add explicit release action --- src/runtime/runtime.cs | 121 ++++++++++++++++++++++------------------- 1 file changed, 64 insertions(+), 57 deletions(-) diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 2443e3edc..7748bafa9 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -198,20 +198,29 @@ internal static void Initialize(bool initSigs = false) IntPtr op; { var builtins = GetBuiltins(); - SetPyMember(ref PyNotImplemented, PyObject_GetAttrString(builtins, "NotImplemented")); - - SetPyMember(ref PyBaseObjectType, PyObject_GetAttrString(builtins, "object")); - - SetPyMember(ref PyNone, PyObject_GetAttrString(builtins, "None")); - SetPyMember(ref PyTrue, PyObject_GetAttrString(builtins, "True")); - SetPyMember(ref PyFalse, PyObject_GetAttrString(builtins, "False")); - - SetPyMember(ref PyBoolType, PyObject_Type(PyTrue)); - SetPyMember(ref PyNoneType, PyObject_Type(PyNone)); - SetPyMember(ref PyTypeType, PyObject_Type(PyNoneType)); + SetPyMember(ref PyNotImplemented, PyObject_GetAttrString(builtins, "NotImplemented"), + () => PyNotImplemented = IntPtr.Zero); + + SetPyMember(ref PyBaseObjectType, PyObject_GetAttrString(builtins, "object"), + () => PyBaseObjectType = IntPtr.Zero); + + SetPyMember(ref PyNone, PyObject_GetAttrString(builtins, "None"), + () => PyNone = IntPtr.Zero); + SetPyMember(ref PyTrue, PyObject_GetAttrString(builtins, "True"), + () => PyTrue = IntPtr.Zero); + SetPyMember(ref PyFalse, PyObject_GetAttrString(builtins, "False"), + () => PyFalse = IntPtr.Zero); + + SetPyMember(ref PyBoolType, PyObject_Type(PyTrue), + () => PyBoolType = IntPtr.Zero); + SetPyMember(ref PyNoneType, PyObject_Type(PyNone), + () => PyNoneType = IntPtr.Zero); + SetPyMember(ref PyTypeType, PyObject_Type(PyNoneType), + () => PyTypeType = IntPtr.Zero); op = PyObject_GetAttrString(builtins, "len"); - SetPyMember(ref PyMethodType, PyObject_Type(op)); + SetPyMember(ref PyMethodType, PyObject_Type(op), + () => PyMethodType = IntPtr.Zero); XDecref(op); // For some arcane reason, builtins.__dict__.__setitem__ is *not* @@ -219,50 +228,61 @@ internal static void Initialize(bool initSigs = false) // // object.__init__ seems safe, though. op = PyObject_GetAttrString(PyBaseObjectType, "__init__"); - SetPyMember(ref PyWrapperDescriptorType, PyObject_Type(op)); + SetPyMember(ref PyWrapperDescriptorType, PyObject_Type(op), + () => PyWrapperDescriptorType = IntPtr.Zero); XDecref(op); - SetPyMember(ref PySuper_Type, PyObject_GetAttrString(builtins, "super")); + SetPyMember(ref PySuper_Type, PyObject_GetAttrString(builtins, "super"), + () => PySuper_Type = IntPtr.Zero); XDecref(builtins); } op = PyString_FromString("string"); - SetPyMember(ref PyStringType, PyObject_Type(op)); + SetPyMember(ref PyStringType, PyObject_Type(op), + () => PyStringType = IntPtr.Zero); XDecref(op); op = PyUnicode_FromString("unicode"); - SetPyMember(ref PyUnicodeType, PyObject_Type(op)); + SetPyMember(ref PyUnicodeType, PyObject_Type(op), + () => PyUnicodeType = IntPtr.Zero); XDecref(op); #if PYTHON3 op = PyBytes_FromString("bytes"); - SetPyMember(ref PyBytesType, PyObject_Type(op)); + SetPyMember(ref PyBytesType, PyObject_Type(op), + () => PyBytesType = IntPtr.Zero); XDecref(op); #endif op = PyTuple_New(0); - SetPyMember(ref PyTupleType, PyObject_Type(op)); + SetPyMember(ref PyTupleType, PyObject_Type(op), + () => PyTupleType = IntPtr.Zero); XDecref(op); op = PyList_New(0); - SetPyMember(ref PyListType, PyObject_Type(op)); + SetPyMember(ref PyListType, PyObject_Type(op), + () => PyListType = IntPtr.Zero); XDecref(op); op = PyDict_New(); - SetPyMember(ref PyDictType, PyObject_Type(op)); + SetPyMember(ref PyDictType, PyObject_Type(op), + () => PyDictType = IntPtr.Zero); XDecref(op); op = PyInt_FromInt32(0); - SetPyMember(ref PyIntType, PyObject_Type(op)); + SetPyMember(ref PyIntType, PyObject_Type(op), + () => PyIntType = IntPtr.Zero); XDecref(op); op = PyLong_FromLong(0); - SetPyMember(ref PyLongType, PyObject_Type(op)); + SetPyMember(ref PyLongType, PyObject_Type(op), + () => PyLongType = IntPtr.Zero); XDecref(op); op = PyFloat_FromDouble(0); - SetPyMember(ref PyFloatType, PyObject_Type(op)); + SetPyMember(ref PyFloatType, PyObject_Type(op), + () => PyFloatType = IntPtr.Zero); XDecref(op); #if !PYTHON2 @@ -274,10 +294,12 @@ internal static void Initialize(bool initSigs = false) IntPtr d = PyDict_New(); IntPtr c = PyClass_New(IntPtr.Zero, d, s); - SetPyMember(ref PyClassType, PyObject_Type(c)); + SetPyMember(ref PyClassType, PyObject_Type(c), + () => PyClassType = IntPtr.Zero); IntPtr i = PyInstance_New(c, IntPtr.Zero, IntPtr.Zero); - SetPyMember(ref PyInstanceType, PyObject_Type(i)); + SetPyMember(ref PyInstanceType, PyObject_Type(i), + () => PyInstanceType = IntPtr.Zero); XDecref(s); XDecref(i); @@ -393,12 +415,12 @@ internal static int AtExit() return 0; } - private static void SetPyMember(ref IntPtr obj, IntPtr value) + private static void SetPyMember(ref IntPtr obj, IntPtr value, Action onRelease) { // XXX: For current usages, value should not be null. PythonException.ThrowIfIsNull(value); obj = value; - _pyRefs.Add(ref obj); + _pyRefs.Add(value, onRelease); } private static void ResetPyMembers() @@ -977,7 +999,7 @@ internal static int PyObject_Compare(IntPtr value1, IntPtr value2) internal static long PyObject_Size(IntPtr pointer) { - return (long) _PyObject_Size(pointer); + return (long)_PyObject_Size(pointer); } [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PyObject_Size")] @@ -1093,7 +1115,7 @@ internal static bool PyLong_Check(IntPtr ob) internal static IntPtr PyLong_FromUnsignedLong(object value) { - if(Is32Bit || IsWindows) + if (Is32Bit || IsWindows) return PyLong_FromUnsignedLong32(Convert.ToUInt32(value)); else return PyLong_FromUnsignedLong64(Convert.ToUInt64(value)); @@ -1283,7 +1305,7 @@ internal static int PySequence_DelSlice(IntPtr pointer, long i1, long i2) internal static long PySequence_Size(IntPtr pointer) { - return (long) _PySequence_Size(pointer); + return (long)_PySequence_Size(pointer); } [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PySequence_Size")] @@ -1308,7 +1330,7 @@ internal static IntPtr PySequence_Repeat(IntPtr pointer, long count) internal static long PySequence_Count(IntPtr pointer, IntPtr value) { - return (long) _PySequence_Count(pointer, value); + return (long)_PySequence_Count(pointer, value); } [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PySequence_Count")] @@ -1351,7 +1373,7 @@ internal static IntPtr PyString_FromString(string value) internal static long PyBytes_Size(IntPtr op) { - return (long) _PyBytes_Size(op); + return (long)_PyBytes_Size(op); } [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PyBytes_Size")] @@ -1582,7 +1604,7 @@ internal static bool PyDict_Check(IntPtr ob) internal static long PyDict_Size(IntPtr pointer) { - return (long) _PyDict_Size(pointer); + return (long)_PyDict_Size(pointer); } [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PyDict_Size")] @@ -1660,7 +1682,7 @@ internal static int PyList_SetSlice(IntPtr pointer, long start, long end, IntPtr internal static long PyList_Size(IntPtr pointer) { - return (long) _PyList_Size(pointer); + return (long)_PyList_Size(pointer); } [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PyList_Size")] @@ -1709,7 +1731,7 @@ internal static IntPtr PyTuple_GetSlice(IntPtr pointer, long start, long end) internal static long PyTuple_Size(IntPtr pointer) { - return (long) _PyTuple_Size(pointer); + return (long)_PyTuple_Size(pointer); } [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PyTuple_Size")] @@ -1976,40 +1998,25 @@ internal static IntPtr GetBuiltins() class PyReferenceCollection { - public List _objects { get; private set; } - - public PyReferenceCollection() - { - _objects = new List(); - } + private List> _actions = new List>(); /// /// Record obj's address to release the obj in the future, /// obj must alive before calling Release. /// - public void Add(ref IntPtr obj) + public void Add(IntPtr ob, Action onRelease) { - unsafe - { - fixed (void* p = &obj) - { - _objects.Add((IntPtr)p); - } - } + _actions.Add(new KeyValuePair(ob, onRelease)); } public void Release() { - foreach (var objRef in _objects) + foreach (var item in _actions) { - unsafe - { - var p = (void**)objRef; - Runtime.XDecref((IntPtr)(*p)); - *p = null; - } + Runtime.XDecref(item.Key); + item.Value?.Invoke(); } - _objects.Clear(); + _actions.Clear(); } } } From 4e5fbe040ac5569bd297d5d87a2960304343a043 Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Tue, 31 Dec 2019 19:31:15 +1100 Subject: [PATCH 038/112] Fix simple typo: heirarchy -> hierarchy (#1026) Closes #1025 --- src/tests/test_repr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/test_repr.py b/src/tests/test_repr.py index d120b0c4c..5131f5d88 100644 --- a/src/tests/test_repr.py +++ b/src/tests/test_repr.py @@ -26,7 +26,7 @@ def test_str_only(): assert " Date: Wed, 11 Sep 2019 21:59:27 -0700 Subject: [PATCH 039/112] enable expanding set of marshaling conversions via PyObjectConversions added sample TupleCodec (only supporting ValueTuple) --- src/embed_tests/Codecs.cs | 86 ++++++++ .../Python.EmbeddingTest.15.csproj | 3 +- src/embed_tests/Python.EmbeddingTest.csproj | 3 +- src/embed_tests/packages.config | 5 +- src/runtime/Codecs/TupleCodecs.cs | 129 ++++++++++++ src/runtime/Python.Runtime.csproj | 6 +- src/runtime/converter.cs | 24 ++- src/runtime/converterextensions.cs | 185 ++++++++++++++++++ src/runtime/pythonengine.cs | 2 + src/runtime/runtime.cs | 9 + 10 files changed, 444 insertions(+), 8 deletions(-) create mode 100644 src/embed_tests/Codecs.cs create mode 100644 src/runtime/Codecs/TupleCodecs.cs create mode 100644 src/runtime/converterextensions.cs diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs new file mode 100644 index 000000000..600215cf0 --- /dev/null +++ b/src/embed_tests/Codecs.cs @@ -0,0 +1,86 @@ +namespace Python.EmbeddingTest { + using System; + using System.Collections.Generic; + using System.Text; + using NUnit.Framework; + using Python.Runtime; + using Python.Runtime.Codecs; + + public class Codecs { + [SetUp] + public void SetUp() { + PythonEngine.Initialize(); + } + + [TearDown] + public void Dispose() { + PythonEngine.Shutdown(); + } + + [Test] + public void ConversionsGeneric() { + ConversionsGeneric, ValueTuple>(); + } + + static void ConversionsGeneric() { + TupleCodec.Register(); + var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); + T restored = default; + using (Py.GIL()) + using (var scope = Py.CreateScope()) { + void Accept(T value) => restored = value; + var accept = new Action(Accept).ToPython(); + scope.Set(nameof(tuple), tuple); + scope.Set(nameof(accept), accept); + scope.Exec($"{nameof(accept)}({nameof(tuple)})"); + Assert.AreEqual(expected: tuple, actual: restored); + } + } + + [Test] + public void ConversionsObject() { + ConversionsObject, ValueTuple>(); + } + static void ConversionsObject() { + TupleCodec.Register(); + var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); + T restored = default; + using (Py.GIL()) + using (var scope = Py.CreateScope()) { + void Accept(object value) => restored = (T)value; + var accept = new Action(Accept).ToPython(); + scope.Set(nameof(tuple), tuple); + scope.Set(nameof(accept), accept); + scope.Exec($"{nameof(accept)}({nameof(tuple)})"); + Assert.AreEqual(expected: tuple, actual: restored); + } + } + + [Test] + public void TupleRoundtripObject() { + TupleRoundtripObject, ValueTuple>(); + } + static void TupleRoundtripObject() { + var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); + using (Py.GIL()) { + var pyTuple = TupleCodec.Instance.TryEncode(tuple); + Assert.IsTrue(TupleCodec.Instance.TryDecode(pyTuple, out object restored)); + Assert.AreEqual(expected: tuple, actual: restored); + } + } + + [Test] + public void TupleRoundtripGeneric() { + TupleRoundtripGeneric, ValueTuple>(); + } + + static void TupleRoundtripGeneric() { + var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); + using (Py.GIL()) { + var pyTuple = TupleCodec.Instance.TryEncode(tuple); + Assert.IsTrue(TupleCodec.Instance.TryDecode(pyTuple, out T restored)); + Assert.AreEqual(expected: tuple, actual: restored); + } + } + } +} diff --git a/src/embed_tests/Python.EmbeddingTest.15.csproj b/src/embed_tests/Python.EmbeddingTest.15.csproj index 4f6b2de46..c335135b7 100644 --- a/src/embed_tests/Python.EmbeddingTest.15.csproj +++ b/src/embed_tests/Python.EmbeddingTest.15.csproj @@ -23,7 +23,7 @@ ..\..\ $(SolutionDir)\bin\ $(OutputPath)\$(TargetFramework)_publish - 6 + 7.3 prompt $(PYTHONNET_DEFINE_CONSTANTS) XPLAT @@ -81,6 +81,7 @@ + diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index faa55fa27..d8488011a 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -14,7 +14,7 @@ 1591 ..\..\ $(SolutionDir)\bin\ - 6 + 7.3 true prompt @@ -80,6 +80,7 @@ + diff --git a/src/embed_tests/packages.config b/src/embed_tests/packages.config index 8c175f441..4052311fb 100644 --- a/src/embed_tests/packages.config +++ b/src/embed_tests/packages.config @@ -1,5 +1,6 @@ - + - + + \ No newline at end of file diff --git a/src/runtime/Codecs/TupleCodecs.cs b/src/runtime/Codecs/TupleCodecs.cs new file mode 100644 index 000000000..7c01eee46 --- /dev/null +++ b/src/runtime/Codecs/TupleCodecs.cs @@ -0,0 +1,129 @@ +namespace Python.Runtime.Codecs +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + public sealed class TupleCodec : IPyObjectEncoder, IPyObjectDecoder + { + TupleCodec() { } + public static TupleCodec Instance { get; } = new TupleCodec(); + + public bool CanEncode(Type type) + => type.Namespace == typeof(TTuple).Namespace && type.Name.StartsWith(typeof(TTuple).Name + '`') + || type == typeof(object) || type == typeof(TTuple); + + public PyObject TryEncode(object value) + { + if (value == null) return null; + + var tupleType = value.GetType(); + if (tupleType == typeof(object)) return null; + if (!this.CanEncode(tupleType)) return null; + if (tupleType == typeof(TTuple)) return new PyTuple(); + + long fieldCount = tupleType.GetGenericArguments().Length; + var tuple = Runtime.PyTuple_New(fieldCount); + Exceptions.ErrorCheck(tuple); + int fieldIndex = 0; + foreach (FieldInfo field in tupleType.GetFields()) + { + var item = field.GetValue(value); + IntPtr pyItem = Converter.ToPython(item); + Runtime.XIncref(pyItem); + Runtime.PyTuple_SetItem(tuple, fieldIndex, pyItem); + fieldIndex++; + } + return new PyTuple(Runtime.SelfIncRef(tuple)); + } + + public bool CanDecode(PyObject objectType, Type targetType) + => objectType.Handle == Runtime.PyTupleType && this.CanEncode(targetType); + + public bool TryDecode(PyObject pyObj, out T value) + { + if (pyObj == null) throw new ArgumentNullException(nameof(pyObj)); + + value = default; + + if (!Runtime.PyTuple_Check(pyObj.Handle)) return false; + + if (typeof(T) == typeof(object)) + { + bool converted = Decode(pyObj, out object result); + if (converted) + { + value = (T)result; + return true; + } + + return false; + } + + var itemTypes = typeof(T).GetGenericArguments(); + long itemCount = Runtime.PyTuple_Size(pyObj.Handle); + if (itemTypes.Length != itemCount) return false; + + if (itemCount == 0) + { + value = (T)EmptyTuple; + return true; + } + + var elements = new object[itemCount]; + for (int itemIndex = 0; itemIndex < itemTypes.Length; itemIndex++) + { + IntPtr pyItem = Runtime.PyTuple_GetItem(pyObj.Handle, itemIndex); + if (!Converter.ToManaged(pyItem, itemTypes[itemIndex], out elements[itemIndex], setError: false)) + { + return false; + } + } + var factory = tupleCreate[itemCount].MakeGenericMethod(itemTypes); + value = (T)factory.Invoke(null, elements); + return true; + } + + static bool Decode(PyObject tuple, out object value) + { + long itemCount = Runtime.PyTuple_Size(tuple.Handle); + if (itemCount == 0) + { + value = EmptyTuple; + return true; + } + var elements = new object[itemCount]; + var itemTypes = new Type[itemCount]; + value = null; + for (int itemIndex = 0; itemIndex < elements.Length; itemIndex++) + { + var pyItem = Runtime.PyTuple_GetItem(tuple.Handle, itemIndex); + if (!Converter.ToManaged(pyItem, typeof(object), out elements[itemIndex], setError: false)) + { + return false; + } + + itemTypes[itemIndex] = elements[itemIndex]?.GetType() ?? typeof(object); + } + + var factory = tupleCreate[itemCount].MakeGenericMethod(itemTypes); + value = factory.Invoke(null, elements); + return true; + } + + static readonly MethodInfo[] tupleCreate = + typeof(TTuple).GetMethods(BindingFlags.Public | BindingFlags.Static) + .Where(m => m.Name == nameof(Tuple.Create)) + .OrderBy(m => m.GetParameters().Length) + .ToArray(); + + static readonly object EmptyTuple = tupleCreate[0].Invoke(null, parameters: new object[0]); + + public static void Register() + { + PyObjectConversions.RegisterEncoder(Instance); + PyObjectConversions.RegisterDecoder(Instance); + } + } +} diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 0c2f912de..4686d0b3c 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -1,4 +1,4 @@ - + Debug @@ -76,6 +76,8 @@ + + @@ -172,4 +174,4 @@ - + \ No newline at end of file diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index e7e047419..881b32cc9 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -135,8 +135,16 @@ internal static IntPtr ToPython(object value, Type type) return result; } - if (value is IList && !(value is INotifyPropertyChanged) && value.GetType().IsGenericType) - { + if (Type.GetTypeCode(type) == TypeCode.Object && value.GetType() != typeof(object)) { + var encoded = PyObjectConversions.TryEncode(value, type); + if (encoded != null) { + Runtime.XIncref(encoded.Handle); + return encoded.Handle; + } + } + + if (value is IList && !(value is INotifyPropertyChanged) && value.GetType().IsGenericType) + { using (var resultlist = new PyList()) { foreach (object o in (IEnumerable)value) @@ -437,9 +445,21 @@ internal static bool ToManagedValue(IntPtr value, Type obType, return false; } + TypeCode typeCode = Type.GetTypeCode(obType); + if (typeCode == TypeCode.Object) + { + IntPtr pyType = Runtime.PyObject_TYPE(value); + if (PyObjectConversions.TryDecode(value, pyType, obType, out result)) + { + return true; + } + } + return ToPrimitive(value, obType, out result, setError); } + internal delegate bool TryConvertFromPythonDelegate(IntPtr pyObj, out object result); + /// /// Convert a Python value to an instance of a primitive managed type. /// diff --git a/src/runtime/converterextensions.cs b/src/runtime/converterextensions.cs new file mode 100644 index 000000000..a6ae91d50 --- /dev/null +++ b/src/runtime/converterextensions.cs @@ -0,0 +1,185 @@ +namespace Python.Runtime +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + /// + /// Defines conversion to CLR types (unmarshalling) + /// + public interface IPyObjectDecoder + { + /// + /// Checks if this decoder can decode from to + /// + bool CanDecode(PyObject objectType, Type targetType); + /// + /// Attempts do decode into a variable of specified type + /// + /// CLR type to decode into + /// Object to decode + /// The variable, that will receive decoding result + /// + bool TryDecode(PyObject pyObj, out T value); + } + + /// + /// Defines conversion from CLR objects into Python objects (e.g. ) (marshalling) + /// + public interface IPyObjectEncoder + { + /// + /// Checks if encoder can encode CLR objects of specified type + /// + bool CanEncode(Type type); + /// + /// Attempts to encode CLR object into Python object + /// + PyObject TryEncode(object value); + } + + /// + /// This class allows to register additional marshalling codecs. + /// Python.NET will pick suitable encoder/decoder registered first + /// + public static class PyObjectConversions + { + static readonly List decoders = new List(); + static readonly List encoders = new List(); + + /// + /// Registers specified encoder (marshaller) + /// Python.NET will pick suitable encoder/decoder registered first + /// + public static void RegisterEncoder(IPyObjectEncoder encoder) + { + if (encoder == null) throw new ArgumentNullException(nameof(encoder)); + + lock (encoders) + { + encoders.Add(encoder); + } + } + + /// + /// Registers specified decoder (unmarshaller) + /// Python.NET will pick suitable encoder/decoder registered first + /// + public static void RegisterDecoder(IPyObjectDecoder decoder) + { + if (decoder == null) throw new ArgumentNullException(nameof(decoder)); + + lock (decoders) + { + decoders.Add(decoder); + } + } + + #region Encoding + internal static PyObject TryEncode(object obj, Type type) + { + if (obj == null) throw new ArgumentNullException(nameof(obj)); + if (type == null) throw new ArgumentNullException(nameof(type)); + + foreach (var encoder in clrToPython.GetOrAdd(type, GetEncoders)) + { + var result = encoder.TryEncode(obj); + if (result != null) return result; + } + + return null; + } + + static readonly ConcurrentDictionary + clrToPython = new ConcurrentDictionary(); + static IPyObjectEncoder[] GetEncoders(Type type) + { + lock (encoders) + { + return encoders.Where(encoder => encoder.CanEncode(type)).ToArray(); + } + } + #endregion + + #region Decoding + static readonly ConcurrentDictionary + pythonToClr = new ConcurrentDictionary(); + internal static bool TryDecode(IntPtr pyHandle, IntPtr pyType, Type targetType, out object result) + { + if (pyHandle == IntPtr.Zero) throw new ArgumentNullException(nameof(pyHandle)); + if (pyType == IntPtr.Zero) throw new ArgumentNullException(nameof(pyType)); + if (targetType == null) throw new ArgumentNullException(nameof(targetType)); + + var decoder = pythonToClr.GetOrAdd(new TypePair(pyType, targetType), pair => GetDecoder(pair.PyType, pair.ClrType)); + result = null; + if (decoder == null) return false; + return decoder.Invoke(pyHandle, out result); + } + + static Converter.TryConvertFromPythonDelegate GetDecoder(IntPtr sourceType, Type targetType) + { + IPyObjectDecoder decoder; + using (var pyType = new PyObject(sourceType)) + { + lock (decoders) + { + decoder = decoders.Find(d => d.CanDecode(pyType, targetType)); + if (decoder == null) return null; + } + } + + var decode = genericDecode.MakeGenericMethod(targetType); + + bool TryDecode(IntPtr pyHandle, out object result) + { + using (var pyObj = new PyObject(Runtime.SelfIncRef(pyHandle))) + { + var @params = new object[] { pyObj, null }; + bool success = (bool)decode.Invoke(decoder, @params); + result = @params[1]; + return success; + } + } + + return TryDecode; + } + + static readonly MethodInfo genericDecode = typeof(IPyObjectDecoder).GetMethod(nameof(IPyObjectDecoder.TryDecode)); + + #endregion + + internal static void Reset() + { + lock (encoders) + lock (decoders) + { + clrToPython.Clear(); + pythonToClr.Clear(); + encoders.Clear(); + decoders.Clear(); + } + } + + struct TypePair : IEquatable + { + internal readonly IntPtr PyType; + internal readonly Type ClrType; + + public TypePair(IntPtr pyType, Type clrType) + { + this.PyType = pyType; + this.ClrType = clrType; + } + + public override int GetHashCode() + => this.ClrType.GetHashCode() ^ this.PyType.GetHashCode(); + + public bool Equals(TypePair other) + => this.PyType == other.PyType && this.ClrType == other.ClrType; + + public override bool Equals(object obj) => obj is TypePair other && this.Equals(other); + } + } +} diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index 5073067d3..abe0abfa8 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -328,6 +328,8 @@ public static void Shutdown() ExecuteShutdownHandlers(); + PyObjectConversions.Reset(); + initialized = false; } } diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 7748bafa9..f6500309c 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -603,6 +603,15 @@ internal static unsafe void XIncref(IntPtr op) #endif } + /// + /// Increase Python's ref counter for the given object, and get the object back. + /// + internal static IntPtr SelfIncRef(IntPtr op) + { + XIncref(op); + return op; + } + internal static unsafe void XDecref(IntPtr op) { #if PYTHON_WITH_PYDEBUG || NETSTANDARD From 6eca16943968219c42337757b45564f1dd8e6cc8 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 16 Jan 2020 11:04:06 -0800 Subject: [PATCH 040/112] fixed ConversionsObject test failing due to sequence to array conversion taking priority --- src/runtime/converter.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index 881b32cc9..85f4fecb5 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -385,6 +385,13 @@ internal static bool ToManagedValue(IntPtr value, Type obType, return ToPrimitive(value, doubleType, out result, setError); } + // give custom codecs a chance to take over conversion of sequences + IntPtr pyType = Runtime.PyObject_TYPE(value); + if (PyObjectConversions.TryDecode(value, pyType, obType, out result)) + { + return true; + } + if (Runtime.PySequence_Check(value)) { return ToArray(value, typeof(object[]), out result, setError); From 97e33c74482feb8421151c6902ff988e8aba16c7 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 16 Jan 2020 11:29:40 -0800 Subject: [PATCH 041/112] attempt to fix CI build issue with ValueTuple under Mono --- pythonnet.15.sln | 32 +++++++++++++++++++++ src/embed_tests/Python.EmbeddingTest.csproj | 3 ++ 2 files changed, 35 insertions(+) diff --git a/pythonnet.15.sln b/pythonnet.15.sln index 096dfbe9a..a1e738900 100644 --- a/pythonnet.15.sln +++ b/pythonnet.15.sln @@ -19,6 +19,26 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repo", "Repo", "{441A0123-F .editorconfig = .editorconfig EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CI", "CI", "{D301657F-5EAF-4534-B280-B858D651B2E5}" + ProjectSection(SolutionItems) = preProject + .travis.yml = .travis.yml + appveyor.yml = appveyor.yml + ci\appveyor_build_recipe.ps1 = ci\appveyor_build_recipe.ps1 + ci\appveyor_run_tests.ps1 = ci\appveyor_run_tests.ps1 + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{57F5D701-F265-4736-A5A2-07249E7A4DA3}" + ProjectSection(SolutionItems) = preProject + setup.py = setup.py + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "conda.recipe", "conda.recipe", "{7FD2404D-0CE8-4645-8DFB-766470E2150E}" + ProjectSection(SolutionItems) = preProject + conda.recipe\bld.bat = conda.recipe\bld.bat + conda.recipe\meta.yaml = conda.recipe\meta.yaml + conda.recipe\README.md = conda.recipe\README.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -320,11 +340,17 @@ Global {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|x86.ActiveCfg = DebugMono|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|x86.Build.0 = DebugMono|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMono|Any CPU.ActiveCfg = DebugMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMono|Any CPU.Build.0 = DebugMono|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMono|x64.ActiveCfg = DebugMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMono|x64.Build.0 = DebugMono|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMono|x86.ActiveCfg = DebugMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMono|x86.Build.0 = DebugMono|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMonoPY3|Any CPU.ActiveCfg = DebugMonoPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMonoPY3|Any CPU.Build.0 = DebugMonoPY3|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMonoPY3|x64.ActiveCfg = DebugMonoPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMonoPY3|x64.Build.0 = DebugMonoPY3|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMonoPY3|x86.ActiveCfg = DebugMonoPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMonoPY3|x86.Build.0 = DebugMonoPY3|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|Any CPU.ActiveCfg = DebugWin|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|Any CPU.Build.0 = DebugWin|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|x64.ActiveCfg = DebugWin|Any CPU @@ -344,11 +370,17 @@ Global {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|x86.ActiveCfg = ReleaseMono|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|x86.Build.0 = ReleaseMono|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMono|Any CPU.ActiveCfg = ReleaseMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMono|Any CPU.Build.0 = ReleaseMono|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMono|x64.ActiveCfg = ReleaseMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMono|x64.Build.0 = ReleaseMono|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMono|x86.ActiveCfg = ReleaseMono|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMono|x86.Build.0 = ReleaseMono|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMonoPY3|Any CPU.ActiveCfg = ReleaseMonoPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMonoPY3|Any CPU.Build.0 = ReleaseMonoPY3|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMonoPY3|x64.ActiveCfg = ReleaseMonoPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMonoPY3|x64.Build.0 = ReleaseMonoPY3|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMonoPY3|x86.ActiveCfg = ReleaseMonoPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMonoPY3|x86.Build.0 = ReleaseMonoPY3|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|Any CPU.ActiveCfg = ReleaseWin|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|Any CPU.Build.0 = ReleaseWin|Any CPU {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|x64.ActiveCfg = ReleaseWin|Any CPU diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index d8488011a..9ef1db170 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -73,6 +73,9 @@ ..\..\packages\NUnit.3.7.1\lib\net40\nunit.framework.dll + + ..\..\packages\System.ValueTuple.4.5.0\lib\portable-net40+sl4+win8+wp8\System.ValueTuple.dll + From 449338f0196aa9ba4e8846537cb22a4cb6948019 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 30 Jan 2020 08:53:50 -0800 Subject: [PATCH 042/112] added RefereneAssemblies package reference to fix CI build --- src/perf_tests/Python.PerformanceTests.csproj | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/perf_tests/Python.PerformanceTests.csproj b/src/perf_tests/Python.PerformanceTests.csproj index 33949fdc1..4e1d28dcc 100644 --- a/src/perf_tests/Python.PerformanceTests.csproj +++ b/src/perf_tests/Python.PerformanceTests.csproj @@ -1,4 +1,4 @@ - + net461 @@ -12,6 +12,10 @@ + + all + runtime; build; native; contentfiles; analyzers + compile From 39b2347e10b11f23747d45f9e0f2c4a0624d61b7 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Fri, 31 Jan 2020 20:35:05 -0800 Subject: [PATCH 043/112] marked the new codecs API as unstable --- src/runtime/Codecs/TupleCodecs.cs | 1 + src/runtime/Util.cs | 5 ++++- src/runtime/converterextensions.cs | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/runtime/Codecs/TupleCodecs.cs b/src/runtime/Codecs/TupleCodecs.cs index 7c01eee46..f6bcc3fc9 100644 --- a/src/runtime/Codecs/TupleCodecs.cs +++ b/src/runtime/Codecs/TupleCodecs.cs @@ -5,6 +5,7 @@ namespace Python.Runtime.Codecs using System.Linq; using System.Reflection; + [Obsolete(Util.UnstableApiMessage)] public sealed class TupleCodec : IPyObjectEncoder, IPyObjectDecoder { TupleCodec() { } diff --git a/src/runtime/Util.cs b/src/runtime/Util.cs index dc5f78608..16d82fe6e 100644 --- a/src/runtime/Util.cs +++ b/src/runtime/Util.cs @@ -5,6 +5,9 @@ namespace Python.Runtime { internal class Util { + internal const string UnstableApiMessage = + "This API is unstable, and might be changed or removed in the next minor release"; + internal static Int64 ReadCLong(IntPtr tp, int offset) { // On Windows, a C long is always 32 bits. @@ -30,4 +33,4 @@ internal static void WriteCLong(IntPtr type, int offset, Int64 flags) } } } -} \ No newline at end of file +} diff --git a/src/runtime/converterextensions.cs b/src/runtime/converterextensions.cs index a6ae91d50..0d7f0aff2 100644 --- a/src/runtime/converterextensions.cs +++ b/src/runtime/converterextensions.cs @@ -9,6 +9,7 @@ namespace Python.Runtime /// /// Defines conversion to CLR types (unmarshalling) /// + [Obsolete(Util.UnstableApiMessage)] public interface IPyObjectDecoder { /// @@ -28,6 +29,7 @@ public interface IPyObjectDecoder /// /// Defines conversion from CLR objects into Python objects (e.g. ) (marshalling) /// + [Obsolete(Util.UnstableApiMessage)] public interface IPyObjectEncoder { /// @@ -44,6 +46,7 @@ public interface IPyObjectEncoder /// This class allows to register additional marshalling codecs. /// Python.NET will pick suitable encoder/decoder registered first /// + [Obsolete(Util.UnstableApiMessage)] public static class PyObjectConversions { static readonly List decoders = new List(); From daa2901b13233df80d92c25b474640ac0fd37e3d Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Fri, 31 Jan 2020 22:07:41 -0800 Subject: [PATCH 044/112] attempt to fix PyScopeTest.TestThread() reading stale value from res in Python 2.7 x64 --- src/embed_tests/TestPyScope.cs | 7 ++++++- src/runtime/assemblymanager.cs | 11 ++++++++++- src/runtime/moduleobject.cs | 13 +++---------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/embed_tests/TestPyScope.cs b/src/embed_tests/TestPyScope.cs index 21c0d2b3f..7a4aa0228 100644 --- a/src/embed_tests/TestPyScope.cs +++ b/src/embed_tests/TestPyScope.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using NUnit.Framework; using Python.Runtime; @@ -337,9 +338,12 @@ public void TestThread() //add function to the scope //can be call many times, more efficient than ast ps.Exec( + "import clr\n" + + "from System.Threading import Thread\n" + "def update():\n" + " global res, th_cnt\n" + " res += bb + 1\n" + + " Thread.MemoryBarrier()\n" + " th_cnt += 1\n" ); } @@ -364,8 +368,9 @@ public void TestThread() { cnt = ps.Get("th_cnt"); } - System.Threading.Thread.Sleep(10); + Thread.Sleep(10); } + Thread.MemoryBarrier(); using (Py.GIL()) { var result = ps.Get("res"); diff --git a/src/runtime/assemblymanager.cs b/src/runtime/assemblymanager.cs index 3085bb639..f541caa9f 100644 --- a/src/runtime/assemblymanager.cs +++ b/src/runtime/assemblymanager.cs @@ -452,6 +452,7 @@ public static List GetNames(string nsname) /// looking in the currently loaded assemblies for the named /// type. Returns null if the named type cannot be found. /// + [Obsolete("Use LookupTypes and handle name conflicts")] public static Type LookupType(string qname) { foreach (Assembly assembly in assemblies) @@ -465,6 +466,14 @@ public static Type LookupType(string qname) return null; } + /// + /// Returns the objects for the given qualified name, + /// looking in the currently loaded assemblies for the named + /// type. + /// + public static IEnumerable LookupTypes(string qualifiedName) + => assemblies.Select(assembly => assembly.GetType(qualifiedName)).Where(type => type != null); + internal static Type[] GetTypes(Assembly a) { if (a.IsDynamic) @@ -492,4 +501,4 @@ internal static Type[] GetTypes(Assembly a) } } } -} \ No newline at end of file +} diff --git a/src/runtime/moduleobject.cs b/src/runtime/moduleobject.cs index 544f69c81..d2397bfc6 100644 --- a/src/runtime/moduleobject.cs +++ b/src/runtime/moduleobject.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.IO; using System.Reflection; using System.Runtime.InteropServices; @@ -105,13 +106,9 @@ public ManagedType GetAttribute(string name, bool guess) // Look for a type in the current namespace. Note that this // includes types, delegates, enums, interfaces and structs. // Only public namespace members are exposed to Python. - type = AssemblyManager.LookupType(qname); + type = AssemblyManager.LookupTypes(qname).FirstOrDefault(t => t.IsPublic); if (type != null) { - if (!type.IsPublic) - { - return null; - } c = ClassManager.GetClass(type); StoreAttribute(name, c); return c; @@ -131,13 +128,9 @@ public ManagedType GetAttribute(string name, bool guess) return m; } - type = AssemblyManager.LookupType(qname); + type = AssemblyManager.LookupTypes(qname).FirstOrDefault(t => t.IsPublic); if (type != null) { - if (!type.IsPublic) - { - return null; - } c = ClassManager.GetClass(type); StoreAttribute(name, c); return c; From 3362cf8ce1ee7f3b48d1f7ec6374b27d23153cd2 Mon Sep 17 00:00:00 2001 From: Victor Date: Mon, 3 Feb 2020 01:32:36 -0800 Subject: [PATCH 045/112] GILState.Dispose is safe to be called multiple times (#1037) * GILState.Dispose is safe to be called multiple times * test multiple class to GILState.Dispose --- src/embed_tests/TestGILState.cs | 21 +++++++++++++++++++++ src/runtime/pythonengine.cs | 6 +++++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 src/embed_tests/TestGILState.cs diff --git a/src/embed_tests/TestGILState.cs b/src/embed_tests/TestGILState.cs new file mode 100644 index 000000000..ba2ab500f --- /dev/null +++ b/src/embed_tests/TestGILState.cs @@ -0,0 +1,21 @@ +namespace Python.EmbeddingTest +{ + using NUnit.Framework; + using Python.Runtime; + + public class TestGILState + { + /// + /// Ensure, that calling multiple times is safe + /// + [Test] + public void CanDisposeMultipleTimes() + { + using (var gilState = Py.GIL()) + { + for(int i = 0; i < 50; i++) + gilState.Dispose(); + } + } + } +} diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index 5073067d3..aec2a412e 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -643,7 +643,8 @@ public static PyScope CreateScope(string name) public class GILState : IDisposable { - private IntPtr state; + private readonly IntPtr state; + private bool isDisposed; internal GILState() { @@ -652,8 +653,11 @@ internal GILState() public void Dispose() { + if (this.isDisposed) return; + PythonEngine.ReleaseLock(state); GC.SuppressFinalize(this); + this.isDisposed = true; } ~GILState() From dbb88bcede0feea84f86b8d2b1d84c45f177618b Mon Sep 17 00:00:00 2001 From: Victor Date: Mon, 3 Feb 2020 03:50:42 -0800 Subject: [PATCH 046/112] Added parameter validation to PyObject methods (#1021) * added parameter validation to PyObject methods --- src/embed_tests/TestPyObject.cs | 6 ++ src/runtime/pyint.cs | 6 +- src/runtime/pyobject.cs | 133 +++++++++++++++++++++++++++++--- 3 files changed, 130 insertions(+), 15 deletions(-) diff --git a/src/embed_tests/TestPyObject.cs b/src/embed_tests/TestPyObject.cs index 65ac20e9a..d4952d4a3 100644 --- a/src/embed_tests/TestPyObject.cs +++ b/src/embed_tests/TestPyObject.cs @@ -57,5 +57,11 @@ def add(self, x, y): Assert.IsTrue(memberNames.Contains(expectedName), "Could not find member '{0}'.", expectedName); } } + + [Test] + public void InvokeNull() { + var list = PythonEngine.Eval("list"); + Assert.Throws(() => list.Invoke(new PyObject[] {null})); + } } } diff --git a/src/runtime/pyint.cs b/src/runtime/pyint.cs index f6911d9d7..217cf7e20 100644 --- a/src/runtime/pyint.cs +++ b/src/runtime/pyint.cs @@ -62,7 +62,7 @@ public PyInt(int value) /// Creates a new Python int from a uint32 value. /// [CLSCompliant(false)] - public PyInt(uint value) : base(IntPtr.Zero) + public PyInt(uint value) { obj = Runtime.PyInt_FromInt64(value); Runtime.CheckExceptionOccurred(); @@ -75,7 +75,7 @@ public PyInt(uint value) : base(IntPtr.Zero) /// /// Creates a new Python int from an int64 value. /// - public PyInt(long value) : base(IntPtr.Zero) + public PyInt(long value) { obj = Runtime.PyInt_FromInt64(value); Runtime.CheckExceptionOccurred(); @@ -89,7 +89,7 @@ public PyInt(long value) : base(IntPtr.Zero) /// Creates a new Python int from a uint64 value. /// [CLSCompliant(false)] - public PyInt(ulong value) : base(IntPtr.Zero) + public PyInt(ulong value) { obj = Runtime.PyInt_FromInt64((long)value); Runtime.CheckExceptionOccurred(); diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index c86504802..8ae99ecd0 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Dynamic; +using System.Linq; using System.Linq.Expressions; namespace Python.Runtime @@ -43,6 +44,8 @@ public class PyObject : DynamicObject, IEnumerable, IPyDisposable /// public PyObject(IntPtr ptr) { + if (ptr == IntPtr.Zero) throw new ArgumentNullException(nameof(ptr)); + obj = ptr; #if TRACE_ALLOC Traceback = new StackTrace(1); @@ -51,7 +54,7 @@ public PyObject(IntPtr ptr) // Protected default constructor to allow subclasses to manage // initialization in different ways as appropriate. - + [Obsolete("Please, always use PyObject(IntPtr)")] protected PyObject() { #if TRACE_ALLOC @@ -209,6 +212,8 @@ public PyObject GetPythonType() /// public bool TypeCheck(PyObject typeOrClass) { + if (typeOrClass == null) throw new ArgumentNullException(nameof(typeOrClass)); + return Runtime.PyObject_TypeCheck(obj, typeOrClass.obj); } @@ -221,6 +226,8 @@ public bool TypeCheck(PyObject typeOrClass) /// public bool HasAttr(string name) { + if (name == null) throw new ArgumentNullException(nameof(name)); + return Runtime.PyObject_HasAttrString(obj, name) != 0; } @@ -234,6 +241,8 @@ public bool HasAttr(string name) /// public bool HasAttr(PyObject name) { + if (name == null) throw new ArgumentNullException(nameof(name)); + return Runtime.PyObject_HasAttr(obj, name.obj) != 0; } @@ -247,6 +256,8 @@ public bool HasAttr(PyObject name) /// public PyObject GetAttr(string name) { + if (name == null) throw new ArgumentNullException(nameof(name)); + IntPtr op = Runtime.PyObject_GetAttrString(obj, name); if (op == IntPtr.Zero) { @@ -257,7 +268,7 @@ public PyObject GetAttr(string name) /// - /// GetAttr Method + /// GetAttr Method. Returns fallback value if getting attribute fails for any reason. /// /// /// Returns the named attribute of the Python object, or the given @@ -265,6 +276,8 @@ public PyObject GetAttr(string name) /// public PyObject GetAttr(string name, PyObject _default) { + if (name == null) throw new ArgumentNullException(nameof(name)); + IntPtr op = Runtime.PyObject_GetAttrString(obj, name); if (op == IntPtr.Zero) { @@ -285,6 +298,8 @@ public PyObject GetAttr(string name, PyObject _default) /// public PyObject GetAttr(PyObject name) { + if (name == null) throw new ArgumentNullException(nameof(name)); + IntPtr op = Runtime.PyObject_GetAttr(obj, name.obj); if (op == IntPtr.Zero) { @@ -304,6 +319,8 @@ public PyObject GetAttr(PyObject name) /// public PyObject GetAttr(PyObject name, PyObject _default) { + if (name == null) throw new ArgumentNullException(nameof(name)); + IntPtr op = Runtime.PyObject_GetAttr(obj, name.obj); if (op == IntPtr.Zero) { @@ -323,6 +340,9 @@ public PyObject GetAttr(PyObject name, PyObject _default) /// public void SetAttr(string name, PyObject value) { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (value == null) throw new ArgumentNullException(nameof(value)); + int r = Runtime.PyObject_SetAttrString(obj, name, value.obj); if (r < 0) { @@ -341,6 +361,9 @@ public void SetAttr(string name, PyObject value) /// public void SetAttr(PyObject name, PyObject value) { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (value == null) throw new ArgumentNullException(nameof(value)); + int r = Runtime.PyObject_SetAttr(obj, name.obj, value.obj); if (r < 0) { @@ -358,6 +381,8 @@ public void SetAttr(PyObject name, PyObject value) /// public void DelAttr(string name) { + if (name == null) throw new ArgumentNullException(nameof(name)); + int r = Runtime.PyObject_SetAttrString(obj, name, IntPtr.Zero); if (r < 0) { @@ -376,6 +401,8 @@ public void DelAttr(string name) /// public void DelAttr(PyObject name) { + if (name == null) throw new ArgumentNullException(nameof(name)); + int r = Runtime.PyObject_SetAttr(obj, name.obj, IntPtr.Zero); if (r < 0) { @@ -394,6 +421,8 @@ public void DelAttr(PyObject name) /// public virtual PyObject GetItem(PyObject key) { + if (key == null) throw new ArgumentNullException(nameof(key)); + IntPtr op = Runtime.PyObject_GetItem(obj, key.obj); if (op == IntPtr.Zero) { @@ -413,6 +442,8 @@ public virtual PyObject GetItem(PyObject key) /// public virtual PyObject GetItem(string key) { + if (key == null) throw new ArgumentNullException(nameof(key)); + using (var pyKey = new PyString(key)) { return GetItem(pyKey); @@ -447,6 +478,9 @@ public virtual PyObject GetItem(int index) /// public virtual void SetItem(PyObject key, PyObject value) { + if (key == null) throw new ArgumentNullException(nameof(key)); + if (value == null) throw new ArgumentNullException(nameof(value)); + int r = Runtime.PyObject_SetItem(obj, key.obj, value.obj); if (r < 0) { @@ -465,6 +499,9 @@ public virtual void SetItem(PyObject key, PyObject value) /// public virtual void SetItem(string key, PyObject value) { + if (key == null) throw new ArgumentNullException(nameof(key)); + if (value == null) throw new ArgumentNullException(nameof(value)); + using (var pyKey = new PyString(key)) { SetItem(pyKey, value); @@ -482,6 +519,8 @@ public virtual void SetItem(string key, PyObject value) /// public virtual void SetItem(int index, PyObject value) { + if (value == null) throw new ArgumentNullException(nameof(value)); + using (var pyindex = new PyInt(index)) { SetItem(pyindex, value); @@ -499,6 +538,8 @@ public virtual void SetItem(int index, PyObject value) /// public virtual void DelItem(PyObject key) { + if (key == null) throw new ArgumentNullException(nameof(key)); + int r = Runtime.PyObject_DelItem(obj, key.obj); if (r < 0) { @@ -517,6 +558,8 @@ public virtual void DelItem(PyObject key) /// public virtual void DelItem(string key) { + if (key == null) throw new ArgumentNullException(nameof(key)); + using (var pyKey = new PyString(key)) { DelItem(pyKey); @@ -639,10 +682,13 @@ public IEnumerator GetEnumerator() /// /// /// Invoke the callable object with the given arguments, passed as a - /// PyObject[]. A PythonException is raised if the invokation fails. + /// PyObject[]. A PythonException is raised if the invocation fails. /// public PyObject Invoke(params PyObject[] args) { + if (args == null) throw new ArgumentNullException(nameof(args)); + if (args.Contains(null)) throw new ArgumentNullException(); + var t = new PyTuple(args); IntPtr r = Runtime.PyObject_Call(obj, t.obj, IntPtr.Zero); t.Dispose(); @@ -659,10 +705,12 @@ public PyObject Invoke(params PyObject[] args) /// /// /// Invoke the callable object with the given arguments, passed as a - /// Python tuple. A PythonException is raised if the invokation fails. + /// Python tuple. A PythonException is raised if the invocation fails. /// public PyObject Invoke(PyTuple args) { + if (args == null) throw new ArgumentNullException(nameof(args)); + IntPtr r = Runtime.PyObject_Call(obj, args.obj, IntPtr.Zero); if (r == IntPtr.Zero) { @@ -677,12 +725,15 @@ public PyObject Invoke(PyTuple args) /// /// /// Invoke the callable object with the given positional and keyword - /// arguments. A PythonException is raised if the invokation fails. + /// arguments. A PythonException is raised if the invocation fails. /// public PyObject Invoke(PyObject[] args, PyDict kw) { + if (args == null) throw new ArgumentNullException(nameof(args)); + if (args.Contains(null)) throw new ArgumentNullException(); + var t = new PyTuple(args); - IntPtr r = Runtime.PyObject_Call(obj, t.obj, kw != null ? kw.obj : IntPtr.Zero); + IntPtr r = Runtime.PyObject_Call(obj, t.obj, kw?.obj ?? IntPtr.Zero); t.Dispose(); if (r == IntPtr.Zero) { @@ -697,11 +748,13 @@ public PyObject Invoke(PyObject[] args, PyDict kw) /// /// /// Invoke the callable object with the given positional and keyword - /// arguments. A PythonException is raised if the invokation fails. + /// arguments. A PythonException is raised if the invocation fails. /// public PyObject Invoke(PyTuple args, PyDict kw) { - IntPtr r = Runtime.PyObject_Call(obj, args.obj, kw != null ? kw.obj : IntPtr.Zero); + if (args == null) throw new ArgumentNullException(nameof(args)); + + IntPtr r = Runtime.PyObject_Call(obj, args.obj, kw?.obj ?? IntPtr.Zero); if (r == IntPtr.Zero) { throw new PythonException(); @@ -715,10 +768,14 @@ public PyObject Invoke(PyTuple args, PyDict kw) /// /// /// Invoke the named method of the object with the given arguments. - /// A PythonException is raised if the invokation is unsuccessful. + /// A PythonException is raised if the invocation is unsuccessful. /// public PyObject InvokeMethod(string name, params PyObject[] args) { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (args == null) throw new ArgumentNullException(nameof(args)); + if (args.Contains(null)) throw new ArgumentNullException(); + PyObject method = GetAttr(name); PyObject result = method.Invoke(args); method.Dispose(); @@ -731,10 +788,51 @@ public PyObject InvokeMethod(string name, params PyObject[] args) /// /// /// Invoke the named method of the object with the given arguments. - /// A PythonException is raised if the invokation is unsuccessful. + /// A PythonException is raised if the invocation is unsuccessful. /// public PyObject InvokeMethod(string name, PyTuple args) { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (args == null) throw new ArgumentNullException(nameof(args)); + + PyObject method = GetAttr(name); + PyObject result = method.Invoke(args); + method.Dispose(); + return result; + } + + /// + /// InvokeMethod Method + /// + /// + /// Invoke the named method of the object with the given arguments. + /// A PythonException is raised if the invocation is unsuccessful. + /// + public PyObject InvokeMethod(PyObject name, params PyObject[] args) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (args == null) throw new ArgumentNullException(nameof(args)); + if (args.Contains(null)) throw new ArgumentNullException(); + + PyObject method = GetAttr(name); + PyObject result = method.Invoke(args); + method.Dispose(); + return result; + } + + + /// + /// InvokeMethod Method + /// + /// + /// Invoke the named method of the object with the given arguments. + /// A PythonException is raised if the invocation is unsuccessful. + /// + public PyObject InvokeMethod(PyObject name, PyTuple args) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (args == null) throw new ArgumentNullException(nameof(args)); + PyObject method = GetAttr(name); PyObject result = method.Invoke(args); method.Dispose(); @@ -748,10 +846,14 @@ public PyObject InvokeMethod(string name, PyTuple args) /// /// Invoke the named method of the object with the given arguments /// and keyword arguments. Keyword args are passed as a PyDict object. - /// A PythonException is raised if the invokation is unsuccessful. + /// A PythonException is raised if the invocation is unsuccessful. /// public PyObject InvokeMethod(string name, PyObject[] args, PyDict kw) { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (args == null) throw new ArgumentNullException(nameof(args)); + if (args.Contains(null)) throw new ArgumentNullException(); + PyObject method = GetAttr(name); PyObject result = method.Invoke(args, kw); method.Dispose(); @@ -765,10 +867,13 @@ public PyObject InvokeMethod(string name, PyObject[] args, PyDict kw) /// /// Invoke the named method of the object with the given arguments /// and keyword arguments. Keyword args are passed as a PyDict object. - /// A PythonException is raised if the invokation is unsuccessful. + /// A PythonException is raised if the invocation is unsuccessful. /// public PyObject InvokeMethod(string name, PyTuple args, PyDict kw) { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (args == null) throw new ArgumentNullException(nameof(args)); + PyObject method = GetAttr(name); PyObject result = method.Invoke(args, kw); method.Dispose(); @@ -785,6 +890,8 @@ public PyObject InvokeMethod(string name, PyTuple args, PyDict kw) /// public bool IsInstance(PyObject typeOrClass) { + if (typeOrClass == null) throw new ArgumentNullException(nameof(typeOrClass)); + int r = Runtime.PyObject_IsInstance(obj, typeOrClass.obj); if (r < 0) { @@ -804,6 +911,8 @@ public bool IsInstance(PyObject typeOrClass) /// public bool IsSubclass(PyObject typeOrClass) { + if (typeOrClass == null) throw new ArgumentNullException(nameof(typeOrClass)); + int r = Runtime.PyObject_IsSubclass(obj, typeOrClass.obj); if (r < 0) { From f5548e36288622d9acdd4502e91019bdd972f894 Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 13 Feb 2020 05:05:57 -0800 Subject: [PATCH 047/112] CI for performance tests (#992) * attempted to add performance tests to CI * attempt to fix PerformanceTests xplat CI build * enabling building PerformanceTests for Mono * fixed AppVeyor path to Python.PerformanceTests.dll * fixed Mono deb sources to bionic * slightly relaxed perf target for WriteInt64Property * PerformanceTests: explicitly specify platform * use framework-specific build of perf tests in xplat and generic otherwise * added perf tests run to Travis CI * better error message for a failure to run benchmarks * appveyor: don't run perf tests in unsupported configurations * fixed performance test Python version condition in AppVeyor * explicitly notify when performance tests are skipped in AppVeyor * relax performance targets to ~10%, improve perf failure message * switch to the release of Microsoft.NETFramework.ReferenceAssemblies package --- .travis.yml | 6 +- ci/appveyor_run_tests.ps1 | 32 ++++- pythonnet.15.sln | 120 +++++++++++------- setup.py | 10 ++ src/perf_tests/BenchmarkTests.cs | 17 ++- src/perf_tests/Python.PerformanceTests.csproj | 9 +- 6 files changed, 136 insertions(+), 58 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9689c0422..c69ccbc5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,8 @@ python: env: matrix: - - BUILD_OPTS=--xplat NUNIT_PATH="~/.nuget/packages/nunit.consolerunner/3.*/tools/nunit3-console.exe" RUN_TESTS=dotnet EMBED_TESTS_PATH=netcoreapp2.0_publish/ - - BUILD_OPTS="" NUNIT_PATH="./packages/NUnit.*/tools/nunit3-console.exe" RUN_TESTS="mono $NUNIT_PATH" EMBED_TESTS_PATH="" + - BUILD_OPTS=--xplat NUNIT_PATH="~/.nuget/packages/nunit.consolerunner/3.*/tools/nunit3-console.exe" RUN_TESTS=dotnet EMBED_TESTS_PATH=netcoreapp2.0_publish/ PERF_TESTS_PATH=net461/ + - BUILD_OPTS="" NUNIT_PATH="./packages/NUnit.*/tools/nunit3-console.exe" RUN_TESTS="mono $NUNIT_PATH" EMBED_TESTS_PATH="" PERF_TESTS_PATH="" global: - LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so @@ -45,6 +45,8 @@ install: script: - python -m pytest - $RUN_TESTS src/embed_tests/bin/$EMBED_TESTS_PATH/Python.EmbeddingTest.dll + # does not work on Linux, because NuGet package for 2.3 is Windows only + # - "if [[ $TRAVIS_PYTHON_VERSION == '3.5' && $PERF_TESTS_PATH != '' ]]; then mono $NUNIT_PATH src/perf_tests/bin/$PERF_TESTS_PATH/Python.PerformanceTests.dll; fi" after_script: # Waiting on mono-coverage, SharpCover or xr.Baboon diff --git a/ci/appveyor_run_tests.ps1 b/ci/appveyor_run_tests.ps1 index b45440fbe..f94cfb11e 100644 --- a/ci/appveyor_run_tests.ps1 +++ b/ci/appveyor_run_tests.ps1 @@ -3,6 +3,8 @@ # Test Runner framework being used for embedded tests $CS_RUNNER = "nunit3-console" +$XPLAT = $env:BUILD_OPTS -eq "--xplat" + # Needed for ARCH specific runners(NUnit2/XUnit3). Skip for NUnit3 if ($FALSE -and $env:PLATFORM -eq "x86"){ $CS_RUNNER = $CS_RUNNER + "-x86" @@ -11,7 +13,7 @@ if ($FALSE -and $env:PLATFORM -eq "x86"){ # Executable paths for OpenCover # Note if OpenCover fails, it won't affect the exit codes. $OPENCOVER = Resolve-Path .\packages\OpenCover.*\tools\OpenCover.Console.exe -if ($env:BUILD_OPTS -eq "--xplat"){ +if ($XPLAT){ $CS_RUNNER = Resolve-Path $env:USERPROFILE\.nuget\packages\nunit.consolerunner\*\tools\"$CS_RUNNER".exe } else{ @@ -42,9 +44,31 @@ Write-Host ("Starting embedded tests") -ForegroundColor "Green" $CS_STATUS = $LastExitCode if ($CS_STATUS -ne 0) { Write-Host "Embedded tests failed" -ForegroundColor "Red" +} else { + # NuGet for pythonnet-2.3 only has 64-bit binary for Python 3.5 + # the test is only built using modern stack + if (($env:PLATFORM -eq "x64") -and ($XPLAT) -and ($env:PYTHON_VERSION -eq "3.5")) { + # Run C# Performance tests + Write-Host ("Starting performance tests") -ForegroundColor "Green" + if ($XPLAT) { + $CS_PERF_TESTS = ".\src\perf_tests\bin\net461\Python.PerformanceTests.dll" + } + else { + $CS_PERF_TESTS = ".\src\perf_tests\bin\Python.PerformanceTests.dll" + } + &"$CS_RUNNER" "$CS_PERF_TESTS" + $CS_PERF_STATUS = $LastExitCode + if ($CS_PERF_STATUS -ne 0) { + Write-Host "Performance tests (C#) failed" -ForegroundColor "Red" + } + } else { + Write-Host ("Skipping performance tests for ", $env:PYTHON_VERSION) -ForegroundColor "Yellow" + Write-Host ("on platform ", $env:PLATFORM, " xplat: ", $XPLAT) -ForegroundColor "Yellow" + $CS_PERF_STATUS = 0 + } } -if ($env:BUILD_OPTS -eq "--xplat"){ +if ($XPLAT){ if ($env:PLATFORM -eq "x64") { $DOTNET_CMD = "dotnet" } @@ -54,7 +78,7 @@ if ($env:BUILD_OPTS -eq "--xplat"){ # Run Embedded tests for netcoreapp2.0 (OpenCover currently does not supports dotnet core) Write-Host ("Starting embedded tests for netcoreapp2.0") -ForegroundColor "Green" - &$DOTNET_CMD .\src\embed_tests\bin\netcoreapp2.0_publish\Python.EmbeddingTest.dll + &$DOTNET_CMD ".\src\embed_tests\bin\netcoreapp2.0_publish\Python.EmbeddingTest.dll" $CS_STATUS = $LastExitCode if ($CS_STATUS -ne 0) { Write-Host "Embedded tests for netcoreapp2.0 failed" -ForegroundColor "Red" @@ -62,7 +86,7 @@ if ($env:BUILD_OPTS -eq "--xplat"){ } # Set exit code to fail if either Python or Embedded tests failed -if ($PYTHON_STATUS -ne 0 -or $CS_STATUS -ne 0) { +if ($PYTHON_STATUS -ne 0 -or $CS_STATUS -ne 0 -or $CS_PERF_STATUS -ne 0) { Write-Host "Tests failed" -ForegroundColor "Red" $host.SetShouldExit(1) } diff --git a/pythonnet.15.sln b/pythonnet.15.sln index 096dfbe9a..6d1f4fcd9 100644 --- a/pythonnet.15.sln +++ b/pythonnet.15.sln @@ -19,6 +19,26 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repo", "Repo", "{441A0123-F .editorconfig = .editorconfig EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CI", "CI", "{D301657F-5EAF-4534-B280-B858D651B2E5}" + ProjectSection(SolutionItems) = preProject + .travis.yml = .travis.yml + appveyor.yml = appveyor.yml + ci\appveyor_build_recipe.ps1 = ci\appveyor_build_recipe.ps1 + ci\appveyor_run_tests.ps1 = ci\appveyor_run_tests.ps1 + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{57F5D701-F265-4736-A5A2-07249E7A4DA3}" + ProjectSection(SolutionItems) = preProject + setup.py = setup.py + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "conda.recipe", "conda.recipe", "{7FD2404D-0CE8-4645-8DFB-766470E2150E}" + ProjectSection(SolutionItems) = preProject + conda.recipe\bld.bat = conda.recipe\bld.bat + conda.recipe\meta.yaml = conda.recipe\meta.yaml + conda.recipe\README.md = conda.recipe\README.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -313,54 +333,58 @@ Global {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseWinPY3|x64.Build.0 = ReleaseWinPY3|x64 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseWinPY3|x86.ActiveCfg = ReleaseWinPY3|x86 {F94B547A-E97E-4500-8D53-B4D64D076E5F}.ReleaseWinPY3|x86.Build.0 = ReleaseWinPY3|x86 - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|Any CPU.ActiveCfg = DebugMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|Any CPU.Build.0 = DebugMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|x64.ActiveCfg = DebugMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|x64.Build.0 = DebugMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|x86.ActiveCfg = DebugMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|x86.Build.0 = DebugMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMono|Any CPU.ActiveCfg = DebugMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMono|x64.ActiveCfg = DebugMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMono|x86.ActiveCfg = DebugMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMonoPY3|Any CPU.ActiveCfg = DebugMonoPY3|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMonoPY3|x64.ActiveCfg = DebugMonoPY3|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMonoPY3|x86.ActiveCfg = DebugMonoPY3|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|Any CPU.ActiveCfg = DebugWin|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|Any CPU.Build.0 = DebugWin|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|x64.ActiveCfg = DebugWin|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|x64.Build.0 = DebugWin|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|x86.ActiveCfg = DebugWin|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|x86.Build.0 = DebugWin|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWinPY3|Any CPU.ActiveCfg = DebugWinPY3|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWinPY3|Any CPU.Build.0 = DebugWinPY3|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWinPY3|x64.ActiveCfg = DebugWinPY3|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWinPY3|x64.Build.0 = DebugWinPY3|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWinPY3|x86.ActiveCfg = DebugWinPY3|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWinPY3|x86.Build.0 = DebugWinPY3|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|Any CPU.ActiveCfg = ReleaseMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|Any CPU.Build.0 = ReleaseMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|x64.ActiveCfg = ReleaseMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|x64.Build.0 = ReleaseMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|x86.ActiveCfg = ReleaseMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|x86.Build.0 = ReleaseMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMono|Any CPU.ActiveCfg = ReleaseMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMono|x64.ActiveCfg = ReleaseMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMono|x86.ActiveCfg = ReleaseMono|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMonoPY3|Any CPU.ActiveCfg = ReleaseMonoPY3|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMonoPY3|x64.ActiveCfg = ReleaseMonoPY3|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMonoPY3|x86.ActiveCfg = ReleaseMonoPY3|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|Any CPU.ActiveCfg = ReleaseWin|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|Any CPU.Build.0 = ReleaseWin|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|x64.ActiveCfg = ReleaseWin|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|x64.Build.0 = ReleaseWin|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|x86.ActiveCfg = ReleaseWin|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|x86.Build.0 = ReleaseWin|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|Any CPU.ActiveCfg = ReleaseWinPY3|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|Any CPU.Build.0 = ReleaseWinPY3|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x64.ActiveCfg = ReleaseWinPY3|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x64.Build.0 = ReleaseWinPY3|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x86.ActiveCfg = ReleaseWinPY3|Any CPU - {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x86.Build.0 = ReleaseWinPY3|Any CPU + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|Any CPU.ActiveCfg = ReleaseWin|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|Any CPU.Build.0 = ReleaseWin|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|x64.ActiveCfg = DebugWinPY3|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|x64.Build.0 = DebugWinPY3|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|x86.ActiveCfg = DebugWinPY3|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Debug|x86.Build.0 = DebugWinPY3|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMono|Any CPU.ActiveCfg = DebugMono|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMono|x64.ActiveCfg = DebugMono|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMono|x64.Build.0 = DebugMono|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMono|x86.ActiveCfg = DebugMono|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMono|x86.Build.0 = DebugMono|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMonoPY3|Any CPU.ActiveCfg = DebugMonoPY3|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMonoPY3|x64.ActiveCfg = DebugMonoPY3|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMonoPY3|x64.Build.0 = DebugMonoPY3|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMonoPY3|x86.ActiveCfg = DebugMonoPY3|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugMonoPY3|x86.Build.0 = DebugMonoPY3|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|Any CPU.ActiveCfg = DebugWin|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|x64.ActiveCfg = DebugWin|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|x64.Build.0 = DebugWin|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|x86.ActiveCfg = DebugWin|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWin|x86.Build.0 = DebugWin|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWinPY3|Any CPU.ActiveCfg = DebugWinPY3|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWinPY3|x64.ActiveCfg = DebugWinPY3|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWinPY3|x64.Build.0 = DebugWinPY3|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWinPY3|x86.ActiveCfg = DebugWinPY3|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.DebugWinPY3|x86.Build.0 = DebugWinPY3|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|Any CPU.ActiveCfg = ReleaseWin|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|Any CPU.Build.0 = ReleaseWin|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|x64.ActiveCfg = ReleaseWinPY3|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|x64.Build.0 = ReleaseWinPY3|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|x86.ActiveCfg = ReleaseWinPY3|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.Release|x86.Build.0 = ReleaseWinPY3|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMono|Any CPU.ActiveCfg = ReleaseMono|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMono|x64.ActiveCfg = ReleaseMono|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMono|x64.Build.0 = ReleaseMono|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMono|x86.ActiveCfg = ReleaseMono|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMono|x86.Build.0 = ReleaseMono|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMonoPY3|Any CPU.ActiveCfg = ReleaseMonoPY3|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMonoPY3|x64.ActiveCfg = ReleaseMonoPY3|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMonoPY3|x64.Build.0 = ReleaseMonoPY3|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMonoPY3|x86.ActiveCfg = ReleaseMonoPY3|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseMonoPY3|x86.Build.0 = ReleaseMonoPY3|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|Any CPU.ActiveCfg = ReleaseWin|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|x64.ActiveCfg = ReleaseWin|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|x64.Build.0 = ReleaseWin|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|x86.ActiveCfg = ReleaseWin|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWin|x86.Build.0 = ReleaseWin|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|Any CPU.ActiveCfg = ReleaseWinPY3|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x64.ActiveCfg = ReleaseWinPY3|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x64.Build.0 = ReleaseWinPY3|x64 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x86.ActiveCfg = ReleaseWinPY3|x86 + {6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x86.Build.0 = ReleaseWinPY3|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/setup.py b/setup.py index c6e4007e6..db2b4ae68 100644 --- a/setup.py +++ b/setup.py @@ -362,6 +362,16 @@ def build_extension(self, ext): ), shell=use_shell, ) + subprocess.check_call( + " ".join( + cmd + + [ + '"/t:Python_PerformanceTests:publish"', + "/p:TargetFramework=net461", + ] + ), + shell=use_shell, + ) if DEVTOOLS == "Mono" or DEVTOOLS == "dotnet": self._build_monoclr() diff --git a/src/perf_tests/BenchmarkTests.cs b/src/perf_tests/BenchmarkTests.cs index 12ba6c900..baa825ca8 100644 --- a/src/perf_tests/BenchmarkTests.cs +++ b/src/perf_tests/BenchmarkTests.cs @@ -21,21 +21,23 @@ public void SetUp() Environment.CurrentDirectory = Path.Combine(DeploymentRoot, "new"); this.summary = BenchmarkRunner.Run(); Assert.IsNotEmpty(this.summary.Reports); - Assert.IsTrue(this.summary.Reports.All(r => r.Success)); + Assert.IsTrue( + condition: this.summary.Reports.All(r => r.Success), + message: "BenchmarkDotNet failed to execute or collect results of performance tests. See logs above."); } [Test] public void ReadInt64Property() { double optimisticPerfRatio = GetOptimisticPerfRatio(this.summary.Reports); - Assert.LessOrEqual(optimisticPerfRatio, 0.68); + AssertPerformanceIsBetterOrSame(optimisticPerfRatio, target: 0.66); } [Test] public void WriteInt64Property() { double optimisticPerfRatio = GetOptimisticPerfRatio(this.summary.Reports); - Assert.LessOrEqual(optimisticPerfRatio, 0.66); + AssertPerformanceIsBetterOrSame(optimisticPerfRatio, target: 0.64); } static double GetOptimisticPerfRatio( @@ -59,5 +61,14 @@ static double GetOptimisticPerfRatio( } public static string DeploymentRoot => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + public static void AssertPerformanceIsBetterOrSame( + double actual, double target, + double wiggleRoom = 1.1, [CallerMemberName] string testName = null) { + double threshold = target * wiggleRoom; + Assert.LessOrEqual(actual, threshold, + $"{testName}: {actual:F3} > {threshold:F3} (target: {target:F3})" + + ": perf result is higher than the failure threshold."); + } } } diff --git a/src/perf_tests/Python.PerformanceTests.csproj b/src/perf_tests/Python.PerformanceTests.csproj index 33949fdc1..1231cef69 100644 --- a/src/perf_tests/Python.PerformanceTests.csproj +++ b/src/perf_tests/Python.PerformanceTests.csproj @@ -1,14 +1,21 @@ - + net461 DebugMono;DebugMonoPY3;ReleaseMono;ReleaseMonoPY3;DebugWin;DebugWinPY3;ReleaseWin;ReleaseWinPY3 + bin\ false + + x64;x86 + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From 8ae5cf49fb075deda0c30bc59ea03ffdbaeda3d3 Mon Sep 17 00:00:00 2001 From: Andrey Santanna Date: Thu, 20 Feb 2020 12:03:35 -0300 Subject: [PATCH 048/112] #1047 ModuleObject __getattribute__ doesn't treat exceptions ocurred during internal GetAttribute --- src/runtime/moduleobject.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/runtime/moduleobject.cs b/src/runtime/moduleobject.cs index 544f69c81..5e7059b53 100644 --- a/src/runtime/moduleobject.cs +++ b/src/runtime/moduleobject.cs @@ -280,7 +280,18 @@ public static IntPtr tp_getattro(IntPtr ob, IntPtr key) return self.dict; } - ManagedType attr = self.GetAttribute(name, true); + ManagedType attr = null; + + try + { + attr = self.GetAttribute(name, true); + } + catch (Exception e) + { + Exceptions.SetError(e); + return IntPtr.Zero; + } + if (attr == null) { From f25694e47f5ce796d631dc6037d56a3d46df581a Mon Sep 17 00:00:00 2001 From: Andrey Santanna Date: Thu, 20 Feb 2020 12:20:33 -0300 Subject: [PATCH 049/112] #1047 ModuleObject __getattribute__ doesn't treat exceptions ocurred during internal GetAttribute --- AUTHORS.md | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index 26285bf6a..b0d1f0c91 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -15,6 +15,7 @@ - Alex Earl ([@slide](https://github.com/slide)) - Alex Helms ([@alexhelms](https://github.com/alexhelms)) - Alexandre Catarino([@AlexCatarino](https://github.com/AlexCatarino)) +- Andrey Sant'Anna ([@andreydani](https://github.com/andreydani)) - Arvid JB ([@ArvidJB](https://github.com/ArvidJB)) - Benoît Hudson ([@benoithudson](https://github.com/benoithudson)) - Bradley Friedman ([@leith-bartrich](https://github.com/leith-bartrich)) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fd2b1dcf..82fccbe35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. together with Nuitka - Fixes bug where delegates get casts (dotnetcore) - Determine size of interpreter longs at runtime +- Handling exceptions ocurred in ModuleObject's getattribute ## [2.4.0][] From 9e3ed3ae69c54dc26e9e7a15c5c9026ccfa7dfce Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 20 Feb 2020 12:33:03 -0800 Subject: [PATCH 050/112] report AppVeyor build timings --- ci/appveyor_build_recipe.ps1 | 6 +++++- ci/appveyor_run_tests.ps1 | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/ci/appveyor_build_recipe.ps1 b/ci/appveyor_build_recipe.ps1 index 84e0bc7c6..08eae8d5d 100644 --- a/ci/appveyor_build_recipe.ps1 +++ b/ci/appveyor_build_recipe.ps1 @@ -1,5 +1,7 @@ # Build `conda.recipe` only if this is a Pull_Request. Saves time for CI. +$stopwatch = [Diagnostics.Stopwatch]::StartNew() + $env:CONDA_PY = "$env:PY_VER" # Use pre-installed miniconda. Note that location differs if 64bit $env:CONDA_BLD = "C:\miniconda36" @@ -30,7 +32,9 @@ if ($env:APPVEYOR_PULL_REQUEST_NUMBER -or $env:APPVEYOR_REPO_TAG_NAME -or $env:F $CONDA_PKG=(conda build conda.recipe --output) Copy-Item $CONDA_PKG .\dist\ - Write-Host "Completed conda build recipe" -ForegroundColor "Green" + + $timeSpent = $stopwatch.Elapsed + Write-Host "Completed conda build recipe in " $timeSpent -ForegroundColor "Green" # Restore PATH back to original $env:path = $old_path diff --git a/ci/appveyor_run_tests.ps1 b/ci/appveyor_run_tests.ps1 index f94cfb11e..7bd632b19 100644 --- a/ci/appveyor_run_tests.ps1 +++ b/ci/appveyor_run_tests.ps1 @@ -1,5 +1,8 @@ # Script to simplify AppVeyor configuration and resolve path to tools +$stopwatch = [Diagnostics.Stopwatch]::StartNew() +[array]$timings = @() + # Test Runner framework being used for embedded tests $CS_RUNNER = "nunit3-console" @@ -25,6 +28,17 @@ $PY = Get-Command python $CS_TESTS = ".\src\embed_tests\bin\Python.EmbeddingTest.dll" $RUNTIME_DIR = ".\src\runtime\bin\" +function ReportTime { + param([string] $action) + + $timeSpent = $stopwatch.Elapsed + $timings += [pscustomobject]@{action=$action; timeSpent=$timeSpent} + Write-Host $action " in " $timeSpent -ForegroundColor "Green" + $stopwatch.Restart() +} + +ReportTime "Preparation done" + # Run python tests with C# coverage Write-Host ("Starting Python tests") -ForegroundColor "Green" .$OPENCOVER -register:user -searchdirs:"$RUNTIME_DIR" -output:py.coverage ` @@ -33,6 +47,9 @@ Write-Host ("Starting Python tests") -ForegroundColor "Green" $PYTHON_STATUS = $LastExitCode if ($PYTHON_STATUS -ne 0) { Write-Host "Python tests failed, continuing to embedded tests" -ForegroundColor "Red" + ReportTime "" +} else { + ReportTime "Python tests completed" } # Run Embedded tests with C# coverage @@ -44,7 +61,10 @@ Write-Host ("Starting embedded tests") -ForegroundColor "Green" $CS_STATUS = $LastExitCode if ($CS_STATUS -ne 0) { Write-Host "Embedded tests failed" -ForegroundColor "Red" + ReportTime "" } else { + ReportTime "Embedded tests completed" + # NuGet for pythonnet-2.3 only has 64-bit binary for Python 3.5 # the test is only built using modern stack if (($env:PLATFORM -eq "x64") -and ($XPLAT) -and ($env:PYTHON_VERSION -eq "3.5")) { @@ -60,6 +80,9 @@ if ($CS_STATUS -ne 0) { $CS_PERF_STATUS = $LastExitCode if ($CS_PERF_STATUS -ne 0) { Write-Host "Performance tests (C#) failed" -ForegroundColor "Red" + ReportTime "" + } else { + ReportTime "Performance tests (C#) completed" } } else { Write-Host ("Skipping performance tests for ", $env:PYTHON_VERSION) -ForegroundColor "Yellow" @@ -82,9 +105,14 @@ if ($XPLAT){ $CS_STATUS = $LastExitCode if ($CS_STATUS -ne 0) { Write-Host "Embedded tests for netcoreapp2.0 failed" -ForegroundColor "Red" + ReportTime "" + } else { + ReportTime ".NET Core 2.0 tests completed" } } +Write-Host ($timings | Format-Table | Out-String) + # Set exit code to fail if either Python or Embedded tests failed if ($PYTHON_STATUS -ne 0 -or $CS_STATUS -ne 0 -or $CS_PERF_STATUS -ne 0) { Write-Host "Tests failed" -ForegroundColor "Red" From 103fa099015786b291bd8b009f6516099385bc2d Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 20 Feb 2020 14:40:29 -0800 Subject: [PATCH 051/112] reduced number of iterations in performance tests to fit into AppVeyor time limit --- src/perf_tests/PythonCallingNetBenchmark.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/perf_tests/PythonCallingNetBenchmark.cs b/src/perf_tests/PythonCallingNetBenchmark.cs index 4e9461d2e..ef668a911 100644 --- a/src/perf_tests/PythonCallingNetBenchmark.cs +++ b/src/perf_tests/PythonCallingNetBenchmark.cs @@ -19,7 +19,7 @@ public void ReadInt64Property() locals.SetItem("a", new NetObject().ToPython()); PythonEngine.Exec($@" s = 0 -for i in range(300000): +for i in range(50000): s += a.{nameof(NetObject.LongProperty)} ", locals: locals.Handle); } @@ -32,7 +32,7 @@ public void WriteInt64Property() { locals.SetItem("a", new NetObject().ToPython()); PythonEngine.Exec($@" s = 0 -for i in range(300000): +for i in range(50000): a.{nameof(NetObject.LongProperty)} += i ", locals: locals.Handle); } From c4bbc8cc1e3683f4a8df5ad18dcfcfa6717b5314 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 20 Feb 2020 15:40:20 -0800 Subject: [PATCH 052/112] make timings output more prominent --- ci/appveyor_run_tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/appveyor_run_tests.ps1 b/ci/appveyor_run_tests.ps1 index 7bd632b19..cb1c68eed 100644 --- a/ci/appveyor_run_tests.ps1 +++ b/ci/appveyor_run_tests.ps1 @@ -111,7 +111,7 @@ if ($XPLAT){ } } -Write-Host ($timings | Format-Table | Out-String) +Write-Host "Timings:" ($timings | Format-Table | Out-String) -ForegroundColor "Green" # Set exit code to fail if either Python or Embedded tests failed if ($PYTHON_STATUS -ne 0 -or $CS_STATUS -ne 0 -or $CS_PERF_STATUS -ne 0) { From ec982097e21ff7bddf8e78aa8b3d9b7662f8f70a Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 20 Feb 2020 23:25:31 -0800 Subject: [PATCH 053/112] fixed bad IncRef after PyTuple_New --- src/runtime/Codecs/TupleCodecs.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/runtime/Codecs/TupleCodecs.cs b/src/runtime/Codecs/TupleCodecs.cs index f6bcc3fc9..51c08cd3d 100644 --- a/src/runtime/Codecs/TupleCodecs.cs +++ b/src/runtime/Codecs/TupleCodecs.cs @@ -12,8 +12,12 @@ public sealed class TupleCodec : IPyObjectEncoder, IPyObjectDecoder public static TupleCodec Instance { get; } = new TupleCodec(); public bool CanEncode(Type type) - => type.Namespace == typeof(TTuple).Namespace && type.Name.StartsWith(typeof(TTuple).Name + '`') - || type == typeof(object) || type == typeof(TTuple); + { + if (type == typeof(object) || type == typeof(TTuple)) return true; + return type.Namespace == typeof(TTuple).Namespace + // generic versions of tuples are named Tuple`TYPE_ARG_COUNT + && type.Name.StartsWith(typeof(TTuple).Name + '`'); + } public PyObject TryEncode(object value) { @@ -36,7 +40,7 @@ public PyObject TryEncode(object value) Runtime.PyTuple_SetItem(tuple, fieldIndex, pyItem); fieldIndex++; } - return new PyTuple(Runtime.SelfIncRef(tuple)); + return new PyTuple(tuple); } public bool CanDecode(PyObject objectType, Type targetType) From 50a3822bf49f6e99a0b96a78b89af26fd32e0fcc Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 20 Feb 2020 23:31:54 -0800 Subject: [PATCH 054/112] corrected reference counting in Codecs --- src/runtime/converter.cs | 6 ++++-- src/runtime/converterextensions.cs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index 85f4fecb5..a56fa88bb 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -138,8 +138,10 @@ internal static IntPtr ToPython(object value, Type type) if (Type.GetTypeCode(type) == TypeCode.Object && value.GetType() != typeof(object)) { var encoded = PyObjectConversions.TryEncode(value, type); if (encoded != null) { - Runtime.XIncref(encoded.Handle); - return encoded.Handle; + result = encoded.Handle; + Runtime.XIncref(result); + encoded.Dispose(); + return result; } } diff --git a/src/runtime/converterextensions.cs b/src/runtime/converterextensions.cs index 0d7f0aff2..ce8ec2679 100644 --- a/src/runtime/converterextensions.cs +++ b/src/runtime/converterextensions.cs @@ -124,7 +124,7 @@ internal static bool TryDecode(IntPtr pyHandle, IntPtr pyType, Type targetType, static Converter.TryConvertFromPythonDelegate GetDecoder(IntPtr sourceType, Type targetType) { IPyObjectDecoder decoder; - using (var pyType = new PyObject(sourceType)) + using (var pyType = new PyObject(Runtime.SelfIncRef(sourceType))) { lock (decoders) { From 2e19f2c3d3a40502968a77149f4a62a590fdb9a3 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Fri, 21 Feb 2020 09:29:59 -0800 Subject: [PATCH 055/112] don't dispose encoded object in case codec keeps a cache of them --- src/runtime/converter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index a56fa88bb..7c53bdcb1 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -140,7 +140,6 @@ internal static IntPtr ToPython(object value, Type type) if (encoded != null) { result = encoded.Handle; Runtime.XIncref(result); - encoded.Dispose(); return result; } } From 703439030a8e79f0d382b8a96f88ac7f72f16d8a Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Fri, 21 Feb 2020 16:44:49 -0800 Subject: [PATCH 056/112] limit benchmark time in config --- src/perf_tests/BaselineComparisonConfig.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/perf_tests/BaselineComparisonConfig.cs b/src/perf_tests/BaselineComparisonConfig.cs index 06d529ff9..649bb56fd 100644 --- a/src/perf_tests/BaselineComparisonConfig.cs +++ b/src/perf_tests/BaselineComparisonConfig.cs @@ -5,6 +5,7 @@ using BenchmarkDotNet.Configs; using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Horology; namespace Python.PerformanceTests { @@ -18,7 +19,11 @@ public BaselineComparisonConfig() string deploymentRoot = BenchmarkTests.DeploymentRoot; - var baseJob = Job.Default; + var baseJob = Job.Default + .WithLaunchCount(1) + .WithWarmupCount(3) + .WithMaxIterationCount(100) + .WithIterationTime(TimeInterval.FromMilliseconds(100)); this.Add(baseJob .WithId("baseline") .WithEnvironmentVariable(EnvironmentVariableName, From a424998ec6d5452a79d8d0f035a18661666b8ac6 Mon Sep 17 00:00:00 2001 From: Victor Date: Sun, 23 Feb 2020 09:01:19 -0800 Subject: [PATCH 057/112] ensure Py handles are inaccessible after PythonException is disposed (#1055) --- src/runtime/pythonexception.cs | 17 ++++++++++++++--- src/runtime/runtime.cs | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/runtime/pythonexception.cs b/src/runtime/pythonexception.cs index 8a6a24799..7ac922abc 100644 --- a/src/runtime/pythonexception.cs +++ b/src/runtime/pythonexception.cs @@ -21,7 +21,7 @@ public class PythonException : System.Exception, IPyDisposable public PythonException() { IntPtr gs = PythonEngine.AcquireLock(); - Runtime.PyErr_Fetch(ref _pyType, ref _pyValue, ref _pyTB); + Runtime.PyErr_Fetch(out _pyType, out _pyValue, out _pyTB); if (_pyType != IntPtr.Zero && _pyValue != IntPtr.Zero) { string type; @@ -161,12 +161,23 @@ public void Dispose() if (Runtime.Py_IsInitialized() > 0 && !Runtime.IsFinalizing) { IntPtr gs = PythonEngine.AcquireLock(); - Runtime.XDecref(_pyType); - Runtime.XDecref(_pyValue); + if (_pyType != IntPtr.Zero) + { + Runtime.XDecref(_pyType); + _pyType= IntPtr.Zero; + } + + if (_pyValue != IntPtr.Zero) + { + Runtime.XDecref(_pyValue); + _pyValue = IntPtr.Zero; + } + // XXX Do we ever get TraceBack? // if (_pyTB != IntPtr.Zero) { Runtime.XDecref(_pyTB); + _pyTB = IntPtr.Zero; } PythonEngine.ReleaseLock(gs); } diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 7748bafa9..9c7cb42d2 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1933,7 +1933,7 @@ internal static IntPtr PyMem_Realloc(IntPtr ptr, long size) internal static extern IntPtr PyErr_Occurred(); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern void PyErr_Fetch(ref IntPtr ob, ref IntPtr val, ref IntPtr tb); + internal static extern void PyErr_Fetch(out IntPtr ob, out IntPtr val, out IntPtr tb); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyErr_Restore(IntPtr ob, IntPtr val, IntPtr tb); From 399ae545c9c6a4ab0a0f1713e41eb1c41c889ca4 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Sun, 23 Feb 2020 15:10:22 -0800 Subject: [PATCH 058/112] do not dispose object, that might have been just decoded succesfully, as the decoded object might store a reference to the original PyObject --- src/runtime/converterextensions.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/runtime/converterextensions.cs b/src/runtime/converterextensions.cs index ce8ec2679..fd012c6e4 100644 --- a/src/runtime/converterextensions.cs +++ b/src/runtime/converterextensions.cs @@ -137,13 +137,16 @@ static Converter.TryConvertFromPythonDelegate GetDecoder(IntPtr sourceType, Type bool TryDecode(IntPtr pyHandle, out object result) { - using (var pyObj = new PyObject(Runtime.SelfIncRef(pyHandle))) + var pyObj = new PyObject(Runtime.SelfIncRef(pyHandle)); + var @params = new object[] { pyObj, null }; + bool success = (bool)decode.Invoke(decoder, @params); + if (!success) { - var @params = new object[] { pyObj, null }; - bool success = (bool)decode.Invoke(decoder, @params); - result = @params[1]; - return success; + pyObj.Dispose(); } + + result = @params[1]; + return success; } return TryDecode; From e2d333361c036243cec3c9aecab514d3888d8efa Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Sun, 23 Feb 2020 15:11:40 -0800 Subject: [PATCH 059/112] remove incref for tuple fields, as Converter.ToPython is supposed to return a new reference --- src/runtime/Codecs/TupleCodecs.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/runtime/Codecs/TupleCodecs.cs b/src/runtime/Codecs/TupleCodecs.cs index 51c08cd3d..4c81cac0b 100644 --- a/src/runtime/Codecs/TupleCodecs.cs +++ b/src/runtime/Codecs/TupleCodecs.cs @@ -36,7 +36,6 @@ public PyObject TryEncode(object value) { var item = field.GetValue(value); IntPtr pyItem = Converter.ToPython(item); - Runtime.XIncref(pyItem); Runtime.PyTuple_SetItem(tuple, fieldIndex, pyItem); fieldIndex++; } From 22735f864d5c4c9ec450fe88caa5da238ea655a3 Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 25 Feb 2020 02:14:41 -0800 Subject: [PATCH 060/112] Update test dependencies to the latest stable versions (#1054) * update test dependencies to the latest stable versions * print C# embed test names as they run in CI --- .travis.yml | 2 +- ci/appveyor_run_tests.ps1 | 2 +- src/embed_tests/Python.EmbeddingTest.15.csproj | 13 ++++++++----- src/embed_tests/Python.EmbeddingTest.csproj | 10 ++++++++-- src/embed_tests/packages.config | 4 ++-- src/perf_tests/Python.PerformanceTests.csproj | 9 ++++++--- 6 files changed, 26 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index c69ccbc5d..d728d9a2d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ install: script: - python -m pytest - - $RUN_TESTS src/embed_tests/bin/$EMBED_TESTS_PATH/Python.EmbeddingTest.dll + - $RUN_TESTS src/embed_tests/bin/$EMBED_TESTS_PATH/Python.EmbeddingTest.dll --labels=All # does not work on Linux, because NuGet package for 2.3 is Windows only # - "if [[ $TRAVIS_PYTHON_VERSION == '3.5' && $PERF_TESTS_PATH != '' ]]; then mono $NUNIT_PATH src/perf_tests/bin/$PERF_TESTS_PATH/Python.PerformanceTests.dll; fi" diff --git a/ci/appveyor_run_tests.ps1 b/ci/appveyor_run_tests.ps1 index cb1c68eed..bd90943d5 100644 --- a/ci/appveyor_run_tests.ps1 +++ b/ci/appveyor_run_tests.ps1 @@ -55,7 +55,7 @@ if ($PYTHON_STATUS -ne 0) { # Run Embedded tests with C# coverage Write-Host ("Starting embedded tests") -ForegroundColor "Green" .$OPENCOVER -register:user -searchdirs:"$RUNTIME_DIR" -output:cs.coverage ` - -target:"$CS_RUNNER" -targetargs:"$CS_TESTS" ` + -target:"$CS_RUNNER" -targetargs:"$CS_TESTS --labels=All" ` -filter:"+[*]Python.Runtime*" ` -returntargetcode $CS_STATUS = $LastExitCode diff --git a/src/embed_tests/Python.EmbeddingTest.15.csproj b/src/embed_tests/Python.EmbeddingTest.15.csproj index 4f6b2de46..126bc5b63 100644 --- a/src/embed_tests/Python.EmbeddingTest.15.csproj +++ b/src/embed_tests/Python.EmbeddingTest.15.csproj @@ -77,13 +77,16 @@ - - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - + diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index faa55fa27..ffdbcbabe 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -70,8 +70,8 @@ - - ..\..\packages\NUnit.3.7.1\lib\net40\nunit.framework.dll + + ..\..\packages\NUnit.3.12.0\lib\net40\nunit.framework.dll @@ -126,4 +126,10 @@ + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + diff --git a/src/embed_tests/packages.config b/src/embed_tests/packages.config index 8c175f441..7a3fb37c4 100644 --- a/src/embed_tests/packages.config +++ b/src/embed_tests/packages.config @@ -1,5 +1,5 @@ - - + + diff --git a/src/perf_tests/Python.PerformanceTests.csproj b/src/perf_tests/Python.PerformanceTests.csproj index 1231cef69..25af89db0 100644 --- a/src/perf_tests/Python.PerformanceTests.csproj +++ b/src/perf_tests/Python.PerformanceTests.csproj @@ -11,14 +11,17 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + compile From 6c0324525f9642c200d3d013d253e10d2ce0e44d Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 26 Feb 2020 02:47:24 -0800 Subject: [PATCH 061/112] fixed reference counting for exception objects in Py.With (#1062) PyObject(s) constructed for __exit__ method referenced existing Python objects without increasing refcount appropriately, which could lead to double-free. --- src/runtime/Util.cs | 11 +++++++++-- src/runtime/pythonengine.cs | 9 ++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/runtime/Util.cs b/src/runtime/Util.cs index dc5f78608..29a5170ab 100644 --- a/src/runtime/Util.cs +++ b/src/runtime/Util.cs @@ -3,7 +3,7 @@ namespace Python.Runtime { - internal class Util + internal static class Util { internal static Int64 ReadCLong(IntPtr tp, int offset) { @@ -29,5 +29,12 @@ internal static void WriteCLong(IntPtr type, int offset, Int64 flags) Marshal.WriteInt64(type, offset, flags); } } + + /// + /// Null-coalesce: if parameter is not + /// , return it. Otherwise return . + /// + internal static IntPtr Coalesce(this IntPtr primary, IntPtr fallback) + => primary == IntPtr.Zero ? fallback : primary; } -} \ No newline at end of file +} diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index aec2a412e..a2da04af3 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -758,11 +758,14 @@ public static void With(PyObject obj, Action Body) catch (PythonException e) { ex = e; - type = ex.PyType; - val = ex.PyValue; - traceBack = ex.PyTB; + type = ex.PyType.Coalesce(type); + val = ex.PyValue.Coalesce(val); + traceBack = ex.PyTB.Coalesce(traceBack); } + Runtime.XIncref(type); + Runtime.XIncref(val); + Runtime.XIncref(traceBack); var exitResult = obj.InvokeMethod("__exit__", new PyObject(type), new PyObject(val), new PyObject(traceBack)); if (ex != null && !exitResult.IsTrue()) throw ex; From 4271e57967c75850c47f962a04bd036e987fee34 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 26 Feb 2020 02:48:17 -0800 Subject: [PATCH 062/112] added checks to PyCheck_Iter_PyObject_IsIterable_ThreadingLock_Test in attempt to replace segfault with a more meaningful exception (#1061) related to https://github.com/pythonnet/pythonnet/issues/1060 --- src/embed_tests/TestRuntime.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/embed_tests/TestRuntime.cs b/src/embed_tests/TestRuntime.cs index 25b70fac5..157fe4cb7 100644 --- a/src/embed_tests/TestRuntime.cs +++ b/src/embed_tests/TestRuntime.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using NUnit.Framework; using Python.Runtime; using Python.Runtime.Platform; @@ -110,9 +111,15 @@ public static void PyCheck_Iter_PyObject_IsIterable_ThreadingLock_Test() // Create an instance of threading.Lock, which is one of the very few types that does not have the // TypeFlags.HaveIter set in Python 2. This tests a different code path in PyObject_IsIterable and PyIter_Check. var threading = Runtime.Runtime.PyImport_ImportModule("threading"); + Exceptions.ErrorCheck(threading); var threadingDict = Runtime.Runtime.PyModule_GetDict(threading); + Exceptions.ErrorCheck(threadingDict); var lockType = Runtime.Runtime.PyDict_GetItemString(threadingDict, "Lock"); + if (lockType == IntPtr.Zero) + throw new KeyNotFoundException("class 'Lock' was not found in 'threading'"); + var lockInstance = Runtime.Runtime.PyObject_CallObject(lockType, Runtime.Runtime.PyTuple_New(0)); + Exceptions.ErrorCheck(lockInstance); Assert.IsFalse(Runtime.Runtime.PyObject_IsIterable(lockInstance)); Assert.IsFalse(Runtime.Runtime.PyIter_Check(lockInstance)); From 770fc01efc40cca298cf833a7556180c922c119b Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 26 Feb 2020 03:21:20 -0800 Subject: [PATCH 063/112] Safe pointers (#1043) * NewReference type and an example usage * BorrowedReference + example, that exposes dangerous pattern * make BorrowedReference readonly ref struct * BorrowedReference.Pointer is a private readonly field * renamed NewReference.ToPyObject to MoveToPyObject * removed public property Pointer from NewReference and replaced with DangerousGetAddress --- src/runtime/BorrowedReference.cs | 22 ++++++++++++++++ src/runtime/NewReference.cs | 39 ++++++++++++++++++++++++++++ src/runtime/NonCopyableAttribute.cs | 6 +++++ src/runtime/Python.Runtime.15.csproj | 7 +++++ src/runtime/Python.Runtime.csproj | 3 +++ src/runtime/assemblymanager.cs | 4 +-- src/runtime/methodbinder.cs | 2 +- src/runtime/pydict.cs | 16 +++++++++--- src/runtime/runtime.cs | 8 +++--- 9 files changed, 97 insertions(+), 10 deletions(-) create mode 100644 src/runtime/BorrowedReference.cs create mode 100644 src/runtime/NewReference.cs create mode 100644 src/runtime/NonCopyableAttribute.cs diff --git a/src/runtime/BorrowedReference.cs b/src/runtime/BorrowedReference.cs new file mode 100644 index 000000000..7dbc7a811 --- /dev/null +++ b/src/runtime/BorrowedReference.cs @@ -0,0 +1,22 @@ +namespace Python.Runtime +{ + using System; + /// + /// Represents a reference to a Python object, that is being lent, and + /// can only be safely used until execution returns to the caller. + /// + readonly ref struct BorrowedReference + { + readonly IntPtr pointer; + public bool IsNull => this.pointer == IntPtr.Zero; + + /// Gets a raw pointer to the Python object + public IntPtr DangerousGetAddress() + => this.IsNull ? throw new NullReferenceException() : this.pointer; + + BorrowedReference(IntPtr pointer) + { + this.pointer = pointer; + } + } +} diff --git a/src/runtime/NewReference.cs b/src/runtime/NewReference.cs new file mode 100644 index 000000000..3b45f821f --- /dev/null +++ b/src/runtime/NewReference.cs @@ -0,0 +1,39 @@ +namespace Python.Runtime +{ + using System; + /// + /// Represents a reference to a Python object, that is tracked by Python's reference counting. + /// + [NonCopyable] + ref struct NewReference + { + IntPtr pointer; + public bool IsNull => this.pointer == IntPtr.Zero; + + /// Gets a raw pointer to the Python object + public IntPtr DangerousGetAddress() + => this.IsNull ? throw new NullReferenceException() : this.pointer; + + /// + /// Returns wrapper around this reference, which now owns + /// the pointer. Sets the original reference to null, as it no longer owns it. + /// + public PyObject MoveToPyObject() + { + if (this.IsNull) throw new NullReferenceException(); + + var result = new PyObject(this.pointer); + this.pointer = IntPtr.Zero; + return result; + } + /// + /// Removes this reference to a Python object, and sets it to null. + /// + public void Dispose() + { + if (!this.IsNull) + Runtime.XDecref(this.pointer); + this.pointer = IntPtr.Zero; + } + } +} diff --git a/src/runtime/NonCopyableAttribute.cs b/src/runtime/NonCopyableAttribute.cs new file mode 100644 index 000000000..63d36ab42 --- /dev/null +++ b/src/runtime/NonCopyableAttribute.cs @@ -0,0 +1,6 @@ +namespace Python.Runtime +{ + using System; + [AttributeUsage(AttributeTargets.Struct)] + class NonCopyableAttribute : Attribute { } +} diff --git a/src/runtime/Python.Runtime.15.csproj b/src/runtime/Python.Runtime.15.csproj index c31d4bf91..fd4f3416a 100644 --- a/src/runtime/Python.Runtime.15.csproj +++ b/src/runtime/Python.Runtime.15.csproj @@ -129,6 +129,13 @@ + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 0c2f912de..c79afee3e 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -83,6 +83,7 @@ + @@ -119,6 +120,8 @@ + + diff --git a/src/runtime/assemblymanager.cs b/src/runtime/assemblymanager.cs index 3085bb639..9d0296d47 100644 --- a/src/runtime/assemblymanager.cs +++ b/src/runtime/assemblymanager.cs @@ -145,7 +145,7 @@ internal static void UpdatePath() probed.Clear(); for (var i = 0; i < count; i++) { - IntPtr item = Runtime.PyList_GetItem(list, i); + BorrowedReference item = Runtime.PyList_GetItem(list, i); string path = Runtime.GetManagedString(item); if (path != null) { @@ -492,4 +492,4 @@ internal static Type[] GetTypes(Assembly a) } } } -} \ No newline at end of file +} diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs index 8a7fc1930..4e8698da1 100644 --- a/src/runtime/methodbinder.cs +++ b/src/runtime/methodbinder.cs @@ -292,7 +292,7 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth for (int i = 0; i < pynkwargs; ++i) { var keyStr = Runtime.GetManagedString(Runtime.PyList_GetItem(keylist, i)); - kwargDict[keyStr] = Runtime.PyList_GetItem(valueList, i); + kwargDict[keyStr] = Runtime.PyList_GetItem(valueList, i).DangerousGetAddress(); } Runtime.XDecref(keylist); Runtime.XDecref(valueList); diff --git a/src/runtime/pydict.cs b/src/runtime/pydict.cs index 7237d1990..b396f4f3d 100644 --- a/src/runtime/pydict.cs +++ b/src/runtime/pydict.cs @@ -139,12 +139,20 @@ public PyObject Values() /// public PyObject Items() { - IntPtr items = Runtime.PyDict_Items(obj); - if (items == IntPtr.Zero) + var items = Runtime.PyDict_Items(this.obj); + try { - throw new PythonException(); + if (items.IsNull) + { + throw new PythonException(); + } + + return items.MoveToPyObject(); + } + finally + { + items.Dispose(); } - return new PyObject(items); } diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 9c7cb42d2..6d75e4bef 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1509,6 +1509,8 @@ internal static IntPtr PyUnicode_FromString(string s) return PyUnicode_FromUnicode(s, s.Length); } + internal static string GetManagedString(in BorrowedReference borrowedReference) + => GetManagedString(borrowedReference.DangerousGetAddress()); /// /// Function to access the internal PyUnicode/PyString object and /// convert it to a managed string with the correct encoding. @@ -1591,7 +1593,7 @@ internal static bool PyDict_Check(IntPtr ob) internal static extern IntPtr PyDict_Values(IntPtr pointer); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr PyDict_Items(IntPtr pointer); + internal static extern NewReference PyDict_Items(IntPtr pointer); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyDict_Copy(IntPtr pointer); @@ -1631,13 +1633,13 @@ internal static IntPtr PyList_New(long size) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyList_AsTuple(IntPtr pointer); - internal static IntPtr PyList_GetItem(IntPtr pointer, long index) + internal static BorrowedReference PyList_GetItem(IntPtr pointer, long index) { return PyList_GetItem(pointer, new IntPtr(index)); } [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - private static extern IntPtr PyList_GetItem(IntPtr pointer, IntPtr index); + private static extern BorrowedReference PyList_GetItem(IntPtr pointer, IntPtr index); internal static int PyList_SetItem(IntPtr pointer, long index, IntPtr value) { From 8e108b4a09a4ce6a2a116162587d9587cb42f781 Mon Sep 17 00:00:00 2001 From: Victor Date: Fri, 28 Feb 2020 23:55:05 -0800 Subject: [PATCH 064/112] Disable 3.8 tests in AppVeyor temporarily, as they never pass yet anyway (#1058) --- appveyor.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 20d8ed991..445f9bb5a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,22 +23,16 @@ environment: BUILD_OPTS: --xplat - PYTHON_VERSION: 3.7 BUILD_OPTS: --xplat - - PYTHON_VERSION: 3.8 - BUILD_OPTS: --xplat - PYTHON_VERSION: 2.7 - PYTHON_VERSION: 3.5 - PYTHON_VERSION: 3.6 - PYTHON_VERSION: 3.7 - - PYTHON_VERSION: 3.8 matrix: allow_failures: - PYTHON_VERSION: 3.4 BUILD_OPTS: --xplat - PYTHON_VERSION: 3.4 - - PYTHON_VERSION: 3.8 - BUILD_OPTS: --xplat - - PYTHON_VERSION: 3.8 init: # Update Environment Variables based on matrix/platform From d93217621991064eeccef9f2e4a9fdd9261d2ec5 Mon Sep 17 00:00:00 2001 From: Victor Date: Mon, 2 Mar 2020 00:17:59 -0800 Subject: [PATCH 065/112] correctly dispose the result of PyRun_String (#1071) this also changes a few members of NewReference type to make them work in PyRun_String scenario --- src/embed_tests/Python.EmbeddingTest.csproj | 1 + src/embed_tests/References.cs | 40 +++++++++++++++++++++ src/runtime/NewReference.cs | 34 ++++++++++++++---- src/runtime/Python.Runtime.csproj | 1 + src/runtime/ReferenceExtensions.cs | 20 +++++++++++ src/runtime/pydict.cs | 2 +- src/runtime/pyscope.cs | 31 +++++++++++----- src/runtime/pythonengine.cs | 22 ++++++++---- src/runtime/runtime.cs | 2 +- 9 files changed, 129 insertions(+), 24 deletions(-) create mode 100644 src/embed_tests/References.cs create mode 100644 src/runtime/ReferenceExtensions.cs diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 7a0964a8a..a191290ef 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -88,6 +88,7 @@ + diff --git a/src/embed_tests/References.cs b/src/embed_tests/References.cs new file mode 100644 index 000000000..4c7124907 --- /dev/null +++ b/src/embed_tests/References.cs @@ -0,0 +1,40 @@ +namespace Python.EmbeddingTest +{ + using NUnit.Framework; + using Python.Runtime; + + public class References + { + private Py.GILState _gs; + + [SetUp] + public void SetUp() + { + _gs = Py.GIL(); + } + + [TearDown] + public void Dispose() + { + _gs.Dispose(); + } + + [Test] + public void MoveToPyObject_SetsNull() + { + var dict = new PyDict(); + NewReference reference = Runtime.PyDict_Items(dict.Handle); + try + { + Assert.IsFalse(reference.IsNull()); + + using (reference.MoveToPyObject()) + Assert.IsTrue(reference.IsNull()); + } + finally + { + reference.Dispose(); + } + } + } +} diff --git a/src/runtime/NewReference.cs b/src/runtime/NewReference.cs index 3b45f821f..bbeb86dc4 100644 --- a/src/runtime/NewReference.cs +++ b/src/runtime/NewReference.cs @@ -1,6 +1,8 @@ namespace Python.Runtime { using System; + using System.Diagnostics.Contracts; + /// /// Represents a reference to a Python object, that is tracked by Python's reference counting. /// @@ -8,11 +10,6 @@ namespace Python.Runtime ref struct NewReference { IntPtr pointer; - public bool IsNull => this.pointer == IntPtr.Zero; - - /// Gets a raw pointer to the Python object - public IntPtr DangerousGetAddress() - => this.IsNull ? throw new NullReferenceException() : this.pointer; /// /// Returns wrapper around this reference, which now owns @@ -20,7 +17,7 @@ public IntPtr DangerousGetAddress() /// public PyObject MoveToPyObject() { - if (this.IsNull) throw new NullReferenceException(); + if (this.IsNull()) throw new NullReferenceException(); var result = new PyObject(this.pointer); this.pointer = IntPtr.Zero; @@ -31,9 +28,32 @@ public PyObject MoveToPyObject() /// public void Dispose() { - if (!this.IsNull) + if (!this.IsNull()) Runtime.XDecref(this.pointer); this.pointer = IntPtr.Zero; } + + [Pure] + internal static IntPtr DangerousGetAddress(in NewReference reference) + => IsNull(reference) ? throw new NullReferenceException() : reference.pointer; + [Pure] + internal static bool IsNull(in NewReference reference) + => reference.pointer == IntPtr.Zero; + } + + /// + /// These members can not be directly in type, + /// because this is always passed by value, which we need to avoid. + /// (note this in NewReference vs the usual this NewReference) + /// + static class NewReferenceExtensions + { + /// Gets a raw pointer to the Python object + [Pure] + public static IntPtr DangerousGetAddress(this in NewReference reference) + => NewReference.DangerousGetAddress(reference); + [Pure] + public static bool IsNull(this in NewReference reference) + => NewReference.IsNull(reference); } } diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 1d40c2a38..fd2d35bde 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -141,6 +141,7 @@ + diff --git a/src/runtime/ReferenceExtensions.cs b/src/runtime/ReferenceExtensions.cs new file mode 100644 index 000000000..8fa2731b7 --- /dev/null +++ b/src/runtime/ReferenceExtensions.cs @@ -0,0 +1,20 @@ +namespace Python.Runtime +{ + using System.Diagnostics.Contracts; + + static class ReferenceExtensions + { + /// + /// Checks if the reference points to Python object None. + /// + [Pure] + public static bool IsNone(this in NewReference reference) + => reference.DangerousGetAddress() == Runtime.PyNone; + /// + /// Checks if the reference points to Python object None. + /// + [Pure] + public static bool IsNone(this BorrowedReference reference) + => reference.DangerousGetAddress() == Runtime.PyNone; + } +} diff --git a/src/runtime/pydict.cs b/src/runtime/pydict.cs index b396f4f3d..7ff7a83c8 100644 --- a/src/runtime/pydict.cs +++ b/src/runtime/pydict.cs @@ -142,7 +142,7 @@ public PyObject Items() var items = Runtime.PyDict_Items(this.obj); try { - if (items.IsNull) + if (items.IsNull()) { throw new PythonException(); } diff --git a/src/runtime/pyscope.cs b/src/runtime/pyscope.cs index 4008ce29a..8738824f5 100644 --- a/src/runtime/pyscope.cs +++ b/src/runtime/pyscope.cs @@ -278,11 +278,19 @@ public PyObject Eval(string code, PyDict locals = null) Check(); IntPtr _locals = locals == null ? variables : locals.obj; var flag = (IntPtr)Runtime.Py_eval_input; - IntPtr ptr = Runtime.PyRun_String( + + NewReference reference = Runtime.PyRun_String( code, flag, variables, _locals ); - Runtime.CheckExceptionOccurred(); - return new PyObject(ptr); + try + { + Runtime.CheckExceptionOccurred(); + return reference.MoveToPyObject(); + } + finally + { + reference.Dispose(); + } } /// @@ -316,15 +324,22 @@ public void Exec(string code, PyDict locals = null) private void Exec(string code, IntPtr _globals, IntPtr _locals) { var flag = (IntPtr)Runtime.Py_file_input; - IntPtr ptr = Runtime.PyRun_String( + NewReference reference = Runtime.PyRun_String( code, flag, _globals, _locals ); - Runtime.CheckExceptionOccurred(); - if (ptr != Runtime.PyNone) + + try { - throw new PythonException(); + Runtime.CheckExceptionOccurred(); + if (!reference.IsNone()) + { + throw new PythonException(); + } + } + finally + { + reference.Dispose(); } - Runtime.XDecref(ptr); } /// diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index d5492ebb9..df2d98641 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -543,12 +543,13 @@ public static PyObject Eval(string code, IntPtr? globals = null, IntPtr? locals /// public static void Exec(string code, IntPtr? globals = null, IntPtr? locals = null) { - PyObject result = RunString(code, globals, locals, RunFlagType.File); - if (result.obj != Runtime.PyNone) + using (PyObject result = RunString(code, globals, locals, RunFlagType.File)) { - throw new PythonException(); + if (result.obj != Runtime.PyNone) + { + throw new PythonException(); + } } - result.Dispose(); } @@ -594,13 +595,20 @@ internal static PyObject RunString(string code, IntPtr? globals, IntPtr? locals, try { - IntPtr result = Runtime.PyRun_String( + NewReference result = Runtime.PyRun_String( code, (IntPtr)flag, globals.Value, locals.Value ); - Runtime.CheckExceptionOccurred(); + try + { + Runtime.CheckExceptionOccurred(); - return new PyObject(result); + return result.MoveToPyObject(); + } + finally + { + result.Dispose(); + } } finally { diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 963c9f475..9c9d674a6 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -802,7 +802,7 @@ public static extern int Py_Main( internal static extern int PyRun_SimpleString(string code); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr PyRun_String(string code, IntPtr st, IntPtr globals, IntPtr locals); + internal static extern NewReference PyRun_String(string code, IntPtr st, IntPtr globals, IntPtr locals); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyEval_EvalCode(IntPtr co, IntPtr globals, IntPtr locals); From 8ad10620a5a76b6bb326c45c9af86a2672106d95 Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 3 Mar 2020 00:27:46 -0800 Subject: [PATCH 066/112] Enable C# parameters of type `object` accept any argument, passed from Python (#889) * added a regression test for #881 * when converting to object, wrap values of unknown type into PyObject instead of failing This enables overload resolution with object parameters to behave the same way PyObject parameters behave - e.g. allow any Python object to be passed as PyObject as fallback. Resolves #811 * fixed ObjectField conversion test * fixed test_object_indexer to pass on custom class key * use object() instance in OverloadResolution_UnknownToObject test --- CHANGELOG.md | 1 + src/embed_tests/Python.EmbeddingTest.csproj | 1 + src/embed_tests/TestInstanceWrapping.cs | 58 +++++++++++++++++++++ src/runtime/converter.cs | 9 ++-- src/tests/test_conversion.py | 9 ++-- src/tests/test_indexer.py | 12 ++--- 6 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 src/embed_tests/TestInstanceWrapping.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 82fccbe35..5bc0c9981 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Reattach python exception traceback information (#545) - PythonEngine.Intialize will now call `Py_InitializeEx` with a default value of 0, so signals will not be configured by default on embedding. This is different from the previous behaviour, where `Py_Initialize` was called instead, which sets initSigs to 1. ([#449][i449]) - Refactored MethodBinder.Bind in preparation to make it extensible (#829) +- When calling C# from Python, enable passing argument of any type to a parameter of C# type `object` by wrapping it into `PyObject` instance. ([#881][i881]) - Look for installed Windows 10 sdk's during installation instead of relying on specific versions. ### Fixed diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index a191290ef..9c5f97711 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -94,6 +94,7 @@ + diff --git a/src/embed_tests/TestInstanceWrapping.cs b/src/embed_tests/TestInstanceWrapping.cs new file mode 100644 index 000000000..8be207c00 --- /dev/null +++ b/src/embed_tests/TestInstanceWrapping.cs @@ -0,0 +1,58 @@ +using System; +using System.Globalization; +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest +{ + public class TestInstanceWrapping + { + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + // regression test for https://github.com/pythonnet/pythonnet/issues/811 + [Test] + public void OverloadResolution_UnknownToObject() + { + var overloaded = new Overloaded(); + using (Py.GIL()) + { + var o = overloaded.ToPython(); + + dynamic callWithSelf = PythonEngine.Eval("lambda o: o.ObjOrClass(object())"); + callWithSelf(o); + Assert.AreEqual(Overloaded.Object, overloaded.Value); + } + } + + class Base {} + class Derived: Base { } + + class Overloaded: Derived + { + public int Value { get; set; } + public void IntOrStr(int arg) => this.Value = arg; + public void IntOrStr(string arg) => + this.Value = int.Parse(arg, NumberStyles.Integer, CultureInfo.InvariantCulture); + + public const int Object = 1; + public const int ConcreteClass = 2; + public void ObjOrClass(object _) => this.Value = Object; + public void ObjOrClass(Overloaded _) => this.Value = ConcreteClass; + + public const int Base = ConcreteClass + 1; + public const int Derived = Base + 1; + public void BaseOrDerived(Base _) => this.Value = Base; + public void BaseOrDerived(Derived _) => this.Value = Derived; + } + } +} diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index 7c53bdcb1..3add8aba0 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -398,12 +398,9 @@ internal static bool ToManagedValue(IntPtr value, Type obType, return ToArray(value, typeof(object[]), out result, setError); } - if (setError) - { - Exceptions.SetError(Exceptions.TypeError, "value cannot be converted to Object"); - } - - return false; + Runtime.XIncref(value); // PyObject() assumes ownership + result = new PyObject(value); + return true; } // Conversion to 'Type' is done using the same mappings as above for objects. diff --git a/src/tests/test_conversion.py b/src/tests/test_conversion.py index 0ba10a80e..e61eda26c 100644 --- a/src/tests/test_conversion.py +++ b/src/tests/test_conversion.py @@ -595,11 +595,10 @@ def test_object_conversion(): # need to test subclass here - with pytest.raises(TypeError): - class Foo(object): - pass - ob = ConversionTest() - ob.ObjectField = Foo + class Foo(object): + pass + ob.ObjectField = Foo + assert ob.ObjectField == Foo def test_enum_conversion(): diff --git a/src/tests/test_indexer.py b/src/tests/test_indexer.py index 6f18550d9..ca4fd3b89 100644 --- a/src/tests/test_indexer.py +++ b/src/tests/test_indexer.py @@ -438,13 +438,13 @@ def test_object_indexer(): ob[long(1)] = "long" assert ob[long(1)] == "long" - with pytest.raises(TypeError): - class Eggs(object): - pass + class Eggs(object): + pass - key = Eggs() - ob = Test.ObjectIndexerTest() - ob[key] = "wrong" + key = Eggs() + ob = Test.ObjectIndexerTest() + ob[key] = "eggs_key" + assert ob[key] == "eggs_key" def test_interface_indexer(): From 9fd877e555b2469c848a2726140377a87e039cf5 Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 3 Mar 2020 10:52:02 -0800 Subject: [PATCH 067/112] reimplemented some of the PyList members using BorrowedReference (#1068) --- src/embed_tests/pyimport.cs | 2 +- src/runtime/BorrowedReference.cs | 5 ++++- src/runtime/pylist.cs | 8 ++++---- src/runtime/pyobject.cs | 19 ++++++++++++++++++- src/runtime/runtime.cs | 12 ++++++------ 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/embed_tests/pyimport.cs b/src/embed_tests/pyimport.cs index acb3433de..6b2408745 100644 --- a/src/embed_tests/pyimport.cs +++ b/src/embed_tests/pyimport.cs @@ -39,7 +39,7 @@ public void SetUp() IntPtr str = Runtime.Runtime.PyString_FromString(testPath); IntPtr path = Runtime.Runtime.PySys_GetObject("path"); - Runtime.Runtime.PyList_Append(path, str); + Runtime.Runtime.PyList_Append(new BorrowedReference(path), str); } [TearDown] diff --git a/src/runtime/BorrowedReference.cs b/src/runtime/BorrowedReference.cs index 7dbc7a811..a3bf29056 100644 --- a/src/runtime/BorrowedReference.cs +++ b/src/runtime/BorrowedReference.cs @@ -14,7 +14,10 @@ readonly ref struct BorrowedReference public IntPtr DangerousGetAddress() => this.IsNull ? throw new NullReferenceException() : this.pointer; - BorrowedReference(IntPtr pointer) + /// + /// Creates new instance of from raw pointer. Unsafe. + /// + public BorrowedReference(IntPtr pointer) { this.pointer = pointer; } diff --git a/src/runtime/pylist.cs b/src/runtime/pylist.cs index b22d9d51f..347cc3000 100644 --- a/src/runtime/pylist.cs +++ b/src/runtime/pylist.cs @@ -120,7 +120,7 @@ public static PyList AsList(PyObject value) /// public void Append(PyObject item) { - int r = Runtime.PyList_Append(obj, item.obj); + int r = Runtime.PyList_Append(this.Reference, item.obj); if (r < 0) { throw new PythonException(); @@ -135,7 +135,7 @@ public void Append(PyObject item) /// public void Insert(int index, PyObject item) { - int r = Runtime.PyList_Insert(obj, index, item.obj); + int r = Runtime.PyList_Insert(this.Reference, index, item.obj); if (r < 0) { throw new PythonException(); @@ -151,7 +151,7 @@ public void Insert(int index, PyObject item) /// public void Reverse() { - int r = Runtime.PyList_Reverse(obj); + int r = Runtime.PyList_Reverse(this.Reference); if (r < 0) { throw new PythonException(); @@ -167,7 +167,7 @@ public void Reverse() /// public void Sort() { - int r = Runtime.PyList_Sort(obj); + int r = Runtime.PyList_Sort(this.Reference); if (r < 0) { throw new PythonException(); diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index 8ae99ecd0..37d53eeec 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -33,6 +33,8 @@ public class PyObject : DynamicObject, IEnumerable, IPyDisposable private bool disposed = false; private bool _finalized = false; + internal BorrowedReference Reference => new BorrowedReference(obj); + /// /// PyObject Constructor /// @@ -52,9 +54,24 @@ public PyObject(IntPtr ptr) #endif } + /// + /// Creates new pointing to the same object as + /// the . Increments refcount, allowing + /// to have ownership over its own reference. + /// + internal PyObject(BorrowedReference reference) + { + if (reference.IsNull) throw new ArgumentNullException(nameof(reference)); + + obj = Runtime.SelfIncRef(reference.DangerousGetAddress()); +#if TRACE_ALLOC + Traceback = new StackTrace(1); +#endif + } + // Protected default constructor to allow subclasses to manage // initialization in different ways as appropriate. - [Obsolete("Please, always use PyObject(IntPtr)")] + [Obsolete("Please, always use PyObject(*Reference)")] protected PyObject() { #if TRACE_ALLOC diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 9c9d674a6..bae3daa15 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -341,7 +341,7 @@ internal static void Initialize(bool initSigs = false) string rtdir = RuntimeEnvironment.GetRuntimeDirectory(); IntPtr path = PySys_GetObject("path"); IntPtr item = PyString_FromString(rtdir); - PyList_Append(path, item); + PyList_Append(new BorrowedReference(path), item); XDecref(item); AssemblyManager.UpdatePath(); } @@ -1658,22 +1658,22 @@ internal static int PyList_SetItem(IntPtr pointer, long index, IntPtr value) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] private static extern int PyList_SetItem(IntPtr pointer, IntPtr index, IntPtr value); - internal static int PyList_Insert(IntPtr pointer, long index, IntPtr value) + internal static int PyList_Insert(BorrowedReference pointer, long index, IntPtr value) { return PyList_Insert(pointer, new IntPtr(index), value); } [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - private static extern int PyList_Insert(IntPtr pointer, IntPtr index, IntPtr value); + private static extern int PyList_Insert(BorrowedReference pointer, IntPtr index, IntPtr value); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern int PyList_Append(IntPtr pointer, IntPtr value); + internal static extern int PyList_Append(BorrowedReference pointer, IntPtr value); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern int PyList_Reverse(IntPtr pointer); + internal static extern int PyList_Reverse(BorrowedReference pointer); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern int PyList_Sort(IntPtr pointer); + internal static extern int PyList_Sort(BorrowedReference pointer); internal static IntPtr PyList_GetSlice(IntPtr pointer, long start, long end) { From 653a9329e5360bc683a852172d66c6e587ad2f07 Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 5 Mar 2020 22:44:45 -0800 Subject: [PATCH 068/112] adds extension object.GetRawPythonProxy() (#1078) GetRawPythonProxy creates a PyObject pointing to the specified object without performing any coversions, which lets .NET code to pass CLR objects as-is, if it needs Python to have direct access to them. This enables codecs to create arbitrary proxy objects, bypassing default conversions or other registered codecs. --- CHANGELOG.md | 3 ++- pythonnet.15.sln | 3 +++ src/embed_tests/TestConverter.cs | 23 +++++++++++++++++++++++ src/runtime/NewReference.cs | 6 ++++++ src/runtime/clrobject.cs | 12 ++++++++++++ src/runtime/converter.cs | 11 +++++++++++ 6 files changed, 57 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bc0c9981..db126bd1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added function that sets Py_NoSiteFlag to 1. - Added support for Jetson Nano. - Added support for __len__ for .NET classes that implement ICollection +- Added `object.GetRawPythonProxy() -> PyObject` extension method, that bypasses any conversions ### Changed @@ -21,6 +22,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Removes PyLong_GetMax and PyClass_New when targetting Python3 - Added support for converting python iterators to C# arrays - Changed usage of obselete function GetDelegateForFunctionPointer(IntPtr, Type) to GetDelegateForFunctionPointer(IntPtr) +- When calling C# from Python, enable passing argument of any type to a parameter of C# type `object` by wrapping it into `PyObject` instance. ([#881][i881]) - Added support for kwarg parameters when calling .NET methods from Python ### Fixed @@ -58,7 +60,6 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Reattach python exception traceback information (#545) - PythonEngine.Intialize will now call `Py_InitializeEx` with a default value of 0, so signals will not be configured by default on embedding. This is different from the previous behaviour, where `Py_Initialize` was called instead, which sets initSigs to 1. ([#449][i449]) - Refactored MethodBinder.Bind in preparation to make it extensible (#829) -- When calling C# from Python, enable passing argument of any type to a parameter of C# type `object` by wrapping it into `PyObject` instance. ([#881][i881]) - Look for installed Windows 10 sdk's during installation instead of relying on specific versions. ### Fixed diff --git a/pythonnet.15.sln b/pythonnet.15.sln index 6d1f4fcd9..ce863817f 100644 --- a/pythonnet.15.sln +++ b/pythonnet.15.sln @@ -17,6 +17,9 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repo", "Repo", "{441A0123-F4C6-4EE4-9AEE-315FD79BE2D5}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + .gitignore = .gitignore + CHANGELOG.md = CHANGELOG.md + README.rst = README.rst EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CI", "CI", "{D301657F-5EAF-4534-B280-B858D651B2E5}" diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index caaec311b..078f4c0f8 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using NUnit.Framework; using Python.Runtime; @@ -44,5 +46,26 @@ public void TestConvertDoubleToManaged( Assert.IsTrue(converted); Assert.IsTrue(((double) convertedValue).Equals(testValue)); } + + [Test] + public void RawListProxy() + { + var list = new List {"hello", "world"}; + var listProxy = list.GetRawPythonProxy(); + var clrObject = (CLRObject)ManagedType.GetManagedObject(listProxy.Handle); + Assert.AreSame(list, clrObject.inst); + } + + [Test] + public void RawPyObjectProxy() + { + var pyObject = "hello world!".ToPython(); + var pyObjectProxy = pyObject.GetRawPythonProxy(); + var clrObject = (CLRObject)ManagedType.GetManagedObject(pyObjectProxy.Handle); + Assert.AreSame(pyObject, clrObject.inst); + + var proxiedHandle = pyObjectProxy.GetAttr("Handle").As(); + Assert.AreEqual(pyObject.Handle, proxiedHandle); + } } } diff --git a/src/runtime/NewReference.cs b/src/runtime/NewReference.cs index bbeb86dc4..3ab4b6530 100644 --- a/src/runtime/NewReference.cs +++ b/src/runtime/NewReference.cs @@ -33,6 +33,12 @@ public void Dispose() this.pointer = IntPtr.Zero; } + /// + /// Creates from a raw pointer + /// + public static NewReference DangerousFromPointer(IntPtr pointer) + => new NewReference {pointer = pointer}; + [Pure] internal static IntPtr DangerousGetAddress(in NewReference reference) => IsNull(reference) ? throw new NullReferenceException() : reference.pointer; diff --git a/src/runtime/clrobject.cs b/src/runtime/clrobject.cs index 502677655..13c15f862 100644 --- a/src/runtime/clrobject.cs +++ b/src/runtime/clrobject.cs @@ -68,5 +68,17 @@ internal static IntPtr GetInstHandle(object ob) CLRObject co = GetInstance(ob); return co.pyHandle; } + + /// + /// Creates proxy for the given object, + /// and returns a to it. + /// + internal static NewReference MakeNewReference(object obj) + { + if (obj is null) throw new ArgumentNullException(nameof(obj)); + + // TODO: CLRObject currently does not have Dispose or finalizer which might change in the future + return NewReference.DangerousFromPointer(GetInstHandle(obj)); + } } } diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index 3add8aba0..a7b7b5c48 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -967,5 +967,16 @@ public static PyObject ToPython(this object o) { return new PyObject(Converter.ToPython(o, o?.GetType())); } + + /// + /// Gets raw Python proxy for this object (bypasses all conversions, + /// except null <==> None) + /// + public static PyObject GetRawPythonProxy(this object o) + { + if (o is null) return new PyObject(new BorrowedReference(Runtime.PyNone)); + + return CLRObject.MakeNewReference(o).MoveToPyObject(); + } } } From 925c1660d73883b9636c27d3732c328a321cebb8 Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 5 Mar 2020 22:45:42 -0800 Subject: [PATCH 069/112] Fix synchronization in PyScopeTest.TestThread as suggested in da97502006791bb0597446766ad00a6f9d291895 (#1070) Fixes #1067. --- src/embed_tests/TestPyScope.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/embed_tests/TestPyScope.cs b/src/embed_tests/TestPyScope.cs index 7a4aa0228..701e698ec 100644 --- a/src/embed_tests/TestPyScope.cs +++ b/src/embed_tests/TestPyScope.cs @@ -338,16 +338,16 @@ public void TestThread() //add function to the scope //can be call many times, more efficient than ast ps.Exec( - "import clr\n" + - "from System.Threading import Thread\n" + + "import threading\n" + + "lock = threading.Lock()\n" + "def update():\n" + - " global res, th_cnt\n" + + " global res, th_cnt\n" + + " with lock:\n" + " res += bb + 1\n" + - " Thread.MemoryBarrier()\n" + " th_cnt += 1\n" ); } - int th_cnt = 3; + int th_cnt = 100; for (int i = 0; i < th_cnt; i++) { System.Threading.Thread th = new System.Threading.Thread(() => @@ -368,9 +368,8 @@ public void TestThread() { cnt = ps.Get("th_cnt"); } - Thread.Sleep(10); + Thread.Yield(); } - Thread.MemoryBarrier(); using (Py.GIL()) { var result = ps.Get("res"); From a320d49b0b102190d98b6bab706041b3c5422d02 Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 10 Mar 2020 01:35:15 -0700 Subject: [PATCH 070/112] fixed tuple codec not clearing Python exception after unsuccessful element decoding attempt (#1083) --- src/runtime/Codecs/TupleCodecs.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/runtime/Codecs/TupleCodecs.cs b/src/runtime/Codecs/TupleCodecs.cs index 4c81cac0b..a9ae33fe0 100644 --- a/src/runtime/Codecs/TupleCodecs.cs +++ b/src/runtime/Codecs/TupleCodecs.cs @@ -81,6 +81,7 @@ public bool TryDecode(PyObject pyObj, out T value) IntPtr pyItem = Runtime.PyTuple_GetItem(pyObj.Handle, itemIndex); if (!Converter.ToManaged(pyItem, itemTypes[itemIndex], out elements[itemIndex], setError: false)) { + Exceptions.Clear(); return false; } } @@ -105,6 +106,7 @@ static bool Decode(PyObject tuple, out object value) var pyItem = Runtime.PyTuple_GetItem(tuple.Handle, itemIndex); if (!Converter.ToManaged(pyItem, typeof(object), out elements[itemIndex], setError: false)) { + Exceptions.Clear(); return false; } From d145ab1c4835664928871474e62eb9a06dca7e6c Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 10 Mar 2020 01:37:32 -0700 Subject: [PATCH 071/112] Python runtime must be initialized before trying to acquire GIL (#1086) --- src/embed_tests/TestGILState.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/embed_tests/TestGILState.cs b/src/embed_tests/TestGILState.cs index ba2ab500f..bf6f02dc6 100644 --- a/src/embed_tests/TestGILState.cs +++ b/src/embed_tests/TestGILState.cs @@ -17,5 +17,17 @@ public void CanDisposeMultipleTimes() gilState.Dispose(); } } + + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } } } From 638dc1c7772f7b08a593c89b58635c7bb970661f Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Tue, 10 Mar 2020 23:30:47 -0700 Subject: [PATCH 072/112] allow borrowing from NewReference implemented as an implicit conversion --- src/embed_tests/References.cs | 15 +++++++++++++++ src/runtime/NewReference.cs | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/src/embed_tests/References.cs b/src/embed_tests/References.cs index 4c7124907..1d29e85c7 100644 --- a/src/embed_tests/References.cs +++ b/src/embed_tests/References.cs @@ -36,5 +36,20 @@ public void MoveToPyObject_SetsNull() reference.Dispose(); } } + + [Test] + public void CanBorrowFromNewReference() + { + var dict = new PyDict(); + NewReference reference = Runtime.PyDict_Items(dict.Handle); + try + { + PythonException.ThrowIfIsNotZero(Runtime.PyList_Reverse(reference)); + } + finally + { + reference.Dispose(); + } + } } } diff --git a/src/runtime/NewReference.cs b/src/runtime/NewReference.cs index 3ab4b6530..6e66232d0 100644 --- a/src/runtime/NewReference.cs +++ b/src/runtime/NewReference.cs @@ -11,6 +11,10 @@ ref struct NewReference { IntPtr pointer; + [Pure] + public static implicit operator BorrowedReference(in NewReference reference) + => new BorrowedReference(reference.pointer); + /// /// Returns wrapper around this reference, which now owns /// the pointer. Sets the original reference to null, as it no longer owns it. @@ -36,6 +40,7 @@ public void Dispose() /// /// Creates from a raw pointer /// + [Pure] public static NewReference DangerousFromPointer(IntPtr pointer) => new NewReference {pointer = pointer}; From 5d28a8d9244e58e61a5c295b1b5a326ecdb88479 Mon Sep 17 00:00:00 2001 From: Alex Earl Date: Wed, 8 Apr 2020 16:50:33 -0700 Subject: [PATCH 073/112] Add Format method to pythonexception (#1031) Allows formatting a PythonException using traceback.format_exception --- CHANGELOG.md | 1 + src/embed_tests/TestPythonException.cs | 36 ++++++++++++++++++++++ src/runtime/pythonexception.cs | 42 ++++++++++++++++++++++++++ 3 files changed, 79 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db126bd1c..625f12de1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added support for Jetson Nano. - Added support for __len__ for .NET classes that implement ICollection - Added `object.GetRawPythonProxy() -> PyObject` extension method, that bypasses any conversions +- Added PythonException.Format method to format exceptions the same as traceback.format_exception ### Changed diff --git a/src/embed_tests/TestPythonException.cs b/src/embed_tests/TestPythonException.cs index 57a8d54af..000c32ca3 100644 --- a/src/embed_tests/TestPythonException.cs +++ b/src/embed_tests/TestPythonException.cs @@ -54,5 +54,41 @@ public void TestPythonErrorTypeName() Assert.That(ex.PythonTypeName, Is.EqualTo("ModuleNotFoundError").Or.EqualTo("ImportError")); } } + + [Test] + public void TestPythonExceptionFormat() + { + try + { + PythonEngine.Exec("raise ValueError('Error!')"); + Assert.Fail("Exception should have been raised"); + } + catch (PythonException ex) + { + Assert.That(ex.Format(), Does.Contain("Traceback").And.Contains("(most recent call last):").And.Contains("ValueError: Error!")); + } + } + + [Test] + public void TestPythonExceptionFormatNoError() + { + var ex = new PythonException(); + Assert.AreEqual(ex.StackTrace, ex.Format()); + } + + [Test] + public void TestPythonExceptionFormatNoTraceback() + { + try + { + var module = PythonEngine.ImportModule("really____unknown___module"); + Assert.Fail("Unknown module should not be loaded"); + } + catch (PythonException ex) + { + // ImportError/ModuleNotFoundError do not have a traceback when not running in a script + Assert.AreEqual(ex.StackTrace, ex.Format()); + } + } } } diff --git a/src/runtime/pythonexception.cs b/src/runtime/pythonexception.cs index 7ac922abc..8efdccc91 100644 --- a/src/runtime/pythonexception.cs +++ b/src/runtime/pythonexception.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.CompilerServices; +using System.Text; namespace Python.Runtime { @@ -145,6 +146,47 @@ public string PythonTypeName get { return _pythonTypeName; } } + /// + /// Formats this PythonException object into a message as would be printed + /// out via the Python console. See traceback.format_exception + /// + public string Format() + { + string res; + IntPtr gs = PythonEngine.AcquireLock(); + try + { + if (_pyTB != IntPtr.Zero && _pyType != IntPtr.Zero && _pyValue != IntPtr.Zero) + { + Runtime.XIncref(_pyType); + Runtime.XIncref(_pyValue); + Runtime.XIncref(_pyTB); + using (PyObject pyType = new PyObject(_pyType)) + using (PyObject pyValue = new PyObject(_pyValue)) + using (PyObject pyTB = new PyObject(_pyTB)) + using (PyObject tb_mod = PythonEngine.ImportModule("traceback")) + { + var buffer = new StringBuilder(); + var values = tb_mod.InvokeMethod("format_exception", pyType, pyValue, pyTB); + foreach (PyObject val in values) + { + buffer.Append(val.ToString()); + } + res = buffer.ToString(); + } + } + else + { + res = StackTrace; + } + } + finally + { + PythonEngine.ReleaseLock(gs); + } + return res; + } + /// /// Dispose Method /// From 2a83fe5981aa81a7ca32b34ad17b21d7aaab7a8c Mon Sep 17 00:00:00 2001 From: Alex Earl Date: Wed, 8 Apr 2020 17:58:17 -0700 Subject: [PATCH 074/112] Update vswhere usage (#1105) Updates to newer version of vswhere.exe Follows the recommended way of finding MSBuild.exe from https://github.com/microsoft/vswhere#example Also, updates to allow finding newer versions of VS. --- CHANGELOG.md | 1 + setup.py | 36 ++++++++++++------------------------ tools/vswhere/vswhere.exe | Bin 402040 -> 458872 bytes 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 625f12de1..3cc571ad4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Changed usage of obselete function GetDelegateForFunctionPointer(IntPtr, Type) to GetDelegateForFunctionPointer(IntPtr) - When calling C# from Python, enable passing argument of any type to a parameter of C# type `object` by wrapping it into `PyObject` instance. ([#881][i881]) - Added support for kwarg parameters when calling .NET methods from Python +- Changed method for finding MSBuild using vswhere ### Fixed diff --git a/setup.py b/setup.py index db2b4ae68..cabb176af 100644 --- a/setup.py +++ b/setup.py @@ -457,26 +457,20 @@ def _find_msbuild_tool(self, tool="msbuild.exe", use_windows_sdk=False): # trying to search path with help of vswhere when MSBuild 15.0 and higher installed. if tool == "msbuild.exe" and use_windows_sdk == False: try: - basePathes = subprocess.check_output( + basePaths = subprocess.check_output( [ "tools\\vswhere\\vswhere.exe", "-latest", "-version", - "[15.0, 16.0)", + "[15.0,)", "-requires", "Microsoft.Component.MSBuild", - "-property", - "InstallationPath", + "-find", + "MSBuild\**\Bin\MSBuild.exe", ] ).splitlines() - if len(basePathes): - return os.path.join( - basePathes[0].decode(sys.stdout.encoding or "utf-8"), - "MSBuild", - "15.0", - "Bin", - "MSBuild.exe", - ) + if len(basePaths): + return basePaths[0].decode(sys.stdout.encoding or "utf-8") except: pass # keep trying to search by old method. @@ -528,26 +522,20 @@ def _find_msbuild_tool(self, tool="msbuild.exe", use_windows_sdk=False): def _find_msbuild_tool_15(self): """Return full path to one of the Microsoft build tools""" try: - basePathes = subprocess.check_output( + basePaths = subprocess.check_output( [ "tools\\vswhere\\vswhere.exe", "-latest", "-version", - "[15.0, 16.0)", + "[15.0,)", "-requires", "Microsoft.Component.MSBuild", - "-property", - "InstallationPath", + "-find", + "MSBuild\**\Bin\MSBuild.exe", ] ).splitlines() - if len(basePathes): - return os.path.join( - basePathes[0].decode(sys.stdout.encoding or "utf-8"), - "MSBuild", - "15.0", - "Bin", - "MSBuild.exe", - ) + if len(basePaths): + return basePaths[0].decode(sys.stdout.encoding or "utf-8") else: raise RuntimeError("MSBuild >=15.0 could not be found.") except subprocess.CalledProcessError as e: diff --git a/tools/vswhere/vswhere.exe b/tools/vswhere/vswhere.exe index 3eb2df00987641952f6cb94bbe9127958256b9d8..582e82868dddc97bec4a8c8353f729348be55f56 100644 GIT binary patch literal 458872 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

rG9aqQ9oNre|=>m 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 z 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

B(E)|cNo@{AOD>9yyD)Nk< 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%0D#_#BHV;EJ>euFqi5NMmquoA+;)UnLId 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

nA&iki61p(P=8cH29?dR$zpaxQQZDm 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 zπ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%0D@?;oA*SC=;pP)&+H#xaBg;>g7V9+muza=c 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 From 92bc1454efe881c94f1f53425166c4385b8a7e1f Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Tue, 21 Apr 2020 16:26:21 -0700 Subject: [PATCH 075/112] fix compile error when PYTHON_WITHOUT_ENABLE_SHARED is not set #977 --- src/runtime/runtime.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index bae3daa15..3d1fe2d39 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1976,11 +1976,9 @@ internal static void SetNoSiteFlag() { var loader = LibraryLoader.Get(OperatingSystem); - IntPtr dllLocal; - if (_PythonDll != "__Internal") - { - dllLocal = loader.Load(_PythonDll); - } + IntPtr dllLocal = _PythonDll != "__Internal" + ? loader.Load(_PythonDll) + : IntPtr.Zero; try { From 555f5626d4c95b10518eab9357a07bd323e736fe Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 22 Apr 2020 03:07:41 -0700 Subject: [PATCH 076/112] Codec groups: EncoderGroup and DecoderGroup (#1085) * Added Codecs: EncoderGroup and DecoderGroup These classes would help to manage codec layers. For example, a library could register its own codecs, but also allow anyone to inject their codecs before library's own: public static EncoderGroup BeforeLibraryEncoders { get; } = new EncoderGroup(); void LibraryRegisterCodecs(){ PyObjectConversions.RegisterEncoder(BeforeLibraryEncoders); PyObjectConversions.RegisterEncoder(LibraryEncoder.Instance); } Then in a program using that library: Library.BeforeLibraryEncoders.Encoders.Add(preencoder); --- src/embed_tests/CodecGroups.cs | 132 ++++++++++++++++++++ src/embed_tests/Codecs.cs | 37 ++++++ src/embed_tests/Python.EmbeddingTest.csproj | 1 + src/runtime/Codecs/DecoderGroup.cs | 78 ++++++++++++ src/runtime/Codecs/EncoderGroup.cs | 79 ++++++++++++ src/runtime/Python.Runtime.csproj | 2 + src/runtime/converterextensions.cs | 9 +- 7 files changed, 334 insertions(+), 4 deletions(-) create mode 100644 src/embed_tests/CodecGroups.cs create mode 100644 src/runtime/Codecs/DecoderGroup.cs create mode 100644 src/runtime/Codecs/EncoderGroup.cs diff --git a/src/embed_tests/CodecGroups.cs b/src/embed_tests/CodecGroups.cs new file mode 100644 index 000000000..68cf2d6e5 --- /dev/null +++ b/src/embed_tests/CodecGroups.cs @@ -0,0 +1,132 @@ +namespace Python.EmbeddingTest +{ + using System; + using System.Linq; + using NUnit.Framework; + using Python.Runtime; + using Python.Runtime.Codecs; + + public class CodecGroups + { + [Test] + public void GetEncodersByType() + { + var encoder1 = new ObjectToRawProxyEncoder(); + var encoder2 = new ObjectToRawProxyEncoder(); + var group = new EncoderGroup { + new ObjectToRawProxyEncoder>(), + encoder1, + encoder2, + }; + + var got = group.GetEncoders(typeof(Uri)).ToArray(); + CollectionAssert.AreEqual(new[]{encoder1, encoder2}, got); + } + + [Test] + public void CanEncode() + { + var group = new EncoderGroup { + new ObjectToRawProxyEncoder>(), + new ObjectToRawProxyEncoder(), + }; + + Assert.IsTrue(group.CanEncode(typeof(Tuple))); + Assert.IsTrue(group.CanEncode(typeof(Uri))); + Assert.IsFalse(group.CanEncode(typeof(string))); + } + + [Test] + public void Encodes() + { + var encoder0 = new ObjectToRawProxyEncoder>(); + var encoder1 = new ObjectToRawProxyEncoder(); + var encoder2 = new ObjectToRawProxyEncoder(); + var group = new EncoderGroup { + encoder0, + encoder1, + encoder2, + }; + + var uri = group.TryEncode(new Uri("data:")); + var clrObject = (CLRObject)ManagedType.GetManagedObject(uri.Handle); + Assert.AreSame(encoder1, clrObject.inst); + Assert.AreNotSame(encoder2, clrObject.inst); + + var tuple = group.TryEncode(Tuple.Create(1)); + clrObject = (CLRObject)ManagedType.GetManagedObject(tuple.Handle); + Assert.AreSame(encoder0, clrObject.inst); + } + + [Test] + public void GetDecodersByTypes() + { + var pyint = new PyInt(10).GetPythonType(); + var pyfloat = new PyFloat(10).GetPythonType(); + var pystr = new PyString("world").GetPythonType(); + var decoder1 = new DecoderReturningPredefinedValue(pyint, decodeResult: 42); + var decoder2 = new DecoderReturningPredefinedValue(pyfloat, decodeResult: "atad:"); + var group = new DecoderGroup { + decoder1, + decoder2, + }; + + var decoder = group.GetDecoder(pyfloat, typeof(string)); + Assert.AreSame(decoder2, decoder); + decoder = group.GetDecoder(pystr, typeof(string)); + Assert.IsNull(decoder); + decoder = group.GetDecoder(pyint, typeof(long)); + Assert.AreSame(decoder1, decoder); + } + [Test] + public void CanDecode() + { + var pyint = new PyInt(10).GetPythonType(); + var pyfloat = new PyFloat(10).GetPythonType(); + var pystr = new PyString("world").GetPythonType(); + var decoder1 = new DecoderReturningPredefinedValue(pyint, decodeResult: 42); + var decoder2 = new DecoderReturningPredefinedValue(pyfloat, decodeResult: "atad:"); + var group = new DecoderGroup { + decoder1, + decoder2, + }; + + Assert.IsTrue(group.CanDecode(pyint, typeof(long))); + Assert.IsFalse(group.CanDecode(pyint, typeof(int))); + Assert.IsTrue(group.CanDecode(pyfloat, typeof(string))); + Assert.IsFalse(group.CanDecode(pystr, typeof(string))); + } + + [Test] + public void Decodes() + { + var pyint = new PyInt(10).GetPythonType(); + var pyfloat = new PyFloat(10).GetPythonType(); + var decoder1 = new DecoderReturningPredefinedValue(pyint, decodeResult: 42); + var decoder2 = new DecoderReturningPredefinedValue(pyfloat, decodeResult: "atad:"); + var group = new DecoderGroup { + decoder1, + decoder2, + }; + + Assert.IsTrue(group.TryDecode(new PyInt(10), out long longResult)); + Assert.AreEqual(42, longResult); + Assert.IsTrue(group.TryDecode(new PyFloat(10), out string strResult)); + Assert.AreSame("atad:", strResult); + + Assert.IsFalse(group.TryDecode(new PyInt(10), out int _)); + } + + [SetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [TearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + } +} diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index 600215cf0..0d15ca55f 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -83,4 +83,41 @@ static void TupleRoundtripGeneric() { } } } + + ///

+ /// "Decodes" only objects of exact type . + /// Result is just a raw Python object proxy. + /// + class ObjectToRawProxyEncoder : IPyObjectEncoder + { + public bool CanEncode(Type type) => type == typeof(T); + public PyObject TryEncode(object value) => this.GetRawPythonProxy(); + } + + /// + /// Decodes object of specified Python type to the predefined value + /// + /// Type of the + class DecoderReturningPredefinedValue : IPyObjectDecoder + { + public PyObject TheOnlySupportedSourceType { get; } + public TTarget DecodeResult { get; } + + public DecoderReturningPredefinedValue(PyObject objectType, TTarget decodeResult) + { + this.TheOnlySupportedSourceType = objectType; + this.DecodeResult = decodeResult; + } + + public bool CanDecode(PyObject objectType, Type targetType) + => objectType.Handle == TheOnlySupportedSourceType.Handle + && targetType == typeof(TTarget); + public bool TryDecode(PyObject pyObj, out T value) + { + if (typeof(T) != typeof(TTarget)) + throw new ArgumentException(nameof(T)); + value = (T)(object)DecodeResult; + return true; + } + } } diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 9c5f97711..5dee66e64 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -83,6 +83,7 @@ + diff --git a/src/runtime/Codecs/DecoderGroup.cs b/src/runtime/Codecs/DecoderGroup.cs new file mode 100644 index 000000000..8a290d5d4 --- /dev/null +++ b/src/runtime/Codecs/DecoderGroup.cs @@ -0,0 +1,78 @@ +namespace Python.Runtime.Codecs +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + + /// + /// Represents a group of s. Useful to group them by priority. + /// + [Obsolete(Util.UnstableApiMessage)] + public sealed class DecoderGroup: IPyObjectDecoder, IEnumerable + { + readonly List decoders = new List(); + + /// + /// Add specified decoder to the group + /// + public void Add(IPyObjectDecoder item) + { + if (item is null) throw new ArgumentNullException(nameof(item)); + + this.decoders.Add(item); + } + /// + /// Remove all decoders from the group + /// + public void Clear() => this.decoders.Clear(); + + /// + public bool CanDecode(PyObject objectType, Type targetType) + => this.decoders.Any(decoder => decoder.CanDecode(objectType, targetType)); + /// + public bool TryDecode(PyObject pyObj, out T value) + { + if (pyObj is null) throw new ArgumentNullException(nameof(pyObj)); + + var decoder = this.GetDecoder(pyObj.GetPythonType(), typeof(T)); + if (decoder is null) + { + value = default; + return false; + } + return decoder.TryDecode(pyObj, out value); + } + + /// + public IEnumerator GetEnumerator() => this.decoders.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => this.decoders.GetEnumerator(); + } + + [Obsolete(Util.UnstableApiMessage)] + public static class DecoderGroupExtensions + { + /// + /// Gets a concrete instance of + /// (potentially selecting one from a collection), + /// that can decode from to , + /// or null if a matching decoder can not be found. + /// + [Obsolete(Util.UnstableApiMessage)] + public static IPyObjectDecoder GetDecoder( + this IPyObjectDecoder decoder, + PyObject objectType, Type targetType) + { + if (decoder is null) throw new ArgumentNullException(nameof(decoder)); + + if (decoder is IEnumerable composite) + { + return composite + .Select(nestedDecoder => nestedDecoder.GetDecoder(objectType, targetType)) + .FirstOrDefault(d => d != null); + } + + return decoder.CanDecode(objectType, targetType) ? decoder : null; + } + } +} diff --git a/src/runtime/Codecs/EncoderGroup.cs b/src/runtime/Codecs/EncoderGroup.cs new file mode 100644 index 000000000..a5708c0bb --- /dev/null +++ b/src/runtime/Codecs/EncoderGroup.cs @@ -0,0 +1,79 @@ +namespace Python.Runtime.Codecs +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + + /// + /// Represents a group of s. Useful to group them by priority. + /// + [Obsolete(Util.UnstableApiMessage)] + public sealed class EncoderGroup: IPyObjectEncoder, IEnumerable + { + readonly List encoders = new List(); + + /// + /// Add specified encoder to the group + /// + public void Add(IPyObjectEncoder item) + { + if (item is null) throw new ArgumentNullException(nameof(item)); + this.encoders.Add(item); + } + /// + /// Remove all encoders from the group + /// + public void Clear() => this.encoders.Clear(); + + /// + public bool CanEncode(Type type) => this.encoders.Any(encoder => encoder.CanEncode(type)); + /// + public PyObject TryEncode(object value) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + foreach (var encoder in this.GetEncoders(value.GetType())) + { + var result = encoder.TryEncode(value); + if (result != null) + { + return result; + } + } + + return null; + } + + /// + public IEnumerator GetEnumerator() => this.encoders.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => this.encoders.GetEnumerator(); + } + + [Obsolete(Util.UnstableApiMessage)] + public static class EncoderGroupExtensions + { + /// + /// Gets specific instances of + /// (potentially selecting one from a collection), + /// that can encode the specified . + /// + [Obsolete(Util.UnstableApiMessage)] + public static IEnumerable GetEncoders(this IPyObjectEncoder decoder, Type type) + { + if (decoder is null) throw new ArgumentNullException(nameof(decoder)); + + if (decoder is IEnumerable composite) + { + foreach (var nestedEncoder in composite) + foreach (var match in nestedEncoder.GetEncoders(type)) + { + yield return match; + } + } else if (decoder.CanEncode(type)) + { + yield return decoder; + } + } + } +} diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index fd2d35bde..0a4359796 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -76,6 +76,8 @@ + + diff --git a/src/runtime/converterextensions.cs b/src/runtime/converterextensions.cs index fd012c6e4..667fc6f00 100644 --- a/src/runtime/converterextensions.cs +++ b/src/runtime/converterextensions.cs @@ -5,6 +5,7 @@ namespace Python.Runtime using System.Collections.Generic; using System.Linq; using System.Reflection; + using Python.Runtime.Codecs; /// /// Defines conversion to CLR types (unmarshalling) @@ -49,8 +50,8 @@ public interface IPyObjectEncoder [Obsolete(Util.UnstableApiMessage)] public static class PyObjectConversions { - static readonly List decoders = new List(); - static readonly List encoders = new List(); + static readonly DecoderGroup decoders = new DecoderGroup(); + static readonly EncoderGroup encoders = new EncoderGroup(); /// /// Registers specified encoder (marshaller) @@ -101,7 +102,7 @@ static IPyObjectEncoder[] GetEncoders(Type type) { lock (encoders) { - return encoders.Where(encoder => encoder.CanEncode(type)).ToArray(); + return encoders.GetEncoders(type).ToArray(); } } #endregion @@ -128,7 +129,7 @@ static Converter.TryConvertFromPythonDelegate GetDecoder(IntPtr sourceType, Type { lock (decoders) { - decoder = decoders.Find(d => d.CanDecode(pyType, targetType)); + decoder = decoders.GetDecoder(pyType, targetType); if (decoder == null) return null; } } From 1fb2e63283eb5d02a42e7560f2a6d0b7ffe05c66 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 22 Apr 2020 03:53:00 -0700 Subject: [PATCH 077/112] Remove unnecessary glib-dev dependency #1120 (#1121) --- setup.py | 8 +++----- src/monoclr/pynetclr.h | 3 +-- src/monoclr/pynetinit.c | 6 +++--- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/setup.py b/setup.py index cabb176af..2dc3e2ca8 100644 --- a/setup.py +++ b/setup.py @@ -398,11 +398,9 @@ def _build_monoclr(self): return raise mono_cflags = _check_output("pkg-config --cflags mono-2", shell=True) - glib_libs = _check_output("pkg-config --libs glib-2.0", shell=True) - glib_cflags = _check_output("pkg-config --cflags glib-2.0", shell=True) - cflags = mono_cflags.strip() + " " + glib_cflags.strip() - libs = mono_libs.strip() + " " + glib_libs.strip() - + cflags = mono_cflags.strip() + libs = mono_libs.strip() + # build the clr python module clr_ext = Extension( "clr", diff --git a/src/monoclr/pynetclr.h b/src/monoclr/pynetclr.h index c5e181156..5b25ee8c9 100644 --- a/src/monoclr/pynetclr.h +++ b/src/monoclr/pynetclr.h @@ -7,7 +7,6 @@ #include #include #include -#include #define MONO_VERSION "v4.0.30319.1" #define MONO_DOMAIN "Python.Runtime" @@ -27,7 +26,7 @@ typedef struct PyNet_Args *PyNet_Init(int); void PyNet_Finalize(PyNet_Args *); -void main_thread_handler(gpointer user_data); +void main_thread_handler(PyNet_Args *user_data); char *PyNet_ExceptionToString(MonoObject *); #endif // PYNET_CLR_H diff --git a/src/monoclr/pynetinit.c b/src/monoclr/pynetinit.c index 8b49ddae3..7208878de 100644 --- a/src/monoclr/pynetinit.c +++ b/src/monoclr/pynetinit.c @@ -91,9 +91,9 @@ MonoMethod *getMethodFromClass(MonoClass *cls, char *name) return method; } -void main_thread_handler(gpointer user_data) +void main_thread_handler(PyNet_Args *user_data) { - PyNet_Args *pn_args = (PyNet_Args *)user_data; + PyNet_Args *pn_args = user_data; MonoMethod *init; MonoImage *pr_image; MonoClass *pythonengine; @@ -241,7 +241,7 @@ void main_thread_handler(gpointer user_data) // Get string from a Mono exception char *PyNet_ExceptionToString(MonoObject *e) { - MonoMethodDesc *mdesc = mono_method_desc_new(":ToString()", FALSE); + MonoMethodDesc *mdesc = mono_method_desc_new(":ToString()", 0 /*FALSE*/); MonoMethod *mmethod = mono_method_desc_search_in_class(mdesc, mono_get_object_class()); mono_method_desc_free(mdesc); From 8522b5f9eca92bd87afe8ed95f84b5bf1491fd2b Mon Sep 17 00:00:00 2001 From: Victor Date: Fri, 24 Apr 2020 01:33:38 -0700 Subject: [PATCH 078/112] update NonCopyableAnalyzer (#1123) --- src/embed_tests/Python.EmbeddingTest.15.csproj | 1 + src/runtime/Python.Runtime.15.csproj | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/embed_tests/Python.EmbeddingTest.15.csproj b/src/embed_tests/Python.EmbeddingTest.15.csproj index d8952a3a9..e163c9242 100644 --- a/src/embed_tests/Python.EmbeddingTest.15.csproj +++ b/src/embed_tests/Python.EmbeddingTest.15.csproj @@ -77,6 +77,7 @@ + diff --git a/src/runtime/Python.Runtime.15.csproj b/src/runtime/Python.Runtime.15.csproj index fd4f3416a..2147731c6 100644 --- a/src/runtime/Python.Runtime.15.csproj +++ b/src/runtime/Python.Runtime.15.csproj @@ -1,4 +1,4 @@ - + net40;netstandard2.0 @@ -130,7 +130,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From f707698c9f6ec63a7978f21e77026d9046d759d6 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 29 Apr 2020 07:06:18 -0700 Subject: [PATCH 079/112] Add RawProxyEncoder (#1122) Now Python host can force raw encoding for autoconverted .NET types. Enables workaround for #514 --- src/embed_tests/CodecGroups.cs | 16 ++++++++-------- src/embed_tests/Codecs.cs | 4 ++-- src/runtime/Codecs/RawProxyEncoder.cs | 21 +++++++++++++++++++++ src/runtime/Python.Runtime.csproj | 1 + src/testing/conversiontest.cs | 5 ++++- src/tests/test_conversion.py | 18 ++++++++++++++++++ 6 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 src/runtime/Codecs/RawProxyEncoder.cs diff --git a/src/embed_tests/CodecGroups.cs b/src/embed_tests/CodecGroups.cs index 68cf2d6e5..5dd40210f 100644 --- a/src/embed_tests/CodecGroups.cs +++ b/src/embed_tests/CodecGroups.cs @@ -11,10 +11,10 @@ public class CodecGroups [Test] public void GetEncodersByType() { - var encoder1 = new ObjectToRawProxyEncoder(); - var encoder2 = new ObjectToRawProxyEncoder(); + var encoder1 = new ObjectToEncoderInstanceEncoder(); + var encoder2 = new ObjectToEncoderInstanceEncoder(); var group = new EncoderGroup { - new ObjectToRawProxyEncoder>(), + new ObjectToEncoderInstanceEncoder>(), encoder1, encoder2, }; @@ -27,8 +27,8 @@ public void GetEncodersByType() public void CanEncode() { var group = new EncoderGroup { - new ObjectToRawProxyEncoder>(), - new ObjectToRawProxyEncoder(), + new ObjectToEncoderInstanceEncoder>(), + new ObjectToEncoderInstanceEncoder(), }; Assert.IsTrue(group.CanEncode(typeof(Tuple))); @@ -39,9 +39,9 @@ public void CanEncode() [Test] public void Encodes() { - var encoder0 = new ObjectToRawProxyEncoder>(); - var encoder1 = new ObjectToRawProxyEncoder(); - var encoder2 = new ObjectToRawProxyEncoder(); + var encoder0 = new ObjectToEncoderInstanceEncoder>(); + var encoder1 = new ObjectToEncoderInstanceEncoder(); + var encoder2 = new ObjectToEncoderInstanceEncoder(); var group = new EncoderGroup { encoder0, encoder1, diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index 0d15ca55f..d872dbd12 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -86,9 +86,9 @@ static void TupleRoundtripGeneric() { /// /// "Decodes" only objects of exact type . - /// Result is just a raw Python object proxy. + /// Result is just the raw proxy to the encoder instance itself. /// - class ObjectToRawProxyEncoder : IPyObjectEncoder + class ObjectToEncoderInstanceEncoder : IPyObjectEncoder { public bool CanEncode(Type type) => type == typeof(T); public PyObject TryEncode(object value) => this.GetRawPythonProxy(); diff --git a/src/runtime/Codecs/RawProxyEncoder.cs b/src/runtime/Codecs/RawProxyEncoder.cs new file mode 100644 index 000000000..dd6f21ee0 --- /dev/null +++ b/src/runtime/Codecs/RawProxyEncoder.cs @@ -0,0 +1,21 @@ +using System; + +namespace Python.Runtime.Codecs +{ + /// + /// A .NET object encoder, that returns raw proxies (e.g. no conversion to Python types). + /// You must inherit from this class and override . + /// + [Obsolete(Util.UnstableApiMessage)] + public class RawProxyEncoder: IPyObjectEncoder + { + public PyObject TryEncode(object value) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + return value.GetRawPythonProxy(); + } + + public virtual bool CanEncode(Type type) => false; + } +} diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 0a4359796..2e47805cb 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -78,6 +78,7 @@ + diff --git a/src/testing/conversiontest.cs b/src/testing/conversiontest.cs index 06ab7cb4e..1f9d64e1b 100644 --- a/src/testing/conversiontest.cs +++ b/src/testing/conversiontest.cs @@ -1,7 +1,9 @@ namespace Python.Test { + using System.Collections.Generic; + /// - /// Supports units tests for field access. + /// Supports unit tests for field access. /// public class ConversionTest { @@ -32,6 +34,7 @@ public ConversionTest() public byte[] ByteArrayField; public sbyte[] SByteArrayField; + public readonly List ListField = new List(); public T? Echo(T? arg) where T: struct { return arg; diff --git a/src/tests/test_conversion.py b/src/tests/test_conversion.py index e61eda26c..1386a0358 100644 --- a/src/tests/test_conversion.py +++ b/src/tests/test_conversion.py @@ -6,6 +6,8 @@ import System import pytest from Python.Test import ConversionTest, UnicodeString +from Python.Runtime import PyObjectConversions +from Python.Runtime.Codecs import RawProxyEncoder from ._compat import indexbytes, long, unichr, text_type, PY2, PY3 @@ -700,3 +702,19 @@ def test_sbyte_array_conversion(): array = ob.SByteArrayField for i, _ in enumerate(value): assert array[i] == indexbytes(value, i) + +def test_codecs(): + """Test codec registration from Python""" + class ListAsRawEncoder(RawProxyEncoder): + __namespace__ = "Python.Test" + def CanEncode(self, clr_type): + return clr_type.Name == "List`1" and clr_type.Namespace == "System.Collections.Generic" + + list_raw_encoder = ListAsRawEncoder() + PyObjectConversions.RegisterEncoder(list_raw_encoder) + + ob = ConversionTest() + + l = ob.ListField + l.Add(42) + assert ob.ListField.Count == 1 From 2e0874ad3b96f259c29517038388ab5da1d9cb6d Mon Sep 17 00:00:00 2001 From: Victor Date: Fri, 1 May 2020 01:36:19 -0700 Subject: [PATCH 080/112] Removes new object.GetRawPythonProxy extension method in favor of existing PyObject.FromManagedObject (#1132) Reverts most of https://github.com/pythonnet/pythonnet/pull/1078 --- CHANGELOG.md | 1 - src/embed_tests/Codecs.cs | 2 +- src/embed_tests/TestConverter.cs | 4 ++-- src/runtime/Codecs/RawProxyEncoder.cs | 2 +- src/runtime/clrobject.cs | 12 ------------ src/runtime/converter.cs | 11 ----------- src/runtime/pyobject.cs | 3 ++- 7 files changed, 6 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cc571ad4..f754cbc4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,6 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added function that sets Py_NoSiteFlag to 1. - Added support for Jetson Nano. - Added support for __len__ for .NET classes that implement ICollection -- Added `object.GetRawPythonProxy() -> PyObject` extension method, that bypasses any conversions - Added PythonException.Format method to format exceptions the same as traceback.format_exception ### Changed diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index d872dbd12..18fcd32d1 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -91,7 +91,7 @@ static void TupleRoundtripGeneric() { class ObjectToEncoderInstanceEncoder : IPyObjectEncoder { public bool CanEncode(Type type) => type == typeof(T); - public PyObject TryEncode(object value) => this.GetRawPythonProxy(); + public PyObject TryEncode(object value) => PyObject.FromManagedObject(this); } /// diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index 078f4c0f8..40219973b 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -51,7 +51,7 @@ public void TestConvertDoubleToManaged( public void RawListProxy() { var list = new List {"hello", "world"}; - var listProxy = list.GetRawPythonProxy(); + var listProxy = PyObject.FromManagedObject(list); var clrObject = (CLRObject)ManagedType.GetManagedObject(listProxy.Handle); Assert.AreSame(list, clrObject.inst); } @@ -60,7 +60,7 @@ public void RawListProxy() public void RawPyObjectProxy() { var pyObject = "hello world!".ToPython(); - var pyObjectProxy = pyObject.GetRawPythonProxy(); + var pyObjectProxy = PyObject.FromManagedObject(pyObject); var clrObject = (CLRObject)ManagedType.GetManagedObject(pyObjectProxy.Handle); Assert.AreSame(pyObject, clrObject.inst); diff --git a/src/runtime/Codecs/RawProxyEncoder.cs b/src/runtime/Codecs/RawProxyEncoder.cs index dd6f21ee0..a1b6c52b3 100644 --- a/src/runtime/Codecs/RawProxyEncoder.cs +++ b/src/runtime/Codecs/RawProxyEncoder.cs @@ -13,7 +13,7 @@ public PyObject TryEncode(object value) { if (value is null) throw new ArgumentNullException(nameof(value)); - return value.GetRawPythonProxy(); + return PyObject.FromManagedObject(value); } public virtual bool CanEncode(Type type) => false; diff --git a/src/runtime/clrobject.cs b/src/runtime/clrobject.cs index 13c15f862..502677655 100644 --- a/src/runtime/clrobject.cs +++ b/src/runtime/clrobject.cs @@ -68,17 +68,5 @@ internal static IntPtr GetInstHandle(object ob) CLRObject co = GetInstance(ob); return co.pyHandle; } - - /// - /// Creates proxy for the given object, - /// and returns a to it. - /// - internal static NewReference MakeNewReference(object obj) - { - if (obj is null) throw new ArgumentNullException(nameof(obj)); - - // TODO: CLRObject currently does not have Dispose or finalizer which might change in the future - return NewReference.DangerousFromPointer(GetInstHandle(obj)); - } } } diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index a7b7b5c48..3add8aba0 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -967,16 +967,5 @@ public static PyObject ToPython(this object o) { return new PyObject(Converter.ToPython(o, o?.GetType())); } - - /// - /// Gets raw Python proxy for this object (bypasses all conversions, - /// except null <==> None) - /// - public static PyObject GetRawPythonProxy(this object o) - { - if (o is null) return new PyObject(new BorrowedReference(Runtime.PyNone)); - - return CLRObject.MakeNewReference(o).MoveToPyObject(); - } } } diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index 37d53eeec..96968e678 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -111,7 +111,8 @@ public IntPtr Handle /// - /// FromManagedObject Method + /// Gets raw Python proxy for this object (bypasses all conversions, + /// except null <==> None) /// /// /// Given an arbitrary managed object, return a Python instance that From 960286d51bf912c52f93a6dc06449b2d1ef1f342 Mon Sep 17 00:00:00 2001 From: Meinrad Recheis Date: Mon, 11 May 2020 07:39:25 +0200 Subject: [PATCH 081/112] Add IsNone() and Runtime.None (#1137) * Add `PyObject.IsNone()` * Add `Runtime.None` which returns a reference to `None` as a `PyObject` to be able to pass it into Python from .NET --- AUTHORS.md | 1 + CHANGELOG.md | 2 ++ src/runtime/pyobject.cs | 4 ++++ src/runtime/runtime.cs | 10 ++++++++++ 4 files changed, 17 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index b0d1f0c91..19cd4f5ed 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -45,6 +45,7 @@ - Luke Stratman ([@lstratman](https://github.com/lstratman)) - Konstantin Posudevskiy ([@konstantin-posudevskiy](https://github.com/konstantin-posudevskiy)) - Matthias Dittrich ([@matthid](https://github.com/matthid)) +- Meinrad Recheis ([@henon](https://github.com/henon)) - Mohamed Koubaa ([@koubaa](https://github.com/koubaa)) - Patrick Stewart ([@patstew](https://github.com/patstew)) - Raphael Nestler ([@rnestler](https://github.com/rnestler)) diff --git a/CHANGELOG.md b/CHANGELOG.md index f754cbc4e..13baf557e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added support for Jetson Nano. - Added support for __len__ for .NET classes that implement ICollection - Added PythonException.Format method to format exceptions the same as traceback.format_exception +- Added Runtime.None to be able to pass None as parameter into Python from .NET +- Added PyObject.IsNone() to check if a Python object is None in .NET. ### Changed diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index 96968e678..491c62e30 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -979,6 +979,10 @@ public bool IsTrue() return Runtime.PyObject_IsTrue(obj) != 0; } + /// + /// Return true if the object is None + /// + public bool IsNone() => CheckNone(this) == null; /// /// Dir Method diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 3d1fe2d39..2e06c9cba 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -473,6 +473,16 @@ private static void ResetPyMembers() internal static IntPtr PyNone; internal static IntPtr Error; + public static PyObject None + { + get + { + var none = Runtime.PyNone; + Runtime.XIncref(none); + return new PyObject(none); + } + } + /// /// Check if any Python Exceptions occurred. /// If any exist throw new PythonException. From 2910074e24f76f3095dba1406ffac2122990c894 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 11 May 2020 22:21:07 +0200 Subject: [PATCH 082/112] Link stdc++ and force-preload libmono on Linux (#1139) * Fix geninterop.py include paths * Ensure that monoclr is linked as a C++ library * Force preload libmono and fix bug in LD_LIBRARY_PATH forwarding --- setup.py | 1 + src/monoclr/pynetclr.h | 8 ++++++++ src/monoclr/pynetinit.c | 11 ++++++++++- tools/geninterop/geninterop.py | 4 +++- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2dc3e2ca8..ed0852bb5 100644 --- a/setup.py +++ b/setup.py @@ -404,6 +404,7 @@ def _build_monoclr(self): # build the clr python module clr_ext = Extension( "clr", + language="c++", sources=["src/monoclr/pynetinit.c", "src/monoclr/clrmod.c"], extra_compile_args=cflags.split(" "), extra_link_args=libs.split(" "), diff --git a/src/monoclr/pynetclr.h b/src/monoclr/pynetclr.h index 5b25ee8c9..1863b1d43 100644 --- a/src/monoclr/pynetclr.h +++ b/src/monoclr/pynetclr.h @@ -12,6 +12,10 @@ #define MONO_DOMAIN "Python.Runtime" #define PR_ASSEMBLY "Python.Runtime.dll" +#ifdef __cplusplus +extern "C" { +#endif + typedef struct { MonoDomain *domain; @@ -29,4 +33,8 @@ void PyNet_Finalize(PyNet_Args *); void main_thread_handler(PyNet_Args *user_data); char *PyNet_ExceptionToString(MonoObject *); +#ifdef __cplusplus +} +#endif + #endif // PYNET_CLR_H diff --git a/src/monoclr/pynetinit.c b/src/monoclr/pynetinit.c index 7208878de..149d52296 100644 --- a/src/monoclr/pynetinit.c +++ b/src/monoclr/pynetinit.c @@ -19,6 +19,15 @@ PyNet_Args *PyNet_Init(int ext) pn_args->shutdown = NULL; pn_args->module = NULL; +#ifdef __linux__ + // Force preload libmono-2.0 as global. Without this, on some systems + // symbols from libmono are not found by libmononative (which implements + // some of the System.* namespaces). Since the only happened on Linux so + // far, we hardcode the library name, load the symbols into the global + // namespace and leak the handle. + dlopen("libmono-2.0.so", RTLD_LAZY | RTLD_GLOBAL); +#endif + if (ext == 0) { pn_args->init_name = "Python.Runtime:Initialize()"; @@ -122,7 +131,7 @@ void main_thread_handler(PyNet_Args *user_data) strcpy(new_ld_library_path, py_libdir); strcat(new_ld_library_path, ":"); strcat(new_ld_library_path, ld_library_path); - setenv("LD_LIBRARY_PATH", py_libdir, 1); + setenv("LD_LIBRARY_PATH", new_ld_library_path, 1); } } diff --git a/tools/geninterop/geninterop.py b/tools/geninterop/geninterop.py index 1f4751939..902296229 100644 --- a/tools/geninterop/geninterop.py +++ b/tools/geninterop/geninterop.py @@ -174,6 +174,8 @@ def preprocess_python_headers(): include_py = sysconfig.get_config_var("INCLUDEPY") include_dirs.append(include_py) + include_args = [c for p in include_dirs for c in ["-I", p]] + defines = [ "-D", "__attribute__(x)=", "-D", "__inline__=inline", @@ -198,7 +200,7 @@ def preprocess_python_headers(): defines.extend(("-D", "PYTHON_WITH_WIDE_UNICODE")) python_h = os.path.join(include_py, "Python.h") - cmd = ["clang", "-pthread", "-I"] + include_dirs + defines + ["-E", python_h] + cmd = ["clang", "-pthread"] + include_args + defines + ["-E", python_h] # normalize as the parser doesn't like windows line endings. lines = [] From 610d309d62cecb1767c32a3d29df2ed06d13cde8 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Tue, 17 Mar 2020 12:24:31 -0700 Subject: [PATCH 083/112] simplified Finalizer This is an attempt to make finalizer much simpler. Instead of using .NET async tasks, Py_AddPendingCall or other synchronization mechanism we simply move objects to be finalized into a ConcurrentQueue, which is periodically drained when a new PyObject is constructed. --- CHANGELOG.md | 1 + src/embed_tests/TestFinalizer.cs | 2 +- src/runtime/finalizer.cs | 98 +++++++++++--------------------- src/runtime/pyobject.cs | 31 +++++----- src/runtime/pythonengine.cs | 1 + 5 files changed, 50 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13baf557e..22098362e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - When calling C# from Python, enable passing argument of any type to a parameter of C# type `object` by wrapping it into `PyObject` instance. ([#881][i881]) - Added support for kwarg parameters when calling .NET methods from Python - Changed method for finding MSBuild using vswhere +- Reworked `Finalizer`. Now objects drop into its queue upon finalization, which is periodically drained when new objects are created. ### Fixed diff --git a/src/embed_tests/TestFinalizer.cs b/src/embed_tests/TestFinalizer.cs index 650ee5686..f82767af1 100644 --- a/src/embed_tests/TestFinalizer.cs +++ b/src/embed_tests/TestFinalizer.cs @@ -77,7 +77,7 @@ public void CollectBasicObject() } try { - Finalizer.Instance.Collect(forceDispose: false); + Finalizer.Instance.Collect(); } finally { diff --git a/src/runtime/finalizer.cs b/src/runtime/finalizer.cs index dd5c0b4dd..ba562cc26 100644 --- a/src/runtime/finalizer.cs +++ b/src/runtime/finalizer.cs @@ -28,9 +28,7 @@ public class ErrorArgs : EventArgs public bool Enable { get; set; } private ConcurrentQueue _objQueue = new ConcurrentQueue(); - private bool _pending = false; - private readonly object _collectingLock = new object(); - private Task _finalizerTask; + private int _throttled; #region FINALIZER_CHECK @@ -75,19 +73,16 @@ private Finalizer() Threshold = 200; } - public void Collect(bool forceDispose = true) + [Obsolete("forceDispose parameter is unused. All objects are disposed regardless.")] + public void Collect(bool forceDispose) => this.DisposeAll(); + public void Collect() => this.DisposeAll(); + + internal void ThrottledCollect() { - if (Instance._finalizerTask != null - && !Instance._finalizerTask.IsCompleted) - { - var ts = PythonEngine.BeginAllowThreads(); - Instance._finalizerTask.Wait(); - PythonEngine.EndAllowThreads(ts); - } - else if (forceDispose) - { - Instance.DisposeAll(); - } + _throttled = unchecked(this._throttled + 1); + if (!Enable || _throttled < Threshold) return; + _throttled = 0; + this.Collect(); } public List GetCollectedObjects() @@ -101,62 +96,18 @@ internal void AddFinalizedObject(IPyDisposable obj) { return; } - if (Runtime.Py_IsInitialized() == 0) - { - // XXX: Memory will leak if a PyObject finalized after Python shutdown, - // for avoiding that case, user should call GC.Collect manual before shutdown. - return; - } + #if FINALIZER_CHECK lock (_queueLock) #endif { - _objQueue.Enqueue(obj); - } - GC.ReRegisterForFinalize(obj); - if (!_pending && _objQueue.Count >= Threshold) - { - AddPendingCollect(); + this._objQueue.Enqueue(obj); } } internal static void Shutdown() { - if (Runtime.Py_IsInitialized() == 0) - { - Instance._objQueue = new ConcurrentQueue(); - return; - } - Instance.Collect(forceDispose: true); - } - - private void AddPendingCollect() - { - if(Monitor.TryEnter(_collectingLock)) - { - try - { - if (!_pending) - { - _pending = true; - // should already be complete but just in case - _finalizerTask?.Wait(); - - _finalizerTask = Task.Factory.StartNew(() => - { - using (Py.GIL()) - { - Instance.DisposeAll(); - _pending = false; - } - }); - } - } - finally - { - Monitor.Exit(_collectingLock); - } - } + Instance.DisposeAll(); } private void DisposeAll() @@ -178,12 +129,18 @@ private void DisposeAll() try { obj.Dispose(); - Runtime.CheckExceptionOccurred(); } catch (Exception e) { - // We should not bother the main thread - ErrorHandler?.Invoke(this, new ErrorArgs() + var handler = ErrorHandler; + if (handler is null) + { + throw new FinalizationException( + "Python object finalization failed", + disposable: obj, innerException: e); + } + + handler.Invoke(this, new ErrorArgs() { Error = e }); @@ -267,4 +224,15 @@ private void ValidateRefCount() } #endif } + + public class FinalizationException : Exception + { + public IPyDisposable Disposable { get; } + + public FinalizationException(string message, IPyDisposable disposable, Exception innerException) + : base(message, innerException) + { + this.Disposable = disposable ?? throw new ArgumentNullException(nameof(disposable)); + } + } } diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index 491c62e30..de813a855 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -30,8 +30,6 @@ public class PyObject : DynamicObject, IEnumerable, IPyDisposable #endif protected internal IntPtr obj = IntPtr.Zero; - private bool disposed = false; - private bool _finalized = false; internal BorrowedReference Reference => new BorrowedReference(obj); @@ -49,6 +47,7 @@ public PyObject(IntPtr ptr) if (ptr == IntPtr.Zero) throw new ArgumentNullException(nameof(ptr)); obj = ptr; + Finalizer.Instance.ThrottledCollect(); #if TRACE_ALLOC Traceback = new StackTrace(1); #endif @@ -64,6 +63,7 @@ internal PyObject(BorrowedReference reference) if (reference.IsNull) throw new ArgumentNullException(nameof(reference)); obj = Runtime.SelfIncRef(reference.DangerousGetAddress()); + Finalizer.Instance.ThrottledCollect(); #if TRACE_ALLOC Traceback = new StackTrace(1); #endif @@ -74,6 +74,7 @@ internal PyObject(BorrowedReference reference) [Obsolete("Please, always use PyObject(*Reference)")] protected PyObject() { + Finalizer.Instance.ThrottledCollect(); #if TRACE_ALLOC Traceback = new StackTrace(1); #endif @@ -87,12 +88,6 @@ protected PyObject() { return; } - if (_finalized || disposed) - { - return; - } - // Prevent a infinity loop by calling GC.WaitForPendingFinalizers - _finalized = true; Finalizer.Instance.AddFinalizedObject(this); } @@ -183,17 +178,19 @@ public T As() /// protected virtual void Dispose(bool disposing) { - if (!disposed) + if (this.obj == IntPtr.Zero) { - if (Runtime.Py_IsInitialized() > 0 && !Runtime.IsFinalizing) - { - IntPtr gs = PythonEngine.AcquireLock(); - Runtime.XDecref(obj); - obj = IntPtr.Zero; - PythonEngine.ReleaseLock(gs); - } - disposed = true; + return; + } + + if (Runtime.Py_IsInitialized() == 0) + throw new InvalidOperationException("Python runtime must be initialized"); + + if (!Runtime.IsFinalizing) + { + Runtime.XDecref(this.obj); } + this.obj = IntPtr.Zero; } public void Dispose() diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index df2d98641..11fc88cd4 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -80,6 +80,7 @@ public static string PythonHome } set { + // this value is null in the beginning Marshal.FreeHGlobal(_pythonHome); _pythonHome = UcsMarshaler.Py3UnicodePy2StringtoPtr(value); Runtime.Py_SetPythonHome(_pythonHome); From 72fae73386ce06153157c9392a0fb7beb8332211 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Fri, 1 May 2020 11:22:25 -0700 Subject: [PATCH 084/112] check if PyObject.Dispose throws any Python exceptions --- src/runtime/clrobject.cs | 4 ++-- src/runtime/pyobject.cs | 24 +++++++++++++++++++++++- src/runtime/runtime.cs | 5 +++-- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/runtime/clrobject.cs b/src/runtime/clrobject.cs index 502677655..a596c97b2 100644 --- a/src/runtime/clrobject.cs +++ b/src/runtime/clrobject.cs @@ -35,13 +35,13 @@ internal CLRObject(object ob, IntPtr tp) } - internal static CLRObject GetInstance(object ob, IntPtr pyType) + static CLRObject GetInstance(object ob, IntPtr pyType) { return new CLRObject(ob, pyType); } - internal static CLRObject GetInstance(object ob) + static CLRObject GetInstance(object ob) { ClassBase cc = ClassManager.GetClass(ob.GetType()); return GetInstance(ob, cc.tpHandle); diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index de813a855..699ebf873 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -188,7 +188,29 @@ protected virtual void Dispose(bool disposing) if (!Runtime.IsFinalizing) { - Runtime.XDecref(this.obj); + long refcount = Runtime.Refcount(this.obj); + Debug.Assert(refcount > 0, "Object refcount is 0 or less"); + + if (refcount == 1) + { + Runtime.PyErr_Fetch(out var errType, out var errVal, out var traceback); + + try + { + Runtime.XDecref(this.obj); + Runtime.CheckExceptionOccurred(); + } + finally + { + // Python requires finalizers to preserve exception: + // https://docs.python.org/3/extending/newtypes.html#finalization-and-de-allocation + Runtime.PyErr_Restore(errType, errVal, traceback); + } + } + else + { + Runtime.XDecref(this.obj); + } } this.obj = IntPtr.Zero; } diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 2e06c9cba..3d2610fea 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.Contracts; using System.Runtime.InteropServices; using System.Security; using System.Text; @@ -8,7 +9,6 @@ namespace Python.Runtime { - /// /// Encapsulates the low-level Python C API. Note that it is /// the responsibility of the caller to have acquired the GIL @@ -106,7 +106,7 @@ public class Runtime internal static object IsFinalizingLock = new object(); internal static bool IsFinalizing; - internal static bool Is32Bit = IntPtr.Size == 4; + internal static bool Is32Bit => IntPtr.Size == 4; // .NET core: System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows) internal static bool IsWindows = Environment.OSVersion.Platform == PlatformID.Win32NT; @@ -659,6 +659,7 @@ internal static unsafe void XDecref(IntPtr op) #endif } + [Pure] internal static unsafe long Refcount(IntPtr op) { var p = (void*)op; From 467d1fd8502da17b96479ad9525524eca0ce60d2 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Fri, 27 Sep 2019 21:30:04 -0700 Subject: [PATCH 085/112] allow excluding public .NET types from being exposed to Python Introduced PyExportAttribute and handling for it in AssemblyManager --- CHANGELOG.md | 1 + setup.py | 2 ++ src/runtime/PyExportAttribute.cs | 16 ++++++++++++++++ src/runtime/Python.Runtime.csproj | 18 ++++++++++-------- src/runtime/assemblymanager.cs | 16 +++++++++++----- src/runtime/polyfill/ReflectionPolifills.cs | 21 +++++++++++++++++++-- src/testing/Python.Test.csproj | 1 + src/testing/nonexportable.cs | 8 ++++++++ src/tests/test_class.py | 10 ++++++++-- 9 files changed, 76 insertions(+), 17 deletions(-) create mode 100644 src/runtime/PyExportAttribute.cs create mode 100644 src/testing/nonexportable.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 22098362e..f3f801841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added function that sets Py_NoSiteFlag to 1. - Added support for Jetson Nano. - Added support for __len__ for .NET classes that implement ICollection +- Added `PyExport` attribute to hide .NET types from Python - Added PythonException.Format method to format exceptions the same as traceback.format_exception - Added Runtime.None to be able to pass None as parameter into Python from .NET - Added PyObject.IsNone() to check if a Python object is None in .NET. diff --git a/setup.py b/setup.py index ed0852bb5..370058041 100644 --- a/setup.py +++ b/setup.py @@ -306,6 +306,7 @@ def build_extension(self, ext): _config = "{0}Win".format(CONFIG) _solution_file = "pythonnet.sln" _custom_define_constants = False + defines.append("NET40") elif DEVTOOLS == "MsDev15": _xbuild = '"{0}"'.format(self._find_msbuild_tool_15()) _config = "{0}Win".format(CONFIG) @@ -316,6 +317,7 @@ def build_extension(self, ext): _config = "{0}Mono".format(CONFIG) _solution_file = "pythonnet.sln" _custom_define_constants = False + defines.append("NET40") elif DEVTOOLS == "dotnet": _xbuild = "dotnet msbuild" _config = "{0}Mono".format(CONFIG) diff --git a/src/runtime/PyExportAttribute.cs b/src/runtime/PyExportAttribute.cs new file mode 100644 index 000000000..52a8be15d --- /dev/null +++ b/src/runtime/PyExportAttribute.cs @@ -0,0 +1,16 @@ +namespace Python.Runtime { + using System; + + /// + /// Controls visibility to Python for public .NET type or an entire assembly + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Delegate | AttributeTargets.Enum + | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Assembly, + AllowMultiple = false, + Inherited = false)] + public class PyExportAttribute : Attribute + { + internal readonly bool Export; + public PyExportAttribute(bool export) { this.Export = export; } + } +} diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 2e47805cb..46ad6fcae 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -29,46 +29,46 @@ x64 --> - PYTHON2;PYTHON27;UCS4 + NET40;PYTHON2;PYTHON27;UCS4 true pdbonly - PYTHON3;PYTHON38;UCS4 + NET40;PYTHON3;PYTHON38;UCS4 true pdbonly true - PYTHON2;PYTHON27;UCS4;TRACE;DEBUG + NET40;PYTHON2;PYTHON27;UCS4;TRACE;DEBUG false full true - PYTHON3;PYTHON38;UCS4;TRACE;DEBUG + NET40;PYTHON3;PYTHON38;UCS4;TRACE;DEBUG false full - PYTHON2;PYTHON27;UCS2 + NET40;PYTHON2;PYTHON27;UCS2 true pdbonly - PYTHON3;PYTHON38;UCS2 + NET40;PYTHON3;PYTHON38;UCS2 true pdbonly true - PYTHON2;PYTHON27;UCS2;TRACE;DEBUG + NET40;PYTHON2;PYTHON27;UCS2;TRACE;DEBUG false full true - PYTHON3;PYTHON38;UCS2;TRACE;DEBUG + NET40;PYTHON3;PYTHON38;UCS2;TRACE;DEBUG false full @@ -131,6 +131,7 @@ + @@ -151,6 +152,7 @@ + diff --git a/src/runtime/assemblymanager.cs b/src/runtime/assemblymanager.cs index e4ddaf915..46909a370 100644 --- a/src/runtime/assemblymanager.cs +++ b/src/runtime/assemblymanager.cs @@ -346,6 +346,10 @@ public static bool LoadImplicit(string name, bool warn = true) /// internal static void ScanAssembly(Assembly assembly) { + if (assembly.GetCustomAttribute()?.Export == false) + { + return; + } // A couple of things we want to do here: first, we want to // gather a list of all of the namespaces contributed to by // the assembly. @@ -458,7 +462,7 @@ public static Type LookupType(string qname) foreach (Assembly assembly in assemblies) { Type type = assembly.GetType(qname); - if (type != null) + if (type != null && IsExported(type)) { return type; } @@ -472,7 +476,7 @@ public static Type LookupType(string qname) /// type. /// public static IEnumerable LookupTypes(string qualifiedName) - => assemblies.Select(assembly => assembly.GetType(qualifiedName)).Where(type => type != null); + => assemblies.Select(assembly => assembly.GetType(qualifiedName)).Where(type => type != null && IsExported(type)); internal static Type[] GetTypes(Assembly a) { @@ -480,19 +484,19 @@ internal static Type[] GetTypes(Assembly a) { try { - return a.GetTypes(); + return a.GetTypes().Where(IsExported).ToArray(); } catch (ReflectionTypeLoadException exc) { // Return all types that were successfully loaded - return exc.Types.Where(x => x != null).ToArray(); + return exc.Types.Where(x => x != null && IsExported(x)).ToArray(); } } else { try { - return a.GetExportedTypes(); + return a.GetExportedTypes().Where(IsExported).ToArray(); } catch (FileNotFoundException) { @@ -500,5 +504,7 @@ internal static Type[] GetTypes(Assembly a) } } } + + static bool IsExported(Type type) => type.GetCustomAttribute()?.Export != false; } } diff --git a/src/runtime/polyfill/ReflectionPolifills.cs b/src/runtime/polyfill/ReflectionPolifills.cs index a7e9c879a..7a3609b9c 100644 --- a/src/runtime/polyfill/ReflectionPolifills.cs +++ b/src/runtime/polyfill/ReflectionPolifills.cs @@ -1,12 +1,14 @@ using System; +using System.Linq; using System.Reflection; using System.Reflection.Emit; namespace Python.Runtime { -#if NETSTANDARD + [Obsolete("This API is for internal use only")] public static class ReflectionPolifills { +#if NETSTANDARD public static AssemblyBuilder DefineDynamicAssembly(this AppDomain appDomain, AssemblyName assemblyName, AssemblyBuilderAccess assemblyBuilderAccess) { return AssemblyBuilder.DefineDynamicAssembly(assemblyName, assemblyBuilderAccess); @@ -16,6 +18,21 @@ public static Type CreateType(this TypeBuilder typeBuilder) { return typeBuilder.GetTypeInfo().GetType(); } - } #endif +#if NET40 + public static T GetCustomAttribute(this Type type) where T: Attribute + { + return type.GetCustomAttributes(typeof(T), inherit: false) + .Cast() + .SingleOrDefault(); + } + + public static T GetCustomAttribute(this Assembly assembly) where T: Attribute + { + return assembly.GetCustomAttributes(typeof(T), inherit: false) + .Cast() + .SingleOrDefault(); + } +#endif + } } diff --git a/src/testing/Python.Test.csproj b/src/testing/Python.Test.csproj index 515fd928c..63526c060 100644 --- a/src/testing/Python.Test.csproj +++ b/src/testing/Python.Test.csproj @@ -92,6 +92,7 @@ + diff --git a/src/testing/nonexportable.cs b/src/testing/nonexportable.cs new file mode 100644 index 000000000..a29c78589 --- /dev/null +++ b/src/testing/nonexportable.cs @@ -0,0 +1,8 @@ +namespace Python.Test +{ + using Python.Runtime; + + // this class should not be visible to Python + [PyExport(false)] + public class NonExportable { } +} diff --git a/src/tests/test_class.py b/src/tests/test_class.py index 612ce442e..6db3c36b7 100644 --- a/src/tests/test_class.py +++ b/src/tests/test_class.py @@ -14,7 +14,6 @@ def test_basic_reference_type(): """Test usage of CLR defined reference types.""" assert System.String.Empty == "" - def test_basic_value_type(): """Test usage of CLR defined value types.""" assert System.Int32.MaxValue == 2147483647 @@ -29,7 +28,6 @@ def test_class_standard_attrs(): assert isinstance(ClassTest.__dict__, DictProxyType) assert len(ClassTest.__doc__) > 0 - def test_class_docstrings(): """Test standard class docstring generation""" from Python.Test import ClassTest @@ -58,6 +56,14 @@ def test_non_public_class(): with pytest.raises(AttributeError): _ = Test.InternalClass +def test_non_exported(): + """Test [PyExport(false)]""" + with pytest.raises(ImportError): + from Python.Test import NonExportable + + with pytest.raises(AttributeError): + _ = Test.NonExportable + def test_basic_subclass(): """Test basic subclass of a managed class.""" From 2699fdcca4731d75ed6d24d1081dd5f243a91869 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Fri, 1 May 2020 11:02:10 -0700 Subject: [PATCH 086/112] temporarily remove NET40 define to see if we can get going without it --- setup.py | 2 -- src/runtime/Python.Runtime.csproj | 16 ++++++++-------- src/runtime/polyfill/ReflectionPolifills.cs | 2 -- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index 370058041..ed0852bb5 100644 --- a/setup.py +++ b/setup.py @@ -306,7 +306,6 @@ def build_extension(self, ext): _config = "{0}Win".format(CONFIG) _solution_file = "pythonnet.sln" _custom_define_constants = False - defines.append("NET40") elif DEVTOOLS == "MsDev15": _xbuild = '"{0}"'.format(self._find_msbuild_tool_15()) _config = "{0}Win".format(CONFIG) @@ -317,7 +316,6 @@ def build_extension(self, ext): _config = "{0}Mono".format(CONFIG) _solution_file = "pythonnet.sln" _custom_define_constants = False - defines.append("NET40") elif DEVTOOLS == "dotnet": _xbuild = "dotnet msbuild" _config = "{0}Mono".format(CONFIG) diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 46ad6fcae..75f5e2fab 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -29,46 +29,46 @@ x64 --> - NET40;PYTHON2;PYTHON27;UCS4 + PYTHON2;PYTHON27;UCS4 true pdbonly - NET40;PYTHON3;PYTHON38;UCS4 + PYTHON3;PYTHON38;UCS4 true pdbonly true - NET40;PYTHON2;PYTHON27;UCS4;TRACE;DEBUG + PYTHON2;PYTHON27;UCS4;TRACE;DEBUG false full true - NET40;PYTHON3;PYTHON38;UCS4;TRACE;DEBUG + PYTHON3;PYTHON38;UCS4;TRACE;DEBUG false full - NET40;PYTHON2;PYTHON27;UCS2 + PYTHON2;PYTHON27;UCS2 true pdbonly - NET40;PYTHON3;PYTHON38;UCS2 + PYTHON3;PYTHON38;UCS2 true pdbonly true - NET40;PYTHON2;PYTHON27;UCS2;TRACE;DEBUG + PYTHON2;PYTHON27;UCS2;TRACE;DEBUG false full true - NET40;PYTHON3;PYTHON38;UCS2;TRACE;DEBUG + PYTHON3;PYTHON38;UCS2;TRACE;DEBUG false full diff --git a/src/runtime/polyfill/ReflectionPolifills.cs b/src/runtime/polyfill/ReflectionPolifills.cs index 7a3609b9c..b9ce78d63 100644 --- a/src/runtime/polyfill/ReflectionPolifills.cs +++ b/src/runtime/polyfill/ReflectionPolifills.cs @@ -19,7 +19,6 @@ public static Type CreateType(this TypeBuilder typeBuilder) return typeBuilder.GetTypeInfo().GetType(); } #endif -#if NET40 public static T GetCustomAttribute(this Type type) where T: Attribute { return type.GetCustomAttributes(typeof(T), inherit: false) @@ -33,6 +32,5 @@ public static T GetCustomAttribute(this Assembly assembly) where T: Attribute .Cast() .SingleOrDefault(); } -#endif } } From 6d2f0bda8cfb46793b1fda0798bc549466636ddd Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 14 May 2020 12:31:50 -0700 Subject: [PATCH 087/112] Improve "No constructor matches given arguments" message with more details (#1143) Similar to #900, but for constructors (reuses the same code) Related issues: #811, #265, #1116 --- src/runtime/constructorbinder.cs | 11 +++++- src/runtime/methodbinder.cs | 59 +++++++++++++++++++------------- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/runtime/constructorbinder.cs b/src/runtime/constructorbinder.cs index 1fc541920..0f9806c0e 100644 --- a/src/runtime/constructorbinder.cs +++ b/src/runtime/constructorbinder.cs @@ -1,5 +1,6 @@ using System; using System.Reflection; +using System.Text; namespace Python.Runtime { @@ -93,7 +94,15 @@ internal object InvokeRaw(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info) if (binding == null) { - Exceptions.SetError(Exceptions.TypeError, "no constructor matches given arguments"); + var errorMessage = new StringBuilder("No constructor matches given arguments"); + if (info != null && info.IsConstructor && info.DeclaringType != null) + { + errorMessage.Append(" for ").Append(info.DeclaringType.Name); + } + + errorMessage.Append(": "); + AppendArgumentTypes(to: errorMessage, args); + Exceptions.SetError(Exceptions.TypeError, errorMessage.ToString()); return null; } } diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs index 4e8698da1..64e038897 100644 --- a/src/runtime/methodbinder.cs +++ b/src/runtime/methodbinder.cs @@ -599,6 +599,40 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i return Invoke(inst, args, kw, info, null); } + protected static void AppendArgumentTypes(StringBuilder to, IntPtr args) + { + long argCount = Runtime.PyTuple_Size(args); + to.Append("("); + for (long argIndex = 0; argIndex < argCount; argIndex++) + { + var arg = Runtime.PyTuple_GetItem(args, argIndex); + if (arg != IntPtr.Zero) + { + var type = Runtime.PyObject_Type(arg); + if (type != IntPtr.Zero) + { + try + { + var description = Runtime.PyObject_Unicode(type); + if (description != IntPtr.Zero) + { + to.Append(Runtime.GetManagedString(description)); + Runtime.XDecref(description); + } + } + finally + { + Runtime.XDecref(type); + } + } + } + + if (argIndex + 1 < argCount) + to.Append(", "); + } + to.Append(')'); + } + internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, MethodInfo[] methodinfo) { Binding binding = Bind(inst, args, kw, info, methodinfo); @@ -613,29 +647,8 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i value.Append($" for {methodinfo[0].Name}"); } - long argCount = Runtime.PyTuple_Size(args); - value.Append(": ("); - for(long argIndex = 0; argIndex < argCount; argIndex++) { - var arg = Runtime.PyTuple_GetItem(args, argIndex); - if (arg != IntPtr.Zero) { - var type = Runtime.PyObject_Type(arg); - if (type != IntPtr.Zero) { - try { - var description = Runtime.PyObject_Unicode(type); - if (description != IntPtr.Zero) { - value.Append(Runtime.GetManagedString(description)); - Runtime.XDecref(description); - } - } finally { - Runtime.XDecref(type); - } - } - } - - if (argIndex + 1 < argCount) - value.Append(", "); - } - value.Append(')'); + value.Append(": "); + AppendArgumentTypes(to: value, args); Exceptions.SetError(Exceptions.TypeError, value.ToString()); return IntPtr.Zero; } From d8f5ab01d521afe4f62b9e6ceab9a79bf254a21a Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 14 May 2020 21:33:44 +0200 Subject: [PATCH 088/112] Only run conda builds for tagged commits --- ci/appveyor_build_recipe.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/appveyor_build_recipe.ps1 b/ci/appveyor_build_recipe.ps1 index 08eae8d5d..ecb7708f2 100644 --- a/ci/appveyor_build_recipe.ps1 +++ b/ci/appveyor_build_recipe.ps1 @@ -13,7 +13,7 @@ if ($env:PLATFORM -eq "x86"){ $env:CONDA_BLD = "$env:CONDA_BLD" + "-x64" } -if ($env:APPVEYOR_PULL_REQUEST_NUMBER -or $env:APPVEYOR_REPO_TAG_NAME -or $env:FORCE_CONDA_BUILD -eq "True") { +if ($env:APPVEYOR_REPO_TAG_NAME -or $env:FORCE_CONDA_BUILD -eq "True") { # Update PATH, and keep a copy to restore at end of this PowerShell script $old_path = $env:path $env:path = "$env:CONDA_BLD;$env:CONDA_BLD\Scripts;" + $env:path From d4eac3afb96112be76d97683d28fbaa7bbfa1dbf Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 10 May 2020 16:34:22 +0200 Subject: [PATCH 089/112] Reactivate Python 3.8 CI build --- .travis.yml | 1 + appveyor.yml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index d728d9a2d..b5e358c30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ python: - 3.5 - 3.6 - 3.7 + - 3.8 env: matrix: diff --git a/appveyor.yml b/appveyor.yml index 445f9bb5a..73c84381b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,10 +23,13 @@ environment: BUILD_OPTS: --xplat - PYTHON_VERSION: 3.7 BUILD_OPTS: --xplat + - PYTHON_VERSION: 3.8 + BUILD_OPTS: --xplat - PYTHON_VERSION: 2.7 - PYTHON_VERSION: 3.5 - PYTHON_VERSION: 3.6 - PYTHON_VERSION: 3.7 + - PYTHON_VERSION: 3.8 matrix: allow_failures: From b17b9d3a0e0ce13d32995dc16c36d34664ab8317 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 15 May 2020 00:44:43 +0200 Subject: [PATCH 090/112] Set __classcell__ if it exists If `PyCell_Set` is not called with a cell it will raise an exception, which is perfectly valid here. --- src/runtime/runtime.cs | 9 +++++++++ src/runtime/typemanager.cs | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 3d2610fea..5d5e6e68e 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1966,6 +1966,15 @@ internal static IntPtr PyMem_Realloc(IntPtr ptr, long size) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyErr_Print(); + //==================================================================== + // Cell API + //==================================================================== + + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern NewReference PyCell_Get(IntPtr cell); + + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern int PyCell_Set(IntPtr cell, IntPtr value); //==================================================================== // Miscellaneous diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index bb920b74f..8658c937a 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -280,6 +280,14 @@ internal static IntPtr CreateSubType(IntPtr py_name, IntPtr py_base_type, IntPtr IntPtr cls_dict = Marshal.ReadIntPtr(py_type, TypeOffset.tp_dict); Runtime.PyDict_Update(cls_dict, py_dict); + // Update the __classcell__ if it exists + IntPtr cell = Runtime.PyDict_GetItemString(cls_dict, "__classcell__"); + if (cell != IntPtr.Zero) + { + Runtime.PyCell_Set(cell, py_type); + Runtime.PyDict_DelItemString(cls_dict, "__classcell__"); + } + return py_type; } catch (Exception e) From a9b91a3405d972cf3f8b96c669919a4537113d9a Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 15 May 2020 19:08:13 +0200 Subject: [PATCH 091/112] Make the newest python versions run first in CI --- .travis.yml | 8 ++++---- appveyor.yml | 22 ++++++++-------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index b5e358c30..2062a35da 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,11 @@ dist: xenial sudo: false language: python python: - - 2.7 - - 3.5 - - 3.6 - - 3.7 - 3.8 + - 3.7 + - 3.6 + - 3.5 + - 2.7 env: matrix: diff --git a/appveyor.yml b/appveyor.yml index 73c84381b..363e67389 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -15,27 +15,21 @@ environment: CODECOV_ENV: PYTHON_VERSION, PLATFORM matrix: - - PYTHON_VERSION: 2.7 + - PYTHON_VERSION: 3.8 BUILD_OPTS: --xplat - - PYTHON_VERSION: 3.5 + - PYTHON_VERSION: 3.7 BUILD_OPTS: --xplat - PYTHON_VERSION: 3.6 BUILD_OPTS: --xplat - - PYTHON_VERSION: 3.7 + - PYTHON_VERSION: 3.5 BUILD_OPTS: --xplat - - PYTHON_VERSION: 3.8 + - PYTHON_VERSION: 2.7 BUILD_OPTS: --xplat - - PYTHON_VERSION: 2.7 - - PYTHON_VERSION: 3.5 - - PYTHON_VERSION: 3.6 - - PYTHON_VERSION: 3.7 - PYTHON_VERSION: 3.8 - -matrix: - allow_failures: - - PYTHON_VERSION: 3.4 - BUILD_OPTS: --xplat - - PYTHON_VERSION: 3.4 + - PYTHON_VERSION: 3.7 + - PYTHON_VERSION: 3.6 + - PYTHON_VERSION: 3.5 + - PYTHON_VERSION: 2.7 init: # Update Environment Variables based on matrix/platform From e213a9745da46b02ab9c29b5949890aa9da7ca75 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 15 May 2020 19:09:03 +0200 Subject: [PATCH 092/112] Implement InitializePlatformData without calling Python --- src/runtime/runtime.cs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 5d5e6e68e..eea04b64d 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -356,6 +356,7 @@ internal static void Initialize(bool initSigs = false) /// private static void InitializePlatformData() { +#if !NETSTANDARD IntPtr op; IntPtr fn; IntPtr platformModule = PyImport_ImportModule("platform"); @@ -391,6 +392,34 @@ private static void InitializePlatformData() MType = MachineType.Other; } Machine = MType; +#else + OperatingSystem = OperatingSystemType.Other; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + OperatingSystem = OperatingSystemType.Linux; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + OperatingSystem = OperatingSystemType.Darwin; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + OperatingSystem = OperatingSystemType.Windows; + + switch (RuntimeInformation.ProcessArchitecture) + { + case Architecture.X86: + Machine = MachineType.i386; + break; + case Architecture.X64: + Machine = MachineType.x86_64; + break; + case Architecture.Arm: + Machine = MachineType.armv7l; + break; + case Architecture.Arm64: + Machine = MachineType.aarch64; + break; + default: + Machine = MachineType.Other; + break; + } +#endif } internal static void Shutdown() From a7949b9ff0d0e3fd6fd64b7fc0b82111dfbaebbb Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 15 May 2020 19:23:49 +0200 Subject: [PATCH 093/112] Stop exposing Python's MachineName on Runtime --- src/embed_tests/TestRuntime.cs | 2 -- src/runtime/runtime.cs | 9 ++------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/embed_tests/TestRuntime.cs b/src/embed_tests/TestRuntime.cs index 157fe4cb7..6ab8e18c7 100644 --- a/src/embed_tests/TestRuntime.cs +++ b/src/embed_tests/TestRuntime.cs @@ -29,8 +29,6 @@ public static void PlatformCache() Runtime.Runtime.Initialize(); Assert.That(Runtime.Runtime.Machine, Is.Not.EqualTo(MachineType.Other)); - Assert.That(!string.IsNullOrEmpty(Runtime.Runtime.MachineName)); - Assert.That(Runtime.Runtime.OperatingSystem, Is.Not.EqualTo(OperatingSystemType.Other)); Assert.That(!string.IsNullOrEmpty(Runtime.Runtime.OperatingSystemName)); diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index eea04b64d..5ed3ed824 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -154,11 +154,6 @@ public class Runtime /// public static MachineType Machine { get; private set; }/* set in Initialize using python's platform.machine */ - /// - /// Gets the machine architecture as reported by python's platform.machine(). - /// - public static string MachineName { get; private set; } - internal static bool IsPython2 = pyversionnumber < 30; internal static bool IsPython3 = pyversionnumber >= 30; @@ -370,7 +365,7 @@ private static void InitializePlatformData() fn = PyObject_GetAttrString(platformModule, "machine"); op = PyObject_Call(fn, emptyTuple, IntPtr.Zero); - MachineName = GetManagedString(op); + string machineName = GetManagedString(op); XDecref(op); XDecref(fn); @@ -387,7 +382,7 @@ private static void InitializePlatformData() OperatingSystem = OSType; MachineType MType; - if (!MachineTypeMapping.TryGetValue(MachineName.ToLower(), out MType)) + if (!MachineTypeMapping.TryGetValue(machineName.ToLower(), out MType)) { MType = MachineType.Other; } From c6ae15b6cb71bd22e67b4d32e3267f3e74012e1f Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 15 May 2020 19:50:16 +0200 Subject: [PATCH 094/112] Drop OperatingSystemName from the interface as well --- src/embed_tests/TestRuntime.cs | 1 - src/runtime/runtime.cs | 15 +++++---------- src/runtime/typemanager.cs | 4 ++-- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/embed_tests/TestRuntime.cs b/src/embed_tests/TestRuntime.cs index 6ab8e18c7..4129d3df3 100644 --- a/src/embed_tests/TestRuntime.cs +++ b/src/embed_tests/TestRuntime.cs @@ -30,7 +30,6 @@ public static void PlatformCache() Assert.That(Runtime.Runtime.Machine, Is.Not.EqualTo(MachineType.Other)); Assert.That(Runtime.Runtime.OperatingSystem, Is.Not.EqualTo(OperatingSystemType.Other)); - Assert.That(!string.IsNullOrEmpty(Runtime.Runtime.OperatingSystemName)); // Don't shut down the runtime: if the python engine was initialized // but not shut down by another test, we'd end up in a bad state. diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 5ed3ed824..2893f141a 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -123,12 +123,6 @@ public class Runtime /// public static OperatingSystemType OperatingSystem { get; private set; } - /// - /// Gets the operating system as reported by python's platform.system(). - /// - public static string OperatingSystemName { get; private set; } - - /// /// Map lower-case version of the python machine name to the processor /// type. There are aliases, e.g. x86_64 and amd64 are two names for @@ -359,7 +353,7 @@ private static void InitializePlatformData() fn = PyObject_GetAttrString(platformModule, "system"); op = PyObject_Call(fn, emptyTuple, IntPtr.Zero); - OperatingSystemName = GetManagedString(op); + string operatingSystemName = GetManagedString(op); XDecref(op); XDecref(fn); @@ -375,7 +369,7 @@ private static void InitializePlatformData() // Now convert the strings into enum values so we can do switch // statements rather than constant parsing. OperatingSystemType OSType; - if (!OperatingSystemTypeMapping.TryGetValue(OperatingSystemName, out OSType)) + if (!OperatingSystemTypeMapping.TryGetValue(operatingSystemName, out OSType)) { OSType = OperatingSystemType.Other; } @@ -388,14 +382,15 @@ private static void InitializePlatformData() } Machine = MType; #else - OperatingSystem = OperatingSystemType.Other; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) OperatingSystem = OperatingSystemType.Linux; else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) OperatingSystem = OperatingSystemType.Darwin; else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) OperatingSystem = OperatingSystemType.Windows; - + else + OperatingSystem = OperatingSystemType.Other; + switch (RuntimeInformation.ProcessArchitecture) { case Architecture.X86: diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index 8658c937a..f984e5d87 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -625,7 +625,7 @@ int MAP_ANONYMOUS case OperatingSystemType.Linux: return 0x20; default: - throw new NotImplementedException($"mmap is not supported on {Runtime.OperatingSystemName}"); + throw new NotImplementedException($"mmap is not supported on this operating system"); } } } @@ -659,7 +659,7 @@ internal static IMemoryMapper CreateMemoryMapper() case OperatingSystemType.Windows: return new WindowsMemoryMapper(); default: - throw new NotImplementedException($"No support for {Runtime.OperatingSystemName}"); + throw new NotImplementedException($"No support for this operating system"); } } From 8e54d2686937017fc442e8e204bd84ff1fea91c1 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 16 May 2020 00:17:30 +0200 Subject: [PATCH 095/112] Code review adjustments - Use `BorrowedReference` where applicable - Readd `OperatingSystemName` and `MachineName` for now --- src/runtime/runtime.cs | 11 +++++++++-- src/runtime/typemanager.cs | 12 ++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 2893f141a..0e391a134 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1,3 +1,4 @@ +using System.Reflection.Emit; using System; using System.Diagnostics.Contracts; using System.Runtime.InteropServices; @@ -118,6 +119,12 @@ public class Runtime { "Linux", OperatingSystemType.Linux }, }; + [Obsolete] + public static string OperatingSystemName => OperatingSystem.ToString(); + + [Obsolete] + public static string MachineName => Machine.ToString(); + /// /// Gets the operating system as reported by python's platform.system(). /// @@ -1990,10 +1997,10 @@ internal static IntPtr PyMem_Realloc(IntPtr ptr, long size) //==================================================================== [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern NewReference PyCell_Get(IntPtr cell); + internal static extern NewReference PyCell_Get(BorrowedReference cell); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern int PyCell_Set(IntPtr cell, IntPtr value); + internal static extern int PyCell_Set(BorrowedReference cell, IntPtr value); //==================================================================== // Miscellaneous diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index f984e5d87..7d73b0138 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -281,8 +281,8 @@ internal static IntPtr CreateSubType(IntPtr py_name, IntPtr py_base_type, IntPtr Runtime.PyDict_Update(cls_dict, py_dict); // Update the __classcell__ if it exists - IntPtr cell = Runtime.PyDict_GetItemString(cls_dict, "__classcell__"); - if (cell != IntPtr.Zero) + var cell = new BorrowedReference(Runtime.PyDict_GetItemString(cls_dict, "__classcell__")); + if (!cell.IsNull) { Runtime.PyCell_Set(cell, py_type); Runtime.PyDict_DelItemString(cls_dict, "__classcell__"); @@ -625,7 +625,9 @@ int MAP_ANONYMOUS case OperatingSystemType.Linux: return 0x20; default: - throw new NotImplementedException($"mmap is not supported on this operating system"); + throw new NotImplementedException( + $"mmap is not supported on {Runtime.OperatingSystem}" + ); } } } @@ -659,7 +661,9 @@ internal static IMemoryMapper CreateMemoryMapper() case OperatingSystemType.Windows: return new WindowsMemoryMapper(); default: - throw new NotImplementedException($"No support for this operating system"); + throw new NotImplementedException( + $"No support for {Runtime.OperatingSystem}" + ); } } From ed2b7e8605c2c3e3b546b97dec70c8ad888ce2c5 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 16 May 2020 18:59:24 +0200 Subject: [PATCH 096/112] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3f801841..3cbf85d45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added PythonException.Format method to format exceptions the same as traceback.format_exception - Added Runtime.None to be able to pass None as parameter into Python from .NET - Added PyObject.IsNone() to check if a Python object is None in .NET. +- Support for Python 3.8 ### Changed @@ -29,6 +30,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added support for kwarg parameters when calling .NET methods from Python - Changed method for finding MSBuild using vswhere - Reworked `Finalizer`. Now objects drop into its queue upon finalization, which is periodically drained when new objects are created. +- Marked `Runtime.OperatingSystemName` and `Runtime.MachineName` as `Obsolete`, should never have been `public` in the first place. They also don't necessarily return a result that matches the `platform` module's. ### Fixed @@ -37,6 +39,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Fixes bug where delegates get casts (dotnetcore) - Determine size of interpreter longs at runtime - Handling exceptions ocurred in ModuleObject's getattribute +- Fill `__classcell__` correctly for Python subclasses of .NET types ## [2.4.0][] From 4a92d80a4b8daa9d16f85ce9c5ccaaa6c812fab8 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 16 May 2020 22:34:48 +0200 Subject: [PATCH 097/112] Improve performance of unwrapping .NET objects passed from Python (#930) This addresses the following scenario: 1. A C# object `a` is created and filled with some data. 2. `a` is passed via Python.NET to Python. To do that Python.NET creates a wrapper object `w`, and stores reference to `a` in one of its fields. 3. Python code later passes `w` back to C#, e.g. calls `SomeCSharpMethod(w)`. 4. Python.NET has to unwrap `w`, so it reads the reference to `a` from it. Prior to this change in 4. Python.NET had to determine what kind of an object `a` is. If it is an exception, a different offset needed to be used. That check was very expensive (up to 4 calls into Python interpreter). This change replaces that check with computing offset unconditionally by subtracting a constant from the object size (which is read from the wrapper), thus avoiding calls to Python interpreter. Co-authored-by: Victor Milovanov --- CHANGELOG.md | 1 + src/perf_tests/BenchmarkTests.cs | 4 +- src/runtime/classbase.cs | 2 +- src/runtime/classderived.cs | 2 +- src/runtime/clrobject.cs | 4 +- src/runtime/interop.cs | 136 ++++++++++++++++++++++--------- src/runtime/managedtype.cs | 2 +- src/runtime/moduleobject.cs | 2 +- src/runtime/runtime.cs | 5 ++ src/runtime/typemanager.cs | 6 +- 10 files changed, 113 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cbf85d45..e3da7ae4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added argument types information to "No method matches given arguments" message - Moved wheel import in setup.py inside of a try/except to prevent pip collection failures - Removes PyLong_GetMax and PyClass_New when targetting Python3 +- Improved performance of calls from Python to C# - Added support for converting python iterators to C# arrays - Changed usage of obselete function GetDelegateForFunctionPointer(IntPtr, Type) to GetDelegateForFunctionPointer(IntPtr) - When calling C# from Python, enable passing argument of any type to a parameter of C# type `object` by wrapping it into `PyObject` instance. ([#881][i881]) diff --git a/src/perf_tests/BenchmarkTests.cs b/src/perf_tests/BenchmarkTests.cs index baa825ca8..6e0afca69 100644 --- a/src/perf_tests/BenchmarkTests.cs +++ b/src/perf_tests/BenchmarkTests.cs @@ -30,14 +30,14 @@ public void SetUp() public void ReadInt64Property() { double optimisticPerfRatio = GetOptimisticPerfRatio(this.summary.Reports); - AssertPerformanceIsBetterOrSame(optimisticPerfRatio, target: 0.66); + AssertPerformanceIsBetterOrSame(optimisticPerfRatio, target: 0.57); } [Test] public void WriteInt64Property() { double optimisticPerfRatio = GetOptimisticPerfRatio(this.summary.Reports); - AssertPerformanceIsBetterOrSame(optimisticPerfRatio, target: 0.64); + AssertPerformanceIsBetterOrSame(optimisticPerfRatio, target: 0.57); } static double GetOptimisticPerfRatio( diff --git a/src/runtime/classbase.cs b/src/runtime/classbase.cs index 41636c404..144bac9d3 100644 --- a/src/runtime/classbase.cs +++ b/src/runtime/classbase.cs @@ -291,7 +291,7 @@ public static IntPtr tp_repr(IntPtr ob) public static void tp_dealloc(IntPtr ob) { ManagedType self = GetManagedObject(ob); - IntPtr dict = Marshal.ReadIntPtr(ob, ObjectOffset.DictOffset(ob)); + IntPtr dict = Marshal.ReadIntPtr(ob, ObjectOffset.TypeDictOffset(self.tpHandle)); if (dict != IntPtr.Zero) { Runtime.XDecref(dict); diff --git a/src/runtime/classderived.cs b/src/runtime/classderived.cs index ec3734ea5..af16b1359 100644 --- a/src/runtime/classderived.cs +++ b/src/runtime/classderived.cs @@ -877,7 +877,7 @@ public static void Finalize(IPythonDerivedType obj) // the C# object is being destroyed which must mean there are no more // references to the Python object as well so now we can dealloc the // python object. - IntPtr dict = Marshal.ReadIntPtr(self.pyHandle, ObjectOffset.DictOffset(self.pyHandle)); + IntPtr dict = Marshal.ReadIntPtr(self.pyHandle, ObjectOffset.TypeDictOffset(self.tpHandle)); if (dict != IntPtr.Zero) { Runtime.XDecref(dict); diff --git a/src/runtime/clrobject.cs b/src/runtime/clrobject.cs index a596c97b2..5c7ad7891 100644 --- a/src/runtime/clrobject.cs +++ b/src/runtime/clrobject.cs @@ -14,11 +14,11 @@ internal CLRObject(object ob, IntPtr tp) long flags = Util.ReadCLong(tp, TypeOffset.tp_flags); if ((flags & TypeFlags.Subclass) != 0) { - IntPtr dict = Marshal.ReadIntPtr(py, ObjectOffset.DictOffset(tp)); + IntPtr dict = Marshal.ReadIntPtr(py, ObjectOffset.TypeDictOffset(tp)); if (dict == IntPtr.Zero) { dict = Runtime.PyDict_New(); - Marshal.WriteIntPtr(py, ObjectOffset.DictOffset(tp), dict); + Marshal.WriteIntPtr(py, ObjectOffset.TypeDictOffset(tp), dict); } } diff --git a/src/runtime/interop.cs b/src/runtime/interop.cs index ca3c35bfd..039feddc7 100644 --- a/src/runtime/interop.cs +++ b/src/runtime/interop.cs @@ -1,6 +1,7 @@ using System; using System.Collections; -using System.Collections.Specialized; +using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Reflection; using System.Text; @@ -68,11 +69,47 @@ public ModulePropertyAttribute() } } + internal static class ManagedDataOffsets + { + static ManagedDataOffsets() + { + FieldInfo[] fi = typeof(ManagedDataOffsets).GetFields(BindingFlags.Static | BindingFlags.Public); + for (int i = 0; i < fi.Length; i++) + { + fi[i].SetValue(null, -(i * IntPtr.Size) - IntPtr.Size); + } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - internal class ObjectOffset + size = fi.Length * IntPtr.Size; + } + + public static readonly int ob_data; + public static readonly int ob_dict; + + private static int BaseOffset(IntPtr type) + { + Debug.Assert(type != IntPtr.Zero); + int typeSize = Marshal.ReadInt32(type, TypeOffset.tp_basicsize); + Debug.Assert(typeSize > 0 && typeSize <= ExceptionOffset.Size()); + return typeSize; + } + public static int DataOffset(IntPtr type) + { + return BaseOffset(type) + ob_data; + } + + public static int DictOffset(IntPtr type) + { + return BaseOffset(type) + ob_dict; + } + + public static int Size { get { return size; } } + + private static readonly int size; + } + + internal static class OriginalObjectOffsets { - static ObjectOffset() + static OriginalObjectOffsets() { int size = IntPtr.Size; var n = 0; // Py_TRACE_REFS add two pointers to PyObject_HEAD @@ -83,42 +120,58 @@ static ObjectOffset() #endif ob_refcnt = (n + 0) * size; ob_type = (n + 1) * size; - ob_dict = (n + 2) * size; - ob_data = (n + 3) * size; } - public static int magic(IntPtr ob) + public static int Size { get { return size; } } + + private static readonly int size = +#if PYTHON_WITH_PYDEBUG + 4 * IntPtr.Size; +#else + 2 * IntPtr.Size; +#endif + +#if PYTHON_WITH_PYDEBUG + public static int _ob_next; + public static int _ob_prev; +#endif + public static int ob_refcnt; + public static int ob_type; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + internal class ObjectOffset + { + static ObjectOffset() + { +#if PYTHON_WITH_PYDEBUG + _ob_next = OriginalObjectOffsets._ob_next; + _ob_prev = OriginalObjectOffsets._ob_prev; +#endif + ob_refcnt = OriginalObjectOffsets.ob_refcnt; + ob_type = OriginalObjectOffsets.ob_type; + + size = OriginalObjectOffsets.Size + ManagedDataOffsets.Size; + } + + public static int magic(IntPtr type) { - if ((Runtime.PyObject_TypeCheck(ob, Exceptions.BaseException) || - (Runtime.PyType_Check(ob) && Runtime.PyType_IsSubtype(ob, Exceptions.BaseException)))) - { - return ExceptionOffset.ob_data; - } - return ob_data; + return ManagedDataOffsets.DataOffset(type); } - public static int DictOffset(IntPtr ob) + public static int TypeDictOffset(IntPtr type) { - if ((Runtime.PyObject_TypeCheck(ob, Exceptions.BaseException) || - (Runtime.PyType_Check(ob) && Runtime.PyType_IsSubtype(ob, Exceptions.BaseException)))) - { - return ExceptionOffset.ob_dict; - } - return ob_dict; + return ManagedDataOffsets.DictOffset(type); } - public static int Size(IntPtr ob) + public static int Size(IntPtr pyType) { - if ((Runtime.PyObject_TypeCheck(ob, Exceptions.BaseException) || - (Runtime.PyType_Check(ob) && Runtime.PyType_IsSubtype(ob, Exceptions.BaseException)))) + if (IsException(pyType)) { return ExceptionOffset.Size(); } -#if PYTHON_WITH_PYDEBUG - return 6 * IntPtr.Size; -#else - return 4 * IntPtr.Size; -#endif + + return size; } #if PYTHON_WITH_PYDEBUG @@ -127,8 +180,15 @@ public static int Size(IntPtr ob) #endif public static int ob_refcnt; public static int ob_type; - private static int ob_dict; - private static int ob_data; + private static readonly int size; + + private static bool IsException(IntPtr pyObject) + { + var type = Runtime.PyObject_TYPE(pyObject); + return Runtime.PyType_IsSameAsOrSubtype(type, ofType: Exceptions.BaseException) + || Runtime.PyType_IsSameAsOrSubtype(type, ofType: Runtime.PyTypeType) + && Runtime.PyType_IsSubtype(pyObject, Exceptions.BaseException); + } } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] @@ -137,19 +197,17 @@ internal class ExceptionOffset static ExceptionOffset() { Type type = typeof(ExceptionOffset); - FieldInfo[] fi = type.GetFields(); - int size = IntPtr.Size; + FieldInfo[] fi = type.GetFields(BindingFlags.Static | BindingFlags.Public); for (int i = 0; i < fi.Length; i++) { - fi[i].SetValue(null, (i * size) + ObjectOffset.ob_type + size); + fi[i].SetValue(null, (i * IntPtr.Size) + OriginalObjectOffsets.Size); } - } - public static int Size() - { - return ob_data + IntPtr.Size; + size = fi.Length * IntPtr.Size + OriginalObjectOffsets.Size + ManagedDataOffsets.Size; } + public static int Size() { return size; } + // PyException_HEAD // (start after PyObject_HEAD) public static int dict = 0; @@ -163,9 +221,7 @@ public static int Size() public static int suppress_context = 0; #endif - // extra c# data - public static int ob_dict; - public static int ob_data; + private static readonly int size; } diff --git a/src/runtime/managedtype.cs b/src/runtime/managedtype.cs index 3191da949..23f5898d1 100644 --- a/src/runtime/managedtype.cs +++ b/src/runtime/managedtype.cs @@ -33,7 +33,7 @@ internal static ManagedType GetManagedObject(IntPtr ob) { IntPtr op = tp == ob ? Marshal.ReadIntPtr(tp, TypeOffset.magic()) - : Marshal.ReadIntPtr(ob, ObjectOffset.magic(ob)); + : Marshal.ReadIntPtr(ob, ObjectOffset.magic(tp)); if (op == IntPtr.Zero) { return null; diff --git a/src/runtime/moduleobject.cs b/src/runtime/moduleobject.cs index 0a3933005..15e4feee8 100644 --- a/src/runtime/moduleobject.cs +++ b/src/runtime/moduleobject.cs @@ -54,7 +54,7 @@ public ModuleObject(string name) Runtime.XDecref(pyfilename); Runtime.XDecref(pydocstring); - Marshal.WriteIntPtr(pyHandle, ObjectOffset.DictOffset(pyHandle), dict); + Marshal.WriteIntPtr(pyHandle, ObjectOffset.TypeDictOffset(tpHandle), dict); InitializeModuleMembers(); } diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 0e391a134..17511dfe9 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1889,6 +1889,11 @@ internal static bool PyObject_TypeCheck(IntPtr ob, IntPtr tp) return (t == tp) || PyType_IsSubtype(t, tp); } + internal static bool PyType_IsSameAsOrSubtype(IntPtr type, IntPtr ofType) + { + return (type == ofType) || PyType_IsSubtype(type, ofType); + } + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyType_GenericNew(IntPtr type, IntPtr args, IntPtr kw); diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index 7d73b0138..3df873eb5 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -87,7 +87,7 @@ internal static IntPtr CreateType(Type impl) // Set tp_basicsize to the size of our managed instance objects. Marshal.WriteIntPtr(type, TypeOffset.tp_basicsize, (IntPtr)ob_size); - var offset = (IntPtr)ObjectOffset.DictOffset(type); + var offset = (IntPtr)ObjectOffset.TypeDictOffset(type); Marshal.WriteIntPtr(type, TypeOffset.tp_dictoffset, offset); InitializeSlots(type, impl); @@ -125,7 +125,6 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType) IntPtr base_ = IntPtr.Zero; int ob_size = ObjectOffset.Size(Runtime.PyTypeType); - int tp_dictoffset = ObjectOffset.DictOffset(Runtime.PyTypeType); // XXX Hack, use a different base class for System.Exception // Python 2.5+ allows new style class exceptions but they *must* @@ -133,9 +132,10 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType) if (typeof(Exception).IsAssignableFrom(clrType)) { ob_size = ObjectOffset.Size(Exceptions.Exception); - tp_dictoffset = ObjectOffset.DictOffset(Exceptions.Exception); } + int tp_dictoffset = ob_size + ManagedDataOffsets.ob_dict; + if (clrType == typeof(Exception)) { base_ = Exceptions.Exception; From 49a230b5e0a81ff1772d4ea15f3283c7df78a63c Mon Sep 17 00:00:00 2001 From: Alex Earl Date: Sat, 16 May 2020 13:42:16 -0700 Subject: [PATCH 098/112] Fix params argument handling (#1106) Add a check to see if the last parameter is a params parameter. Set the correct flag to show that it is a paramsArray function. Fixes #943 and #331. --- CHANGELOG.md | 3 +- src/runtime/methodbinder.cs | 57 +++++++++++++++++++++++++++++++------ src/tests/test_method.py | 31 ++++++++++++++++++++ 3 files changed, 81 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3da7ae4d..56a815d85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,8 +39,9 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. together with Nuitka - Fixes bug where delegates get casts (dotnetcore) - Determine size of interpreter longs at runtime -- Handling exceptions ocurred in ModuleObject's getattribute +- Handling exceptions ocurred in ModuleObject's getattribute - Fill `__classcell__` correctly for Python subclasses of .NET types +- Fixed issue with params methods that are not passed an array. ## [2.4.0][] diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs index 64e038897..b74f21754 100644 --- a/src/runtime/methodbinder.cs +++ b/src/runtime/methodbinder.cs @@ -369,6 +369,41 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth return null; } + static IntPtr HandleParamsArray(IntPtr args, int arrayStart, int pyArgCount, out bool isNewReference) + { + isNewReference = false; + IntPtr op; + // for a params method, we may have a sequence or single/multiple items + // here we look to see if the item at the paramIndex is there or not + // and then if it is a sequence itself. + if ((pyArgCount - arrayStart) == 1) + { + // we only have one argument left, so we need to check it + // to see if it is a sequence or a single item + IntPtr item = Runtime.PyTuple_GetItem(args, arrayStart); + if (!Runtime.PyString_Check(item) && Runtime.PySequence_Check(item)) + { + // it's a sequence (and not a string), so we use it as the op + op = item; + } + else + { + isNewReference = true; + op = Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount); + if (item != IntPtr.Zero) + { + Runtime.XDecref(item); + } + } + } + else + { + isNewReference = true; + op = Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount); + } + return op; + } + /// /// Attempts to convert Python positional argument tuple and keyword argument table /// into an array of managed objects, that can be passed to a method. @@ -397,8 +432,9 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, { var parameter = pi[paramIndex]; bool hasNamedParam = kwargDict.ContainsKey(parameter.Name); + bool isNewReference = false; - if (paramIndex >= pyArgCount && !hasNamedParam) + if (paramIndex >= pyArgCount && !(hasNamedParam || (paramsArray && paramIndex == arrayStart))) { if (defaultArgList != null) { @@ -415,11 +451,14 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, } else { - op = (arrayStart == paramIndex) - // map remaining Python arguments to a tuple since - // the managed function accepts it - hopefully :] - ? Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount) - : Runtime.PyTuple_GetItem(args, paramIndex); + if(arrayStart == paramIndex) + { + op = HandleParamsArray(args, arrayStart, pyArgCount, out isNewReference); + } + else + { + op = Runtime.PyTuple_GetItem(args, paramIndex); + } } bool isOut; @@ -428,7 +467,7 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, return null; } - if (arrayStart == paramIndex) + if (isNewReference) { // TODO: is this a bug? Should this happen even if the conversion fails? // GetSlice() creates a new reference but GetItem() @@ -543,7 +582,7 @@ static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] pa { defaultArgList = null; var match = false; - paramsArray = false; + paramsArray = parameters.Length > 0 ? Attribute.IsDefined(parameters[parameters.Length - 1], typeof(ParamArrayAttribute)) : false; if (positionalArgumentCount == parameters.Length) { @@ -572,7 +611,7 @@ static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] pa // to be passed in as the parameter value defaultArgList.Add(parameters[v].GetDefaultValue()); } - else + else if(!paramsArray) { match = false; } diff --git a/src/tests/test_method.py b/src/tests/test_method.py index 34f460d59..69f1b5e72 100644 --- a/src/tests/test_method.py +++ b/src/tests/test_method.py @@ -257,6 +257,37 @@ def test_non_params_array_in_last_place(): result = MethodTest.TestNonParamsArrayInLastPlace(1, 2, 3) assert result +def test_params_methods_with_no_params(): + """Tests that passing no arguments to a params method + passes an empty array""" + result = MethodTest.TestValueParamsArg() + assert len(result) == 0 + + result = MethodTest.TestOneArgWithParams('Some String') + assert len(result) == 0 + + result = MethodTest.TestTwoArgWithParams('Some String', 'Some Other String') + assert len(result) == 0 + +def test_params_methods_with_non_lists(): + """Tests that passing single parameters to a params + method will convert into an array on the .NET side""" + result = MethodTest.TestOneArgWithParams('Some String', [1, 2, 3, 4]) + assert len(result) == 4 + + result = MethodTest.TestOneArgWithParams('Some String', 1, 2, 3, 4) + assert len(result) == 4 + + result = MethodTest.TestOneArgWithParams('Some String', [5]) + assert len(result) == 1 + + result = MethodTest.TestOneArgWithParams('Some String', 5) + assert len(result) == 1 + +def test_params_method_with_lists(): + """Tests passing multiple lists to a params object[] method""" + result = MethodTest.TestObjectParamsArg([],[]) + assert len(result) == 2 def test_string_out_params(): """Test use of string out-parameters.""" From b98e67460bba790fd0028633f024eb415bcbc630 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 17 May 2020 21:56:10 +0200 Subject: [PATCH 099/112] Update project lead list --- AUTHORS.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 19cd4f5ed..5dbf1e1ce 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -2,8 +2,11 @@ ## Development Lead -- Barton Cline ([@BartonCline](https://github.com/BartonCline)) - Benedikt Reinartz ([@filmor](https://github.com/filmor)) +- Victor Milovanov ([@lostmsu](https://github.com/lostmsu)) + +## Former Development Leads +- Barton Cline ([@BartonCline](https://github.com/BartonCline)) - Brian Lloyd ([@brianlloyd](https://github.com/brianlloyd)) - David Anthoff ([@davidanthoff](https://github.com/davidanthoff)) - Denis Akhiyarov ([@denfromufa](https://github.com/denfromufa)) @@ -54,7 +57,6 @@ - Sean Freitag ([@cowboygneox](https://github.com/cowboygneox)) - Serge Weinstock ([@sweinst](https://github.com/sweinst)) - Simon Mourier ([@smourier](https://github.com/smourier)) -- Victor Milovanov ([@lostmsu](https://github.com/lostmsu)) - Viktoria Kovescses ([@vkovec](https://github.com/vkovec)) - Ville M. Vainio ([@vivainio](https://github.com/vivainio)) - Virgil Dupras ([@hsoft](https://github.com/hsoft)) From 9ba56bfd41d9542738a769fe42f10718b9cfc15f Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 17 May 2020 21:56:41 +0200 Subject: [PATCH 100/112] Update copyright notice and use Python.NET everywhere --- CHANGELOG.md | 4 ++-- LICENSE | 2 +- README.rst | 7 +++---- demo/wordpad.py | 2 +- src/SharedAssemblyInfo.cs | 4 ++-- src/runtime/Properties/AssemblyInfo.cs | 2 +- src/runtime/Python.Runtime.15.csproj | 4 ++-- 7 files changed, 12 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56a815d85..83b0600bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog -All notable changes to Python for .NET will be documented in this file. -This project adheres to [Semantic Versioning][]. +All notable changes to Python.NET will be documented in this file. This +project adheres to [Semantic Versioning][]. This document follows the conventions laid out in [Keep a CHANGELOG][]. diff --git a/LICENSE b/LICENSE index 59abd9c81..19c31a12f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2006-2019 the contributors of the "Python for .NET" project +Copyright (c) 2006-2020 the contributors of the Python.NET project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/README.rst b/README.rst index ee6573d84..72917d721 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -pythonnet - Python for .NET +pythonnet - Python.NET =========================== |Join the chat at https://gitter.im/pythonnet/pythonnet| @@ -8,7 +8,7 @@ pythonnet - Python for .NET |license shield| |pypi package version| |conda-forge version| |python supported shield| |stackexchange shield| -Python for .NET is a package that gives Python programmers nearly +Python.NET is a package that gives Python programmers nearly seamless integration with the .NET Common Language Runtime (CLR) and provides a powerful application scripting tool for .NET developers. It allows Python code to interact with the CLR, and may also be used to @@ -17,8 +17,7 @@ embed Python into a .NET application. Calling .NET code from Python ----------------------------- -Python for .NET allows CLR namespaces to be treated essentially as -Python packages. +Python.NET allows CLR namespaces to be treated essentially as Python packages. .. code-block:: diff --git a/demo/wordpad.py b/demo/wordpad.py index 876372100..409c8ad4d 100644 --- a/demo/wordpad.py +++ b/demo/wordpad.py @@ -382,7 +382,7 @@ def InitializeComponent(self): self.label1.Size = System.Drawing.Size(296, 140) self.label1.TabIndex = 2 self.label1.Text = "Python Wordpad - an example winforms " \ - "application using Python for .NET" + "application using Python.NET" self.AutoScaleBaseSize = System.Drawing.Size(5, 13) self.ClientSize = System.Drawing.Size(300, 150) diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs index dc72b0bdf..ba3d4bf12 100644 --- a/src/SharedAssemblyInfo.cs +++ b/src/SharedAssemblyInfo.cs @@ -8,8 +8,8 @@ // associated with an assembly. [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("pythonnet")] -[assembly: AssemblyProduct("Python for .NET")] -[assembly: AssemblyCopyright("Copyright (c) 2006-2019 the contributors of the 'Python for .NET' project")] +[assembly: AssemblyProduct("Python.NET")] +[assembly: AssemblyCopyright("Copyright (c) 2006-2020 the contributors of the Python.NET project")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/src/runtime/Properties/AssemblyInfo.cs b/src/runtime/Properties/AssemblyInfo.cs index a5d33c7ab..cab9df30b 100644 --- a/src/runtime/Properties/AssemblyInfo.cs +++ b/src/runtime/Properties/AssemblyInfo.cs @@ -4,7 +4,7 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("Python for .NET")] +[assembly: AssemblyTitle("Python.NET")] [assembly: AssemblyDescription("")] [assembly: AssemblyDefaultAlias("Python.Runtime.dll")] diff --git a/src/runtime/Python.Runtime.15.csproj b/src/runtime/Python.Runtime.15.csproj index 2147731c6..9fcbf68ad 100644 --- a/src/runtime/Python.Runtime.15.csproj +++ b/src/runtime/Python.Runtime.15.csproj @@ -11,8 +11,8 @@ 2.4.1 true false - Python for .NET - Copyright (c) 2006-2019 the contributors of the 'Python for .NET' project + Python.NET + Copyright (c) 2006-2020 the contributors of the Python.NET project Python and CLR (.NET and Mono) cross-platform language interop pythonnet https://github.com/pythonnet/pythonnet/blob/master/LICENSE From 60098ea3f2ebad2c09b5116905e46975d346b32f Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 17 May 2020 22:16:22 +0200 Subject: [PATCH 101/112] Update README - Add .NET Foundation - Remove Python 3.8.0 warning --- README.rst | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index 72917d721..55f0e50a1 100644 --- a/README.rst +++ b/README.rst @@ -89,18 +89,24 @@ Output: int32 [ 6. 10. 12.] + + +Resources +--------- + Information on installation, FAQ, troubleshooting, debugging, and projects using pythonnet can be found in the Wiki: https://github.com/pythonnet/pythonnet/wiki -Python 3.8.0 support --------------------- +Mailing list + https://mail.python.org/mailman/listinfo/pythondotnet +Chat + https://gitter.im/pythonnet/pythonnet -Some features are disabled in Python 3.8.0 because of -`this bug in Python `_. The error is -``System.EntryPointNotFoundException : Unable to find an entry point named -'Py_CompileString' in DLL 'python38'``. This will be fixed in Python 3.8.1. +.NET Foundation +--------------- +This project is supported by the `.NET Foundation `_. .. |Join the chat at https://gitter.im/pythonnet/pythonnet| image:: https://badges.gitter.im/pythonnet/pythonnet.svg :target: https://gitter.im/pythonnet/pythonnet?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge @@ -120,8 +126,3 @@ Some features are disabled in Python 3.8.0 because of :target: http://stackoverflow.com/questions/tagged/python.net .. |conda-forge version| image:: https://img.shields.io/conda/vn/conda-forge/pythonnet.svg :target: https://anaconda.org/conda-forge/pythonnet - -Resources ---------- -Mailing list: https://mail.python.org/mailman/listinfo/pythondotnet -Chat: https://gitter.im/pythonnet/pythonnet From 182893c8dac3e9890e31d707365a0491a35965d3 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 17 May 2020 22:19:01 +0200 Subject: [PATCH 102/112] Add link to Code of Conduct --- CONTRIBUTING.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ea0f3408e..ffeb792f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,11 +1,15 @@ # How to contribute -PythonNet is developed and maintained by unpaid community members so well +Python.NET is developed and maintained by unpaid community members so well written, documented and tested pull requests are encouraged. By submitting a pull request for this project, you agree to license your contribution under the MIT license to this project. +This project has adopted the code of conduct defined by the Contributor +Covenant to clarify expected behavior in our community. For more information +see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). + ## Getting Started - Make sure you have a [GitHub account](https://github.com/signup/free) @@ -41,3 +45,4 @@ contribution under the MIT license to this project. - [General GitHub documentation](https://help.github.com/) - [GitHub pull request documentation](https://help.github.com/send-pull-requests/) +- [.NET Foundation Code of Conduct](https://dotnetfoundation.org/about/code-of-conduct) From f2f59559131647f5bc890cc6ed89f49899bb86f3 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 18 May 2020 09:33:32 +0200 Subject: [PATCH 103/112] Drop conda recipe as the one in conda-forge should be maintained (#1147) --- appveyor.yml | 1 - ci/appveyor_build_recipe.ps1 | 43 ------------------------------------ conda.recipe/README.md | 5 ----- conda.recipe/bld.bat | 6 ----- conda.recipe/meta.yaml | 29 ------------------------ tox.ini | 27 ---------------------- 6 files changed, 111 deletions(-) delete mode 100644 ci/appveyor_build_recipe.ps1 delete mode 100644 conda.recipe/README.md delete mode 100644 conda.recipe/bld.bat delete mode 100644 conda.recipe/meta.yaml delete mode 100644 tox.ini diff --git a/appveyor.yml b/appveyor.yml index 363e67389..7abab3fca 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -56,7 +56,6 @@ build_script: test_script: - pip install --no-index --find-links=.\dist\ pythonnet - ps: .\ci\appveyor_run_tests.ps1 - - ps: .\ci\appveyor_build_recipe.ps1 on_finish: # Temporary disable multiple upload due to codecov limit of 20 per commit. diff --git a/ci/appveyor_build_recipe.ps1 b/ci/appveyor_build_recipe.ps1 deleted file mode 100644 index ecb7708f2..000000000 --- a/ci/appveyor_build_recipe.ps1 +++ /dev/null @@ -1,43 +0,0 @@ -# Build `conda.recipe` only if this is a Pull_Request. Saves time for CI. - -$stopwatch = [Diagnostics.Stopwatch]::StartNew() - -$env:CONDA_PY = "$env:PY_VER" -# Use pre-installed miniconda. Note that location differs if 64bit -$env:CONDA_BLD = "C:\miniconda36" - -if ($env:PLATFORM -eq "x86"){ - $env:CONDA_BLD_ARCH=32 -} else { - $env:CONDA_BLD_ARCH=64 - $env:CONDA_BLD = "$env:CONDA_BLD" + "-x64" -} - -if ($env:APPVEYOR_REPO_TAG_NAME -or $env:FORCE_CONDA_BUILD -eq "True") { - # Update PATH, and keep a copy to restore at end of this PowerShell script - $old_path = $env:path - $env:path = "$env:CONDA_BLD;$env:CONDA_BLD\Scripts;" + $env:path - - Write-Host "Starting conda install" -ForegroundColor "Green" - conda config --set always_yes True - conda config --set changeps1 False - conda config --set auto_update_conda False - conda install conda-build jinja2 anaconda-client --quiet - conda info - - # why `2>&1 | %{ "$_" }`? Redirect STDERR to STDOUT - # see: http://stackoverflow.com/a/20950421/5208670 - Write-Host "Starting conda build recipe" -ForegroundColor "Green" - conda build conda.recipe --quiet 2>&1 | %{ "$_" } - - $CONDA_PKG=(conda build conda.recipe --output) - Copy-Item $CONDA_PKG .\dist\ - - $timeSpent = $stopwatch.Elapsed - Write-Host "Completed conda build recipe in " $timeSpent -ForegroundColor "Green" - - # Restore PATH back to original - $env:path = $old_path -} else { - Write-Host "Skipping conda build recipe" -ForegroundColor "Green" -} diff --git a/conda.recipe/README.md b/conda.recipe/README.md deleted file mode 100644 index 42241999f..000000000 --- a/conda.recipe/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Conda Recipe - -The files here are needed to build Python.Net with conda - -http://conda.pydata.org/docs/building/recipe.html diff --git a/conda.recipe/bld.bat b/conda.recipe/bld.bat deleted file mode 100644 index 27b55f2de..000000000 --- a/conda.recipe/bld.bat +++ /dev/null @@ -1,6 +0,0 @@ -:: build it - -:: set path to modern MSBuild -set PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;C:\Program Files (x86)\MSBuild\15.0\Bin;%PATH% - -%PYTHON% setup.py install diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml deleted file mode 100644 index 545a01268..000000000 --- a/conda.recipe/meta.yaml +++ /dev/null @@ -1,29 +0,0 @@ -package: - name: pythonnet - version: {{ GIT_DESCRIBE_VERSION }} - -build: - skip: True # [not win] - number: {{ GIT_DESCRIBE_NUMBER }} - {% if environ.get('GIT_DESCRIBE_NUMBER', '0') == '0' %}string: py{{ environ.get('PY_VER').replace('.', '') }}_0 - {% else %}string: py{{ environ.get('PY_VER').replace('.', '') }}_{{ environ.get('GIT_BUILD_STR', 'GIT_STUB') }}{% endif %} - -source: - git_url: ../ - -requirements: - build: - - python - - setuptools - - run: - - python - -test: - imports: - - clr - -about: - home: https://github.com/pythonnet/pythonnet - license: MIT - license_file: LICENSE diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 3c9be5c63..000000000 --- a/tox.ini +++ /dev/null @@ -1,27 +0,0 @@ -[tox] -skip_missing_interpreters=True -envlist = - py27 - py33 - py34 - py35 - py36 - py37 - check - -[testenv] -deps = - pytest -commands = - {posargs:py.test} - -[testenv:check] -ignore_errors=True -skip_install=True -basepython=python3.5 -deps = - check-manifest - flake8 -commands = - flake8 src setup.py - python setup.py check --strict --metadata From 21cb776fa5253e4d834ad2a4e8071591d8e127ee Mon Sep 17 00:00:00 2001 From: NickSavin Date: Tue, 5 Mar 2019 17:02:36 +0300 Subject: [PATCH 104/112] Encode strings passed to PyRun_String as UTF8 --- src/runtime/runtime.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 17511dfe9..8cf24ba70 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -839,7 +839,11 @@ public static extern int Py_Main( internal static extern int PyRun_SimpleString(string code); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] +#if PYTHON2 internal static extern NewReference PyRun_String(string code, IntPtr st, IntPtr globals, IntPtr locals); +#else + internal static extern NewReference PyRun_String([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string code, IntPtr st, IntPtr globals, IntPtr locals); +#endif [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyEval_EvalCode(IntPtr co, IntPtr globals, IntPtr locals); From 09bbae3b6907c6a52025995164eac3db099c4052 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 18 May 2020 12:59:49 +0200 Subject: [PATCH 105/112] Prepare for 2.5.0 release --- CHANGELOG.md | 65 ++++++++++++------- setup.py | 4 +- src/SharedAssemblyInfo.cs | 2 +- src/clrmodule/ClrModule.cs | 2 +- src/clrmodule/clrmodule.15.csproj | 2 +- src/console/Console.15.csproj | 2 +- .../Python.EmbeddingTest.15.csproj | 2 +- src/runtime/Python.Runtime.15.csproj | 4 +- src/runtime/resources/clr.py | 2 +- src/testing/Python.Test.15.csproj | 2 +- 10 files changed, 54 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83b0600bc..4fa6264cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,45 +5,66 @@ project adheres to [Semantic Versioning][]. This document follows the conventions laid out in [Keep a CHANGELOG][]. -## [unreleased][] +## [Unreleased][] ### Added -- Added automatic NuGet package generation in appveyor and local builds -- Added function that sets Py_NoSiteFlag to 1. -- Added support for Jetson Nano. -- Added support for __len__ for .NET classes that implement ICollection -- Added `PyExport` attribute to hide .NET types from Python -- Added PythonException.Format method to format exceptions the same as traceback.format_exception -- Added Runtime.None to be able to pass None as parameter into Python from .NET -- Added PyObject.IsNone() to check if a Python object is None in .NET. +### Changed + +### Fixed + +## [2.5.0-rc2][] - 2020-06-07 + +This version improves performance on benchmarks significantly compared to 2.3. + +### Added + +- Automatic NuGet package generation in appveyor and local builds +- Function that sets `Py_NoSiteFlag` to 1. +- Support for Jetson Nano. +- Support for `__len__` for .NET classes that implement ICollection +- `PyExport` attribute to hide .NET types from Python +- `PythonException.Format` method to format exceptions the same as + `traceback.format_exception` +- `Runtime.None` to be able to pass `None` as parameter into Python from .NET +- `PyObject.IsNone()` to check if a Python object is None in .NET. - Support for Python 3.8 +- Codecs as the designated way to handle automatic conversions between + .NET and Python types ### Changed - Added argument types information to "No method matches given arguments" message - Moved wheel import in setup.py inside of a try/except to prevent pip collection failures -- Removes PyLong_GetMax and PyClass_New when targetting Python3 +- Removes `PyLong_GetMax` and `PyClass_New` when targetting Python3 - Improved performance of calls from Python to C# - Added support for converting python iterators to C# arrays -- Changed usage of obselete function GetDelegateForFunctionPointer(IntPtr, Type) to GetDelegateForFunctionPointer(IntPtr) -- When calling C# from Python, enable passing argument of any type to a parameter of C# type `object` by wrapping it into `PyObject` instance. ([#881][i881]) +- Changed usage of the obsolete function + `GetDelegateForFunctionPointer(IntPtr, Type)` to + `GetDelegateForFunctionPointer(IntPtr)` +- When calling C# from Python, enable passing argument of any type to a + parameter of C# type `object` by wrapping it into `PyObject` instance. + ([#881][i881]) - Added support for kwarg parameters when calling .NET methods from Python - Changed method for finding MSBuild using vswhere -- Reworked `Finalizer`. Now objects drop into its queue upon finalization, which is periodically drained when new objects are created. -- Marked `Runtime.OperatingSystemName` and `Runtime.MachineName` as `Obsolete`, should never have been `public` in the first place. They also don't necessarily return a result that matches the `platform` module's. +- Reworked `Finalizer`. Now objects drop into its queue upon finalization, + which is periodically drained when new objects are created. +- Marked `Runtime.OperatingSystemName` and `Runtime.MachineName` as + `Obsolete`, should never have been `public` in the first place. They also + don't necessarily return a result that matches the `platform` module's. ### Fixed -- Fixed runtime that fails loading when using pythonnet in an environment - together with Nuitka -- Fixes bug where delegates get casts (dotnetcore) -- Determine size of interpreter longs at runtime -- Handling exceptions ocurred in ModuleObject's getattribute -- Fill `__classcell__` correctly for Python subclasses of .NET types -- Fixed issue with params methods that are not passed an array. +- Fixed runtime that fails loading when using pythonnet in an environment + together with Nuitka +- Fixes bug where delegates get casts (dotnetcore) +- Determine size of interpreter longs at runtime +- Handling exceptions ocurred in ModuleObject's getattribute +- Fill `__classcell__` correctly for Python subclasses of .NET types +- Fixed issue with params methods that are not passed an array. +- Use UTF8 to encode strings passed to `PyRun_String` on Python 3 -## [2.4.0][] +## [2.4.0][] - 2019-05-15 ### Added diff --git a/setup.py b/setup.py index ed0852bb5..693a6d46f 100644 --- a/setup.py +++ b/setup.py @@ -400,7 +400,7 @@ def _build_monoclr(self): mono_cflags = _check_output("pkg-config --cflags mono-2", shell=True) cflags = mono_cflags.strip() libs = mono_libs.strip() - + # build the clr python module clr_ext = Extension( "clr", @@ -633,7 +633,7 @@ def run(self): setup( name="pythonnet", - version="2.4.1-dev", + version="2.5.0rc2", description=".Net and Mono integration for Python", url="https://pythonnet.github.io/", license="MIT", diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs index ba3d4bf12..dd057b144 100644 --- a/src/SharedAssemblyInfo.cs +++ b/src/SharedAssemblyInfo.cs @@ -25,4 +25,4 @@ // Version Information. Keeping it simple. May need to revisit for Nuget // See: https://codingforsmarties.wordpress.com/2016/01/21/how-to-version-assemblies-destined-for-nuget/ // AssemblyVersion can only be numeric -[assembly: AssemblyVersion("2.4.1")] +[assembly: AssemblyVersion("2.5.0")] diff --git a/src/clrmodule/ClrModule.cs b/src/clrmodule/ClrModule.cs index 7fc654fd6..377be66d1 100644 --- a/src/clrmodule/ClrModule.cs +++ b/src/clrmodule/ClrModule.cs @@ -53,7 +53,7 @@ public static void initclr() { #if USE_PYTHON_RUNTIME_VERSION // Has no effect until SNK works. Keep updated anyways. - Version = new Version("2.4.1"), + Version = new Version("2.5.0"), #endif CultureInfo = CultureInfo.InvariantCulture }; diff --git a/src/clrmodule/clrmodule.15.csproj b/src/clrmodule/clrmodule.15.csproj index 326620c00..7fc9c2dda 100644 --- a/src/clrmodule/clrmodule.15.csproj +++ b/src/clrmodule/clrmodule.15.csproj @@ -9,7 +9,7 @@ clrmodule clrmodule clrmodule - 2.4.1 + 2.5.0 false false false diff --git a/src/console/Console.15.csproj b/src/console/Console.15.csproj index 4e765fea4..3c51caa31 100644 --- a/src/console/Console.15.csproj +++ b/src/console/Console.15.csproj @@ -8,7 +8,7 @@ nPython Python.Runtime nPython - 2.4.1 + 2.5.0 false false false diff --git a/src/embed_tests/Python.EmbeddingTest.15.csproj b/src/embed_tests/Python.EmbeddingTest.15.csproj index e163c9242..eb6957656 100644 --- a/src/embed_tests/Python.EmbeddingTest.15.csproj +++ b/src/embed_tests/Python.EmbeddingTest.15.csproj @@ -10,7 +10,7 @@ Python.EmbeddingTest Python.EmbeddingTest Python.EmbeddingTest - 2.4.1 + 2.5.0 false false false diff --git a/src/runtime/Python.Runtime.15.csproj b/src/runtime/Python.Runtime.15.csproj index 9fcbf68ad..d753a5dff 100644 --- a/src/runtime/Python.Runtime.15.csproj +++ b/src/runtime/Python.Runtime.15.csproj @@ -1,4 +1,4 @@ - + net40;netstandard2.0 @@ -8,7 +8,7 @@ Python.Runtime Python.Runtime pythonnet - 2.4.1 + 2.5.0 true false Python.NET diff --git a/src/runtime/resources/clr.py b/src/runtime/resources/clr.py index 45265226a..78fae0d3a 100644 --- a/src/runtime/resources/clr.py +++ b/src/runtime/resources/clr.py @@ -2,7 +2,7 @@ Code in this module gets loaded into the main clr module. """ -__version__ = "2.4.1" +__version__ = "2.5.0" class clrproperty(object): diff --git a/src/testing/Python.Test.15.csproj b/src/testing/Python.Test.15.csproj index 8c23fe4b5..0e19adf91 100644 --- a/src/testing/Python.Test.15.csproj +++ b/src/testing/Python.Test.15.csproj @@ -7,7 +7,7 @@ Python.Test Python.Test Python.Test - 2.4.1 + 2.5.0 bin\ false $(OutputPath)\$(AssemblyName).xml From 7929e013cdd048169c285026ecf6954577842569 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 9 Jun 2020 16:46:36 +0200 Subject: [PATCH 106/112] Add Python 3.8 in the setup.py classifier list --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 693a6d46f..a4f0cb70e 100644 --- a/setup.py +++ b/setup.py @@ -638,7 +638,7 @@ def run(self): url="https://pythonnet.github.io/", license="MIT", author="The Python for .Net developers", - author_email="pythondotnet@python.org", + author_email="pythonnet@python.org", setup_requires=setup_requires, long_description=_get_long_description(), ext_modules=[Extension("clr", sources=list(_get_source_files()))], @@ -655,6 +655,7 @@ def run(self): "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Operating System :: MacOS :: MacOS X", From 2d8631b8f2d7c0831441d28b7653f36f4e310e03 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 13 Jun 2020 14:24:26 +0200 Subject: [PATCH 107/112] Make pycparser an unconditional requirement for now --- setup.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index a4f0cb70e..bb2f1f2d0 100644 --- a/setup.py +++ b/setup.py @@ -618,11 +618,7 @@ def run(self): if setupdir: os.chdir(setupdir) -setup_requires = [] -if not os.path.exists(_get_interop_filename()): - setup_requires.append("pycparser") - -cmdclass={ +Macmdclass={ "install": InstallPythonnet, "build_ext": BuildExtPythonnet, "install_lib": InstallLibPythonnet, @@ -639,7 +635,7 @@ def run(self): license="MIT", author="The Python for .Net developers", author_email="pythonnet@python.org", - setup_requires=setup_requires, + setup_requires=["pycparser"], long_description=_get_long_description(), ext_modules=[Extension("clr", sources=list(_get_source_files()))], data_files=[("{install_platlib}", ["{build_lib}/Python.Runtime.dll"])], From 6c0b867d5e656e9fbbd0f95935dcca7432a0db2a Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 13 Jun 2020 18:20:56 +0200 Subject: [PATCH 108/112] Fix typo --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bb2f1f2d0..50554516d 100644 --- a/setup.py +++ b/setup.py @@ -618,7 +618,7 @@ def run(self): if setupdir: os.chdir(setupdir) -Macmdclass={ +cmdclass={ "install": InstallPythonnet, "build_ext": BuildExtPythonnet, "install_lib": InstallLibPythonnet, From df4425aa5623b2517518ac2d77b6c40a067a42b4 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 14 Jun 2020 12:51:26 +0200 Subject: [PATCH 109/112] Make pip recognise the dependency on pycparser --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 50554516d..374d0ffa8 100644 --- a/setup.py +++ b/setup.py @@ -633,9 +633,9 @@ def run(self): description=".Net and Mono integration for Python", url="https://pythonnet.github.io/", license="MIT", - author="The Python for .Net developers", + author="The Contributors of the Python.NET Project", author_email="pythonnet@python.org", - setup_requires=["pycparser"], + install_requires=["pycparser"], long_description=_get_long_description(), ext_modules=[Extension("clr", sources=list(_get_source_files()))], data_files=[("{install_platlib}", ["{build_lib}/Python.Runtime.dll"])], From 96de0627941c392585e8d04e6e5332da83e2c75b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 14 Jun 2020 12:56:52 +0200 Subject: [PATCH 110/112] Drop bumpversion as we never used it anyhow --- .bumpversion.cfg | 29 ----------------------------- setup.cfg | 6 ------ 2 files changed, 35 deletions(-) delete mode 100644 .bumpversion.cfg diff --git a/.bumpversion.cfg b/.bumpversion.cfg deleted file mode 100644 index ac1d31324..000000000 --- a/.bumpversion.cfg +++ /dev/null @@ -1,29 +0,0 @@ -[bumpversion] -current_version = 2.4.0.dev0 -parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\.(?P[a-z]+)(?P\d+))? -serialize = - {major}.{minor}.{patch}.{release}{dev} - {major}.{minor}.{patch} - -[bumpversion:part:release] -optional_value = dummy -values = - dev - dummy - -[bumpversion:part:dev] - -[bumpversion:file:setup.py] - -[bumpversion:file:conda.recipe/meta.yaml] - -[bumpversion:file:src/runtime/resources/clr.py] - -[bumpversion:file:src/SharedAssemblyInfo.cs] -serialize = - {major}.{minor}.{patch} - -[bumpversion:file:src/clrmodule/ClrModule.cs] -serialize = - {major}.{minor}.{patch} - diff --git a/setup.cfg b/setup.cfg index 38aa3eb3d..19c6f9fc9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,9 +1,3 @@ -# [bumpversion] comments. bumpversion deleted all comments on its file. -# Don't combine `.bumpversion.cfg` with `setup.cfg`. Messes up formatting. -# Don't use `first_value = 1`. It will break `release` bump -# Keep `optional = dummy` needed to bump to release. -# See: https://github.com/peritus/bumpversion/issues/59 - [tool:pytest] xfail_strict = True # -r fsxX: show extra summary info for: (f)ailed, (s)kip, (x)failed, (X)passed From 0a59005701973fefd3e53f13e5a97fd38275e90b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 14 Jun 2020 12:58:17 +0200 Subject: [PATCH 111/112] Release 2.5.0 --- CHANGELOG.md | 3 ++- setup.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fa6264cb..ad4016450 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Fixed -## [2.5.0-rc2][] - 2020-06-07 +## [2.5.0][] - 2020-06-14 This version improves performance on benchmarks significantly compared to 2.3. @@ -52,6 +52,7 @@ This version improves performance on benchmarks significantly compared to 2.3. - Marked `Runtime.OperatingSystemName` and `Runtime.MachineName` as `Obsolete`, should never have been `public` in the first place. They also don't necessarily return a result that matches the `platform` module's. +- Unconditionally depend on `pycparser` for the interop module generation ### Fixed diff --git a/setup.py b/setup.py index 374d0ffa8..216620211 100644 --- a/setup.py +++ b/setup.py @@ -629,7 +629,7 @@ def run(self): setup( name="pythonnet", - version="2.5.0rc2", + version="2.5.0", description=".Net and Mono integration for Python", url="https://pythonnet.github.io/", license="MIT", From 801525a754f89d58607869c1eb8cd0d1b38133ba Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 14 Jun 2020 13:06:15 +0200 Subject: [PATCH 112/112] Make test install on AppVeyor use the index for pycparser --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 7abab3fca..b58b72372 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -54,7 +54,7 @@ build_script: - coverage run setup.py bdist_wheel %BUILD_OPTS% test_script: - - pip install --no-index --find-links=.\dist\ pythonnet + - pip install --find-links=.\dist\ pythonnet - ps: .\ci\appveyor_run_tests.ps1 on_finish: