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 z50Gze8F-RDW><8}u6WMfJe8f%@IEgg9brFh-sjD8VK%tm;w!7DmyBYIC4_mq>`#OS zXt@%1DOZ~wtKb$QO}vQtF#P;pk%R=d5cL6P7thh)pRrW&c9_M_dv-K%h{_S#sdmyE zm;f{@cS8~42wlO?I70Xkg9Y!VebJ|+rl8HVRWd;k6v$fpfF{V-KZj?MCjFRSw&VHG zWSn`eH$Nkd3rv#Jz}y@9I_|W~d7`cfQdke=ZcyK(a`l}Ww;r#TAH)3%Twl0kT2FF7 zHZ})iA&^e9r34{%=Rc{<4@so;?ak2*7_+21yVF{gqk>v6 z`)CCgL4@lIrm`<*fWcuFry0rOt#Qn7MP_=6(*vb!SfNgbuq{4-;N2HF}}&wFlWHS3ASEuzvU#>QpGQ+L`!7CH zhyxm11z~^AZ#&*{F0RPbJ6lbp-c&jQ+-msc(9PO>f<)Mh=)Ysz`T?n zVL}MKFB4!er#Q87K4Iw1U7V0JnF3jFFbM{whEZyGkp5#1n7bgRE&?wvd*?hhko+zI zA5;Z6uO9RSQ&((Vs?Pw=j#}4zV9tm(QllotE5khK_9riqiD?$2c9GF{%IL$po@*w1 zh$f~%#4GjCn+&~cGh}7j&8=KE*t27i^qUjV)Ha+0B^m3AB)9JBER2lnQci!8v%eO1 zNUx)HWCE$GSgr+M^Tb(gIWJ@w_ z<^`T|=6;@@`#I%KS}i7Uv|G;06j^P5VH^>8>}0Qui#$Nd8=3k#kPI{QZfRBm)+^AC zM1vLAa-jWXnd&8w6HINqiLhDOV&&71hxf<)0~Q3ox5_dBx`rDgsLKtoP&vr<6AJ`O zthi66+D>!(C=rU-n-vcdg6HW!SW}+^E%+x8*6ed4eFTm_E?c_i9hNvKvhZa__4BM* z{|wkyX8D#L+cIJ``PA)zJeGl$15yC~^jxc!}wS3P6oubfA)1le!-5jYR2D{)qON{)dzvt&$Z)k)X# z*F|@=Pm!~ZN=|uPKVQ#ZE%bS+oht+rHvfz|V~S7*?Nk1Pzv>u_0sBg58`A0_J61en z89AS%R9BzW2kU`94O41o21(!xb=V}>)szlfE}qqup(|h_4u|eJM)~y{sCSPyY6+aY zht*0sN(%sj0h@S5MAK}rVuv9&4&xpq&Uyxp;fJ$3e=89*K!Zm2@E`}h4AoGrYzAYg z=!jV{W{Sh*p6hp1ZBIzZl^B;NIv%yBw2nwSN2L<6_vn|@A3Mh3JT7EPL(IoTqd*p0 zHA>Fz92T8^S$g~5NY5sGri!BqcP60wZj*t2{t#2@Q<+qQ*z2&zgJ#nS72A`iC6dIP z!tNSS0O8ZDK;qER8j8Kb?N6%jq=PHf%mZ|hbv6wmCJ8J6Je#PO_hBHqSI3UkUvB5i zWBDssH4<7J`(aQQJ%dMe};nYY3YN*d@AQToLyRC`7%|`cy47P`8kYWxGi^Qi`8kTASSoFZGkY z;{bm+ZA4_QUgA?xNNZ1wcF2Nn&o2v)@nIy7j>6h*CJmT=h zh?pGemoL*3UWe8~FYEX~n)U|>8C+yy;f85BEp805_ozp8Ne)c2_o`Rm7(@4LN(kq< zL-#z-!g+$|HjiK3FROjM`ZY`^6%r5F4iS?PkI}nZ{Y+Rwvz}O&vGx#w)7AQzWS&h)+r*OVFM+buaiw^#HRR1dt z+2+)Tiq>j~&ZHm%a(IG2A=G&q!riBu?xvz8({xa@)|ENft?Iah68?^u>eJMGrtleg z!>D}?T*Okht(1Lykhr9WvyZb$A-nZS(3uOR%erorJq*F68Yj2j!xWrkP8Ua%0{bnO zy;ft|+co*3mh5H25}JhywzkYnfNs~Y-xy{%)CWQt09?U!4B+B|nF$g;8Xm&lF@87G z=f{vffI8*5%>|BJ^g!c9b;UN9z&&_~m*hElXXEcG$D+%kP_Cib9Rx@4=d0=Ps#WF! zyC@Q&EoXPIQ*(k4ZI)#`4_ig*S%T3)w_3NzftAM2a<=t|bHm4btU(H6qlv1Ucw>D=i9Yo-U+Qu*L#u!mJQsf{DTQ*?Vx%-6 zFt4&FHaM8mA}vt++5D_7(y|fm&d5f9eNA*d^sh1#M$1M{)P6xW!i2f?TdbAlsQ5^s zkE3fjU&1pcuJD-IVdq4N@;m`C3~$ld)p2C;u+FgOi10Z-2N&+>8GYG*Fd0N%GNl56 zNuS6hn(0Sp9h9Rach2FHIY}crO@t;@R7qM<8W);Ol^D!tgeH{dps-S1SZUJPf|D3+ zmaf=7&z&fyJu^BM$*O3DL=!Q(18E5OYQ=*yOWa_0mShIUW6$)l?#k?);g;0)5_irb zcVG=W?L|Y(3#}F6ToFfiDG+f-o;!}XVaN16S&=&HeJ?f?W(Y zDdVD9H97vlKl4EZ^!dF?jB(UmkUG-pjE)u3>fds!0p7?{bSy!R3?(~1bXGn!i?>5s zg(QVxLRb)5l_^0a7Nn|+PU$-Lsn?L~3ON8VaAREfJvS~9*GN*8zWO3^LmsX+Sc5H1 z9%2fq5BKY>KGc<}IuFNU42-p{RYGpC^Tju)J?d$hmL+= zmt=!VQ{TH+HYfy@Y)}Lj0&ogdD*>-1+O^uu%UFR9Vfxn!$@@nNx$3M!IK|G_Qj1^?gLf-Xat{1u`=wjZ^Sl|g z1${-de6sa@aSPfDoBH`J$j9C)i^q=;!taNHWIM4{t^9D(_i0U zfZ~*2{S#*%m!ElT$vovCULB>&cBntfPq|xy(lb4Y_vjo6*j}J1g&H0wEG`A8^==XBL4eai@b4b)6GzB|wTZrr zP%@Rlt5ohkF*9UciRoXb`h@ienZ+4Q;*57?iJ{8^U-o)6iInl>OG<$@6Hv1KW#ysOp~NH`Ic*IZl!-KcV~LHGW1&vJraBpQH)0Q?j*q4^aaV z)61rl`L^VCNR^SBng(SkJrbuPeB6G4ig<(%{|*%~TYdC(S)^Y=MM&3uITbO)CtpHE ze8A(gsR-#%p&V90AX!_N}NkKt7`Woy8I^ zOpmW4!`q6k4bz7v)PNKuG8O{dD{?(xRPRQ!!POS>AHxDheqTS0HXw%K=>|2G%`5wL zUMAlF@)S}Aea0EVwJXM8z5)c<9%?gzb1LkdXL{FyRR&+Y7Qm$hpn6Gt9jgJ2D&9_A zMtQscDs-t^O`VTx?GIS5@; zR=d{T&(#e(T`zXkc~ZTO48{q3V^O8%!wYPC05!_sB-=F~)Tu!W?PI}R9 ze6kH6@%20nHSbOgdS0HbpKnn$>=+n}dNn4j6*U#{%K?o~h9K(_EE}791+r`lfMo;1 zzLP;x@cfr^;~7lR8MxYhEcJ{9uBZIO;1nG{)qB8ym1I^0+EQ>_eUVgMkH7Z-^wI{k z<{$*lcuElk&OuIsf2a}^@->bLi4jjok+d2ctB8MlOr9Z<{um!a^syEgY)lZPpSx}16A@wU3!-p86& zvO#-k6s85fLS@e9J}Ky`ueYj$6DgU2%V@RppZlN|m&ae#^QztsGmXm#6EuYaA5|A} zC?E=~mAJAFJZdIDruWf1Oja8>;w0h4SoM1zi7CMjjCZ61gZIeq7WGa2dqCa8FG{bP z*EFSQY6vu9WA0IySpd+N#@~*1S{wwqT*R9)<5fwZ44feSGf@R1h!S4tJvK7o&^2lqk_lG!Ei|spM`9{1;o~~hb)|&m z(o3ctRY999NW_bhO{?Vb^LQTNLs0m9WLMi3;Tf_M{B2T--~oof2+!cAw9aVDl+TM> zjpDUx17*oh(I#Jt{;*YDzkuSyl17!^P5nudJRO0n@vT&Hb*x3*j9zg}I zS4+K&q_-8pg0~~I)q!PgmqUHOSTC}lN(H*UCJ9?rA*MD2syIv;P&F}N6_i@&aOjUaxQa&G$|1kI-RvwR;3W@4= zwlWDi7_bk<3Nu(X<$sj1+GNa8AN_!WwyUGtb^nT>;`2WH)_@oQ6>CTOA2MmtM&i2v z4GJ4}-Cr#wSg!kc?r+b^&rGkX`U#(&I4P-iU`3sp)$@}Rt25oBJ@}ELp6$WEN*>_B zFN2=d<4+S~7>Zr(d(yh%jj;Bx*!w#!!jSiYyyuOGZ?Eq+8t8krkvnSkd6L9g(Y#a@sMl$*I( z3euVd_$h&Ow&A|S(QR00+Y`rkccoI{bLIdk-f9Q{MXH$AzO+ zjF$4a>BQnqICcFhkZ)Xs*>k ztl}Pk=kW-P#`NDaPvK(+Y}||Fuq0P!Tu?NeaaXNmY!u`Y{w|+?Md6Ye>Y(&RNv8Vm zN6EM(RqfRG026KD0olm27lxlb%`>c=2A<#-waz2b)7!UMGkVahPGu*?>{jo)c*Wxq zZ$Nlck3<{ z1gJe>^qL~eH&!_jHW|%MH5qVHqs56RSb$_sGZV9q7x*)g+o`6P$*Y%gu)+GQ%0IU1 z=(EOrjTs5F)%-MLe!4L~1O5}_t5kZoC+WyxD8I5yqimW)Zrj?WY$tk+va7!$z#+zh z=|S$Ys+mSvZs@(a#)8?NKL+xMS~EBFSrR&*1Y5$lh}-m;o$JX7t;-`< zBW;xHCmy^nk`emM&9&X;_*MiP-j}8(v@aJLFTX1?rsGs~lf%gKJws)3aSY7M3>7&f z;Nh=mBdU;Rw$h<)f|YdqLAq+TbQRl}1>jqg=}#c()N@F>hci(%t%A|J zOXF+!&*vidLf%XUaV_3rpHo)z<<{t0^?(Lg?Z&ZS%g;Q?=ihBz6E03D5;o6Y3H*CMar#_TN-eUqC zGMIG&Pyh`H&?`@iiRs6@kDB`{1QK8i&%{3h_k{iIy#L89Z~zTNi5beNRt;BxuxOqt z3-zc<_4<9{9T_|qxb7b7GTD5GU|sKuC0+9o&xwYNvI@1)dNq-tI!{`cV964qcE$T- zNj18W)+77$ywPwcao$?2Bn2R7N7a4t8ye&JG;ozaxe8$2qoAV>nV)iV^cV6ZxZC`U zXSzr-Cez6H_%Dv*(a_d33h=zK;@_nGLFL)r=nr!Mmk*XLR&J|+9D1Nk*{qv<+?hh% z%JU}h!pT+U(Qxzgx*)WHr{X0(ZcVsU*!B>quaNKwq7v}16^FU7g^nDoGQDiAEk zc+K;M6^7ljH83Tf>v}8K!I50on^Ef#MXr^rP-hybPZFq!&H7lVI}QCb(7~S#Jjlce zbz)scuLUclvfY8pB-_F8tFqU}vx(;y*89H{Lq)=`-bMn_^1XRf5@PJ+9TEfiUtr|? zg1n`$$Q68dxt$iIVV|{zxKUBdGRAlzuFeS^$@pAx=4bx2tW#By+;9UM5*bveE6E?ocJ)i- zHC8!3e_FUu2`R_AMh|=5nc^O=(#2(+G$9B$-IJPM94U66?@ zBCyGQJ_5Jv4}8`KSDDqlV0DOu`T$SNQ@jjm+sW&60$`h^Dnx7B4b%X|N~nSV;Fa{| zOG`Kpy?tr2C^6*|F9Mn;@j1of==$g5{VF~L+NuDb5<3x1Q?mL@0!i-ZI%VrRX~QI&rO0T) z_M?Q*!q){miEg9NfnV#<#*D0^feU~)>bGGcyI4IZD!g*BDh;Q8YE;@IjzDtQKFC*E z&*Ml?_vgp;JVIfn>()a)nZNsB~jlW`+vVZUNHD zYI5>snq0zHS8$z#Je#f=N}m-0SUf0@dcX^PW>7{O%Z6s#xtpu(qLTxw+rh?Jmn zX^b{2yIGv9Vj}ZFT%vmxTqWAlTkP6n3LRr{G|Z{tJ*N8k1TWYLChpzap>vS6i2+B^ zZ$=C_WSq@?l{&QNnkEmpTj7?eW)frlmobD8g*h7^ws&~u+XFU01C13)bbvcuS1QJ^i=|mbgnD8yKi@niwLic(87_@tK1u|Oag6$U=<9c>S#O8W#hmjOI zIkmwyFY*q~5?k#NF!eELJ0=*MOVzSpOIuS#O%ggXebl;zQNT1#s^`Xp&JW5W} zSzkq&D3r$PEq7$KhBl2fOaOu$6>U53zt~*CUVNg@GuUvR?Gtm)iG5uIWBj5MU6LiZ z2P^D6-QK0fpz};j!vB7So$=)Fum&Ci)bbUr9QKh?ljiA-Dvc;`F#wyO4{;xWs1E%l zkszHVcCls`*?)ZiVT&5Pb$AktJ_#%nQ3%hISvL*$oEvQGwZD{Au*fO<$eOG!Pq*u# zf9HuAAo(9N+-fx;qrjl&Be3SYaDN(JLFXL(!`Ou23|~)%Gh=9xw1EMuH~_5YZ$6 z_bF0JVXNw)i3PbsR1=XUByvQ2R-&Z9DntpO=@mEv_aWPhyvbX8pbndw;GI1M3s>te zLTXvSQxsB(fsBxv0vI1x+W=gq(P|j;RBNFiXEjpZ& z@f?#@=AvsUD58~kE6NI%?K#!5R9H35fMZuqrUiuQl+db$t6gn9R08Yu1)1t$8iJ)v zrrIklYseJ)mre?S3|XC-I`9}zp)`I$S^V_qBGHM8Jx;NF$uGhdamRL>MVv=YahYVN zP7|Yz7VSVOVh3ZT)0~HOV}KYS!1tkJ6;sEfl;=-Zm;EQhB}^5Pbe!66%S^{Hk;2W_ zD%E}0k~tX&T%a*8&G1P+#(cMK#@f4>Sk_iA-kwk?6V&A2b975|!`-`O%7OYTu`OX! zv10%a);~7l;A&iOjNe%kIfu*f{s}dH=Mq=`Ae!FCH(Kmnm5_R$8RZUYQarSnl|RDe z^&}k@mhSdcbphR^4@Rx`Ev=~Zr(tei{<$oMG>6C`K40LE;eQvy8`5>~hKD4h`Kf9T zV?%s<4hJHtM1>D)hK3S8Y`0mmnnMA(iCRwl=)06K`6KPGsO27|Yj61(; zQvBlwQh9pATSj#s_K0gmAb}jI$GeSTV+Hr(6(|LBQeekIW}#@R>*0f>ie#QBF$GQi!97yUCxKO;z zJjL4rZvJA+BSP`yf%Tk5>)ssjGEvM$>9NLTW?@>aak*LH)6P9in3I~Sb$gP7y_~hT zCk|XFX(db7+Mn{ZEQmz9O;-EE-0SWgD3VidQW(s}ljDir#db(-tY|6E3Q%16p-T1e zFUY76uhAoB^9}$X5l+UMQk8&D&FT)Gk5W{Gv+Iy-=z{6m2*Q#BW_2GfrGf+nB|2dZ zsuAp!63j|?>QrRz_I;>^&@D~O-^m< z3c?NdnZexyq8e&#xkr4;`8qfbg&ew=)sn&TRCrErFGpP0M`MVaYyZCMS)BcL8}{!L z4pkRxE>1P__t~{}$6S=k>ZYXq?{Ue=*_PVwS}T$)fdQbN6U_8p^R`|)d@S^Nb)Rr} zbUppPn2yJFp6=8(pq>e?wL-2L6SVe5x_TBOH$PYut{30B-If1lY?qb;OSDi`2Ka+?^08%YHqlqP`I`Y@9jG*K(z6 zKil6A&LK1(UD#%SiNG*;y%is5b9MyM%oe(3hUt7G$JrY!G#9>M&so@O56+nHsi*j{jVHozmndC&_tn`@l1$YX)*foeH-$tb_q9!Zu9dv8|3!mN^o zq5jjMcTb}XT;Cb_4a;ENn~^c7ukj@;VM}O94$n(ub3objSf~0Bsz>&dbC&obNra;9 zoR89QYQjQR`N96&1pB+W2^aD2QT}b_-~0TNck<8ip7rlmu7g~^%)f;9a}%ogC-1+- zZ(IEOxBSX^b40uYgpmb<^q2^45a1jEQI813ghKWX33}0>XR!IEaJmLKxOZ2YZEtv< zLM-|OVo{E<#0B?ur`@|J&DP_y?NyiDNgsFz&7+>a)w`h@J$=E+0!cLK-Y!W5qi*Z5 zL2RDnW5gUe!D#cfLOIsN_MIXhGNDlAwX()ic1UvGt6r1jM{}y9t6wLz_3TBS$u2JE zre^3YKTZgggXW~HH$}v_RT#P`A?<-zb$#bM2?+@gvPZwq_qEo+PuG z>9xjv_EDvu@z?#_I48kd$RLY~L=Q3qqwFm*$nz3M202Lvc`lXKgZyod7venABANX? zjD0xuCt2MfY=EoY2bg_Qc(GkT0$3VDT0@4wx>RjL*!sEQCJw}n>JynWRU`%V#{N>= zueM7@Jd+V8LbaPg|2%tPjr7aIBMsI4->4)t38ZBd73N7bm4<}2r-m1$;*)m{tAc>h zly|>`AH$nU2oQ^8Iw*WnC4MUR*~6>shRt)Dpsz?1_gWuZFp&??11!R(IUW#!@61w% zmUCpC+x3~vd~M$Vj7o14s4wSwyp9IMe^{IA0^$khO2VYSnh= zT$|mY&!zloXY=^l3|ib;f=itqx#3ov#{aJ7At zTMzNj+bW<5>h!y7gOmJYgdi`JmA^-=rIdl|^s8k-p^C+o4kEraaFfM&+{FhNa%U>S zVaW$&^1&GW!TjKP)+><=m<7{9%N>avaC=lXC*8oCGHB7I!5SW1%tw;PQ|K0S&1-_^ z@&U93pQr@h^3>uNGSK@+tI=yn`aP0Tgfqa@&YlXpw$knhjOFh+0UNV`xu7R9Bsn05YU>m1 zigJjb!v3cUA*bN15Y?_H&n^);&;l8GP6P8=+W{zYHJ4|wC6DmcD7K^=LcWYZ4DVb* zUlvnEvsB|l&(nX4(EhlEJF((}$Z`POUF`hf9Na=B6gg4bxr|#uaS47nSd?e;?bJJ|2s51#jzoQzp@{PjMIG1dg@3sD8Q~v9 zbkn>EZ=>waqt7B*-K4_UE1|sd4vA1<1MReei>g0havQ@=eUEy8wE>@)VlGWs52fO_bB-oUGF!xZ(4!m}Js+5OeoUNuFmfr*Fyg)3L7ooZ9`UJ7Tp&vi5hFDps zMaMjC5je9uZkGlxIE0jx?&m7j2AEPj8jjj#@X+%!jRGTKj=m&E_ zuGxZjaHpAi(kSmRZ#xO?zQsJxDBsOKivn($^Xt8$A`N&FQVpyP&8|=~WyD?I%H3GxAT`V~x(p8nKZGUSk%Z5sNK%s?<-# z$YdKG-7=FbiAvZj67~{oSmj(S#*O&5l3ifxSy}OMqf@=Xq96(1M{qhwTr-_)ZbH#a zkyKWSl>UTWk0DH^NDAcUNxrg*#4JRY)-SKI5W&XO88wTy7z?AgVjosN$ADZ0;0M}! zXPf%zFX`%dD42AnwMFNZ54|Lu9*!DjifeNk9${^ZmO+aYE-w=?Dh}Tn$dX-~`;aOT zFtaELS6nEATT>;_Z7ZD=y5AwvW@rkqXRB2x2Nv}h=XcdL3Q0VbBpd>_C>*QHNu9@S)46i(xouvOe5vU}Il8=qE8fy^C+a+X4VRiIhrNSH zK)&hPyg$xQq;!2`gP|xMTx%Z}@tO01DF{p^nhlVVaHkPRUCxDp6bUD>BY2E|(_NeA zXV#JCT&hZNgGV_@7bdU#)=4hY{rK;y`*j{=IfECx;2f2{2vl4uE?qb=O zWtd%?QTRbQ4v#uSPji%NhkBAkE885cqvU|6cqsUCWUiDY-91NBOG4^lQg$V|2d;2! zW+IIhL#ZJZV(n6QvLGW8(VQEAoPR^Ak$*69xw)UuE|SmET${%+Irx~wm^uc>A^!eQ zr+d>PPb`fadRLL*cU59Of+H-U7osW?)gC!tD$*V_jQy6{?v$m#17$DRAJ^p}pI0Y1 zvPoRTBY(1$7|5rimdX|Ch}5DnEK_@|gj4@cuA1q=l`8i~EK|e?1qf=fjzYH{9OP6_RdkWqM11&fdMDHe-d_)KW%H?TF~&6D3n8v% zzohBV&xN>(AE210BXCJEYwhNA&L0`%g#GWexkat;wx|`ra1qS4GZU&VL_jbEeYX(% z1nkAJi~)I6Ao z(Yv4?diybc>vwIQ9+3!IG@P46RC<3aaFXwhDDk4x6(iFjH*XNpuEP zGUZB(ikGEHT`sNw$#o5eOimS=MO_KG@*K{d)D^$x!!@;W5}QS>rN1Zge6q4#YoDa9 zzvYZyQ{f+{^~RpGdNMwCP414{SVTz;YHTS6ZRM(u*Wvlixl6vFM z^pS2aM*2Ew0ye#fh_7_54a*?q`7#2Fxt|+csNRGqF&h?=O)poW)3XBeLZ>eYUhYq% znyOXow|1F>>P01fRXo`|z|b^Ii!E?7YI8!TZ$^lm5ZUja5V`3@Uasz+i!D+5Y#O4! zePH<8ReT#6qaLGCe6M$1p=z&?`daG?sr6fxp}j)4i{4>1#B|V>7YRU52jQy=BYuc# zSDUCER0j2ZMaB^9G)8d?;%78LYuk9o=VncQD0(&Ci>D$m1nMXhhR(D(aPjY~k1F}- zVfm;haGTkZZ@mrPf>C6^?m4wO*Wx6K9Cr009}61+l@vG+Hehb>9CHtpUV>WBn}|5x z;~hN&Nspb&r?k!*i4;}*VZ3Fs;-GJ#$diM~$8#NarbmX#`O)z7aA@&5BsJ54(S$o0 zA!mD%HZU_58}m~QZBV6tbxdmGkco^}a)Eg444*YJO)r#pa-_h~O1KsE9E4=#1KF%f z1iUL%-9tO#^s%qwH?rMM`8gF4w>-!~pbouHXTPP5_&3BU+j}6y;1$f~rxBsH+#q)O z8&RUsueUR{$P?GTEtN(FISBz*PWi?F{OyKZqkJP4{vfUFk5kH<;&k`<xUJf5WTvs4yyCb^E8~U!*m`=dHr)M+F7Uu# ztOB3T@oo%EqP3AWp%Rf#zs#G+70)F`&Ra`-X>2SuVOSgD&a zs@3-gR2laW>dV%1JmDJX>-e+u{8QXwHq;F2hd2MIXP5!{O4%b9{;%!{sm| zooe|4>DS;ykj|o>7RQPw%|_sCg5Q@A+A-8A*9=WOiB=}cs>y-8rhKdSYv^8hPWPX zm3c!aG=9vYAB1OfaSV#BNSWI3RS_o1%Wn(0Uwup@V>gw71m#8JFM*eGJfAjRWqvB> z5b3YusU$fo>e59#M7JxcL7gm9Rp5cqL~b`;Q#ilW8!Hu8?2M{YS8eoo(L znP2}@?6FB*PwL2$(8-m-sn0-QB?PYHVHpqoNq#q%M>(G8v>PK|51kAKCqAP)Z9Ffo zlQj0oo6jU<@YPj*o7TH;BRPmafr~;d`3ZsS2V1boN=RtBiZb&la8qJUH{ZsX73};l zY6V5?{RR2z-H)ioyEk+K5}c$qyd6KNH8US8bjplqa;ofiY1v-0PrXS0l}Vp&?#Hpk z+>3Jk7H6mnT=K0a-WuFXekeCE!|-an!~t8h5Y?jBYU0`#?tp{ z6I`IO3K&dPh;0>~Dm?|_ZX`0P8V72U8YhU(6&9Ut)TA0UScs<^H5phpWEwTo)CQ)S zrZt3mvLtL;3W2Lx!yT^8TfxF?1HBXNiF5HN8iB`UW{ur!NMn`?pMyH8RLE$X3BgaDn z@&NQsX6S*mMC5jX$+270O#Yvr4oppv2zcI=1t6epLr_Lz1IhfI5KK06Me6~7gseH6 z*#rYckl8D_@~5gX)O1*Q>8SFj|83dj8|HAkv@L}gFo@9dDm1SCbi?aZFMfG~v66sV zIMk0wz%YSFO-S1+f7)<)n#p0fSkXAwV8std>Q-Nu)T0RgW0ti8$o})~>@1M^+FD{o zscgZi1&G6JMvQ1e@b%N zStU@|(pF48nxkbP{FSDO&}|-;g{~L9?odf)%!YS`IF{-*GeZpY++HW0fh0YlCwZe} zS)iwCMFy}H>i?koMb!f`39>i-qOrdZrwPG;5sN6$A+G6#82a%KsRL#nhv55(Mk$JB zqrBfJ8x5(1aHkmHhkj;5LQb(W@O4N%VoN>?h%yAtVbFNX4@=}S;9j0&DaC!BM0kcb zTZuiZXJ0ftqtHy`5cA=VawuHhSy8zdEx)kk;vmMf{7s%kuykD`zlSZO%Wm}~HUrdl z;A)Cy!Lgg-J)_27out{`su8|7qe5M42;Uorm6UCFlzYZ$QEH`=HLV-vo}qILim_Yh z>%cf44}7c4KmB(9n58TP*5IO|z;!I^Ccw0F8n5M+`Ey{QOs@P%MVJwp4>Fp2LP^aG>`ZzK8Qyu?O zJ)7p=e!jY|p%W7`=qZEc)YH z`j+D$A(F-I3Vq9RPl%**8_>71R3qnb+ZY_rw`ge+koQ9i#N`PZEFCZFp%aA`AC`$k zY7`&FTofr_q4Uh9i!qDHWn7!V!NU)z*nuVW!@neBPp!M5Sgg3b;z^guW_;$=n|5PjNll#p)|)=T>rU{4dYjD+IrDyQeU9#dYQ_ zh;@V4V)L-;J7n98_G71HLjB~eU%Hn zQZ-r=Mgagxiu?uH@exJ^3%x^-@WR)w*jP7qRc5?QoN*S*?(nPciA?YaTU(;!+n0SDi6U#L z8gCPh(4)_~UM$9ibDYuOVCx3?puuT0pu}1)+Y!6at~S1kR#lZ5xBxXdjV8B=)6Slr z6cJbT6xwA+QHz;i#;R0Ib#Rt%v8%_wtEGvU?pi@1vX56(sIUC%2tIb@U**K3$k=HU zX6+^EjKD>Vz%A53cfVs)_ZOsBs{OF^mN_)jb~KjaP#Ljo5Fn$-#Xy_5x!!2bnDy@= zUi+q))b7gq<33bks?ziQCwS;;dz3bq-jF+C@Z< z+WDYkA{~P)CMX-s1wy>BtM8Ddh3vW5u^7}o4BuO3_3qJoJ< z=S{=i$sG&gn%k*hpTuoIT|f~k5faYo6PA^R9X7L}BQAzuB1Z27Tr!A2luc;miWU&V@vxIs&30@ zs&0!=b%#(Y2m(Wd*&un{Bj;PU+SNYXYe3rm^raC<+b@gaBN`_-zY ztc*LSF#krP4M{!Cmb9Ma-6(h>6T^iN@8--<224Y$a!_g3$VX@CYSJsz1MzZ3ReZz< ziWSN(JTKMk>X~PkNoSNgj8d`M=t-0^jMB6?Y4rI9ppm)ca}D@>lFSNqy7}xj62yHg zoB%d=gO&>CbpDa1;W zxdd|beAy4o(~(&cPF`YfN2a4V!W17<*_yP7+bl~p7og+%4!Z>w7*!Zx9Kq+MB$d!E z32He{tIS5HCpZ*rT}pq*h6O>dE+BVl2zo9zw9@GMYsSFYlicTIiX54WbgL)%5t}zb z`yyav2>g$ndmQOvU+DBLu0U<@$2&Nsw&7Y1wDyn0`>n0B)>Q-=WtX*@oE;}+HO&>$ zM^tFCT~@obYx1B*oIexj>Jqll?yR0TB;LMgU=F|*LOE&wgem9B@dV6|g&JFd$OZfb zOhk@^?Y^=~Oi&-L)m8BnxEt*pj61CFK5)}N+ttI+FxDo+{)kC%xx0}q8hZCd`wiJ$ z|Fi=ZE^$5x(kniu>ebR)5T{uHOZLO}1ReFi|CjU!m8jm3mI}Keyx%?=R!6=}F$@ak zNulQ(<=E#R5Y>%ZY6%M1b1P&-Ia53u*UCJ z-&>_=M+1Vc7(X-ID-p!cqZOjXz;qmrRK#ux^rVR0H>h{!aOBk5ds6fyc(9rkQ|K@% zVNo{#QE?TYie!BKWPRGJ8z{BgpVs00Eme4w8`*RrITO^1a>jzy{L*b;J)1ZC_=~lt zdXK|JOj<7Y&L`Pe-5R120z{TJ7j%*OTX^X`1T5!c7O{)u3buwJRByq2F?UVeQx`bYzrzlO zONvUv3w-LBQwSZ5zO-kTO2XCi1?kmS$r>Aqf7!zJ4f_$6aGx_5Oov{j(}3;|_shOM zUA@kr8EuET0a`BHCQp6gw&&$X?T2Lxy(-s9uC@xk!_0Qoj6}lu+KgA@-~ZrmeE-9d z?`>9uVQdP*JIKx?5XiL9Rt)joFnU#RbsTySi;fVGueXN7Qk_y_NVX|mkHGD+I-GSm zGtqYtq<6ZSS4ATV99725$mG}yy7oG4{JMnMxK28$O-l2H!%lwGcAjv3n_St$L!voW;CCi!F?3+;_O>1xlo(;BYuA@%n@pOg6QNZ5 zPg^+{`jI+^#yES(@sKF9xV zXrFF?I{zf|G$ceBSW8Zbt!OVA1R zjLc-n9JGztH6)rh;)EU90sG?#s=y6q^8q#+V)K5!R_bSTOAKGt8ztSi$U(J`3JLin zco17yAG_gy$GuMFe#HwRPZi{6^O1@Q<0hlYu1eT-%wFRHvWF;@RZjdd(@`3`9`ocI z6iO%^>IyQ0T@Kd)wOA&baIv+LIcF6CQq&vFE^|-KKs7Ig#Hd&Qae*9s#o+oPklkVS zMgTGpse#WPH%{?Gq&d`Wd?*-_Y~;?4T&bpmYQ{H;-$>!$Pr|AbH;(FsSK+&LL|0q} z1rrX4(7{-)3{;9d0i0%$L?kEB6ekv_&4>x_OI3GK8HNOa<4~uDPC?ehMA46mLY+l? zVe^qbo;+KUcdJ9t9UN668B`0Xif;8kAw_@{vUl8hMPrhP#S&LYKn5lV{ZpTgC_sG; z0!Km+FO}xx&~sSpBRnj2V}QVP&=UiqwE2+O-Nype92KRIB|gEMR{yJ)BY~pH< z50x$5r>;(6*J#P83@^w~8C>HfX5`I8U2f#fHVZS1yj-&|v)0JVmq4FF`1xmdJR@~F zK+z(>?eM1%HVAVVLg7?_jzd&Hs1nY1JQIphXP1M6tH;^3`GPu3^)5DFlM`Ki_unaa zjaaXlJA^6!h(;i|6_n2G*P#bQIl|X^uSEA8BS6tfInI5qyz)L3Ws4V_!aeFH63Qtb zVsedOwwOVq+gE?K!x&M{s|3PPHyeek^N4Vc^yxPa5@-;wJ9PhDbwoIM} z5w_QuZY|yq*)*8&tPCT3h=gVF3A`eeYA?!A`Q$9eLTFX0!+1dsIS7o{I-Bu8WX zgp`wsQ-3eVJHB$;+_->qc3l*EK{B-tE_MTbG!c4_84BRnen=YWi~890>bsL<6JmL{-QSTYH=nG5{kY%w_Cp+2*SEfn%I4=Sf52Ld3zENBV zWu=&jggx--(Dp1={C7|@+kNUKSlSbPRTp(uogS75vyZY_*5|JwBF7$@A>tas$wrcN z1PZ#@-7#f9GvpE>Bn8b6`dwxll1fgOW~Hu?vWcu?iDi2dX0Q_p6TaMSWV1ZgS0B)e z8mqQgxvJ7@`seWbvZiG@L#v)6N(KkIg44_wKGo#rGQ}*^z6hA znasw#boe%79{K^JG($*r`ta75&v3wB##;LA>LIetj*ZObJ;qG5_Q*;hzC@=1-&v3@ z3l--+6qOFuBEw1;kddB`vr&J)wEOI|>k_dC>ml@RfgKkIf!ud<)8bf!RleL2@czrP z{(C&KxE}VO$BgFq>*H5O18MF5V}UTN5>Mr11>~ayE|aij>RDl#P=vvL7o;17;B#dl zZ8y=~7G{p}FhX%U0x&V8IYTWB`vhBZ)p-szy8`9`fQ_`!TnBO~gsH)Wp_UA!otaz{ zD6J)f=vPr(-^m|8PO`U0u2OA$ibywf2?K2(#tkQ;6SgjQ$P8gEL@~02_HZHY9xqx6 z4us)2UQ02L-6_X<9K-yxCfO?u=or~9`{0q94=fhzr0OH8+hu{S3sdweTbk@}h|gkyFy*BON`_2g| z_kc>)-=W_;)&4!w)Gr$3S!wepP5%;GoWhStbrw#|$<eL>Pf&u1%o$sPZ0_Wt|G;G0@*p~V$- zq1E%SRb6`KiT}FVx*Q&YUm?;BoeDLk^^6ElTDKzV$nL;08%S$$1BQ8)dR>f2(lNp z&-HO^#DeKIqIupU55>h4t$$h;fii|eGlcEq$O5EGeDG(9<(-;EQgGh2AXSGOV}}48 zP<&0`qP;^p#yiLImj`$sRL}n3X@N{9m>>2jmZ234&*||pb_sKfyCi6OWCdm5=rd?H4BnA3{mNVmzjRe+ABMK=+X|u2+X+qA52dmW3EKa3A z5N~0y5FCW$!-@2vGGUQ=vJmQRbBn_sFrIAP&1 z&F+B-|#=4p{93BZxnljDuL9u}u16jrt#>?DRW9V0OXwS6F-)b;}03}{$5l{`v3 z-l0Z9+wgd$XRyib`D2sBs2gW~mUSAxuOw)p(IrP0vuJ$kFoNdrjJcpKvF?Y|AGkID zKkB{*F3KwFfB5UDqcbWRCKf7|1r|!CfEtDaT279CK*&&QlZe`~obgYJj13gSYC}Kj++Y&pije5KOJ{4PmtyD1c7APh`MwxgIJLjDcgPF*&DYPe+M*V+(Atxd)&& z_5+qoexzj3uvZmJLxks$*$0B!gD4{~U&Cs7)Q+e`krGxf{Sx1_`;0uJN6eTNUq79W zTgCtm4LGjf#G(TgXETUmUjZ90od}#Xm5&4*p+PhnXI}1U3YXnVA!D{6=M*{TLE?3F21OMPk;=9LCsi>N>c%hD z5LNp&&#ber7^^AF%PB0%)f+dP3zo=L{YiY~RY9RxKOrr|-K9P)Ed+Oou&wxwT4`H~ z%Jl^{xyT$8@kT=s@eSD0ZWPmX!LJjcALDiL*f^W*w`(=fgD*jPLkN9U5iNrksR>#n zgpyTxFXeeFe0hP@Gyxi00(LDV^@d3KvvPGqbj{(egxD&-k}}Sp@K5XgKB+4G3KO;= zQvR$|O0o;xh;~rfE(fVBwcUxnImmM%4jQr`0dQY*mKO5~lW z%FL3nQzbz=S18MS<*USOT0kLMxAk1S0B%>dTrol8Vknfi)s=Rf|5S({%?bWy7C#l@ z=dk!0_8b3JCVtw)Pr*r^*eHG`o#Jl?#ZQ~~iE5)n$zul8S@@fSzxnvP7k>-!_YnS8 z;;#&UkK^wt{H?{`di?!2{@%qO@W<^EI8I}<7b{qO4XI5}m{|z)24Z34A5YT9+pd*A z9sxmoA89y;V)_*vQ3}^}_&%Dx$13I5d%qhH=ZebTO5fM;@2v=hMBiO?pd*boa2HoB znwqS&^g?jYajVbZu=rQksxOVy`m}JNrAH?EfJ{9pQ(HS_LVQba4u87@a6Fhs(caJi zy$ZyB>xo(Fc!7Or1|o71B&P>)82~xWG(fWpG%ynL^c~a6L)8!V%Yt)|LXSB++tc?A z@i`2hipck@bu*Gf0sOnO;^>c|00sX_l9f&qd)nb<3V zpF~-{RNJm~Hws3vQ8>M|Ijn!4DnG6kZ5CsSHlm+?WR>k>YY#gRu^Mfrzg=MjlBzkmM2-%M0$wvCO()k}4Xep7gKSetuk*coI`iL^{d1-Ibn z7cS6Q)M##RH5kuhCWNDv-G_pWnVKl)ga~UlDQib{2;jSMEpvzcOfjNceCiG>EY4K` z#FCP7K(H@_j3ckEOa~8UP37~yOix7{9)=FYQ*y*IgpQt{zMccW1m8Wz9c&png*DYd z-?!sf7zq#%l|Bk3d7+LK+ynU+-9<|X3>Tc!a8f-1hGM7VKgBg8ga>C!^`X#W;^NsJ zOMj@;^wOvCpOr_#%rt}i#r-x{+JapyTMY?To;Hw$u!r>&L)`ERLXaySyqkh*jqSw) zj-O!Aj6o^xyd`8B`wFg`V>aFdhW$IdaV%lywDZ3u-a7Q9Hp^kXg+9xHh@M2 zL?D(ph=wq9YYXWCp3AN2*lNDD-wh%=O@~=7Yysrs8#oMI3cZ0BOQStnQ$2*NT%U^z*_@-J~}EI(hW!g4n92wGZFh?XY^ z%fl*K9=Tk`^2$!JtPYOl63m!jv?LIOZvW>7j zqQY{#g5^qj2ujOE!Li(fsTB-M0zp`AB!CU9{Vo~HyI|4a>+wgIsA#G8VmXXs*-lt8 z6_yp3$+SE`4?(dU6CBHgPOu~pgr#0!nXO>C8Dgpr%Wtv!7jv0xhxs(7>rPNa8(YjU z+QWG{J3}?J;?MvNvtPkKF7R?AM5vCg!nC{dMJ%cs1hx;NsoGD=I&4{%g+rz2KX)WJ z-c~82l&jxBA~jEI8InDVg+cV9@^trEYCZmneV#!xw;?Tx4x8&?^3IPqmAkYsD~3c& z*X`JgBe4p$d8}deks-3kh;>ii9&&#^vv76#R@y+L!B8RC%44H`GY1t4L<7WVMtoJ36U^0*IbS3{w3Qi zVP)Dh>C=xji5kt2T_?AMTL!N@tpOCv;M85#@OLJ-ugALqmqVW1?9`czpsYJiK#p>t`D~KGA9@pE5qgc6x~= z!ge~x8Y;C-&t`|mhr%txy25L6a3#;h&P)K?(v)k;5WT?Y0>`MucL*T}ncy_N;aQl- z%#izps_G3hv!1~VJlW;qBVrz~H?Vy9#0gwn^a&Kt;v7ll(z3A=mk%UUHKdvi*dS)J z=RsJ2Kwa!dQtcSuprZmvo_N8aCEh`BtkrBHEt8S(YX>U|oQ}v$#8mCshx(8&jHRG1 z$y!rptV*?RfNiA^NpMB-!AKGWa8(jF8sjcQCesGG{DIVUXr#Spufb`p1!`8vOk;~J zqoll=lG+vV`b~{wf|A;}FxF$Oir$G1X-ei9uvv=)j0ds~{tI!=Qpgbjy@gs~DbWsFc^ zzyrh|Vn_sxhNmsyFEv8p!-+P_?0F#EGr~Px5qn6uL2;y8L6ST(F`jC-NZ%0m0=^acpV8IWJ&4vT%W*-%c(svxMifM8uiMR9m?N36zvN1VnyjU4z!F(e$CIWQYLuisac|EL*AT58qh%YJ8fFJefXyj;;&uUN6T$fiF9hc#{MqHT0#bVNXzLBI%DxAX zpnGk_))o3X4ri7ErwN8G&~&83W3CpH0A5dhPzK6ShRaZRYi7w%$P`D_nl8aw^9w5H zttrFhcAs z_NYIcThTk&pxI~zPK)TB5!5?_1RNK87KiK9kaQ4X1|}|60vx^Jh7Dh_uf|5ouftqy z59m!z{BACqSWtqzt{VyZI1W7;TO6`&0Heu_lColk(i-Td(i-?CwV`T z9*FX;H%|J7B?lp(Osi0rysmL={VO=;d`-g!qiUhe#yXby8`d#orG-ptW5rad9)2#& z1#g4!1?%vi^gxWaddS35(K>1SEcW|Vd}oqACLQrXp!oa^wtD615h+GDEOv}GuqKc3|~_b@pR*_)X5L zwMLX>y$UNtP965{)!=W&SCjVOeyY>dh;#A=<3VBJ*2o_D`#C6Z(L`FZlRT_1Z>PSr zJ8CL3uI3G)_C|#lMPG!t$s;kzHHLBh)^uhLn0d@c$;lSufm{;quCp1&gqA4xfxNtU^D}d?B%8nDW+GgSCdTL z$jpm8o=jwlL*sdy*_FdM-AZsIj7=t$!Me~Nc~vl0+?om2#6L==GJL zgms6XUIK04pe<$S8+r{9n2c4%{%LY`$H!Lr>+B2x1&#l97m&Y-?g)Klu zl;6tpTRwMKD`_6l*jlQ^B+5v07%M5mOi|$iHQ6SK3WJa>?q`3pC4+3KeBc)8{V3$- zW<^9onO5K6@)yB70`&;gCr{XlUej16I#+m7KeAvnSrXZZF`=4ry`)LeTC}#)$yP&s zmqe+QK2A+-vmS+Gp~`O(_FK5{tKcN7MVV)h)1<9#<4ID7l<5EzKucbCYN+K2+wkPmrw%U)X% z@F-dh@gChKYI;G`G*{hePl%d=blUwb{+%`q$4o`tyxoN<&PVe&FQRDz@_JC#R`d!S z?Lkij@e(|vsy}J25{Fw&9Il)=+-$r{MbTgvd=G&2m$;c^i~^&<(n}Gx z))N15l-`N|*$2Oj`<*`A$vQoOAqaL~YQ3CDeBe%k7tN@>E!C#Y^ZN#NzT&sqmJb~QiRtv*${gbY|=$+(xtFTUy5LprX5Z= zSw!^7)5Ip5Y-3Y6n|xZ$CPx#7Ga@9@4rBzI%&JJqdRnka7rW|y!6tu)iiS6rX+%Ry zAUZHwb#WKAeVzSXFcp-!}Lxv@ghb||OmepcH*ot1jEjxRW4N@Sj zaw3)TZRGi^MrH873BD!ygnJ=E5N*{#<-U8xM zX{a89m$=>wGS^GtRrMo#P#IL4xqdvnbN+lg`M z3#x+fFIr|n1h@k=u3v12aOQCiS^7KnRWkJv@`#CLfKl|Zd?X~bGR^hR(vd`ZE zzEwk9t4VOJ8Vy_jElD?moc7wwX~9HrPO$5%Iez-mP|m(qc?a{Q0wvT}W~?M-l-TO+ zh3V{fGz9l@_LXX^Sa_4I=tWI=8Sg*inlh~wTFpdDk5psHLTy^T@x>639^u2mvTEv$ zFNYA@s!`Y$Pvt2x+iG&cDKON3{%q?dHQTbGlQ`RILPla+l_^;-3ARa~y%rD8Jlxc8VputiQW^eaPhehS2M;!^L~4apku(>}Nym z&BuJz{qBBv201Me<(VPwuC<55PBz8G>T=59|6y+ZjUgH+!pZ)a%%gjn%x0*pBGus08kp!PJIwJhNu8>O4BqGP?%v zLkkw5gp|j1{n&GwV&R?7Vcp;`)sUmN@{mAXe@4_$qUSiiY7B&yA0AVn;7{fX{$xJ| zzh@B?TveLx z2xAqdI8e_p;tS4N7;&LN)9GK)`8)`>GCF zc$ZfTe+l$M9kuWqxCY)uy7-IN!jB}~HJTx-;7McnHAd}ul<;P?CSBIRqqqUOcrCjh zWmP)(DxT=%6_OTyNQbgq1uwEC3Ah(f!n+hDJe0n&9)1>U1IOV?cD>h)zID*_X#3FBzrF2-{SkkG@tm!-06{l;d$!XFuo%4?sS zRjhA7i_s+DgXQ{jLi;_`%{-F`?G;}U+B(|GD~ z3Z5XX@ld*NfqDiMA`E&Uh%nrRu!UHh=&lAG#wKFth=t1CYX+P0nGY*BN`tof8;paEq|<(KZc2qm~~+O z+~9>}(AhcB`YKQ)dpyya5v>J9=%`6)U46Tk($xnAu)pvm98@Apax}b4VKs5VgS_#xcRk63C00hX*8~MusN~dzs4wgoz_my-t>nM z99rubJ$H_!|DomsM#G`JwnGuEAlD&d8*3O6syS$CmBTWjzU@B-);ARO@tTZLxZ9!U zb?wh#U(GedQ;XU>@*OGv3!D#$nALm7VuhD zbP&Tt1i`_&vy&$9Q$`Hy(;zLDS+c&!18;|VBn~XoVQggK7_EXY*P4Nj(+FE^qYi&f zn4;w1nFctDg8SgSXle+&%^I7q4KU!WAZc4e7-Vx?9@M(C#twH{oAhgpyD)65yPd{5 zGlDDE;7C1dI1s?W1!;ZYKJjC^C578k$<`XnB{@xwwa|XcsgFr<(D_-BqbJ znWKZ#yJAT5*d&M9yTf6zixyi990LXpyL}==omkmoG}{TU`yOko(|Cd(VMhxViZPF4 z8X!v642ce$*5P^+Oy5zL7Pkh?;qWCAOTJak5W(i7MwwuB3c(MDY$yJ4CbWgEa1IaMlA)q_7;|YB6U#k!T0Wd?83kHX^L3 zy{J)Ri4;W?GAx127ZRyLn$TGp^6x`rAP3J zTSzmxv>a{wh~71pyPeLxctFtE(q_tlyPovJ!tnq8wFl|{Qu<${qaXJ7N#;2%!|fkY zNsTp5;-JRy1DqjPdM_u=J8geV99xZU$<5tAz}@pu^ZuV3B56*zTMuCyfXRf>yBX>f z-sGO}7zVp;hn@y6?uaS(!qUgky)ZtCSGElM7n`2T_XOUi&viPf$<%aep||PpgNJ#W zdkD>?ua;}=A5m_N^$K-!hoZTIcyp&6LVF*;vHq;w3LMWXD-XnP zNJD`wx$-K%mN=$`ce((bf@&oAXB0sj0omNG=U0+pGRaIr9 zDkw2AH`h^~b>_m*Ibc{qI$*t+#fiYizGxfN1bE6tBOMBOu17p9rn2&`!I46;OH!)<`HGh-GTf>jOtg=G1yNjqWW z%f22#Yc4l=y)dZN0#vC#sCtmBBPbV^9)C~^Wl*>f@*+*4-l;T&xHM&rvd=$^-3unc z5uu`O!o4V%j>ck@#lD`~Pr(E7mU$#|E`4EJ`B^Bj+Yd=G5u1 zW=xUvk%yYWo155KjCp&}5rZ?YiQLQ>ccAa`ng9ph2YF=_)JH0+gTJm>3^#{4O2QbW<4lxCF;kNnoy+?GDkR!y&0%fc7X_(nXrXMFyzm;CB_@@#FGwtE&F_?k#BC*8=K+3HU`|MRtl5(*fdK7_z!gZ z3*b-I3t=DM_dwZq2sM-P@5h~}sTeBKzA#iShM{r~X>lxEH!X^Yw=&YRkZEO}%WJKC z1(vxQpd+w{&F{xB{=#@eMzmE2b6MOlWQ9`9kJigkQpooWvCv> zTCJ3_-vt5bF2zXLzzTS^P+vE*OrVI#dwdi|Xtbt$LJ!(>!{?wG#Kirh9uOwH_gtr8 zS?7V^k`BbzC&RRVfalbgL)viiG6DMjb`*&;vjbSQVcLLgXnJmL>NnP7b8)o+mnJx; zP*n_rw|}r=*m`wZu?ME3U})J`^dLpcw@^8U;`c7p9Lm77Q1>VtO7;rN-Er88VcZ$P zj$_%kt-vD%SUrlDq}t>z*2v^YOQf6rN60nbMOV6JPa2G=hSX0Vyp8v#4zYWi7hi8L zjxspI`@<2x)Bt;pF4^&)I0EnCh8fdMeJDPM6#}9uZ zI`1y>(Y4>G6)f?io}`{4trYkn*LaZ}q`vDpy$Q3hE(-TQ!d*R^v5*W)LjRLT4Fn;; zazL7Q=Pl82dlMNR0Y5s~TEw3aK?5a3QOGcqXGWDFXh9|!xrRA2aZui?Y+5)Q59_j7 z!c}4`EKSIRKYMB<<`Exq(?E=*Jun0Om;C@ z^WDg8I%Kjq{?wWqF&#)rkzI2w&;v%ZLEBNAJwF--=yan`3Dr8fBT#&2$}IINf&-H>UxfkgRxe#Q;; zadeQ;;TqVBl=^ZU{-Yp42rCwNPYe**WCGa0RwqKAteo=Sm>5-IF|{v=gHs33f;eRUBhSodQZCW@>ub@rSoZuC zfCb0xCWHh*Z(()8PC(06LE*wmkcDcRhJ1^VPwq`V!yDP>$g&MSx%kAjjk^xy#U`g< zdHw`VeFVLQu!S*Hxl#Kv^`nB+ED7iFmUzBmUDO0XbVq>&_;@>;jLt5g`q-Nog7%^G zNQ7)>kKzjpa47L-2r6^oXescK2qG%qW;Y^sF zA~Z}%(IG?w{BYtyc0;~=|7`9Wg3}-SN6g>sq?5@Dw3fd4K;=WI%{BR{TNcNcg^)A* zkxq(h+&(=UcdMq6CP4>>6p7wj{t?O6b{H<4-W`I|ud7iM(M!aNf`gvVdH^rD(hDun zU_v;;*e%G0>utI4a>}BCv^cq07XE<^C8gGebS?5}j=wS&L970t#ZhB8Yoe=g*dkI`y;@2G^t*(_80t;+`sZHA-V8U)YD@I>q1~Ke)R5Q_*gs{o)cnOgfEv#TQ|2sx@u*) zy#OhzRsY_ndIUrosgZG~aboMQNJO3h-+`&0kN0)cR8D?-AwL5P5tu&(;|Os6*= z)^^{lxU-%{r$*#TZ`)QhLRa3WWDksq!Rxl+v?Qta7!i~lJwyY(y_ljhF|-$+$e>Np zmuw^&K&|q}N%UI!DqbPx5FTs~p22<^G15e~)1msuE2(T&JAyH%S`7`7)!7f=NAVPnd zVgHt*MoaadA1;}gqO<5>x)0N^b2;vrg`tSvCVdULS&KtnU13+xXiwj2;a6e+2$BR0 z=wtQ_^(X5~?15dXap=~85a!wJprglE9w9^VSU{_{lLDy4V5J53=!tsdk z9Gf}n(>lN~(u}3b1)wMy#>X7SuzdUGSodHxjOk_;)p`AB=LSSW#0&uBf6r1L?+@U76ZmV<`ncwVr|o6}*Kx zp1!pJDz!J3qufKCyaw#&$VaSaBO$;(#iJ;O>`>F4B6KHjh1BSC zchYnjGy+7Rl`S*JY7=8$q->SGvEHvS9n0cSR~Pt zbpn#eJE+DAZ=&RMS}9t1O=)e8^^<~$haqA#gkkTeVH1HRCbDt{bOT4ovyqB1ExtLz za9BlkWV8NwMN`~+__8`o)olo~XlkZqb{Z5^p1zF&&8x6}W`wb~be+x~$10yIH9<%x3PVRq+hJWiCudZA5e~$` z5}u|KIpM4TzhDVD2>K2ChT({+acX&})BybiL@zCSd?M$#C~_n0KF6n?(yv7*RU_Kt zaVL_!oPidc0=o2$prQRJa||-~X6HVoB?p(YG<-1t?krSs#x)I=V_L$Ce9WPbqvH1i zE&FGl-Dh_rNC3c|#wi}F6m`D~xV>$5jy^|(;Yr%DRfv2HXyd^~}fJ|7BMkz0AAqM&-PvXhC zz~q@c*$|jKk0(b3CNJm7(SgaUd2&o(@;aUz8<zZ1 zW}ZAVFu8>%=L9CV^W@tClViG2m|LIXAyng~GpcWf*me&D>W2Q9W2v9W+w zn{!iAE~o8gkj)kSG6F}zpKp|zJ&)@m>CQ!=P%D#W0;1epZfO?{eb8(n6qE84I|>|E zc}=8<*^;J-3y;x4t|bu&=3sJevooU~q!Cy%098YL{q8ogqXeHsP&*6Ug~Gr~lb?)e zFbH@@aqf;&xEdUFakp@~%SoT?bqlux0_Q*D7H;p`U|~3qP`ia|8o7a&bbO#XZCAU^8~nfwA=}oGjXwaQ_C4EORA9d|Z)y ze^Mk$k51i84&s{L2`Mvu=^l@#U~b@IA(>ssL0q{>_6|1+Jp#W!R}v0+J8->Y=Vu&? zejfPM+s@|Qi@x9<;+l@(pd7+_GkdO@wEJ}|0}`5-_$01@_z1LB4@dB=_(CT%x`tWyDQr^r3?Q5RqXzL;w6 zt$$ySfOnQ|8FGsC7d6>C@z2-lUJ>>yFOb^Bl124E_Nv7xEBkaOCqW#Zg^Rw;#pnf7 zQ@#XvsS_f(lRjM0F#hXK`iycnjG%6)6;AqY7T^^peTNE_F4(~x%TgCa|3!4cR;Zw8 z<>fOLx-whwFx)tSA5ff$;UdS}gc%Uq8Dh0Viv+t{oo&T6;am^77~f<0M;M^uBP?3+ z@!D|Lj$TF$eT?HD$^4RgLXW~Lxn*Rvkn!^ou^2AWm83V31dmrp?Md?Q7JkHJZ%OoR zBPf7&cWJ-VYRGjKH64Ei!aCU{79y?@uk;>XX*y4YDn$mBg;Z=?0}@zIqyP43KJyvLxu=$NDxR zKXu=OF^l`c9~UihMxFJ0`lz>W^SE&pknzMIV;lTpLHTn+)?sor&@(u2gIW;u zisDN(9EsZJx4?v=l`GAp?b&`VA+b51fZJ$oIQZbB7;9b&8R)jd1NAMa3f16zt4mmO zMm$_IKn;*l7@EySe?&W*6Z|U0IP%^uwb6Qj?a5GJ2QQ?Uk!|QfowtT=Va@i2P%_*8 z8m^p4*`??D9d_00!p)=zPfCYdD`B99P?EE87zme4Z7dzPY|v-$D~St}rZRLoU^o^> zNv((h(+=PFZ0<{BJ%tg1P0<`Uj)cDV!Cs0pYR_mEgISlG>ljUHRB;yQeo=VR7Epaa z=K?4>f@)(sac{>9^-HL=0nvHg0PHE@YadZ|kg1)W!|{p&_%Z;V6A1aGcRA$C0a*Zk zPzH>C#L*}BEgtAq^FBYXnp03f#$!9%02T+<+__s##jE%s9QOLWipOv)xL5HydQL1@_)JstcO4y!yJ$=H>r-IK9fZ@Yu)jLELj?ZWKpvDIt zwx?bgAAb@JR^yYv@wo^ReFzU8K9o*Zc~N>1AJizp`)JJz{~(DT$ML+EM9-9742f)h z_}uNo=dJ*JK%#^T;-i1~DKJ`9x3#ge)b(N>!EF(6A|)Nv3h>HG-s$10^}5~ZOen6gme|%n6n!Hn0!{%X?w$&DgAC_rqEE|zWVEHW#qfu#ufckGL>VgBP?rFSpIjtOv|6?At;v3 zxIP|a_T113mIQ*Z94xTBTQAdcFQ%st%M%ccy-ui?BafivlQfJ*O(ZOt3d;xu%SoML z`D}13pE(qqmIQ*ZTuuNR*w&shme=@Ud5ap$eaIuQ{5uV!Q3k@YR)yuSQe2=^-eV zmj%bt)CraZg0M^!ST2S#h|iwyFg<-(hN`hFM;<}TmuVP{nnYN(sIcs#V42-1mhBL^ zgN)19aBn%-dPyJ%%W?wPz;?rwiDNm|56j!2Y4FnW2=WLlf2UzIDw?otRAKo?ek=z* zUPTWB{j7t2{3%P7LKPK9N|7P-f} zb&BN&!Lgi;%hbVWNgxQz@d8WoH_2()is|XY(xAq&0(k^2-=SeNDuJ+UQeinr!SYUe z2ujO};8Sh2<14mh-95s6?XWUKN&K)XG>! zcZy|9a4he}HLYN@BoKt<6oKUvU1eIH!}RoFd6^o^SCL21@?#oCqcntNl?uyi6)Yd5 zhoH3l8XMdob9^gqU-^KLw(Q;3;3d>uON6>O14Wm)vgk`k~%kMYI zSYFX7me$}{F2<#=V6-F5wRr#8fe2U(g8yX?Pj1ron385*P%gOq79!#US9XlO3WrP!kP$D3pgC2#y&Zbtd2;_k- zSt&BSlOhLupvZ5c$eu7>1O%cBA7s~xh{F`0s}c3u^R);`85)Y7B~5nfTC%Ssn{`fv zfCAU)O=FB zBdu-mr|@JydM=yYfNYL1oTpm4Lo3fdf(in740T|PHG|!c+Q>kb-?fXOAdhU~Dw@h7*oUIBa)wWZm#7i^`&f>E zi2lg0KvI=dp&`rzvlrbAb}3Z~HC6caP-;gx!!I|()|BAjTqZiKK55(BIr$DanZb560UzA=I$~A2x=+qM(cXvfXgw#K<6~N8+pw6d9L^Qnu#L8V!u?uu>F5B z_REoGI$>HbV!u?ga|b{(9{Z)5ZZsnj559v62$q8A#T5Hx=F=bxmT&UQ$ZB!XlWvRS zN4^P#6CH+i{Ahp=;XBaPVeED1k7F z{_^^}(BM<_7Z+=Jif*gZwR*VM>J4qRH~NdqVO_^E04azoxpj1fXdMcLv9X_mw^}#o z-$zi^npA6*c1574c}^=NH}ATZZUc^lwo4B_ToRT>G#wn>7X{|0d-$mI1Efbfd*Ydo&SXJKsV@V^lUxr+OC1|gghIjk1dtroMP&l)}(&O*ANph(AvHyBQ%vW~%uu49}24ygina3Ban@Z-UXUdNF<{4CV4 zA!HFFf)$-Sjy_fgD_XD-Zc7jn$)<+#I$nZ8$>|7Xq5|b3oPf(v8Umqolc8M7q1@)W zdOzB-M22#J_6Ekij=UzwwaXAlXmO!|{rodfpfFE6L?EFXCDUFka%NWfAg0}tD7u17 zu7)`;36;QsO%*H!C~&WVVY6kCy&29@unBNv>*y{!1!)SkbVpYE_rEhMY_;UjP|fVS z$H=}RjX5FD5Vdb33}34JV?|?e|VX4$gA&ul4+q1d9?#|a(G5O08lmLO#&ML zXEniz%wg*wx_CoteTo-VyA(JN1%q>^0_PBp%c((p!_)!JFd5DmHJm$w!|5FiPCKgS z?Xe0tO~Kn!3o;jm<*=sTRP8x~mmuwVy(I|U)+lg#2sj4p41&-tM}||NhVx&+;oKMu zPJ#mG6`b9JZY+3vT0vfFk46pW>fms`atCQo9jfPa8zS~h z;6+8ZKVZDgEzDsLDsbc=UMV!bZxe7_>;h;`h)L(effM3AP6$g!Bo@g?Jo2k*CRO1@MF?eOn5Q72UV&~BNYt>cZ}1VW4L-tq z5*);EyG#vdQgAqJOm3_i&UdJukMP+7&ZOWg(5vVdg7e-ls`eblOOW=g4hH8T1GFNF;Vh zixZt9&fo`+$eXd!v>zk)e>{hS$D?B1Xi#*Fg0%pJpgu5`h|H*0R#PDC zNWF2brr!8GoDl+slh-da6Ps`VCf82FC|vZuL0uz)V?64FmcCq0m$_97z{RZvJr z`}5q4K2wR$vCK>+`{|MSsm7xe)Um`H)bUIzrsrTDAfr1j9ZdISJc6SE$~fHpg@m9V zJaU{8*3oK12)xxwOve{^OvhD|5ZBQ^VAZ{7w$r#yj_F9hB2*QL)fB3#5x>bVu90^nPe0c8Z2E%S=Rtr4L{Q|FT@tb$U%$NstM9fSIR7L1gP{%d2#X+C= z1$881^MF;>)eh6D4$-P^Q>ReakDH~e9ZZWWf^Xov ziGnUyBI2+d*U_7s0*yqCK|OL@N52mSq$94Qu`G>35N|~Px&MOkiVO6@DW&dV49KF2 zV-I82Q9vLaN6os(+W}!{+66mcqGVd-Pkh941o2hDsl3F8n@$m&>c(UN@|ov|8Wtyp zBVka^Uwz?p9yWotd{F7%Ul_^$v>vAQsjZ0Ucz*+y6pCfzjp(?V{X7P90Wl(0OFSB> zi0G&<`72syFIojZ4)4hAj1y4CzyYSWsnLLxFYzhXBw{;KP^#xpCE@k!vmDLc@F+_4 z(-2#lFUNLVHER~N|isY!GjS|W6c^=6T5tCwM@Cya_3R}_h8cV7Z zksOO`maAD|eTZKe#~u{M(P*O>j^Dex;K>)jaSadPSRse9GGUfcvj*^b^`$9zSJoOm zZvrR8kCp+vWwf%i-Rxb0n7$Nt1xgUdC8PPs_J*_iXbA>3+x!d>%sNh~^#fn=qgEct zQ3Um)s@M9y9q~rB8YHU4=S89_#F44+(zy^tyuEdaJf?oWUbM={3t0BpD6SPp$07%G zr1^KHsO}~Ov&%X33oJ4*avVpwvy_Nd%K_27fk&}g3zs5jFos_Vyu4EE#xI;p^a>E%D`lp0h0j3^ivgwk1glXWRKCZ|VO9&NgzHKW7`sQB!fY zt1>U511^HId5Q1kYy~65D)jLhqK22VRq#~2>W}|L z&NhM9>(ALn@g{WOY?t|x{W;t7iNQGA5C26N`#9S-6ICl%0B76k`{v_pnW9>-qDoPf zA7@*QBHrFoI9n8o5Wl{M7wC+$nM8G7&SvD$FOsv#ouzQLm##ezXWP42aJJ&Z#M#zC zs&cKRJ=-UE2WN9S=RpL3+8+LT9HqDMXC9cC?~dRz*ikwYiEPMHg?CYYGs}Q!mUmZ1 zNsf&F zr3I})xYb75C_=jYh|q_N%!f7X=heL9(-pztC{$zrwd&7gh$8!&LI6akE@^cXw!#!3x) z=6-NZnds=Zu%wf0#pAqU9x&W1_&qQ|S%#`RYzeEymayN#*<#B;ZA<1cnTxd^z9n#9 zMvgCFDABLnM!jCvC3AvId%np)++_pI7wD|K(l$1+*n(}LFXdc#B5ez+kOL0xQJdeie3xoF>-Q5J!Vk#81E}$WcCnt`f35w%wbwqiSD!&+2Q=l{t;Jey^9CFr2<*gA<(td|*q6{Ivp?(h0a&rvVRn(rGY8;_=E}9}OKaq2 z?_9;3Jwfa_yp7*9-n*h#i=CxCWGb5=<6XfES#~@4Mn&Qaf!M!di#eYVPZKMGJI7n& zy{J)a%g-|jMhSAJ@(EDX8TmfEN|3LYK%=tUXs5WOx!B(ksm@4Z4rZt}h(C|>u6ybY z;+MW;uu&MOYU;ZzkhW*CSpXr%c+yF987S)5NJ|P^%PaSB(kFd2e1|<^7bWmJ?Gcyy zz8%IMu?rT{O!oUQrDo)qDr%c3I>$?^G!&HiX|v>r0eBW1p7W=m=E{eqt6wtgZwy&=~!D82<9vUVm z$~anH7TW@4o}!LbvDhzs$%4h^q5OY_#oqn$g<0(N1sz!I0NyYWo!mQPdh%wF*m$1R z<)Tb)#on2G9v1ui&w|A^$Eg;Hs~_d-+5cxO_Rf1du-MzdYq1OQvDh4_3NOeL zF34iNJ*H-{PYn^%;HOf8c~LBO3(soxve*xy%~P@1ZI3A|_O*99ve+vCuM-wqDC!Ml zv9nRVcai5?Tw<|b4;FZj5_tbZEVe6ZydXI{u-KsFn#Z5EdP1!(yKnm3mq1x{Hw=--j_rdOXgfXBnl2ez z(&3;9VT$9V&-Cww!Ol4j9(iz?0am*{uWoP(OWyc!2#vEvUv!jX5IIm!taYXm4$Jy5 zT9sxauhlRP)3HBZhxq|#vAqi?Neg^IC`Sc7+T6P4zEXX$=ZrfHSQ6< zTCU`^P?(9{(teyP;f|BXw-AlIVOJgCk!Bwk1RH^vTNf*%GHwh`JQ7enh?MZWU1vNiYJC4`T&U?W_L%M2!XVYNAF%jd=m0K6aUp zsNdRx5_Qi=s^z>yorGG>L)0=B|TlWP(uA6paz-h+lwkRR_xdLHpy*(cc<9rWI=+Td0TDl6mFeR z6|P)!!P}%L(HE=H&c{sOI^A~*$}0zK{fcEsAJ zK5{s&Z(yq+D(W+kh!$PgZRJslZa>#iWJ3glaD-Y2FVwob;aQ94h(g00cjT$B2b>&o zN?e6)<8$(9)(VOMdC^rhGWdQZFt`XIZki>mgI*r)9Q_n zgotjDqY%~`9}OXu$vPa7bCroNHlciyUhOol!;K2B9s?)ov!qfQV&pt<3Ku>5fLT0o zExI|Y8d>G9$?1_+t}JfcOZyI&HAP&o+GEswC5tixhHu8WQ zY7nZqJnvC^Q6L>j>d%{=wa$@cdoYZ?X4z6MMaJcqIdej6shSmUz~%l1BB;bkY1P$h64Riy;mQ;` zAcUK7=N)9pUB*iBi+c?twtLSB`Rrbd_w&x~nl1n6?7k6KbYPev6pbFt?%N*$XnA)3 zAZGWDi=N$wmif=_FNS-kLx(fo4}9M`&hAnD|A%Jx%^V~D*?of#a);Sn6RjFcJw<~ZU;6*??0y?-GtKVT@Qc|!qXDxUw^QvcX}%kVxWX!#V#oz)tTXQ*U7~d$ zM&B;*?k0y7R~~k-rAy>FmKlyShF$CdQa?uO-=7L0W164bw%4<7Y`it1%y`h1ChT9lFdh&v-mn!N&>)0a-a*8J33C{6-L27F*o|V^JBOFPF$bo3$g z<+P0$J%^&rM`U z2h(8EQV^{Ydt3Oz$@)zt5rm7K#p+A;09QK;*OxpNNe^B0C1-%Jo!zW2sQ^+uqE5-1 z$W(58&0{Pnsl`!~#DQ7fwH3Vv3$AGp^T;Icbcp_uYw0D?}tU=^$90i=wPCsgc}w0X^hX=S9obPGNp-z&X&&18L- zwZZ-0l675Dt@Ihp=hzg~0Ff;Cjk=X!*tjK_w;EKm7d7EvtGzoS0YMOh*ud%^mw7L# zNg?*=OP<3Qew)--OQJ}vZA`J={sMv$ zG=!le4!)-N-s#vYMScaE&G@dd7zrT3v33W+4h$7Q^Hcf3tMy7cGW&v|Hh<&dcB$T%B9sAa|ri=Bk+98 zfqUzM&fVFG99+nnFA=4)*{KH!Uu(9Mx5`Uocbq?w?|Dffa)M0c?PdJn+q03*kJ$rX zfyQv1YapG-F1DUmX{kc5_-s0$keY_t-%~*!| zE4=mp^Z+E97e&=@JpWn<epd|2fuEnoZv?{)}FcH<fdfCI-4I(q6dRXj zm^U0NC{|d^W}0AHr?nO(Fp#XZUWyDGBD%2N_-Y6|WZ>M#_^RgQ)^H2%|JB;RKg<3C z=?nJ&Q4KAs7#g%TK959O(W@B$XwnMoM1chIPvb~?ch>@8+MbGdx*Norz$W8N2f0}e zYB?5t*4y0JAkEccbwhKtfz3tU4$U=STmjFn=V@;8;GoSl$j$vbtHUI)e}Brqxo;zp zH+M3c`*;t(=EnLp7g%BWiv{0unv~|=iZgv`?%k+`EVPdeJ>zY1upW+0<4x`+H`xMr z{(Lk9YjT>6^1d2E>#WjuX>wyeW~VTL?caZ+ZVWBT>>H$0d$79bOP)PV91dvx2@f); z=pa1-Nb;gCSSHp5bWFs_v?#50I8Em-?&B-OAZ3>BjHO}6-5HR`Lu-3;eyY}*Y%eMa zvre)Xxk47-NChhc3a$xW(9&0~jn_n86nLmQo@k|2)7mU%(iBnOB=Rmm@J;m5)GQg9 zGkij7t0loZ`mY8gTD`&bto@{2V4JL}JN$YBsV=+{%O)&NK`X&Tc`bz2O;mqu1XfLI zmA+(hn>?Rs9p+KMmtwgp99|fLXrS%5 zv*GSW?;184urPMnU$e1R8&|M1ppd#Q& z@x>2i>kQ3Qo1iY__i-0#_=R-^Xi@Zv&2`f@zi3m740O9waRuF~==p7X*5wK<#h8hZ-9J9>`G5>)f=6ai0T#ZyGU48S50&^1MffG@nmML>RsIpJ9tswj7ubu8?Dy2E_# zCVq!mC*NTM znjH%b6qRIQbhc-44DQdf1S2GUxO+=S=)qaIy}bs)>@o_ORh~J_9af%sm3~vLBOUQw zmqggxikF8u7RKl|WnSZ47;DDw)zWtQiH9e*`_L*pUhBL$j*=4Szv1R-IQ^T6FSa6` z#?s9d7r$hYBhrx`$0DoH2U>_ME$musAd*gIXR|f68u*gE&NYr&^(Zp~F~9Fu5G2 zYPNC}c^|96nWdRUpoQd$6&+VpO98=L+CHF(7X_;h-O$r@xEL$*Z}w|b03n>sP)hb zCmonEgMG5e+i)8-yamOnN2{4b^0m7M8lY}>QBBfGLJ6Z)9<0`OOVIUsx^~G zstoD|3UFomFX#`SdjUfsBZ{4V19ii96ehz>5b}}N%pR3%+DbJ&be@{-qnf74HNEYt zNu$&>j@M*%ouirvl#~}o2hcEDH^ssgq{F(wVX7)U05_8%W8*F1IAw}R@?_#<#Uo`z z4JE^jaoi4LpffWxb?f4Owz0{HRzvxeMkva zP5ot2c>FKqZbeFq8j6d6i2t2tv5v#Bg2p18KUVuWe{6*a`;WW3s}{~5SE0jrZ41IZ ziF6%m23yDLJHNX-$~O%~J8^gCbD?Rh;m#kc)XpEHQ4ohJ1Xe=4(e(~Qw@(YV-w~^^ z-w~%VPovOpQLMi}gOrB_6G(z=W^qPzR6WzZPR4rj7|p`VzjchRq?JM$qoW|ps>kRd zdY>QQ(o~Gmh{^aA%!Q+Bj5cF%$Yb{4$TeD}e<>2Gs)NbTL5JpD4E89Ga&F9I6lSZ9ZUqUxL&P|Mz03x;9@BzOUL1KQ03pzy2V9jS6uelk4q6r04-rgKAx> zsud^daOy51bg}nl`+6H*(y6y=p9`p+yeBHPCyLs6NGi4zg?Vo~1B;M@LZ!&>xuUlz zrV-1aA~{5>T$J(lHjbpIw}+^EyO;3GPR{QNj(a?c13peax!YfotEQk%%#Cw0QT4fJ zRaIl+QPpwksxJ{$*Jlh1VLD#7@Gz*ZJ44j%UO;s0CDU;PPc(?;KLmhcNRi}6WbQ40 z=1raL{oD(81nKkf9HNAQ$Dm?ky`QH*Xr|A5#b@6227L(xdJ!^&{Xh2J2QJDg`yZcS z1{h>yMny!$q(rmOLeWx34bed?(Ln+vMQu|e`e*t)YHKJq&@w#E=5DrY+n=>9KiO_= z)|yi58i?`7NKo6t)TDCj(vXq5f{D)eeeUx-^M`+0?dS9R{9dn*zMkRvbN`%s?z!il zd+xdC^g&X2NQ(EvQQ1&55AIoMlnkcEl?lE15UFN|U*H3yf};l|KF&)t@?aV418l72 z88HA(KI7?NHKSX}NMNMMnOtM3;iHtnGC={I!fvn)C`oR3mNHld%AYZdX@ogEz9Um@M#ua7rJ0Sz=C+4HF2qA;1{sO)g3N{O zIv)mk0c)eK801c3kTDog3I_rASfEs|FvwqmBM~BI$N=~-ND6<%?6h6z&mf(s%WpuE zTR{)~XUOzV@qSnmGV>vGSH~>mxlheU3ZCF3b!WrZU-X!?3?SVmZUw9|ZbYzfg zGyI6%uqgaD4d?pE4Rp>hs&SAAde63flJ3ullMkmpJaQuPix=@!OWbZS3)QpjA ztBsV#W$&n(#vtf>f?UDSHJWxO1yv^LwTl!5Xe6K-f&(e0-B76-ZR}SEBH_M~uj%-q2KK@@WmkTHliTK# zZotMQ%viJ~ztPp-^BVetK^PNwZQTfyOgiv_#2WTba70NbEe|Kfk;f>8OfGTm8|G!? zS?(FBA^y|YPaWBX6SK#y9_W6XY$7FuZ;Oewj!Uq?{NOF5axL#*4k#@4f1z172Ya_{ zEwo7dLw=cQH`#dPmr3HdSh27q7&?mK&_`SgZLFno<)F0K7Sxfa&0zCTzH=tmMI4pd z;1e>xK*}!U#w2A`)@BmUNxf2#9}OJB?Nqy7(g-(lThcXv)!VR?jc6G+4~2~fae57nN+H*?S{cM8kbM@#O%EAlS3^lM(chDkg6SIuR$cL3H89{oCThGr7HvpzU zVKiz)DsFHU=|MKZB$uL#+fyM9 zcBg`YY)*wZ*97VogLEV|eOw~cFNfpHEER*xS{roMak_ma>5{J6)nAvwTxMNLsVj7v z{jyF7d`pX>lkAuED_*p(R}?wxxguwMwB-U6ITyHQ%vzx8a^zzdq|v#BYjof+?ywk} zDO=n*`4J^3W)SiSLA=o)h`|_G8zA+#hYm8eQXLE9IJQ?$BEU}$fT6j|q zQW8S$lQ^AX{ARFIPlI4Vdtu;;-~fC~*VDTSP_PY4bhEQ)HVB1-KN{dm)hLSUlC%yC z+G%>wZMk= zW(T)aL9qgq4uzzFu{n){W6Qx90UX9T0oT+Yf5^hj^h610UeVGJG8*!*q&dKMe`3!(sMfPVywy%**GuV%3|(4i8GZMga8 zvDghtIh#<<|8H5WBY+PBzcKjV&tgxW=R>ti7Q0~+NFOZrFDYKW_8`6Mip3rPXZg>u z*kpj!6^o7b0p$xw{Qn=a*rUX)ub#zXYoOB%`ZHMU_5c1cEOxY9IkDIWM#(I8DgRr> zwv3cn?45W9i=F%%&SGVw0g{<9|ARFfnE+9f#N?J9#AKw!Iejxr_NMJ19rFN{29|N7 z7JCh5Hi!QWXG#2TB%6xASiJFYm54&gZsPBaYy|&nX4mlVMtmnX?sn`#drTn0*(%cN zu!}cv6Nqrd1Y!%BK=9PNXzN`yn22J@JoSfi>L?|3axd9n0&R2}HB&H|tzV_H*SJ&J zAfPEr31FH^W)3L^>@i9i=Gc9f>eV;d*M%F>oKd|pvF9ya#;KL5&=VV-dIb0TXdsx| zen?3O+O`)+ad=rwSp?gn-1Hi4(+D%;}IPG-MLOgBVf_Q}mZ*X87t@ikOJ= zg(p$zQ0F)@KY^%bNMlLcyv?{qs#?S=R$F0X_fW-3EJNos16m(0i>AoT)(Hx;D5`lh z&|d9!1;>{a)!@uJtrbDZ?niK{(ZCkc1aHLxk^`l@oCyg-c_~c`ugH<687rh|QOcqb zn*tzdic)LO3D+UG>2Ob{8b{qS} zR0Z5n?L={p7-$a-a4jOc_qvQU_WK0%Ey*sPb7iB&DLiy$9mv3x1OFuP=W`s|B-i!6 z$-F5{d-X70D74bTk3t_NSa2RjdqZ{RxDLqhz$E)dqm+Jj{;K?aJ zq6aq1)<{>^1H)xF4@rJK5J_-eMGsu8>#_$LC-=|;+apMO_21tEcU;GNpcN|_nYKRs z7_>DN4V5lHlLGQX3#II6PFykAZN`aN_S!HN@ntrIBlZr9dXV-Df5%7cz#%L4PT?%I zHR-au#9EI-WoF4*9bZ#Lem*!vGeGWirJsIbKt96?m5nmnTsKycJu<5~6qcsSD z@&h5PH%e3xwqHfq9$Fm}!fM&t>r{m8?~Jeq-&GN|pAgou!T_)oo!jmM!kdBe@q z8_}R+=zKiQqVs^e@TwwutvT=q3!cEui%#LumdeVYCPWVu{DYn~!hKHXN4QBvf(A;U zq+@@X=5`-_{y1J8EuvrfJ^JBjzll7hFxvhP%Bc}!Bwp1Ns!JG#O}U}vjRCgQqfs$z z0+ixeSw{!E!cH_CJzW()50bvi+xNmULL5${~R=X|*veQ1?^n!ecSq$X3-sfC`VFHZ1j?g=!Pz6v+n|3RrW;t1d74o7pZ<;_h!f%Xpk z1&Vu(rv{jAV@3m=lpSYH95AK|8MW z`cE)u=RX00{~n%>XozF3&yS|_>nXL)l>Myc8ZWiyl1Uox7e4pbgFn5qs{_6Fh2e%W zX05eV!PJ}fXpL)(|Ck-|P&Z>XM9FfkTo1&FeYYs+fNL!De}yU#u-Qd^qxLC0_AqL- zKJDnls?WaNp%t@XD#crY2tg|6iH7nA(HJ|lq|U!3j#PcO45)2ld#q-eM0 zX`yT1%8s3dXc44XF-psE__WkVd;T2RAl5<$$wOU}9uh&DtKmDy#-<~q8pJ2`8tidl zk7X-c45EW$XGzNsPf|k%lE;i`Qx0h$iZ(-EgbzrhO7s z5U&Rl*url7n@Y$V!NuT9gFOy|D&GBpl{$@UveUYm5<0D06h)@$f+(i+gc24>6BGkLo#GffbO&RMmbp1rA$^zcn|DXtU044S?`X>xj z5!QtWW#~Bi?UOz1xCasH%)Nx7D~nLKjPXH(p9uBddOr>p)N>nz$R64-W2Z{PiU{?d zOweWX-P47;Gin{eDi%=iWzk&$$>zl67;N2@d4gSxFQ2ilhLam1qRds~vCqjill7!@}E_`fw?1A&_4fnRA zlicy^9UWQ`qPF6SBGkrZel6)qgnD~acY}B8TEC_g;jxFNobA^`Q!3u*(3GjSDjjmg zraXD|P5CCayQZxCrQ8%ngnAc`S1m#f@-z%0)K@qPO_b#op@dty7onbo{`4n_P`CYx z^zd>V#OiQWgt|mw(tfN6b?ymoQTZsUdl4#R;Z=!H6Htgpgu2w{DnzLFUsroy6`^i} z8AT5w)Z0ryYClPY`uYaHap@JI9$6z(fZ|EH5RV|{Z0<;TWgHuicE}o6I8uHQvLtmf zq~ud%I{ce))Z*y)!=}9{BkkqyIntRU!hT_b=~vDGSRC!zurWGO0PEca%zb2M1@ zM}-76?L?~Fc7V|Yr8?{r@HY7sdv~W|2U4-t7?eEAk!)`20 z2TS%`GwwV{Vt>k{7Mc;m!~m6e(}x2a0h(YiHkIN&H<}7qLj}aq-9xwo zeCU+C$xRpN^#poNrm8A(qHKBNn?Cb-5oyZ{YWk4UCR5s2`BSHETmM&o#EA$R&+Heh zCMPV0-1hRNP0#|M7WnFh$Fh#p_38D%-t%yGBP)B<1;x=3%4mR;0#&PzelA7Ca2ToC zv|&i1=5<`Bam~9XQL_-&{kYn3%^#Ym`30_raXpIbB(4vTZWO-#71usoz42}Ut~t0$ za6OA_8!ioof<_yli-?HO2jV9PS46NOq*p|*kWjkEKaJMdyLUu{HX#aM_sPMM9} zxd;y=F*RU1Wz0%jtOwb$SYQ&z$(qT*I4nFd8+z1}!>&V#`O)NZg(CTSg97yZ2$^&g zT0rj`S^&nZ*Z_*-vq6iE0vUk2`p>?%qe!TWJY!pIc0DGE&GyRyusbH68lGmaG-u>t z(t#4k2z^cq`*yqA9TJQ&<_>GxpiB&xwc^k;dB*gpo+xcO($Z)QcHJeVs4z78!*B~V z%PRr6;=B!;ctmZOU(5vFFo6d<*s+)7ELnZ8yOtA7nfiP-nx4hGCs@sL2!?MCQ*oXA z{G+(ZlZ3I8M+rAgzF8dW8tDa~l;`Law7~~}n|%OyoL4A#09e7FO+~H1k_U)oo+3MT z77!0`?;zv{huqp!j$D}6fH$YLu~Qfd_9P1TK^rVuO+~*(77&dNe|UIs`(*@{BeU0G zAOfX-yiy5uQ1mNm2c>t{-*^ZcPJ^-cIcynS=;Q2)NkJVrd!du-%T#BU+$a25Jtv2GZ z1GkzKh>E#X0d95rdReMaxYh4r=mT!GdjwF`4Yv|eb(h>~X6@**xF6Fu4_y&}f9>`HFrfngBjx8VxK)Iw+>RZDx#Y^+ zY8DE797+%4V;U zDN>3o4V4>G^9Cr@XRy!zi2C!Rd5gs~?;KKafXJr2M7~WIS*Hh8Yp=fAX2wp1LBe;$8FV=tIduI;EaBZ!MqhI1h z2ON!blS+@w?=T}Azt(R?C?cb#E^+ja7rMsL!kR1L=*Z6QadaEe~ogspxHn zsnI-z1ZhvYnT;T9FKYqnTQY39Ca5*MB$LVRdL96za5B_x4-l_`%m@`pejwB%u2B1h zrH=(|DBs-1kqIT2KSb;X&!h@VzFD}H@}r+PC{?&? zhkf%TKtm)FE>#rOtO*x&@io(Q7o?Q7B&kwrKv0Vn`!F@T*DqdxXiWi9wNwsUH*S}k z-8ASPw;Nz7cxcC=Akh$8nE)UTv5D|mH`v|!$*?wIIE=2y-q*A{2>SS65boRHBDz>N z9X50c@JeN=5*ru5xLPfl=wMb19Trt=ec>_6z!qc^opgAaq{Zl#U&3@4p4|Ws&mQfQ zJ)E*fb;!Z1o6-P1#LC#Kq0WV=f;jZDHsP#9552z`#~KE+u?w3t}+tZ zFcP-2C|FKN!F0qC-sW?cLU))uA3vT`g$R-+tu@CVuC(saAVkz4``U1WQ~?uO9eMV$ zSewOQ{!yDK9k6_|a-8?OY4^Bf6q++JPUwXbug?6_z}WXF%qcd+Rvv1ja14RdEOjet zDGy2<9DBg2PlLr442EZ6lYrcsD|R7bs9S8H_XXj!ii3$+nY}qusyBZ=_q1Z$`rIIZTf*Un(a_Cpvz_Af79g0VFFU;; zWizZSStL}w@_ew_#tKuEZX(vO*&)8hRz#tvf)Gbwd7P(rQeLANc4e@9*#W1yaE0a; zw^v#B2V(^W?+16?pcMRqTVFxA-NAnK18`fx9$w5j&55H;24qp`^9{W1mOh8J zsSO8x9vOrO5*B3od<+Kwz4S@3J8~cRUqGL~dJs@{L7(Ntmw~4)=<})bmwKSj7hvH| z^f~GSjUOIJmoJNl^f z+4}XRuIO{zzX`ov(r03&OrKx3a{8=W=u4kqy!PhVUXxoeeGHa!G$)c)K)QhK^4!fB z;xG#z5Jm_wV+z`tmtbS_Zy>W$c#MPojgJ0ef`iYOa@s;lX@_qvsUrSE%!e#6jVu-- z<>~H9Rc(^1dX2Uqyj6WWva6~xI#uoqv=^R6Zun^F)1hHGG5Bh87D)tt0J+z*WYTX+Ijn3}JyW(l*NE|9US1(KFIc5ATZn zGu7J0sUz*#xl~)!6-Ju3wqm)q+x%-Q=(4sZ+I6CF?Wt|FQd?ef=?an}YUg7B!DH(l zDIX3$(n&52QbKz!iNh{5rxrf>)kaLwUN zd5!h@Ugfr!pW`63K@NCLYGC)1aRods^7vTFL=TS@QIG-3c=$6Ep-B}2VgCx3%gep~ zA)saFCokY;;(Y@dYm~8%Ff_Q^hW_h=?CasSp|`X;+9I-5fw3av9yn))<3N?i3Fip# zxl*U~H5q=mz!bq-L2z)@>7&C7;t~Cf{JefTdFDt*XcjilmWrInGYF?^s2qrDYA*Y| zFwwqOb$y8=m%Lb@p@EH&NAJK)kmyzNP=#IRLjLd}qE~U^r!%|Wr4o^cU1y|R4aq7q zgTk&a1^ZPswacpR=v0+!wm-Wr$@rh;NwedwnkTJfSILuzUB3_45gv9u<~mLyJ+tev zS(J#g>&KcoPa2u-$F3j7(91#WY_DI+EjKu1Y+5LkX`*!s6tMqK{7;0RA&Z;GfrZL7abP$y7R6Nc*>Mo zDVsto;=Gia2~Kj1$YM-&CI_UZvCk}oF!&;Lyq`Nc3AA|PFPjwXyl^GON zHmJxFaF_S9}!!1eXReI-L60 zt~Hx>L9v&P)TB1dap;^22t$fy?~4VpW{($2SmvQD^cpdcoqZFYeAeMM7?6L3hxoFI z!D0}?CkDCbH(-a>_ZMHh#~1XniiwC%9b8jrO5&q$W4(S>)aVpsEg|5+d3It=ZowjA8FLD zln=F&!0K0Dv~RvtDct~)z97vtTMn#PB9Yi=j4ih`te!+C>Ye(5aJnE>0Rh4A-sc<# zFZC(tI&7xs7iAy}2ei}JJ}_sn!Jl9h;|OupA{QxN$)ki+mo^WMAkx^wx4<6*$kwP~ zuUrD&_#pUM^;$jkzVvbT^?o$YQ}4~5dQ;7o)bN!<(Xj?+q87c1wuQi}fC0V&QpxYe zjMM;EA7D&zijk*fhXH?$tQZN1K$@T)R_`)V~$gZpEezg>ojVXQs8EV z{3S->EbIdAFfmoZv9BQ%Dr7H00f`)-ryAmzybiRlU_FIwk&=v9A0f=g;!$Z(GBDf8 z%We=Uv=$VWh6m7~&@|EfV?6&D&OhF)FRg_Vm_Ekvj}iQ1BtAl+QlcrIiNBHbw>Q*3 z)SIZ=-W-<(Q?7b!wAjD_*^DSi2zjCQ3Y?bDk}gtQ5M#U>`lEU%E^Mp-O5H@C5jt20gfLvdt zn>Of%ro&B-Ub^hE0MWEN!y<#=ofI*a-NFPZzr~T?l8Ii?vL6PZz~C%+@AK_?#}ul9 zDm+0IR`DD)cW&-1e6%*&*c;GH)T&V4>VeY6p)4RMm-s+g=Ef9FQ0lsc zGQ4Xj@9YLjs)C@rRfcjMoSShdn;?;Sp$zeb@;68$0Xy@f?@$ z(@mF94(J-nh;E>yDhSF8L=jaiL51=!7@l5Set@OD2g)=Lln+p*@mmPWH9k-tryV>R z<3qcJ@`S$2(X$)}th(YvR0TnqCPVp$844}~{h%Dqq12Z?)&PF_I?^b{90*aQ2MZ)d zSrydS7f!!-_T@6^Jf!hT*zm9gpG8Pp6=dv(wNH)#C+tUS(Idp$|9%1x^POZK&dj~a zSQT^}a}4_veW(iBh#*v|e=@lMR_yx)6AUV-3YtOn_x_vPeco!&%GrcodGj;aZzmB- zxSJ@xIgQlpDa?+P4k;jbE$^H{Y0tmj>QLT{43^kAHN#G|S{_`bM*F77TUdJIQQ+;> z=|D|BRxDV%IIJHzvg@&>ymsPHadn=V4Mh%2h7Pgz+U$1BE{RggN3I^dji{=hz`d=%HPpUcmxj|Labl_+f2YV9l;~)6BXE6WY}O5Vq_1Z zZUC`|eFajSUKP}a9wS)PdU-v2*ceor7c`$u^R4IpuIm}xRXu@fJ?rFp!qLlJ)Kdc% zn5Uc1j!yGwkFM)_{x+eD_B@Z`d3z${dPa3!&)rHrkMMdXmu{n;eHITsc)JI9Tbwta z-J#agEaPnw;cdKJPbvE=#xgZzI*?7vIc!k}pmSw9rT`gFh0)V%fWr@`rR)SyLT&vv zf!eCTFu)&%rGQ}xV5quPX{!PQPCOaeo2VLXEoC?M1Vd%gNDT|)K&ViQH)y0Xm3{~*X zoyhBIL0xQ#e=U316G|<=O3EGydm`8Rf(ygN1Q>5m&(n~LY^-oiyL8wzf-&7GtPQY^#gdd3dRQWO{9!l4 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: pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy