using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using Python.Runtime.Native;
namespace Python.Runtime
{
///
/// This class provides the public interface of the Python runtime.
///
public class PythonEngine : IDisposable
{
private static DelegateManager? delegateManager;
private static bool initialized;
private static IntPtr _pythonHome = IntPtr.Zero;
private static IntPtr _programName = IntPtr.Zero;
private static IntPtr _pythonPath = IntPtr.Zero;
private static InteropConfiguration interopConfiguration = InteropConfiguration.MakeDefault();
public PythonEngine()
{
Initialize();
}
public PythonEngine(params string[] args)
{
Initialize(args);
}
public PythonEngine(IEnumerable args)
{
Initialize(args);
}
public void Dispose()
{
Shutdown();
}
public static bool IsInitialized
{
get { return initialized; }
}
private static void EnsureInitialized()
{
if (!IsInitialized)
throw new InvalidOperationException(
"Python must be initialized for this operation"
);
}
/// Set to true to enable GIL debugging assistance.
public static bool DebugGIL { get; set; } = false;
internal static DelegateManager DelegateManager
{
get
{
if (delegateManager == null)
{
throw new InvalidOperationException(
"DelegateManager has not yet been initialized using Python.Runtime.PythonEngine.Initialize().");
}
return delegateManager;
}
}
public static InteropConfiguration InteropConfiguration
{
get => interopConfiguration;
set
{
if (IsInitialized)
throw new NotSupportedException("Changing interop configuration when engine is running is not supported");
interopConfiguration = value ?? throw new ArgumentNullException(nameof(InteropConfiguration));
}
}
public static string ProgramName
{
get
{
IntPtr p = Runtime.TryUsingDll(() => Runtime.Py_GetProgramName());
return UcsMarshaler.PtrToPy3UnicodePy2String(p) ?? "";
}
set
{
Marshal.FreeHGlobal(_programName);
_programName = Runtime.TryUsingDll(
() => UcsMarshaler.Py3UnicodePy2StringtoPtr(value)
);
Runtime.Py_SetProgramName(_programName);
}
}
public static string PythonHome
{
get
{
EnsureInitialized();
IntPtr p = Runtime.TryUsingDll(() => Runtime.Py_GetPythonHome());
return UcsMarshaler.PtrToPy3UnicodePy2String(p) ?? "";
}
set
{
// this value is null in the beginning
Marshal.FreeHGlobal(_pythonHome);
_pythonHome = UcsMarshaler.Py3UnicodePy2StringtoPtr(value);
Runtime.TryUsingDll(() => Runtime.Py_SetPythonHome(_pythonHome));
}
}
public static string PythonPath
{
get
{
IntPtr p = Runtime.TryUsingDll(() => Runtime.Py_GetPath());
return UcsMarshaler.PtrToPy3UnicodePy2String(p) ?? "";
}
set
{
Marshal.FreeHGlobal(_pythonPath);
_pythonPath = Runtime.TryUsingDll(
() => UcsMarshaler.Py3UnicodePy2StringtoPtr(value)
);
Runtime.Py_SetPath(_pythonPath);
}
}
public static Version MinSupportedVersion => new(3, 7);
public static Version MaxSupportedVersion => new(3, 14, int.MaxValue, int.MaxValue);
public static bool IsSupportedVersion(Version version) => version >= MinSupportedVersion && version = 0; --index)
{
if (ShutdownHandlers[index] == handler)
{
ShutdownHandlers.RemoveAt(index);
break;
}
}
}
///
/// Run all the shutdown handlers.
///
/// They're run in opposite order they were added.
///
static void ExecuteShutdownHandlers()
{
while(ShutdownHandlers.Count > 0)
{
var handler = ShutdownHandlers[ShutdownHandlers.Count - 1];
ShutdownHandlers.RemoveAt(ShutdownHandlers.Count - 1);
handler();
}
}
///
/// AcquireLock Method
///
///
/// Acquire the Python global interpreter lock (GIL). Managed code
/// *must* call this method before using any objects or calling any
/// methods on objects in the Python.Runtime namespace. The only
/// exception is PythonEngine.Initialize, which may be called without
/// first calling AcquireLock.
/// Each call to AcquireLock must be matched by a corresponding call
/// to ReleaseLock, passing the token obtained from AcquireLock.
/// For more information, see the "Extending and Embedding" section
/// of the Python documentation on www.python.org.
///
internal static PyGILState AcquireLock()
{
return Runtime.PyGILState_Ensure();
}
///
/// ReleaseLock Method
///
///
/// Release the Python global interpreter lock using a token obtained
/// from a previous call to AcquireLock.
/// For more information, see the "Extending and Embedding" section
/// of the Python documentation on www.python.org.
///
internal static void ReleaseLock(PyGILState gs)
{
Runtime.PyGILState_Release(gs);
}
///
/// BeginAllowThreads Method
///
///
/// Release the Python global interpreter lock to allow other threads
/// to run. This is equivalent to the Py_BEGIN_ALLOW_THREADS macro
/// provided by the C Python API.
/// For more information, see the "Extending and Embedding" section
/// of the Python documentation on www.python.org.
///
public static unsafe IntPtr BeginAllowThreads()
{
return (IntPtr)Runtime.PyEval_SaveThread();
}
///
/// EndAllowThreads Method
///
///
/// Re-aquire the Python global interpreter lock for the current
/// thread. This is equivalent to the Py_END_ALLOW_THREADS macro
/// provided by the C Python API.
/// For more information, see the "Extending and Embedding" section
/// of the Python documentation on www.python.org.
///
public static unsafe void EndAllowThreads(IntPtr ts)
{
Runtime.PyEval_RestoreThread((PyThreadState*)ts);
}
public static PyObject Compile(string code, string filename = "", RunFlagType mode = RunFlagType.File)
{
var flag = (int)mode;
NewReference ptr = Runtime.Py_CompileString(code, filename, flag);
PythonException.ThrowIfIsNull(ptr);
return ptr.MoveToPyObject();
}
///
/// Eval Method
///
///
/// Evaluate a Python expression and returns the result.
/// It's a subset of Python eval function.
///
public static PyObject Eval(string code, PyDict? globals = null, PyObject? locals = null)
{
PyObject result = RunString(code, globals.BorrowNullable(), locals.BorrowNullable(), RunFlagType.Eval);
return result;
}
///
/// Exec Method
///
///
/// Run a string containing Python code.
/// It's a subset of Python exec function.
///
public static void Exec(string code, PyDict? globals = null, PyObject? locals = null)
{
using PyObject result = RunString(code, globals.BorrowNullable(), locals.BorrowNullable(), RunFlagType.File);
if (result.obj != Runtime.PyNone)
{
throw PythonException.ThrowLastAsClrException();
}
}
///
/// Exec Method
///
///
/// Run a string containing Python code.
/// It's a subset of Python exec function.
///
internal static void Exec(string code, BorrowedReference globals, BorrowedReference locals = default)
{
using PyObject result = RunString(code, globals: globals, locals: locals, RunFlagType.File);
if (result.obj != Runtime.PyNone)
{
throw PythonException.ThrowLastAsClrException();
}
}
///
/// Gets the Python thread ID.
///
/// The Python thread ID.
public static ulong GetPythonThreadID()
{
using PyObject threading = Py.Import("threading");
using PyObject id = threading.InvokeMethod("get_ident");
return id.As();
}
///
/// Interrupts the execution of a thread.
///
/// The Python thread ID.
/// The number of thread states modified; this is normally one, but will be zero if the thread id is not found.
public static int Interrupt(ulong pythonThreadID)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return Runtime.PyThreadState_SetAsyncExcLLP64((uint)pythonThreadID, Exceptions.KeyboardInterrupt);
}
return Runtime.PyThreadState_SetAsyncExcLP64(pythonThreadID, Exceptions.KeyboardInterrupt);
}
///
/// RunString Method. Function has been deprecated and will be removed.
/// Use Exec/Eval/RunSimpleString instead.
///
[Obsolete("RunString is deprecated and will be removed. Use Exec/Eval/RunSimpleString instead.")]
public static PyObject RunString(string code, PyDict? globals = null, PyObject? locals = null)
{
return RunString(code, globals.BorrowNullable(), locals.BorrowNullable(), RunFlagType.File);
}
///
/// Internal RunString Method.
///
///
/// Run a string containing Python code. Returns the result of
/// executing the code string as a PyObject instance, or null if
/// an exception was raised.
///
internal static PyObject RunString(string code, BorrowedReference globals, BorrowedReference locals, RunFlagType flag)
{
if (code is null) throw new ArgumentNullException(nameof(code));
NewReference tempGlobals = default;
if (globals.IsNull)
{
globals = Runtime.PyEval_GetGlobals();
if (globals.IsNull)
{
tempGlobals = Runtime.PyDict_New();
globals = tempGlobals.BorrowOrThrow();
Runtime.PyDict_SetItem(
globals, PyIdentifier.__builtins__,
Runtime.PyEval_GetBuiltins()
);
}
}
if (locals == null)
{
locals = globals;
}
try
{
NewReference result = Runtime.PyRun_String(
code, flag, globals, locals
);
PythonException.ThrowIfIsNull(result);
return result.MoveToPyObject();
}
finally
{
tempGlobals.Dispose();
}
}
}
public enum RunFlagType : int
{
Single = 256,
File = 257, /* Py_file_input */
Eval = 258
}
}