|
|
@ -2,17 +2,19 @@ |
|
|
|
using System.IO; |
|
|
|
using System.IO; |
|
|
|
using System.Threading; |
|
|
|
using System.Threading; |
|
|
|
using System.Threading.Tasks; |
|
|
|
using System.Threading.Tasks; |
|
|
|
|
|
|
|
using Microsoft.Extensions.Logging; |
|
|
|
using NLog; |
|
|
|
using NLog; |
|
|
|
using Python.Runtime; |
|
|
|
using Python.Runtime; |
|
|
|
using StabilityMatrix.Helper; |
|
|
|
using StabilityMatrix.Helper; |
|
|
|
|
|
|
|
using ILogger = NLog.ILogger; |
|
|
|
|
|
|
|
|
|
|
|
namespace StabilityMatrix; |
|
|
|
namespace StabilityMatrix; |
|
|
|
|
|
|
|
|
|
|
|
internal record struct PyVersionInfo(int Major, int Minor, int Micro, string ReleaseLevel, int Serial); |
|
|
|
public record struct PyVersionInfo(int Major, int Minor, int Micro, string ReleaseLevel, int Serial); |
|
|
|
|
|
|
|
|
|
|
|
internal static class PyRunner |
|
|
|
public class PyRunner : IPyRunner |
|
|
|
{ |
|
|
|
{ |
|
|
|
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); |
|
|
|
private readonly ILogger<PyRunner> logger; |
|
|
|
|
|
|
|
|
|
|
|
private const string RelativeDllPath = @"Assets\Python310\python310.dll"; |
|
|
|
private const string RelativeDllPath = @"Assets\Python310\python310.dll"; |
|
|
|
private const string RelativeExePath = @"Assets\Python310\python.exe"; |
|
|
|
private const string RelativeExePath = @"Assets\Python310\python.exe"; |
|
|
@ -23,25 +25,31 @@ internal static class PyRunner |
|
|
|
public static string PipExePath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, RelativePipExePath); |
|
|
|
public static string PipExePath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, RelativePipExePath); |
|
|
|
public static string GetPipPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, RelativeGetPipPath); |
|
|
|
public static string GetPipPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, RelativeGetPipPath); |
|
|
|
|
|
|
|
|
|
|
|
public static PyIOStream? StdOutStream; |
|
|
|
|
|
|
|
public static PyIOStream? StdErrStream; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static readonly SemaphoreSlim PyRunning = new(1, 1); |
|
|
|
private static readonly SemaphoreSlim PyRunning = new(1, 1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private PyIOStream? StdOutStream; |
|
|
|
|
|
|
|
private PyIOStream? StdErrStream; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public PyRunner(ILogger<PyRunner> logger) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
this.logger = logger; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// <summary> |
|
|
|
/// Initializes the Python runtime using the embedded dll. |
|
|
|
/// Initializes the Python runtime using the embedded dll. |
|
|
|
/// Can be called with no effect after initialization. |
|
|
|
/// Can be called with no effect after initialization. |
|
|
|
/// </summary> |
|
|
|
/// </summary> |
|
|
|
/// <exception cref="FileNotFoundException">Thrown if Python DLL not found.</exception> |
|
|
|
/// <exception cref="FileNotFoundException">Thrown if Python DLL not found.</exception> |
|
|
|
public static async Task Initialize() |
|
|
|
public async Task Initialize() |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (PythonEngine.IsInitialized) return; |
|
|
|
if (PythonEngine.IsInitialized) return; |
|
|
|
|
|
|
|
|
|
|
|
Logger.Trace($"Initializing Python runtime with DLL '{DllPath}'"); |
|
|
|
logger.LogInformation("Initializing Python runtime with DLL: {DllPath}", DllPath); |
|
|
|
|
|
|
|
|
|
|
|
// Check PythonDLL exists |
|
|
|
// Check PythonDLL exists |
|
|
|
if (!File.Exists(DllPath)) |
|
|
|
if (!File.Exists(DllPath)) |
|
|
|
{ |
|
|
|
{ |
|
|
|
|
|
|
|
logger.LogError("Python DLL not found"); |
|
|
|
throw new FileNotFoundException("Python DLL not found", DllPath); |
|
|
|
throw new FileNotFoundException("Python DLL not found", DllPath); |
|
|
|
} |
|
|
|
} |
|
|
|
Runtime.PythonDLL = DllPath; |
|
|
|
Runtime.PythonDLL = DllPath; |
|
|
@ -62,7 +70,7 @@ internal static class PyRunner |
|
|
|
/// <summary> |
|
|
|
/// <summary> |
|
|
|
/// One-time setup for get-pip |
|
|
|
/// One-time setup for get-pip |
|
|
|
/// </summary> |
|
|
|
/// </summary> |
|
|
|
public static async Task SetupPip() |
|
|
|
public async Task SetupPip() |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (!File.Exists(GetPipPath)) |
|
|
|
if (!File.Exists(GetPipPath)) |
|
|
|
{ |
|
|
|
{ |
|
|
@ -75,7 +83,7 @@ internal static class PyRunner |
|
|
|
/// <summary> |
|
|
|
/// <summary> |
|
|
|
/// Install a Python package with pip |
|
|
|
/// Install a Python package with pip |
|
|
|
/// </summary> |
|
|
|
/// </summary> |
|
|
|
public static async Task InstallPackage(string package) |
|
|
|
public async Task InstallPackage(string package) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (!File.Exists(PipExePath)) |
|
|
|
if (!File.Exists(PipExePath)) |
|
|
|
{ |
|
|
|
{ |
|
|
@ -92,7 +100,7 @@ internal static class PyRunner |
|
|
|
/// <param name="waitTimeout">Time limit for waiting on PyRunning lock.</param> |
|
|
|
/// <param name="waitTimeout">Time limit for waiting on PyRunning lock.</param> |
|
|
|
/// <param name="cancelToken">Cancellation token.</param> |
|
|
|
/// <param name="cancelToken">Cancellation token.</param> |
|
|
|
/// <exception cref="OperationCanceledException">cancelToken was canceled, or waitTimeout expired.</exception> |
|
|
|
/// <exception cref="OperationCanceledException">cancelToken was canceled, or waitTimeout expired.</exception> |
|
|
|
private static async Task<T> RunInThreadWithLock<T>(Func<T> func, TimeSpan? waitTimeout = null, CancellationToken cancelToken = default) |
|
|
|
private async Task<T> RunInThreadWithLock<T>(Func<T> func, TimeSpan? waitTimeout = null, CancellationToken cancelToken = default) |
|
|
|
{ |
|
|
|
{ |
|
|
|
// Wait to acquire PyRunning lock |
|
|
|
// Wait to acquire PyRunning lock |
|
|
|
await PyRunning.WaitAsync(cancelToken).ConfigureAwait(false); |
|
|
|
await PyRunning.WaitAsync(cancelToken).ConfigureAwait(false); |
|
|
@ -119,7 +127,7 @@ internal static class PyRunner |
|
|
|
/// <param name="waitTimeout">Time limit for waiting on PyRunning lock.</param> |
|
|
|
/// <param name="waitTimeout">Time limit for waiting on PyRunning lock.</param> |
|
|
|
/// <param name="cancelToken">Cancellation token.</param> |
|
|
|
/// <param name="cancelToken">Cancellation token.</param> |
|
|
|
/// <exception cref="OperationCanceledException">cancelToken was canceled, or waitTimeout expired.</exception> |
|
|
|
/// <exception cref="OperationCanceledException">cancelToken was canceled, or waitTimeout expired.</exception> |
|
|
|
private static async Task RunInThreadWithLock(Action action, TimeSpan? waitTimeout = null, CancellationToken cancelToken = default) |
|
|
|
private async Task RunInThreadWithLock(Action action, TimeSpan? waitTimeout = null, CancellationToken cancelToken = default) |
|
|
|
{ |
|
|
|
{ |
|
|
|
// Wait to acquire PyRunning lock |
|
|
|
// Wait to acquire PyRunning lock |
|
|
|
await PyRunning.WaitAsync(cancelToken).ConfigureAwait(false); |
|
|
|
await PyRunning.WaitAsync(cancelToken).ConfigureAwait(false); |
|
|
@ -143,7 +151,7 @@ internal static class PyRunner |
|
|
|
/// Evaluate Python expression and return its value as a string |
|
|
|
/// Evaluate Python expression and return its value as a string |
|
|
|
/// </summary> |
|
|
|
/// </summary> |
|
|
|
/// <param name="expression"></param> |
|
|
|
/// <param name="expression"></param> |
|
|
|
public static async Task<string> Eval(string expression) |
|
|
|
public async Task<string> Eval(string expression) |
|
|
|
{ |
|
|
|
{ |
|
|
|
return await Eval<string>(expression); |
|
|
|
return await Eval<string>(expression); |
|
|
|
} |
|
|
|
} |
|
|
@ -152,7 +160,7 @@ internal static class PyRunner |
|
|
|
/// Evaluate Python expression and return its value |
|
|
|
/// Evaluate Python expression and return its value |
|
|
|
/// </summary> |
|
|
|
/// </summary> |
|
|
|
/// <param name="expression"></param> |
|
|
|
/// <param name="expression"></param> |
|
|
|
public static Task<T> Eval<T>(string expression) |
|
|
|
public Task<T> Eval<T>(string expression) |
|
|
|
{ |
|
|
|
{ |
|
|
|
return RunInThreadWithLock(() => |
|
|
|
return RunInThreadWithLock(() => |
|
|
|
{ |
|
|
|
{ |
|
|
@ -165,7 +173,7 @@ internal static class PyRunner |
|
|
|
/// Execute Python code without returning a value |
|
|
|
/// Execute Python code without returning a value |
|
|
|
/// </summary> |
|
|
|
/// </summary> |
|
|
|
/// <param name="code"></param> |
|
|
|
/// <param name="code"></param> |
|
|
|
public static Task Exec(string code) |
|
|
|
public Task Exec(string code) |
|
|
|
{ |
|
|
|
{ |
|
|
|
return RunInThreadWithLock(() => |
|
|
|
return RunInThreadWithLock(() => |
|
|
|
{ |
|
|
|
{ |
|
|
@ -176,7 +184,7 @@ internal static class PyRunner |
|
|
|
/// <summary> |
|
|
|
/// <summary> |
|
|
|
/// Return the Python version as a PyVersionInfo struct |
|
|
|
/// Return the Python version as a PyVersionInfo struct |
|
|
|
/// </summary> |
|
|
|
/// </summary> |
|
|
|
public static async Task<PyVersionInfo> GetVersionInfo() |
|
|
|
public async Task<PyVersionInfo> GetVersionInfo() |
|
|
|
{ |
|
|
|
{ |
|
|
|
var version = await RunInThreadWithLock(() => |
|
|
|
var version = await RunInThreadWithLock(() => |
|
|
|
{ |
|
|
|
{ |
|
|
|