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;
- }
- }
}