diff --git a/src/runtime/Py.cs b/src/runtime/Py.cs new file mode 100644 index 000000000..7a2369413 --- /dev/null +++ b/src/runtime/Py.cs @@ -0,0 +1,197 @@ +namespace Python.Runtime; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading; + +using Python.Runtime.Native; + +public static class Py +{ + public static GILState GIL() + { + if (!PythonEngine.IsInitialized) + { + PythonEngine.Initialize(); + } + + return PythonEngine.DebugGIL ? new DebugGILState() : new GILState(); + } + + public static PyModule CreateScope() => new(); + public static PyModule CreateScope(string name) + => new(name ?? throw new ArgumentNullException(nameof(name))); + + + public class GILState : IDisposable + { + private readonly PyGILState state; + private bool isDisposed; + + internal GILState() + { + state = PythonEngine.AcquireLock(); + } + + public virtual void Dispose() + { + if (this.isDisposed) return; + + PythonEngine.ReleaseLock(state); + GC.SuppressFinalize(this); + this.isDisposed = true; + } + + ~GILState() + { + throw new InvalidOperationException("GIL must always be released, and it must be released from the same thread that acquired it."); + } + } + + public class DebugGILState : GILState + { + readonly Thread owner; + internal DebugGILState() : base() + { + this.owner = Thread.CurrentThread; + } + public override void Dispose() + { + if (this.owner != Thread.CurrentThread) + throw new InvalidOperationException("GIL must always be released from the same thread, that acquired it"); + + base.Dispose(); + } + } + + public class KeywordArguments : PyDict + { + public KeywordArguments() : base() + { + } + + protected KeywordArguments(SerializationInfo info, StreamingContext context) + : base(info, context) { } + } + + public static KeywordArguments kw(params object?[] kv) + { + var dict = new KeywordArguments(); + if (kv.Length % 2 != 0) + { + throw new ArgumentException("Must have an equal number of keys and values"); + } + for (var i = 0; i < kv.Length; i += 2) + { + var key = kv[i] as string; + if (key is null) + throw new ArgumentException("Keys must be non-null strings"); + + BorrowedReference value; + NewReference temp = default; + if (kv[i + 1] is PyObject pyObj) + { + value = pyObj; + } + else + { + temp = Converter.ToPythonDetectType(kv[i + 1]); + value = temp.Borrow(); + } + using (temp) + { + if (Runtime.PyDict_SetItemString(dict, key, value) != 0) + { + throw new ArgumentException( + string.Format("Cannot add key '{0}' to dictionary.", key), + innerException: PythonException.FetchCurrent()); + } + } + } + return dict; + } + + /// + /// Given a module or package name, import the module and return the resulting object. + /// + /// Fully-qualified module or package name + public static PyObject Import(string name) => PyModule.Import(name); + + public static void SetArgv() + { + IEnumerable args; + try + { + args = Environment.GetCommandLineArgs(); + } + catch (NotSupportedException) + { + args = Enumerable.Empty(); + } + + SetArgv( + new[] { "" }.Concat( + Environment.GetCommandLineArgs().Skip(1) + ) + ); + } + + public static void SetArgv(params string[] argv) + { + SetArgv(argv as IEnumerable); + } + + public static void SetArgv(IEnumerable argv) + { + if (argv is null) throw new ArgumentNullException(nameof(argv)); + + using (GIL()) + { + string[] arr = argv.ToArray(); + Runtime.PySys_SetArgvEx(arr.Length, arr, 0); + Runtime.CheckExceptionOccurred(); + } + } + + public static void With(PyObject obj, Action Body) + { + if (obj is null) throw new ArgumentNullException(nameof(obj)); + if (Body is null) throw new ArgumentNullException(nameof(Body)); + + // Behavior described here: + // https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers + + Exception? ex = null; + PythonException? pyError = null; + + try + { + PyObject enterResult = obj.InvokeMethod("__enter__"); + + Body(enterResult); + } + catch (PythonException e) + { + ex = pyError = e; + } + catch (Exception e) + { + ex = e; + Exceptions.SetError(e); + pyError = PythonException.FetchCurrentRaw(); + } + + PyObject type = pyError?.Type ?? PyObject.None; + PyObject val = pyError?.Value ?? PyObject.None; + PyObject traceBack = pyError?.Traceback ?? PyObject.None; + + var exitResult = obj.InvokeMethod("__exit__", type, val, traceBack); + + if (ex != null && !exitResult.IsTrue()) throw ex; + } + + public static void With(PyObject obj, Action Body) + => With(obj, (PyObject context) => Body(context)); +} diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index 1338e2631..5223bb089 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -5,8 +5,6 @@ using System.Linq; using System.Reflection; using System.Runtime.InteropServices; -using System.Runtime.Serialization; -using System.Threading; using Python.Runtime.Native; @@ -671,189 +669,4 @@ public enum RunFlagType : int File = 257, /* Py_file_input */ Eval = 258 } - - public static class Py - { - public static GILState GIL() - { - if (!PythonEngine.IsInitialized) - { - PythonEngine.Initialize(); - } - - return PythonEngine.DebugGIL ? new DebugGILState() : new GILState(); - } - - public static PyModule CreateScope() => new(); - public static PyModule CreateScope(string name) - => new(name ?? throw new ArgumentNullException(nameof(name))); - - - public class GILState : IDisposable - { - private readonly PyGILState state; - private bool isDisposed; - - internal GILState() - { - state = PythonEngine.AcquireLock(); - } - - public virtual void Dispose() - { - if (this.isDisposed) return; - - PythonEngine.ReleaseLock(state); - GC.SuppressFinalize(this); - this.isDisposed = true; - } - - ~GILState() - { - throw new InvalidOperationException("GIL must always be released, and it must be released from the same thread that acquired it."); - } - } - - public class DebugGILState : GILState - { - readonly Thread owner; - internal DebugGILState() : base() - { - this.owner = Thread.CurrentThread; - } - public override void Dispose() - { - if (this.owner != Thread.CurrentThread) - throw new InvalidOperationException("GIL must always be released from the same thread, that acquired it"); - - base.Dispose(); - } - } - - public class KeywordArguments : PyDict - { - public KeywordArguments() : base() - { - } - - protected KeywordArguments(SerializationInfo info, StreamingContext context) - : base(info, context) { } - } - - public static KeywordArguments kw(params object?[] kv) - { - var dict = new KeywordArguments(); - if (kv.Length % 2 != 0) - { - throw new ArgumentException("Must have an equal number of keys and values"); - } - for (var i = 0; i < kv.Length; i += 2) - { - var key = kv[i] as string; - if (key is null) - throw new ArgumentException("Keys must be non-null strings"); - - BorrowedReference value; - NewReference temp = default; - if (kv[i + 1] is PyObject pyObj) - { - value = pyObj; - } - else - { - temp = Converter.ToPythonDetectType(kv[i + 1]); - value = temp.Borrow(); - } - using (temp) - { - if (Runtime.PyDict_SetItemString(dict, key, value) != 0) - { - throw new ArgumentException( - string.Format("Cannot add key '{0}' to dictionary.", key), - innerException: PythonException.FetchCurrent()); - } - } - } - return dict; - } - - /// - /// Given a module or package name, import the module and return the resulting object. - /// - /// Fully-qualified module or package name - public static PyObject Import(string name) => PyModule.Import(name); - - public static void SetArgv() - { - IEnumerable args; - try - { - args = Environment.GetCommandLineArgs(); - } - catch (NotSupportedException) - { - args = Enumerable.Empty(); - } - - SetArgv( - new[] { "" }.Concat( - Environment.GetCommandLineArgs().Skip(1) - ) - ); - } - - public static void SetArgv(params string[] argv) - { - SetArgv(argv as IEnumerable); - } - - public static void SetArgv(IEnumerable argv) - { - if (argv is null) throw new ArgumentNullException(nameof(argv)); - - using (GIL()) - { - string[] arr = argv.ToArray(); - Runtime.PySys_SetArgvEx(arr.Length, arr, 0); - Runtime.CheckExceptionOccurred(); - } - } - - public static void With(PyObject obj, Action Body) - { - if (obj is null) throw new ArgumentNullException(nameof(obj)); - if (Body is null) throw new ArgumentNullException(nameof(Body)); - - // Behavior described here: - // https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers - - Exception? ex = null; - PythonException? pyError = null; - - try - { - PyObject enterResult = obj.InvokeMethod("__enter__"); - - Body(enterResult); - } - catch (PythonException e) - { - ex = pyError = e; - } - catch (Exception e) - { - ex = e; - Exceptions.SetError(e); - pyError = PythonException.FetchCurrentRaw(); - } - - PyObject type = pyError?.Type ?? PyObject.None; - PyObject val = pyError?.Value ?? PyObject.None; - PyObject traceBack = pyError?.Traceback ?? PyObject.None; - - var exitResult = obj.InvokeMethod("__exit__", type, val, traceBack); - - if (ex != null && !exitResult.IsTrue()) throw ex; - } - } }