diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 30af44995..ac1d31324 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.3.0 +current_version = 2.4.0.dev0 parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\.(?P[a-z]+)(?P\d+))? serialize = {major}.{minor}.{patch}.{release}{dev} diff --git a/.mention-bot b/.mention-bot new file mode 100644 index 000000000..f07fa548e --- /dev/null +++ b/.mention-bot @@ -0,0 +1,35 @@ +{ + "maxReviewers": 5, + "numFilesToCheck": 10, + "message": "@pullRequester, thanks! @reviewers, please review this.", + "alwaysNotifyForPaths": [ + { + "name": "ghuser", + "files": ["src/js/**/*.js"], + "skipTeamPrs": false + } + ], + "fallbackNotifyForPaths": [ + { + "name": "ghuser", + "files": ["src/js/**/*.js"], + "skipTeamPrs": false + } + ], + "findPotentialReviewers": true, + "fileBlacklist": ["*.md"], + "userBlacklist": [], + "userBlacklistForPR": [], + "requiredOrgs": [], + "actions": ["opened"], + "skipAlreadyAssignedPR": false, + "skipAlreadyMentionedPR": false, + "assignToReviewer": false, + "createReviewRequest": false, + "createComment": true, + "skipTitle": "", + "withLabel": "", + "delayed": false, + "delayedUntil": "3d", + "skipCollaboratorPR": false +} diff --git a/AUTHORS.md b/AUTHORS.md index 09358586e..78bb25f9e 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -16,8 +16,10 @@ - Bradley Friedman ([@leith-bartrich](https://github.com/leith-bartrich)) - Christian Heimes ([@tiran](https://github.com/tiran)) - Christoph Gohlke ([@cgohlke](https://github.com/cgohlke)) +- Christopher Pow ([@christopherpow](https://github.com/christopherpow)) - Daniel Fernandez ([@fdanny](https://github.com/fdanny)) - Daniel Santana ([@dgsantana](https://github.com/dgsantana)) +- Dave Hirschfeld ([@dhirschfeld](https://github.com/dhirschfeld)) - David Lechner ([@dlech](https://github.com/dlech)) - Dmitriy Se ([@dmitriyse](https://github.com/dmitriyse)) - He-chien Tsai ([@t3476](https://github.com/t3476)) @@ -25,12 +27,15 @@ - Joe Frayne ([@jfrayne](https://github.com/jfrayne)) - John Burnett ([@johnburnett](https://github.com/johnburnett)) - Luke Stratman ([@lstratman](https://github.com/lstratman)) +- Konstantin Posudevskiy ([@konstantin-posudevskiy](https://github.com/konstantin-posudevskiy)) - Matthias Dittrich ([@matthid](https://github.com/matthid)) - Patrick Stewart ([@patstew](https://github.com/patstew)) - Raphael Nestler ([@rnestler](https://github.com/rnestler)) +- Rickard Holmberg ([@rickardraysearch](https://github.com/rickardraysearch)) - Sam Winstanley ([@swinstanley](https://github.com/swinstanley)) - Sean Freitag ([@cowboygneox](https://github.com/cowboygneox)) - Serge Weinstock ([@sweinst](https://github.com/sweinst)) +- Ville M. Vainio ([@vivainio](https://github.com/vivainio)) - Virgil Dupras ([@hsoft](https://github.com/hsoft)) - Wenguang Yang ([@yagweb](https://github.com/yagweb)) - Xavier Dupré ([@sdpython](https://github.com/sdpython)) @@ -40,3 +45,5 @@ - ([@rico-chet](https://github.com/rico-chet)) - ([@rmadsen-ks](https://github.com/rmadsen-ks)) - ([@stonebig](https://github.com/stonebig)) +- ([@testrunner123](https://github.com/testrunner123)) + diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c7de23c7..38fa56a62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ This project adheres to [Semantic Versioning][]. This document follows the conventions laid out in [Keep a CHANGELOG][]. +## [unreleased][] + +### Added +- Added `clr.GetClrType` (#432, #433) +- Allowed passing `None` for nullable args (#460) +- Added keyword arguments based on C# syntax for calling CPython methods (#461) + +### Changed + +### Fixed + +- Fixed Visual Studio 2017 compat (#434) for setup.py +- Fixed crash on exit of the Python interpreter if a python class + derived from a .NET class has a `__namespace__` or `__assembly__` + attribute (#481) +- Fixed conversion of 'float' and 'double' values (#486) +- Fixed 'clrmethod' for python 2 (#492) + + ## [2.3.0][] - 2017-03-11 ### Added diff --git a/README.md b/README.md index 2794298a6..7d12eafaf 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ from System.Windows.Forms import Form a `using (Py.GIL()) {/* Your code here */}` block. - Import python modules using `dynamic mod = Py.Import("mod")`, then you can call functions as normal, eg `mod.func(args)`. -- Use `mod.func(args, Py.kw("keywordargname", keywordargvalue))` +- Use `mod.func(args, Py.kw("keywordargname", keywordargvalue))` or `mod.func(args, keywordargname=keywordargvalue)` to apply keyword arguments. - All python objects should be declared as `dynamic` type. - Mathematical operations involving python and literal/managed types must @@ -63,15 +63,14 @@ static void Main(string[] args) double c = np.cos(5) + sin(5); Console.WriteLine(c); - /* this block is temporarily disabled due to regression #249 + dynamic a = np.array(new List { 1, 2, 3 }); Console.WriteLine(a.dtype); - dynamic b = np.array(new List { 6, 5, 4 }, Py.kw("dtype", np.int32)); + dynamic b = np.array(new List { 6, 5, 4 }, dtype=np.int32); Console.WriteLine(b.dtype); Console.WriteLine(a * b); - */ Console.ReadKey(); } } diff --git a/ci/appveyor_build_recipe.ps1 b/ci/appveyor_build_recipe.ps1 index 52520f4c2..84e0bc7c6 100644 --- a/ci/appveyor_build_recipe.ps1 +++ b/ci/appveyor_build_recipe.ps1 @@ -11,7 +11,7 @@ if ($env:PLATFORM -eq "x86"){ $env:CONDA_BLD = "$env:CONDA_BLD" + "-x64" } -if ($env:APPVEYOR_PULL_REQUEST_NUMBER -or $env:FORCE_CONDA_BUILD -eq "True") { +if ($env:APPVEYOR_PULL_REQUEST_NUMBER -or $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 diff --git a/conda.recipe/bld.bat b/conda.recipe/bld.bat index 1495f877d..27b55f2de 100644 --- a/conda.recipe/bld.bat +++ b/conda.recipe/bld.bat @@ -1,6 +1,6 @@ :: build it :: set path to modern MSBuild -set PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;%PATH% +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 index 98602481f..3641185bb 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -1,6 +1,6 @@ package: name: pythonnet - version: "2.3.0" + version: "2.4.0.dev0" build: skip: True # [not win] diff --git a/demo/DynamicGrid.py b/demo/DynamicGrid.py new file mode 100644 index 000000000..951a6c248 --- /dev/null +++ b/demo/DynamicGrid.py @@ -0,0 +1,23 @@ +import clr +import sys +if sys.platform.lower() not in ['cli','win32']: + print("only windows is supported for wpf") +clr.AddReference(r"wpf\PresentationFramework") +from System.IO import StreamReader +from System.Windows.Markup import XamlReader +from System.Threading import Thread, ThreadStart, ApartmentState +from System.Windows import Application, Window + + +class MyWindow(Window): + def __init__(self): + stream = StreamReader("DynamicGrid.xaml") + window = XamlReader.Load(stream.BaseStream) + Application().Run(window) + + +if __name__ == '__main__': + thread = Thread(ThreadStart(MyWindow)) + thread.SetApartmentState(ApartmentState.STA) + thread.Start() + thread.Join() diff --git a/demo/DynamicGrid.xaml b/demo/DynamicGrid.xaml new file mode 100644 index 000000000..3c82eb16d --- /dev/null +++ b/demo/DynamicGrid.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/setup.py b/setup.py index b85e2c8ad..c23f6b5bd 100644 --- a/setup.py +++ b/setup.py @@ -65,6 +65,9 @@ ) VS_KEYS = ( + RegKey(sdk_name="MSBuild 15", key=vs_root.format("15.0"), + value_name="MSBuildToolsPath", suffix=""), + RegKey(sdk_name="MSBuild 14", key=vs_root.format("14.0"), value_name="MSBuildToolsPath", suffix=""), @@ -206,7 +209,7 @@ def build_extension(self, ext): 'pythonnet.sln', '/p:Configuration={}'.format(_config), '/p:Platform={}'.format(ARCH), - '/p:DefineConstants="{}"'.format(','.join(defines)), + '/p:DefineConstants="{}"'.format('%3B'.join(defines)), '/p:PythonBuildDir="{}"'.format(os.path.abspath(dest_dir)), '/p:PythonInteropFile="{}"'.format(os.path.basename(interop_file)), '/verbosity:{}'.format(VERBOSITY), @@ -365,7 +368,7 @@ def run(self): setup( name="pythonnet", - version="2.3.0", + version="2.4.0.dev0", 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 24ba26862..c164e75d6 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.3.0")] +[assembly: AssemblyVersion("2.4.0")] diff --git a/src/clrmodule/ClrModule.cs b/src/clrmodule/ClrModule.cs index d24376a1f..3bb3a533b 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.3.0"), + Version = new Version("2.4.0"), #endif CultureInfo = CultureInfo.InvariantCulture }; diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 9c417cd27..d45c622f9 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -1,4 +1,4 @@ - + Debug @@ -84,7 +84,9 @@ + + @@ -96,7 +98,10 @@ + + + @@ -114,6 +119,6 @@ - + - + \ No newline at end of file diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs new file mode 100644 index 000000000..346c8afdc --- /dev/null +++ b/src/embed_tests/TestConverter.cs @@ -0,0 +1,48 @@ +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest +{ + public class TestConverter + { + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + [Test] + public void TestConvertSingleToManaged( + [Values(float.PositiveInfinity, float.NegativeInfinity, float.MinValue, float.MaxValue, float.NaN, + float.Epsilon)] float testValue) + { + var pyFloat = new PyFloat(testValue); + + object convertedValue; + var converted = Converter.ToManaged(pyFloat.Handle, typeof(float), out convertedValue, false); + + Assert.IsTrue(converted); + Assert.IsTrue(((float) convertedValue).Equals(testValue)); + } + + [Test] + public void TestConvertDoubleToManaged( + [Values(double.PositiveInfinity, double.NegativeInfinity, double.MinValue, double.MaxValue, double.NaN, + double.Epsilon)] double testValue) + { + var pyFloat = new PyFloat(testValue); + + object convertedValue; + var converted = Converter.ToManaged(pyFloat.Handle, typeof(double), out convertedValue, false); + + Assert.IsTrue(converted); + Assert.IsTrue(((double) convertedValue).Equals(testValue)); + } + } +} diff --git a/src/embed_tests/TestExample.cs b/src/embed_tests/TestExample.cs new file mode 100644 index 000000000..0cf795f5d --- /dev/null +++ b/src/embed_tests/TestExample.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest +{ + public class TestExample + { + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + [Test] + public void TestReadme() + { + dynamic np; + try + { + np = Py.Import("numpy"); + } + catch (PythonException) + { + Assert.Inconclusive("Numpy or dependency not installed"); + return; + } + + Assert.AreEqual("1.0", np.cos(np.pi * 2).ToString()); + + dynamic sin = np.sin; + StringAssert.StartsWith("-0.95892", sin(5).ToString()); + + double c = np.cos(5) + sin(5); + Assert.AreEqual(-0.675262, c, 0.01); + + dynamic a = np.array(new List { 1, 2, 3 }); + Assert.AreEqual("float64", a.dtype.ToString()); + + dynamic b = np.array(new List { 6, 5, 4 }, Py.kw("dtype", np.int32)); + Assert.AreEqual("int32", b.dtype.ToString()); + + Assert.AreEqual("[ 6. 10. 12.]", (a * b).ToString()); + } + } +} diff --git a/src/embed_tests/TestNamedArguments.cs b/src/embed_tests/TestNamedArguments.cs new file mode 100644 index 000000000..1d7076956 --- /dev/null +++ b/src/embed_tests/TestNamedArguments.cs @@ -0,0 +1,64 @@ +using System; +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest +{ + public class TestNamedArguments + { + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + /// + /// Test named arguments support through Py.kw method + /// + [Test] + public void TestKeywordArgs() + { + dynamic a = CreateTestClass(); + var result = (int)a.Test3(2, Py.kw("a4", 8)); + + Assert.AreEqual(12, result); + } + + + /// + /// Test keyword arguments with .net named arguments + /// + [Test] + public void TestNamedArgs() + { + dynamic a = CreateTestClass(); + var result = (int)a.Test3(2, a4: 8); + + Assert.AreEqual(12, result); + } + + + + private static PyObject CreateTestClass() + { + var locals = new PyDict(); + + PythonEngine.Exec(@" +class cmTest3: + def Test3(self, a1 = 1, a2 = 1, a3 = 1, a4 = 1): + return a1 + a2 + a3 + a4 + +a = cmTest3() +", null, locals.Handle); + + return locals.GetItem("a"); + } + + } +} diff --git a/src/embed_tests/TestPyScope.cs b/src/embed_tests/TestPyScope.cs new file mode 100644 index 000000000..49c15a3a1 --- /dev/null +++ b/src/embed_tests/TestPyScope.cs @@ -0,0 +1,372 @@ +using System; +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest +{ + public class PyScopeTest + { + private PyScope ps; + + [SetUp] + public void SetUp() + { + using (Py.GIL()) + { + ps = Py.CreateScope("test"); + } + } + + [TearDown] + public void Dispose() + { + using (Py.GIL()) + { + ps.Dispose(); + ps = null; + } + } + + /// + /// Eval a Python expression and obtain its return value. + /// + [Test] + public void TestEval() + { + using (Py.GIL()) + { + ps.Set("a", 1); + var result = ps.Eval("a + 2"); + Assert.AreEqual(3, result); + } + } + + /// + /// Exec Python statements and obtain the variables created. + /// + [Test] + public void TestExec() + { + using (Py.GIL()) + { + ps.Set("bb", 100); //declare a global variable + ps.Set("cc", 10); //declare a local variable + ps.Exec("aa = bb + cc + 3"); + var result = ps.Get("aa"); + Assert.AreEqual(113, result); + } + } + + /// + /// Compile an expression into an ast object; + /// Execute the ast and obtain its return value. + /// + [Test] + public void TestCompileExpression() + { + using (Py.GIL()) + { + ps.Set("bb", 100); //declare a global variable + ps.Set("cc", 10); //declare a local variable + PyObject script = PythonEngine.Compile("bb + cc + 3", "", RunFlagType.Eval); + var result = ps.Execute(script); + Assert.AreEqual(113, result); + } + } + + /// + /// Compile Python statements into an ast object; + /// Execute the ast; + /// Obtain the local variables created. + /// + [Test] + public void TestCompileStatements() + { + using (Py.GIL()) + { + ps.Set("bb", 100); //declare a global variable + ps.Set("cc", 10); //declare a local variable + PyObject script = PythonEngine.Compile("aa = bb + cc + 3", "", RunFlagType.File); + ps.Execute(script); + var result = ps.Get("aa"); + Assert.AreEqual(113, result); + } + } + + /// + /// Create a function in the scope, then the function can read variables in the scope. + /// It cannot write the variables unless it uses the 'global' keyword. + /// + [Test] + public void TestScopeFunction() + { + using (Py.GIL()) + { + ps.Set("bb", 100); + ps.Set("cc", 10); + ps.Exec( + "def func1():\n" + + " bb = cc + 10\n"); + dynamic func1 = ps.Get("func1"); + func1(); //call the function, it can be called any times + var result = ps.Get("bb"); + Assert.AreEqual(100, result); + + ps.Set("bb", 100); + ps.Set("cc", 10); + ps.Exec( + "def func2():\n" + + " global bb\n" + + " bb = cc + 10\n"); + dynamic func2 = ps.Get("func2"); + func2(); + result = ps.Get("bb"); + Assert.AreEqual(20, result); + } + } + + /// + /// Create a class in the scope, the class can read variables in the scope. + /// Its methods can write the variables with the help of 'global' keyword. + /// + [Test] + public void TestScopeClass() + { + using (Py.GIL()) + { + dynamic _ps = ps; + _ps.bb = 100; + ps.Exec( + "class Class1():\n" + + " def __init__(self, value):\n" + + " self.value = value\n" + + " def call(self, arg):\n" + + " return self.value + bb + arg\n" + //use scope variables + " def update(self, arg):\n" + + " global bb\n" + + " bb = self.value + arg\n" //update scope variable + ); + dynamic obj1 = _ps.Class1(20); + var result = obj1.call(10).As(); + Assert.AreEqual(130, result); + + obj1.update(10); + result = ps.Get("bb"); + Assert.AreEqual(30, result); + } + } + + /// + /// Import a python module into the session. + /// Equivalent to the Python "import" statement. + /// + [Test] + public void TestImportModule() + { + using (Py.GIL()) + { + dynamic sys = ps.Import("sys"); + Assert.IsTrue(ps.Contains("sys")); + + ps.Exec("sys.attr1 = 2"); + var value1 = ps.Eval("sys.attr1"); + var value2 = sys.attr1.As(); + Assert.AreEqual(2, value1); + Assert.AreEqual(2, value2); + + //import as + ps.Import("sys", "sys1"); + Assert.IsTrue(ps.Contains("sys1")); + } + } + + /// + /// Create a scope and import variables from a scope, + /// exec Python statements in the scope then discard it. + /// + [Test] + public void TestImportScope() + { + using (Py.GIL()) + { + ps.Set("bb", 100); + ps.Set("cc", 10); + + using (var scope = Py.CreateScope()) + { + scope.Import(ps, "ps"); + scope.Exec("aa = ps.bb + ps.cc + 3"); + var result = scope.Get("aa"); + Assert.AreEqual(113, result); + } + + Assert.IsFalse(ps.Contains("aa")); + } + } + + /// + /// Create a scope and import variables from a scope, + /// exec Python statements in the scope then discard it. + /// + [Test] + public void TestImportAllFromScope() + { + using (Py.GIL()) + { + ps.Set("bb", 100); + ps.Set("cc", 10); + + using (var scope = ps.NewScope()) + { + scope.Exec("aa = bb + cc + 3"); + var result = scope.Get("aa"); + Assert.AreEqual(113, result); + } + + Assert.IsFalse(ps.Contains("aa")); + } + } + + /// + /// Create a scope and import variables from a scope, + /// call the function imported. + /// + [Test] + public void TestImportScopeFunction() + { + using (Py.GIL()) + { + ps.Set("bb", 100); + ps.Set("cc", 10); + ps.Exec( + "def func1():\n" + + " return cc + bb\n"); + + using (PyScope scope = ps.NewScope()) + { + //'func1' is imported from the origion scope + scope.Exec( + "def func2():\n" + + " return func1() - cc - bb\n"); + dynamic func2 = scope.Get("func2"); + + var result1 = func2().As(); + Assert.AreEqual(0, result1); + + scope.Set("cc", 20);//it has no effect on the globals of 'func1' + var result2 = func2().As(); + Assert.AreEqual(-10, result2); + scope.Set("cc", 10); //rollback + + ps.Set("cc", 20); + var result3 = func2().As(); + Assert.AreEqual(10, result3); + ps.Set("cc", 10); //rollback + } + } + } + + /// + /// Import a python module into the session with a new name. + /// Equivalent to the Python "import .. as .." statement. + /// + [Test] + public void TestImportScopeByName() + { + using (Py.GIL()) + { + ps.Set("bb", 100); + + using (var scope = Py.CreateScope()) + { + scope.ImportAll("test"); + //scope.ImportModule("test"); + + Assert.IsTrue(scope.Contains("bb")); + } + } + } + + /// + /// Use the locals() and globals() method just like in python module + /// + [Test] + public void TestVariables() + { + (ps.Variables() as dynamic)["ee"] = new PyInt(200); + var a0 = ps.Get("ee"); + Assert.AreEqual(200, a0); + + ps.Exec("locals()['ee'] = 210"); + var a1 = ps.Get("ee"); + Assert.AreEqual(210, a1); + + ps.Exec("globals()['ee'] = 220"); + var a2 = ps.Get("ee"); + Assert.AreEqual(220, a2); + + using (var item = ps.Variables()) + { + item["ee"] = new PyInt(230); + } + var a3 = ps.Get("ee"); + Assert.AreEqual(230, a3); + } + + /// + /// Share a pyscope by multiple threads. + /// + [Test] + public void TestThread() + { + //After the proposal here https://github.com/pythonnet/pythonnet/pull/419 complished, + //the BeginAllowThreads statement blow and the last EndAllowThreads statement + //should be removed. + dynamic _ps = ps; + var ts = PythonEngine.BeginAllowThreads(); + using (Py.GIL()) + { + _ps.res = 0; + _ps.bb = 100; + _ps.th_cnt = 0; + //add function to the scope + //can be call many times, more efficient than ast + ps.Exec( + "def update():\n" + + " global res, th_cnt\n" + + " res += bb + 1\n" + + " th_cnt += 1\n" + ); + } + int th_cnt = 3; + for (int i =0; i< th_cnt; i++) + { + System.Threading.Thread th = new System.Threading.Thread(()=> + { + using (Py.GIL()) + { + //ps.GetVariable("update")(); //call the scope function dynamicly + _ps.update(); + } + }); + th.Start(); + } + //equivalent to Thread.Join, make the main thread join the GIL competition + int cnt = 0; + while(cnt != th_cnt) + { + using (Py.GIL()) + { + cnt = ps.Get("th_cnt"); + } + System.Threading.Thread.Sleep(10); + } + using (Py.GIL()) + { + var result = ps.Get("res"); + Assert.AreEqual(101* th_cnt, result); + } + PythonEngine.EndAllowThreads(ts); + } + } +} diff --git a/src/embed_tests/TestPyWith.cs b/src/embed_tests/TestPyWith.cs new file mode 100644 index 000000000..fd3f8e662 --- /dev/null +++ b/src/embed_tests/TestPyWith.cs @@ -0,0 +1,88 @@ +using System; +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest +{ + public class TestPyWith + { + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + /// + /// Test that exception is raised in context manager that ignores it. + /// + [Test] + public void TestWithPositive() + { + var locals = new PyDict(); + + PythonEngine.Exec(@" +class CmTest: + def __enter__(self): + print('Enter') + return self + def __exit__(self, t, v, tb): + # Exception not handled, return will be False + print('Exit') + def fail(self): + return 5 / 0 + +a = CmTest() +", null, locals.Handle); + + var a = locals.GetItem("a"); + + try + { + Py.With(a, cmTest => + { + cmTest.fail(); + }); + } + catch (PythonException e) + { + Assert.IsTrue(e.Message.Contains("ZeroDivisionError")); + } + } + + + /// + /// Test that exception is not raised in context manager that handles it + /// + [Test] + public void TestWithNegative() + { + var locals = new PyDict(); + + PythonEngine.Exec(@" +class CmTest: + def __enter__(self): + print('Enter') + return self + def __exit__(self, t, v, tb): + # Signal exception is handled by returning true + return True + def fail(self): + return 5 / 0 + +a = CmTest() +", null, locals.Handle); + + var a = locals.GetItem("a"); + Py.With(a, cmTest => + { + cmTest.fail(); + }); + } + } +} diff --git a/src/embed_tests/TestRuntime.cs b/src/embed_tests/TestRuntime.cs index 22e6db0a9..2e0598da7 100644 --- a/src/embed_tests/TestRuntime.cs +++ b/src/embed_tests/TestRuntime.cs @@ -1,4 +1,4 @@ -using System; +using System; using NUnit.Framework; using Python.Runtime; @@ -47,5 +47,46 @@ public static void RefCountTest() Runtime.Runtime.Py_Finalize(); } + + [Test] + public static void PyCheck_Iter_PyObject_IsIterable_Test() + { + Runtime.Runtime.Py_Initialize(); + + // Tests that a python list is an iterable, but not an iterator + var pyList = Runtime.Runtime.PyList_New(0); + Assert.IsFalse(Runtime.Runtime.PyIter_Check(pyList)); + Assert.IsTrue(Runtime.Runtime.PyObject_IsIterable(pyList)); + + // Tests that a python list iterator is both an iterable and an iterator + var pyListIter = Runtime.Runtime.PyObject_GetIter(pyList); + Assert.IsTrue(Runtime.Runtime.PyObject_IsIterable(pyListIter)); + Assert.IsTrue(Runtime.Runtime.PyIter_Check(pyListIter)); + + // Tests that a python float is neither an iterable nor an iterator + var pyFloat = Runtime.Runtime.PyFloat_FromDouble(2.73); + Assert.IsFalse(Runtime.Runtime.PyObject_IsIterable(pyFloat)); + Assert.IsFalse(Runtime.Runtime.PyIter_Check(pyFloat)); + + Runtime.Runtime.Py_Finalize(); + } + + [Test] + public static void PyCheck_Iter_PyObject_IsIterable_ThreadingLock_Test() + { + Runtime.Runtime.Py_Initialize(); + + // 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"); + var threadingDict = Runtime.Runtime.PyModule_GetDict(threading); + var lockType = Runtime.Runtime.PyDict_GetItemString(threadingDict, "Lock"); + var lockInstance = Runtime.Runtime.PyObject_CallObject(lockType, Runtime.Runtime.PyTuple_New(0)); + + Assert.IsFalse(Runtime.Runtime.PyObject_IsIterable(lockInstance)); + Assert.IsFalse(Runtime.Runtime.PyIter_Check(lockInstance)); + + Runtime.Runtime.Py_Finalize(); + } } } diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 8580b7f61..ee1a3c701 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -1,4 +1,4 @@ - + Debug @@ -127,6 +127,7 @@ + @@ -161,6 +162,6 @@ - + diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index b66150a29..5179c849b 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Runtime.InteropServices; @@ -133,6 +134,22 @@ internal static IntPtr ToPython(object value, Type type) return result; } + if (value is IList && value.GetType().IsGenericType) + { + using (var resultlist = new PyList()) + { + foreach (object o in (IEnumerable)value) + { + using (var p = new PyObject(ToPython(o, o?.GetType()))) + { + resultlist.Append(p); + } + } + Runtime.XIncref(resultlist.Handle); + return resultlist.Handle; + } + } + // it the type is a python subclass of a managed type then return the // underlying python object rather than construct a new wrapper object. var pyderived = value as IPythonDerivedType; @@ -301,6 +318,17 @@ internal static bool ToManagedValue(IntPtr value, Type obType, return true; } + if (obType.IsGenericType && obType.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + if( value == Runtime.PyNone ) + { + result = null; + return true; + } + // Set type to underlying type + obType = obType.GetGenericArguments()[0]; + } + if (obType.IsArray) { return ToArray(value, obType, out result, setError); @@ -742,10 +770,14 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo goto type_error; } double dd = Runtime.PyFloat_AsDouble(op); + Runtime.CheckExceptionOccurred(); Runtime.XDecref(op); if (dd > Single.MaxValue || dd < Single.MinValue) { - goto overflow; + if (!double.IsInfinity(dd)) + { + goto overflow; + } } result = (float)dd; return true; @@ -757,11 +789,8 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo goto type_error; } double d = Runtime.PyFloat_AsDouble(op); + Runtime.CheckExceptionOccurred(); Runtime.XDecref(op); - if (d > Double.MaxValue || d < Double.MinValue) - { - goto overflow; - } result = d; return true; } diff --git a/src/runtime/moduleobject.cs b/src/runtime/moduleobject.cs index 258d77bac..e683026f9 100644 --- a/src/runtime/moduleobject.cs +++ b/src/runtime/moduleobject.cs @@ -403,6 +403,23 @@ public static Assembly AddReference(string name) return assembly; } + /// + /// Get a Type instance for a class object. + /// clr.GetClrType(IComparable) gives you the Type for IComparable, + /// that you can e.g. perform reflection on. Similar to typeof(IComparable) in C# + /// or clr.GetClrType(IComparable) in IronPython. + /// + /// + /// + /// The Type object + + [ModuleFunction] + [ForbidPythonThreads] + public static Type GetClrType(Type type) + { + return type; + } + [ModuleFunction] [ForbidPythonThreads] public static string FindAssembly(string name) diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index 47f413409..1b41b0893 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Dynamic; using System.Linq.Expressions; @@ -96,6 +96,27 @@ public object AsManagedObject(Type t) } return result; } + + /// + /// As Method + /// + /// + /// Return a managed object of the given type, based on the + /// value of the Python object. + /// + public T As() + { + if (typeof(T) == typeof(PyObject) || typeof(T) == typeof(object)) + { + return (T)(this as object); + } + object result; + if (!Converter.ToManaged(obj, typeof(T), out result, false)) + { + throw new InvalidCastException("cannot convert object to target type"); + } + return (T)result; + } /// @@ -781,7 +802,7 @@ public bool IsCallable() /// public bool IsIterable() { - return Runtime.PyIter_Check(obj); + return Runtime.PyObject_IsIterable(obj); } @@ -915,6 +936,34 @@ public override bool TrySetMember(SetMemberBinder binder, object value) return true; } + private void GetArgs(object[] inargs, CallInfo callInfo, out PyTuple args, out PyDict kwargs) + { + if (callInfo == null || callInfo.ArgumentNames.Count == 0) + { + GetArgs(inargs, out args, out kwargs); + return; + } + + // Support for .net named arguments + var namedArgumentCount = callInfo.ArgumentNames.Count; + var regularArgumentCount = callInfo.ArgumentCount - namedArgumentCount; + + var argTuple = Runtime.PyTuple_New(regularArgumentCount); + for (int i = 0; i < regularArgumentCount; ++i) + { + AddArgument(argTuple, i, inargs[i]); + } + args = new PyTuple(argTuple); + + var namedArgs = new object[namedArgumentCount * 2]; + for (int i = 0; i < namedArgumentCount; ++i) + { + namedArgs[i * 2] = callInfo.ArgumentNames[i]; + namedArgs[i * 2 + 1] = inargs[regularArgumentCount + i]; + } + kwargs = Py.kw(namedArgs); + } + private void GetArgs(object[] inargs, out PyTuple args, out PyDict kwargs) { int arg_count; @@ -925,22 +974,10 @@ private void GetArgs(object[] inargs, out PyTuple args, out PyDict kwargs) IntPtr argtuple = Runtime.PyTuple_New(arg_count); for (var i = 0; i < arg_count; i++) { - IntPtr ptr; - if (inargs[i] is PyObject) - { - ptr = ((PyObject)inargs[i]).Handle; - Runtime.XIncref(ptr); - } - else - { - ptr = Converter.ToPython(inargs[i], inargs[i]?.GetType()); - } - if (Runtime.PyTuple_SetItem(argtuple, i, ptr) < 0) - { - throw new PythonException(); - } + AddArgument(argtuple, i, inargs[i]); } args = new PyTuple(argtuple); + kwargs = null; for (int i = arg_count; i < inargs.Length; i++) { @@ -959,6 +996,32 @@ private void GetArgs(object[] inargs, out PyTuple args, out PyDict kwargs) } } + private static void AddArgument(IntPtr argtuple, int i, object target) + { + IntPtr ptr = GetPythonObject(target); + + if (Runtime.PyTuple_SetItem(argtuple, i, ptr) < 0) + { + throw new PythonException(); + } + } + + private static IntPtr GetPythonObject(object target) + { + IntPtr ptr; + if (target is PyObject) + { + ptr = ((PyObject)target).Handle; + Runtime.XIncref(ptr); + } + else + { + ptr = Converter.ToPython(target, target?.GetType()); + } + + return ptr; + } + public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { if (this.HasAttr(binder.Name) && this.GetAttr(binder.Name).IsCallable()) @@ -967,7 +1030,7 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o PyDict kwargs = null; try { - GetArgs(args, out pyargs, out kwargs); + GetArgs(args, binder.CallInfo, out pyargs, out kwargs); result = CheckNone(InvokeMethod(binder.Name, pyargs, kwargs)); } finally @@ -997,7 +1060,7 @@ public override bool TryInvoke(InvokeBinder binder, object[] args, out object re PyDict kwargs = null; try { - GetArgs(args, out pyargs, out kwargs); + GetArgs(args, binder.CallInfo, out pyargs, out kwargs); result = CheckNone(Invoke(pyargs, kwargs)); } finally diff --git a/src/runtime/pyscope.cs b/src/runtime/pyscope.cs new file mode 100644 index 000000000..1991772dc --- /dev/null +++ b/src/runtime/pyscope.cs @@ -0,0 +1,655 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Dynamic; + +namespace Python.Runtime +{ + public class PyScopeException : Exception + { + public PyScopeException(string message) + : base(message) + { + + } + } + + /// + /// Classes/methods have this attribute must be used with GIL obtained. + /// + public class PyGILAttribute : Attribute + { + } + + [PyGIL] + public class PyScope : DynamicObject, IDisposable + { + public readonly string Name; + + /// + /// the python Module object the scope associated with. + /// + internal readonly IntPtr obj; + + /// + /// the variable dict of the scope. + /// + internal readonly IntPtr variables; + + private bool _isDisposed; + + /// + /// The Manager this scope associated with. + /// It provides scopes this scope can import. + /// + internal readonly PyScopeManager Manager; + + /// + /// event which will be triggered after the scope disposed. + /// + public event Action OnDispose; + + /// + /// Constructor + /// + /// + /// Create a scope based on a Python Module. + /// + internal PyScope(IntPtr ptr, PyScopeManager manager) + { + if (Runtime.PyObject_Type(ptr) != Runtime.PyModuleType) + { + throw new PyScopeException("object is not a module"); + } + Manager = manager ?? PyScopeManager.Global; + obj = ptr; + //Refcount of the variables not increase + variables = Runtime.PyModule_GetDict(obj); + if (variables == IntPtr.Zero) + { + throw new PythonException(); + } + Runtime.PyDict_SetItemString( + variables, "__builtins__", + Runtime.PyEval_GetBuiltins() + ); + this.Name = this.Get("__name__"); + } + + /// + /// return the variable dict of the scope. + /// + /// + public PyDict Variables() + { + Runtime.XIncref(variables); + return new PyDict(variables); + } + + /// + /// Create a scope, and import all from this scope + /// + /// + public PyScope NewScope() + { + var scope = Manager.Create(); + scope.ImportAll(this); + return scope; + } + + /// + /// Import method + /// + /// + /// Import a scope or a module of given name, + /// scope will be looked up first. + /// + public dynamic Import(string name, string asname = null) + { + Check(); + if (String.IsNullOrEmpty(asname)) + { + asname = name; + } + PyScope scope; + Manager.TryGet(name, out scope); + if (scope != null) + { + Import(scope, asname); + return scope; + } + else + { + PyObject module = PythonEngine.ImportModule(name); + Import(module, asname); + return module; + } + } + + /// + /// Import method + /// + /// + /// Import a scope as a variable of given name. + /// + public void Import(PyScope scope, string asname) + { + this.Set(asname, scope.obj); + } + + /// + /// Import Method + /// + /// + /// The 'import .. as ..' statement in Python. + /// Import a module as a variable into the scope. + /// + public void Import(PyObject module, string asname = null) + { + if (String.IsNullOrEmpty(asname)) + { + asname = module.GetAttr("__name__").As(); + } + Set(asname, module); + } + + /// + /// ImportAll Method + /// + /// + /// The 'import * from ..' statement in Python. + /// Import all content of a scope/module of given name into the scope, scope will be looked up first. + /// + public void ImportAll(string name) + { + PyScope scope; + Manager.TryGet(name, out scope); + if (scope != null) + { + ImportAll(scope); + return; + } + else + { + PyObject module = PythonEngine.ImportModule(name); + ImportAll(module); + } + } + + /// + /// ImportAll Method + /// + /// + /// Import all variables of the scope into this scope. + /// + public void ImportAll(PyScope scope) + { + int result = Runtime.PyDict_Update(variables, scope.variables); + if (result < 0) + { + throw new PythonException(); + } + } + + /// + /// ImportAll Method + /// + /// + /// Import all variables of the module into this scope. + /// + public void ImportAll(PyObject module) + { + if (Runtime.PyObject_Type(module.obj) != Runtime.PyModuleType) + { + throw new PyScopeException("object is not a module"); + } + var module_dict = Runtime.PyModule_GetDict(module.obj); + int result = Runtime.PyDict_Update(variables, module_dict); + if (result < 0) + { + throw new PythonException(); + } + } + + /// + /// ImportAll Method + /// + /// + /// Import all variables in the dictionary into this scope. + /// + public void ImportAll(PyDict dict) + { + int result = Runtime.PyDict_Update(variables, dict.obj); + if (result < 0) + { + throw new PythonException(); + } + } + + /// + /// Execute method + /// + /// + /// Execute a Python ast and return the result as a PyObject. + /// The ast can be either an expression or stmts. + /// + public PyObject Execute(PyObject script, PyDict locals = null) + { + Check(); + IntPtr _locals = locals == null ? variables : locals.obj; + IntPtr ptr = Runtime.PyEval_EvalCode(script.Handle, variables, _locals); + Runtime.CheckExceptionOccurred(); + if (ptr == Runtime.PyNone) + { + Runtime.XDecref(ptr); + return null; + } + return new PyObject(ptr); + } + + /// + /// Execute method + /// + /// + /// Execute a Python ast and return the result as a PyObject, + /// and convert the result to a Managed Object of given type. + /// The ast can be either an expression or stmts. + /// + public T Execute(PyObject script, PyDict locals = null) + { + Check(); + PyObject pyObj = Execute(script, locals); + if (pyObj == null) + { + return default(T); + } + var obj = pyObj.As(); + return obj; + } + + /// + /// Eval method + /// + /// + /// Evaluate a Python expression and return the result as a PyObject + /// or null if an exception is raised. + /// + 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( + code, flag, variables, _locals + ); + Runtime.CheckExceptionOccurred(); + return new PyObject(ptr); + } + + /// + /// Evaluate a Python expression + /// + /// + /// Evaluate a Python expression + /// and convert the result to a Managed Object of given type. + /// + public T Eval(string code, PyDict locals = null) + { + Check(); + PyObject pyObj = Eval(code, locals); + var obj = pyObj.As(); + return obj; + } + + /// + /// Exec Method + /// + /// + /// Exec a Python script and save its local variables in the current local variable dict. + /// + public void Exec(string code, PyDict locals = null) + { + Check(); + IntPtr _locals = locals == null ? variables : locals.obj; + Exec(code, variables, _locals); + } + + private void Exec(string code, IntPtr _globals, IntPtr _locals) + { + var flag = (IntPtr)Runtime.Py_file_input; + IntPtr ptr = Runtime.PyRun_String( + code, flag, _globals, _locals + ); + Runtime.CheckExceptionOccurred(); + if (ptr != Runtime.PyNone) + { + throw new PythonException(); + } + Runtime.XDecref(ptr); + } + + /// + /// Set Variable Method + /// + /// + /// Add a new variable to the variables dict if it not exist + /// or update its value if the variable exists. + /// + public void Set(string name, object value) + { + IntPtr _value = Converter.ToPython(value, value?.GetType()); + Set(name, _value); + Runtime.XDecref(_value); + } + + private void Set(string name, IntPtr value) + { + Check(); + using (var pyKey = new PyString(name)) + { + int r = Runtime.PyObject_SetItem(variables, pyKey.obj, value); + if (r < 0) + { + throw new PythonException(); + } + } + } + + /// + /// Remove Method + /// + /// + /// Remove a variable from the variables dict. + /// + public void Remove(string name) + { + Check(); + using (var pyKey = new PyString(name)) + { + int r = Runtime.PyObject_DelItem(variables, pyKey.obj); + if (r < 0) + { + throw new PythonException(); + } + } + } + + /// + /// Contains Method + /// + /// + /// Returns true if the variable exists in the scope. + /// + public bool Contains(string name) + { + Check(); + using (var pyKey = new PyString(name)) + { + return Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0; + } + } + + /// + /// Get Method + /// + /// + /// Returns the value of the variable of given name. + /// If the variable does not exist, throw an Exception. + /// + public PyObject Get(string name) + { + PyObject scope; + var state = TryGet(name, out scope); + if(!state) + { + throw new PyScopeException($"The scope of name '{Name}' has no attribute '{name}'"); + } + return scope; + } + + /// + /// TryGet Method + /// + /// + /// Returns the value of the variable, local variable first. + /// If the variable does not exist, return null. + /// + public bool TryGet(string name, out PyObject value) + { + Check(); + using (var pyKey = new PyString(name)) + { + if (Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0) + { + IntPtr op = Runtime.PyObject_GetItem(variables, pyKey.obj); + if (op == IntPtr.Zero) + { + throw new PythonException(); + } + if (op == Runtime.PyNone) + { + Runtime.XDecref(op); + value = null; + return true; + } + value = new PyObject(op); + return true; + } + else + { + value = null; + return false; + } + } + } + + /// + /// Get Method + /// + /// + /// Obtain the value of the variable of given name, + /// and convert the result to a Managed Object of given type. + /// If the variable does not exist, throw an Exception. + /// + public T Get(string name) + { + Check(); + PyObject pyObj = Get(name); + if (pyObj == null) + { + return default(T); + } + return pyObj.As(); + } + + /// + /// TryGet Method + /// + /// + /// Obtain the value of the variable of given name, + /// and convert the result to a Managed Object of given type. + /// If the variable does not exist, return false. + /// + public bool TryGet(string name, out T value) + { + Check(); + PyObject pyObj; + var result = TryGet(name, out pyObj); + if (!result) + { + value = default(T); + return false; + } + if (pyObj == null) + { + if(typeof(T).IsValueType) + { + throw new PyScopeException($"The value of the attribute '{name}' is None which cannot be convert to '{typeof(T).ToString()}'"); + } + else + { + value = default(T); + return true; + } + } + value = pyObj.As(); + return true; + } + + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + result = this.Get(binder.Name); + return true; + } + + public override bool TrySetMember(SetMemberBinder binder, object value) + { + this.Set(binder.Name, value); + return true; + } + + private void Check() + { + if (_isDisposed) + { + throw new PyScopeException($"The scope of name '{Name}' object has been disposed"); + } + } + + public void Dispose() + { + if (_isDisposed) + { + return; + } + _isDisposed = true; + Runtime.XDecref(obj); + this.OnDispose?.Invoke(this); + } + + ~PyScope() + { + Dispose(); + } + } + + public class PyScopeManager + { + public readonly static PyScopeManager Global = new PyScopeManager(); + + private Dictionary NamedScopes = new Dictionary(); + + internal PyScope NewScope(string name) + { + if (name == null) + { + name = ""; + } + var module = Runtime.PyModule_New(name); + if (module == IntPtr.Zero) + { + throw new PythonException(); + } + return new PyScope(module, this); + } + + /// + /// Create Method + /// + /// + /// Create an anonymous scope. + /// + [PyGIL] + public PyScope Create() + { + var scope = this.NewScope(null); + return scope; + } + + /// + /// Create Method + /// + /// + /// Create an named scope of given name. + /// + [PyGIL] + public PyScope Create(string name) + { + if (String.IsNullOrEmpty(name)) + { + throw new ArgumentNullException(nameof(name)); + } + if (name != null && Contains(name)) + { + throw new PyScopeException($"A scope of name '{name}' does already exist"); + } + var scope = this.NewScope(name); + scope.OnDispose += Remove; + NamedScopes[name] = scope; + return scope; + } + + /// + /// Contains Method + /// + /// + /// return true if the scope exists in this manager. + /// + public bool Contains(string name) + { + return NamedScopes.ContainsKey(name); + } + + /// + /// Get Method + /// + /// + /// Find the scope in this manager. + /// If the scope not exist, an Exception will be thrown. + /// + public PyScope Get(string name) + { + if (String.IsNullOrEmpty(name)) + { + throw new ArgumentNullException(nameof(name)); + } + if (NamedScopes.ContainsKey(name)) + { + return NamedScopes[name]; + } + throw new PyScopeException($"There is no scope named '{name}' registered in this manager"); + } + + /// + /// Get Method + /// + /// + /// Try to find the scope in this manager. + /// + public bool TryGet(string name, out PyScope scope) + { + return NamedScopes.TryGetValue(name, out scope); + } + + /// + /// Remove Method + /// + /// + /// remove the scope from this manager. + /// + public void Remove(PyScope scope) + { + NamedScopes.Remove(scope.Name); + } + + [PyGIL] + public void Clear() + { + var scopes = NamedScopes.Values.ToList(); + foreach (var scope in scopes) + { + scope.Dispose(); + } + } + } +} diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index 1fd3b239a..556da698f 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -294,6 +294,7 @@ public static void Shutdown() { if (initialized) { + PyScopeManager.Global.Clear(); Marshal.FreeHGlobal(_pythonHome); _pythonHome = IntPtr.Zero; Marshal.FreeHGlobal(_programName); @@ -421,6 +422,13 @@ public static PyObject ModuleFromString(string name, string code) return new PyObject(m); } + public static PyObject Compile(string code, string filename = "", RunFlagType mode = RunFlagType.File) + { + var flag = (IntPtr)mode; + IntPtr ptr = Runtime.Py_CompileString(code, filename, flag); + Runtime.CheckExceptionOccurred(); + return new PyObject(ptr); + } /// /// Eval Method @@ -539,6 +547,18 @@ public static GILState GIL() return new GILState(); } + public static PyScope CreateScope() + { + var scope = PyScopeManager.Global.Create(); + return scope; + } + + public static PyScope CreateScope(string name) + { + var scope = PyScopeManager.Global.Create(name); + return scope; + } + public class GILState : IDisposable { private IntPtr state; @@ -632,5 +652,34 @@ public static void SetArgv(IEnumerable argv) Runtime.CheckExceptionOccurred(); } } + + public static void With(PyObject obj, Action Body) + { + // Behavior described here: + // https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers + + IntPtr type = Runtime.PyNone; + IntPtr val = Runtime.PyNone; + IntPtr traceBack = Runtime.PyNone; + PythonException ex = null; + + try + { + PyObject enterResult = obj.InvokeMethod("__enter__"); + + Body(enterResult); + } + catch (PythonException e) + { + ex = e; + type = ex.PyType; + val = ex.PyValue; + traceBack = ex.PyTB; + } + + var exitResult = obj.InvokeMethod("__exit__", new PyObject(type), new PyObject(val), new PyObject(traceBack)); + + if (ex != null && !exitResult.IsTrue()) throw ex; + } } } diff --git a/src/runtime/resources/clr.py b/src/runtime/resources/clr.py index 91f1f6b15..e708a54ac 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.3.0" +__version__ = "2.4.0.dev0" class clrproperty(object): diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 346ea745f..73caeb854 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Runtime.InteropServices; using System.Security; using System.Text; @@ -274,11 +274,10 @@ internal static void Initialize() Error = new IntPtr(-1); -#if PYTHON3 IntPtr dllLocal = IntPtr.Zero; if (PythonDll != "__Internal") { - NativeMethods.LoadLibrary(PythonDll); + dllLocal = NativeMethods.LoadLibrary(PythonDll); } _PyObject_NextNotImplemented = NativeMethods.GetProcAddress(dllLocal, "_PyObject_NextNotImplemented"); #if !(MONO_LINUX || MONO_OSX) @@ -286,7 +285,6 @@ internal static void Initialize() { NativeMethods.FreeLibrary(dllLocal); } -#endif #endif // Initialize modules that depend on the runtime class. @@ -349,8 +347,8 @@ internal static int AtExit() #if PYTHON3 internal static IntPtr PyBytesType; - internal static IntPtr _PyObject_NextNotImplemented; #endif + internal static IntPtr _PyObject_NextNotImplemented; internal static IntPtr PyNotImplemented; internal const int Py_LT = 0; @@ -570,7 +568,7 @@ internal static unsafe long Refcount(IntPtr op) /// Limit this function usage for Testing and Py_Debug builds /// /// PyObject Ptr - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void Py_IncRef(IntPtr ob); /// @@ -578,157 +576,160 @@ internal static unsafe long Refcount(IntPtr op) /// Limit this function usage for Testing and Py_Debug builds /// /// PyObject Ptr - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void Py_DecRef(IntPtr ob); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void Py_Initialize(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int Py_IsInitialized(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void Py_Finalize(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr Py_NewInterpreter(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void Py_EndInterpreter(IntPtr threadState); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyThreadState_New(IntPtr istate); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyThreadState_Get(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyThread_get_key_value(IntPtr key); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyThread_get_thread_ident(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyThread_set_key_value(IntPtr key, IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyThreadState_Swap(IntPtr key); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyGILState_Ensure(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyGILState_Release(IntPtr gs); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyGILState_GetThisThreadState(); #if PYTHON3 - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] public static extern int Py_Main( int argc, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(StrArrayMarshaler))] string[] argv ); #elif PYTHON2 - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] public static extern int Py_Main(int argc, string[] argv); #endif - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyEval_InitThreads(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyEval_ThreadsInitialized(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyEval_AcquireLock(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyEval_ReleaseLock(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyEval_AcquireThread(IntPtr tstate); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyEval_ReleaseThread(IntPtr tstate); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyEval_SaveThread(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyEval_RestoreThread(IntPtr tstate); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyEval_GetBuiltins(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyEval_GetGlobals(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyEval_GetLocals(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr Py_GetProgramName(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void Py_SetProgramName(IntPtr name); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr Py_GetPythonHome(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void Py_SetPythonHome(IntPtr home); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr Py_GetPath(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void Py_SetPath(IntPtr home); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr Py_GetVersion(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr Py_GetPlatform(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr Py_GetCopyright(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr Py_GetCompiler(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr Py_GetBuildInfo(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyRun_SimpleString(string code); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyRun_String(string code, IntPtr st, IntPtr globals, IntPtr locals); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr PyEval_EvalCode(IntPtr co, IntPtr globals, IntPtr locals); + + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr Py_CompileString(string code, string file, IntPtr tok); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyImport_ExecCodeModule(string name, IntPtr code); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyCFunction_NewEx(IntPtr ml, IntPtr self, IntPtr mod); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyCFunction_Call(IntPtr func, IntPtr args, IntPtr kw); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyClass_New(IntPtr bases, IntPtr dict, IntPtr name); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyInstance_New(IntPtr cls, IntPtr args, IntPtr kw); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyInstance_NewRaw(IntPtr cls, IntPtr dict); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyMethod_New(IntPtr func, IntPtr self, IntPtr cls); @@ -777,44 +778,59 @@ internal static string PyObject_GetTypeName(IntPtr op) return Marshal.PtrToStringAnsi(ppName); } - [DllImport(PythonDll)] + /// + /// Test whether the Python object is an iterable. + /// + internal static bool PyObject_IsIterable(IntPtr pointer) + { + var ob_type = Marshal.ReadIntPtr(pointer, ObjectOffset.ob_type); +#if PYTHON2 + long tp_flags = Marshal.ReadInt64(ob_type, TypeOffset.tp_flags); + if ((tp_flags & TypeFlags.HaveIter) == 0) + return false; +#endif + IntPtr tp_iter = Marshal.ReadIntPtr(ob_type, TypeOffset.tp_iter); + return tp_iter != IntPtr.Zero; + } + + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyObject_HasAttrString(IntPtr pointer, string name); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyObject_GetAttrString(IntPtr pointer, string name); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyObject_SetAttrString(IntPtr pointer, string name, IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyObject_HasAttr(IntPtr pointer, IntPtr name); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyObject_GetAttr(IntPtr pointer, IntPtr name); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyObject_SetAttr(IntPtr pointer, IntPtr name, IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyObject_GetItem(IntPtr pointer, IntPtr key); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyObject_SetItem(IntPtr pointer, IntPtr key, IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyObject_DelItem(IntPtr pointer, IntPtr key); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyObject_GetIter(IntPtr op); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyObject_Call(IntPtr pointer, IntPtr args, IntPtr kw); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyObject_CallObject(IntPtr pointer, IntPtr args); #if PYTHON3 - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyObject_RichCompareBool(IntPtr value1, IntPtr value2, int opid); internal static int PyObject_Compare(IntPtr value1, IntPtr value2) @@ -842,46 +858,47 @@ internal static int PyObject_Compare(IntPtr value1, IntPtr value2) return -1; } #elif PYTHON2 - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyObject_Compare(IntPtr value1, IntPtr value2); #endif - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyObject_IsInstance(IntPtr ob, IntPtr type); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyObject_IsSubclass(IntPtr ob, IntPtr type); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyCallable_Check(IntPtr pointer); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyObject_IsTrue(IntPtr pointer); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyObject_Not(IntPtr pointer); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyObject_Size(IntPtr pointer); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyObject_Hash(IntPtr op); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyObject_Repr(IntPtr pointer); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyObject_Str(IntPtr pointer); #if PYTHON3 - [DllImport(PythonDll, EntryPoint = "PyObject_Str")] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyObject_Str")] internal static extern IntPtr PyObject_Unicode(IntPtr pointer); #elif PYTHON2 - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyObject_Unicode(IntPtr pointer); #endif - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyObject_Dir(IntPtr pointer); @@ -890,20 +907,21 @@ internal static int PyObject_Compare(IntPtr value1, IntPtr value2) //==================================================================== #if PYTHON3 - [DllImport(PythonDll, EntryPoint = "PyNumber_Long")] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyNumber_Long")] internal static extern IntPtr PyNumber_Int(IntPtr ob); #elif PYTHON2 - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_Int(IntPtr ob); #endif - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_Long(IntPtr ob); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_Float(IntPtr ob); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern bool PyNumber_Check(IntPtr ob); internal static bool PyInt_Check(IntPtr ob) @@ -929,28 +947,32 @@ internal static IntPtr PyInt_FromInt64(long value) } #if PYTHON3 - [DllImport(PythonDll, EntryPoint = "PyLong_FromLong")] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyLong_FromLong")] private static extern IntPtr PyInt_FromLong(IntPtr value); - [DllImport(PythonDll, EntryPoint = "PyLong_AsLong")] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyLong_AsLong")] internal static extern int PyInt_AsLong(IntPtr value); - [DllImport(PythonDll, EntryPoint = "PyLong_FromString")] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyLong_FromString")] internal static extern IntPtr PyInt_FromString(string value, IntPtr end, int radix); - [DllImport(PythonDll, EntryPoint = "PyLong_GetMax")] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyLong_GetMax")] internal static extern int PyInt_GetMax(); #elif PYTHON2 - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr PyInt_FromLong(IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyInt_AsLong(IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyInt_FromString(string value, IntPtr end, int radix); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyInt_GetMax(); #endif @@ -959,34 +981,34 @@ internal static bool PyLong_Check(IntPtr ob) return PyObject_TYPE(ob) == PyLongType; } - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyLong_FromLong(long value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyLong_FromUnsignedLong(uint value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyLong_FromDouble(double value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyLong_FromLongLong(long value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyLong_FromUnsignedLongLong(ulong value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyLong_FromString(string value, IntPtr end, int radix); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyLong_AsLong(IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern uint PyLong_AsUnsignedLong(IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern long PyLong_AsLongLong(IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern ulong PyLong_AsUnsignedLongLong(IntPtr value); internal static bool PyFloat_Check(IntPtr ob) @@ -994,88 +1016,88 @@ internal static bool PyFloat_Check(IntPtr ob) return PyObject_TYPE(ob) == PyFloatType; } - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyFloat_FromDouble(double value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyFloat_FromString(IntPtr value, IntPtr junk); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern double PyFloat_AsDouble(IntPtr ob); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_Add(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_Subtract(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_Multiply(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_Divide(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_And(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_Xor(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_Or(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_Lshift(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_Rshift(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_Power(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_Remainder(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_InPlaceAdd(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_InPlaceSubtract(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_InPlaceMultiply(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_InPlaceDivide(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_InPlaceAnd(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_InPlaceXor(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_InPlaceOr(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_InPlaceLshift(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_InPlaceRshift(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_InPlacePower(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_InPlaceRemainder(IntPtr o1, IntPtr o2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_Negative(IntPtr o1); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_Positive(IntPtr o1); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyNumber_Invert(IntPtr o1); @@ -1083,49 +1105,49 @@ internal static bool PyFloat_Check(IntPtr ob) // Python sequence API //==================================================================== - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern bool PySequence_Check(IntPtr pointer); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PySequence_GetItem(IntPtr pointer, int index); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PySequence_SetItem(IntPtr pointer, int index, IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PySequence_DelItem(IntPtr pointer, int index); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PySequence_GetSlice(IntPtr pointer, int i1, int i2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PySequence_SetSlice(IntPtr pointer, int i1, int i2, IntPtr v); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PySequence_DelSlice(IntPtr pointer, int i1, int i2); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PySequence_Size(IntPtr pointer); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PySequence_Contains(IntPtr pointer, IntPtr item); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PySequence_Concat(IntPtr pointer, IntPtr other); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PySequence_Repeat(IntPtr pointer, int count); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PySequence_Index(IntPtr pointer, IntPtr item); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PySequence_Count(IntPtr pointer, IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PySequence_Tuple(IntPtr pointer); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PySequence_List(IntPtr pointer); @@ -1150,10 +1172,10 @@ internal static IntPtr PyString_FromString(string value) } #if PYTHON3 - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyBytes_FromString(string op); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyBytes_Size(IntPtr op); internal static IntPtr PyBytes_AS_STRING(IntPtr ob) @@ -1161,22 +1183,23 @@ internal static IntPtr PyBytes_AS_STRING(IntPtr ob) return ob + BytesOffset.ob_sval; } - [DllImport(PythonDll, EntryPoint = "PyUnicode_FromStringAndSize")] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyUnicode_FromStringAndSize")] internal static extern IntPtr PyString_FromStringAndSize( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string value, int size ); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyUnicode_FromStringAndSize(IntPtr value, int size); #elif PYTHON2 - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyString_FromStringAndSize(string value, int size); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyString_AsString(IntPtr op); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyString_Size(IntPtr pointer); #endif @@ -1186,13 +1209,13 @@ internal static bool PyUnicode_Check(IntPtr ob) } #if PYTHON3 - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyUnicode_FromObject(IntPtr ob); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyUnicode_FromEncodedObject(IntPtr ob, IntPtr enc, IntPtr err); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyUnicode_FromKindAndData( int kind, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UcsMarshaler))] string s, @@ -1204,34 +1227,40 @@ internal static IntPtr PyUnicode_FromUnicode(string s, int size) return PyUnicode_FromKindAndData(UCS, s, size); } - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyUnicode_GetSize(IntPtr ob); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyUnicode_AsUnicode(IntPtr ob); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyUnicode_FromOrdinal(int c); #elif PYTHON2 - [DllImport(PythonDll, EntryPoint = PyUnicodeEntryPoint + "FromObject")] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = PyUnicodeEntryPoint + "FromObject")] internal static extern IntPtr PyUnicode_FromObject(IntPtr ob); - [DllImport(PythonDll, EntryPoint = PyUnicodeEntryPoint + "FromEncodedObject")] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = PyUnicodeEntryPoint + "FromEncodedObject")] internal static extern IntPtr PyUnicode_FromEncodedObject(IntPtr ob, IntPtr enc, IntPtr err); - [DllImport(PythonDll, EntryPoint = PyUnicodeEntryPoint + "FromUnicode")] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = PyUnicodeEntryPoint + "FromUnicode")] internal static extern IntPtr PyUnicode_FromUnicode( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UcsMarshaler))] string s, int size ); - [DllImport(PythonDll, EntryPoint = PyUnicodeEntryPoint + "GetSize")] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = PyUnicodeEntryPoint + "GetSize")] internal static extern int PyUnicode_GetSize(IntPtr ob); - [DllImport(PythonDll, EntryPoint = PyUnicodeEntryPoint + "AsUnicode")] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = PyUnicodeEntryPoint + "AsUnicode")] internal static extern IntPtr PyUnicode_AsUnicode(IntPtr ob); - [DllImport(PythonDll, EntryPoint = PyUnicodeEntryPoint + "FromOrdinal")] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = PyUnicodeEntryPoint + "FromOrdinal")] internal static extern IntPtr PyUnicode_FromOrdinal(int c); #endif @@ -1288,52 +1317,52 @@ internal static bool PyDict_Check(IntPtr ob) return PyObject_TYPE(ob) == PyDictType; } - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyDict_New(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyDictProxy_New(IntPtr dict); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyDict_GetItem(IntPtr pointer, IntPtr key); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyDict_GetItemString(IntPtr pointer, string key); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyDict_SetItem(IntPtr pointer, IntPtr key, IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyDict_SetItemString(IntPtr pointer, string key, IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyDict_DelItem(IntPtr pointer, IntPtr key); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyDict_DelItemString(IntPtr pointer, string key); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyMapping_HasKey(IntPtr pointer, IntPtr key); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyDict_Keys(IntPtr pointer); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyDict_Values(IntPtr pointer); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyDict_Items(IntPtr pointer); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyDict_Copy(IntPtr pointer); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyDict_Update(IntPtr pointer, IntPtr other); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyDict_Clear(IntPtr pointer); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyDict_Size(IntPtr pointer); @@ -1346,37 +1375,37 @@ internal static bool PyList_Check(IntPtr ob) return PyObject_TYPE(ob) == PyListType; } - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyList_New(int size); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyList_AsTuple(IntPtr pointer); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyList_GetItem(IntPtr pointer, int index); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyList_SetItem(IntPtr pointer, int index, IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyList_Insert(IntPtr pointer, int index, IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyList_Append(IntPtr pointer, IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyList_Reverse(IntPtr pointer); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyList_Sort(IntPtr pointer); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyList_GetSlice(IntPtr pointer, int start, int end); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyList_SetSlice(IntPtr pointer, int start, int end, IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyList_Size(IntPtr pointer); @@ -1389,19 +1418,19 @@ internal static bool PyTuple_Check(IntPtr ob) return PyObject_TYPE(ob) == PyTupleType; } - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyTuple_New(int size); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyTuple_GetItem(IntPtr pointer, int index); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyTuple_SetItem(IntPtr pointer, int index, IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyTuple_GetSlice(IntPtr pointer, int start, int end); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyTuple_Size(IntPtr pointer); @@ -1409,19 +1438,19 @@ internal static bool PyTuple_Check(IntPtr ob) // Python iterator API //==================================================================== -#if PYTHON2 - [DllImport(PythonDll)] - internal static extern bool PyIter_Check(IntPtr pointer); -#elif PYTHON3 internal static bool PyIter_Check(IntPtr pointer) { - var ob_type = (IntPtr)Marshal.PtrToStructure(pointer + ObjectOffset.ob_type, typeof(IntPtr)); - IntPtr tp_iternext = ob_type + TypeOffset.tp_iternext; - return tp_iternext != null && tp_iternext != _PyObject_NextNotImplemented; - } + var ob_type = Marshal.ReadIntPtr(pointer, ObjectOffset.ob_type); +#if PYTHON2 + long tp_flags = Marshal.ReadInt64(ob_type, TypeOffset.tp_flags); + if ((tp_flags & TypeFlags.HaveIter) == 0) + return false; #endif + IntPtr tp_iternext = Marshal.ReadIntPtr(ob_type, TypeOffset.tp_iternext); + return tp_iternext != IntPtr.Zero && tp_iternext != _PyObject_NextNotImplemented; + } - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyIter_Next(IntPtr pointer); @@ -1429,47 +1458,47 @@ internal static bool PyIter_Check(IntPtr pointer) // Python module API //==================================================================== - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyModule_New(string name); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern string PyModule_GetName(IntPtr module); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyModule_GetDict(IntPtr module); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern string PyModule_GetFilename(IntPtr module); #if PYTHON3 - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyModule_Create2(IntPtr module, int apiver); #endif - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyImport_Import(IntPtr name); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyImport_ImportModule(string name); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyImport_ReloadModule(IntPtr module); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyImport_AddModule(string name); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyImport_GetModuleDict(); #if PYTHON3 - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PySys_SetArgvEx( int argc, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(StrArrayMarshaler))] string[] argv, int updatepath ); #elif PYTHON2 - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PySys_SetArgvEx( int argc, string[] argv, @@ -1477,10 +1506,10 @@ int updatepath ); #endif - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PySys_GetObject(string name); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PySys_SetObject(string name, IntPtr ob); @@ -1493,10 +1522,10 @@ internal static bool PyType_Check(IntPtr ob) return PyObject_TypeCheck(ob, PyTypeType); } - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyType_Modified(IntPtr type); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern bool PyType_IsSubtype(IntPtr t1, IntPtr t2); internal static bool PyObject_TypeCheck(IntPtr ob, IntPtr tp) @@ -1505,37 +1534,37 @@ internal static bool PyObject_TypeCheck(IntPtr ob, IntPtr tp) return (t == tp) || PyType_IsSubtype(t, tp); } - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyType_GenericNew(IntPtr type, IntPtr args, IntPtr kw); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyType_GenericAlloc(IntPtr type, int n); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyType_Ready(IntPtr type); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr _PyType_Lookup(IntPtr type, IntPtr name); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyObject_GenericGetAttr(IntPtr obj, IntPtr name); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyObject_GenericSetAttr(IntPtr obj, IntPtr name, IntPtr value); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr _PyObject_GetDictPtr(IntPtr obj); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyObject_GC_New(IntPtr tp); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyObject_GC_Del(IntPtr tp); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyObject_GC_Track(IntPtr tp); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyObject_GC_UnTrack(IntPtr tp); @@ -1543,13 +1572,13 @@ internal static bool PyObject_TypeCheck(IntPtr ob, IntPtr tp) // Python memory API //==================================================================== - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyMem_Malloc(int size); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyMem_Realloc(IntPtr ptr, int size); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyMem_Free(IntPtr ptr); @@ -1557,40 +1586,40 @@ internal static bool PyObject_TypeCheck(IntPtr ob, IntPtr tp) // Python exception API //==================================================================== - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyErr_SetString(IntPtr ob, string message); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyErr_SetObject(IntPtr ob, IntPtr message); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyErr_SetFromErrno(IntPtr ob); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyErr_SetNone(IntPtr ob); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyErr_ExceptionMatches(IntPtr exception); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyErr_GivenExceptionMatches(IntPtr ob, IntPtr val); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyErr_NormalizeException(IntPtr ob, IntPtr val, IntPtr tb); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyErr_Occurred(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyErr_Fetch(ref IntPtr ob, ref IntPtr val, ref IntPtr tb); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyErr_Restore(IntPtr ob, IntPtr val, IntPtr tb); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyErr_Clear(); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern void PyErr_Print(); @@ -1598,10 +1627,10 @@ internal static bool PyObject_TypeCheck(IntPtr ob, IntPtr tp) // Miscellaneous //==================================================================== - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyMethod_Self(IntPtr ob); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyMethod_Function(IntPtr ob); } } diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index 6f373f036..ad0fddcc1 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -206,6 +206,7 @@ internal static IntPtr CreateSubType(IntPtr py_name, IntPtr py_base_type, IntPtr if (0 != Runtime.PyMapping_HasKey(py_dict, assemblyKey.Handle)) { var pyAssembly = new PyObject(Runtime.PyDict_GetItem(py_dict, assemblyKey.Handle)); + Runtime.XIncref(pyAssembly.Handle); disposeList.Add(pyAssembly); if (!Converter.ToManagedValue(pyAssembly.Handle, typeof(string), out assembly, false)) { @@ -218,6 +219,7 @@ internal static IntPtr CreateSubType(IntPtr py_name, IntPtr py_base_type, IntPtr if (0 != Runtime.PyMapping_HasKey(py_dict, namespaceKey.Handle)) { var pyNamespace = new PyObject(Runtime.PyDict_GetItem(py_dict, namespaceKey.Handle)); + Runtime.XIncref(pyNamespace.Handle); disposeList.Add(pyNamespace); if (!Converter.ToManagedValue(pyNamespace.Handle, typeof(string), out namespaceStr, false)) { diff --git a/src/testing/Python.Test.csproj b/src/testing/Python.Test.csproj index ce8dca10d..8a8d9ed2b 100644 --- a/src/testing/Python.Test.csproj +++ b/src/testing/Python.Test.csproj @@ -108,6 +108,6 @@ - + diff --git a/src/testing/conversiontest.cs b/src/testing/conversiontest.cs index 7179c7607..36294594c 100644 --- a/src/testing/conversiontest.cs +++ b/src/testing/conversiontest.cs @@ -32,8 +32,14 @@ public ConversionTest() public byte[] ByteArrayField; public sbyte[] SByteArrayField; + + public T? Echo(T? arg) where T: struct { + return arg; + } + } + public interface ISpam { diff --git a/src/tests/test_clrmethod.py b/src/tests/test_clrmethod.py new file mode 100644 index 000000000..a6078bece --- /dev/null +++ b/src/tests/test_clrmethod.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +"""Test clrmethod and clrproperty support for calling methods and getting/setting python properties from CLR.""" + +import Python.Test as Test +import System +import pytest +import clr + +class ExampleClrClass(System.Object): + __namespace__ = "PyTest" + def __init__(self): + self._x = 3 + @clr.clrmethod(int, [int]) + def test(self, x): + return x*2 + + def get_X(self): + return self._x + def set_X(self, value): + self._x = value + X = clr.clrproperty(int, get_X, set_X) + + @clr.clrproperty(int) + def Y(self): + return self._x * 2 + +def test_set_and_get_property_from_py(): + """Test setting and getting clr-accessible properties from python.""" + t = ExampleClrClass() + assert t.X == 3 + assert t.Y == 3 * 2 + t.X = 4 + assert t.X == 4 + assert t.Y == 4 * 2 + +def test_set_and_get_property_from_clr(): + """Test setting and getting clr-accessible properties from the clr.""" + t = ExampleClrClass() + assert t.GetType().GetProperty("X").GetValue(t) == 3 + assert t.GetType().GetProperty("Y").GetValue(t) == 3 * 2 + t.GetType().GetProperty("X").SetValue(t, 4) + assert t.GetType().GetProperty("X").GetValue(t) == 4 + assert t.GetType().GetProperty("Y").GetValue(t) == 4 * 2 + + +def test_set_and_get_property_from_clr_and_py(): + """Test setting and getting clr-accessible properties alternatingly from the clr and from python.""" + t = ExampleClrClass() + assert t.GetType().GetProperty("X").GetValue(t) == 3 + assert t.GetType().GetProperty("Y").GetValue(t) == 3 * 2 + assert t.X == 3 + assert t.Y == 3 * 2 + t.GetType().GetProperty("X").SetValue(t, 4) + assert t.GetType().GetProperty("X").GetValue(t) == 4 + assert t.GetType().GetProperty("Y").GetValue(t) == 4 * 2 + assert t.X == 4 + assert t.Y == 4 * 2 + t.X = 5 + assert t.GetType().GetProperty("X").GetValue(t) == 5 + assert t.GetType().GetProperty("Y").GetValue(t) == 5 * 2 + assert t.X == 5 + assert t.Y == 5 * 2 + +def test_method_invocation_from_py(): + """Test calling a clr-accessible method from python.""" + t = ExampleClrClass() + assert t.test(41) == 41*2 + +def test_method_invocation_from_clr(): + """Test calling a clr-accessible method from the clr.""" + t = ExampleClrClass() + assert t.GetType().GetMethod("test").Invoke(t, [37]) == 37*2 diff --git a/src/tests/test_conversion.py b/src/tests/test_conversion.py index ac263ef5d..53e5d8051 100644 --- a/src/tests/test_conversion.py +++ b/src/tests/test_conversion.py @@ -466,17 +466,6 @@ def test_double_conversion(): with pytest.raises(TypeError): ConversionTest().DoubleField = None - with pytest.raises(OverflowError): - ConversionTest().DoubleField = 1.7976931348623159e308 - - with pytest.raises(OverflowError): - ConversionTest().DoubleField = -1.7976931348623159e308 - - with pytest.raises(OverflowError): - _ = System.Double(1.7976931348623159e308) - - with pytest.raises(OverflowError): - _ = System.Double(-1.7976931348623159e308) def test_decimal_conversion(): @@ -641,6 +630,8 @@ def test_enum_conversion(): def test_null_conversion(): """Test null conversion.""" + import System + ob = ConversionTest() ob.StringField = None @@ -652,6 +643,10 @@ def test_null_conversion(): ob.SpamField = None assert ob.SpamField is None + pi = 22/7 + assert ob.Echo[System.Double](pi) == pi + assert ob.Echo[System.DateTime](None) is None + # Primitive types and enums should not be set to null. with pytest.raises(TypeError): diff --git a/src/tests/test_module.py b/src/tests/test_module.py index 2255ea411..e02aa6e01 100644 --- a/src/tests/test_module.py +++ b/src/tests/test_module.py @@ -352,6 +352,26 @@ def test_clr_add_reference(): with pytest.raises(FileNotFoundException): AddReference("somethingtotallysilly") +def test_clr_get_clr_type(): + """Test clr.GetClrType().""" + from clr import GetClrType + import System + from System import IComparable + from System import ArgumentException + assert GetClrType(System.String).FullName == "System.String" + comparable = GetClrType(IComparable) + assert comparable.FullName == "System.IComparable" + assert comparable.IsInterface + assert GetClrType(int).FullName == "System.Int32" + assert GetClrType(str).FullName == "System.String" + assert GetClrType(float).FullName == "System.Double" + dblarr = System.Array[System.Double] + assert GetClrType(dblarr).FullName == "System.Double[]" + + with pytest.raises(TypeError): + GetClrType(1) + with pytest.raises(TypeError): + GetClrType("thiswillfail") def test_assembly_load_thread_safety(): from Python.Test import ModuleTest diff --git a/src/tests/test_subclass.py b/src/tests/test_subclass.py index 8e862a56d..739c24c07 100644 --- a/src/tests/test_subclass.py +++ b/src/tests/test_subclass.py @@ -85,7 +85,6 @@ def OnTestEvent(self, value): return DerivedEventTest -@pytest.mark.skip(reason="FIXME: test randomly pass/fails") def test_base_class(): """Test base class managed type""" ob = SubClassTest() @@ -98,7 +97,6 @@ def test_base_class(): assert list(SubClassTest.test_list(ob)) == ["a", "b", "c"] -@pytest.mark.skip(reason="FIXME: test randomly pass/fails") def test_interface(): """Test python classes can derive from C# interfaces""" InterfaceTestClass = interface_test_class_fixture() @@ -112,7 +110,6 @@ def test_interface(): assert id(x) == id(ob) -@pytest.mark.skip(reason="FIXME: test randomly pass/fails") def test_derived_class(): """Test python class derived from managed type""" DerivedClass = derived_class_fixture() diff --git a/tools/nuget/nuget.exe b/tools/nuget/nuget.exe index e42e6d827..463f8e137 100644 Binary files a/tools/nuget/nuget.exe and b/tools/nuget/nuget.exe differ