* add new indexes, and change year column to int * Change gist to gin, and change year to int * Producer changes for new gin query * Fully map the rtn response using json dump from Pydantic Also updates Rtn to 0.1.9 * Add housekeeping script to reconcile imdb ids. * Join Torrent onto the ingested torrent table Ensure that a torrent can always find the details of where it came from, and how it was parsed. * Version bump for release * missing quote on table name
124 lines
3.6 KiB
C#
124 lines
3.6 KiB
C#
namespace SharedContracts.Python;
|
|
|
|
public class PythonEngineService(ILogger<PythonEngineService> logger) : IPythonEngineService
|
|
{
|
|
private IntPtr _mainThreadState;
|
|
private bool _isInitialized;
|
|
|
|
public ILogger<PythonEngineService> Logger { get; } = logger;
|
|
|
|
public dynamic? Sys { get; private set; }
|
|
|
|
public Task InitializePythonEngine(CancellationToken cancellationToken)
|
|
{
|
|
if (_isInitialized)
|
|
{
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
try
|
|
{
|
|
var pythonDllEnv = Environment.GetEnvironmentVariable("PYTHONNET_PYDLL");
|
|
|
|
if (string.IsNullOrWhiteSpace(pythonDllEnv))
|
|
{
|
|
Logger.LogWarning("PYTHONNET_PYDLL env is not set. Exiting Application");
|
|
Environment.Exit(1);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
Runtime.PythonDLL = pythonDllEnv;
|
|
PythonEngine.Initialize();
|
|
_mainThreadState = PythonEngine.BeginAllowThreads();
|
|
|
|
_isInitialized = true;
|
|
Logger.LogInformation("Python engine initialized");
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.LogError(e, $"Failed to initialize Python engine: {e.Message}");
|
|
Environment.Exit(1);
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public T ExecuteCommandOrScript<T>(string command, PyModule module, bool throwOnErrors) =>
|
|
ExecutePythonOperation(
|
|
() =>
|
|
{
|
|
var pyCompile = PythonEngine.Compile(command);
|
|
var nativeResult = module.Execute(pyCompile);
|
|
return nativeResult.As<T>();
|
|
}, nameof(ExecuteCommandOrScript), throwOnErrors);
|
|
|
|
public T ExecutePythonOperation<T>(Func<T> operation, string operationName, bool throwOnErrors) =>
|
|
ExecutePythonOperationWithDefault(operation, default, operationName, throwOnErrors, true);
|
|
|
|
public T ExecutePythonOperationWithDefault<T>(Func<T> operation, T? defaultValue, string operationName, bool throwOnErrors, bool logErrors) =>
|
|
ExecutePythonOperationInternal(operation, defaultValue, operationName, throwOnErrors, logErrors);
|
|
|
|
public void ExecuteOnGIL(Action act, bool throwOnErrors)
|
|
{
|
|
Sys ??= LoadSys();
|
|
|
|
try
|
|
{
|
|
using var gil = Py.GIL();
|
|
act();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Python Error: {Message} ({OperationName})", ex.Message, nameof(ExecuteOnGIL));
|
|
|
|
if (throwOnErrors)
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
public Task StopPythonEngine(CancellationToken cancellationToken)
|
|
{
|
|
PythonEngine.EndAllowThreads(_mainThreadState);
|
|
PythonEngine.Shutdown();
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private static dynamic LoadSys()
|
|
{
|
|
using var gil = Py.GIL();
|
|
var sys = Py.Import("sys");
|
|
|
|
return sys;
|
|
}
|
|
|
|
// ReSharper disable once EntityNameCapturedOnly.Local
|
|
private T ExecutePythonOperationInternal<T>(Func<T> operation, T? defaultValue, string operationName, bool throwOnErrors, bool logErrors)
|
|
{
|
|
Sys ??= LoadSys();
|
|
|
|
var result = defaultValue;
|
|
|
|
try
|
|
{
|
|
using var gil = Py.GIL();
|
|
result = operation();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (logErrors)
|
|
{
|
|
Logger.LogError(ex, "Python Error: {Message} ({OperationName})", ex.Message, nameof(operationName));
|
|
}
|
|
|
|
if (throwOnErrors)
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
} |