Multi-Platform Package Manager for Stable Diffusion
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

128 lines
4.6 KiB

using NLog;
using Python.Runtime;
using StabilityMatrix.Core.Models;
namespace StabilityMatrix.Core.Python;
/// <summary>
/// Extracts command arguments from Python source file.
/// </summary>
public class ArgParser
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly IPyRunner pyRunner;
private string rootPath;
private string moduleName;
public ArgParser(IPyRunner pyRunner, string rootPath, string moduleName)
{
this.pyRunner = pyRunner;
this.rootPath = rootPath;
this.moduleName = moduleName;
}
/// <summary>
/// Convert a PyObject to a value that can be used as a LaunchOption value.
/// </summary>
public static object PyObjectToOptionValue(PyObject obj)
{
var type = obj.GetPythonType().Name;
return type switch
{
"bool" => obj.As<bool>(),
"int" => obj.As<int>(),
"str" => obj.As<string>(),
_ => throw new ArgumentException($"Unknown option type {type}")
};
}
/// <summary>
/// Convert a PyObject to a LaunchOptionType enum.
/// </summary>
public static LaunchOptionType? PyObjectToOptionType(PyObject typeObj)
{
var typeName = typeObj.GetAttr("__name__").As<string>();
return typeName switch
{
"bool" => LaunchOptionType.Bool,
"int" => LaunchOptionType.Int,
"str" => LaunchOptionType.String,
_ => null
};
}
public async Task<List<LaunchOptionDefinition>> GetArgsAsync()
{
await pyRunner.Initialize();
return await pyRunner.RunInThreadWithLock(() =>
{
using var scope = Py.CreateScope();
dynamic sys = scope.Import("sys");
dynamic argparse = scope.Import("argparse");
// Add root path to sys.path
sys.path.insert(0, rootPath);
// Import module
var argsModule = scope.Import(moduleName);
var argsDict = argsModule.GetAttr("__dict__").As<PyDict>();
// Find ArgumentParser object in module
dynamic? argParser = null;
var argParserType = argparse.ArgumentParser;
foreach (var obj in argsDict.Values())
{
if (obj.IsInstance(argParserType))
{
argParser = obj;
break;
}
}
if (argParser == null)
{
throw new ArgumentException($"Could not find ArgumentParser object in module '{moduleName}'");
}
// Loop through arguments
var definitions = new List<LaunchOptionDefinition>();
foreach (var action in argParser._actions)
{
var name = (action.dest as PyObject)?.As<string>();
if (name == null)
{
throw new Exception("Argument option did not have a `dest` value");
}
var optionStrings = ((PyObject) action.option_strings).As<string[]>();
var dest = (action.dest as PyObject)?.As<string>();
// var nArgs = (action.nargs as PyObject)?.As<int>();
var isConst = (action.@const as PyObject)?.IsTrue() ?? false;
var isRequired = (action.required as PyObject)?.IsTrue() ?? false;
var type = action.type as PyObject;
// Bool types will have a type of None (null)
var optionType = type == null ? LaunchOptionType.Bool : PyObjectToOptionType(type);
if (optionType == null)
{
Logger.Warn("Skipping option {Dest} with type {Name}", dest, type);
continue;
}
// Parse default
var @default = action.@default as PyObject;
var defaultValue = @default != null ? PyObjectToOptionValue(@default) : null;
var help = (action.help as PyObject)?.As<string>();
definitions.Add(new LaunchOptionDefinition
{
Name = help ?? name,
Description = help,
Options = new List<string> { optionStrings[0] },
// ReSharper disable once ConstantNullCoalescingCondition
Type = optionType ?? LaunchOptionType.Bool,
DefaultValue = defaultValue,
MinSelectedOptions = isRequired ? 1 : 0,
});
}
return definitions;
});
}
}