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() => 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)
{
if (kv[i] is not string key)
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(args.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));
}