I have a need to create an instance of a python class and store it in a C# variable. The input data for the process is a full module name and a class name. I am trying to use the following approach:
// Here's a sample input:
var moduleName = "sp.content.abilities.torpedo"
var className = "AbilityTorpedo"
// Here's my module loading and class instaitiation
var moduleScope = ScriptEngine.ImportModule(moduleName);
var scriptClass = moduleScope.GetVariable(className);
return ScriptEngine.Operations.CreateInstance(scriptClass);
My folder structure looks like this and the "Scripts" directory is added as one of the IronPython's search paths (the torpedo.py
does indeed contain the AbilityTorpedo
class):
Scripts
│ .editorconfig
│ __main__.py
│
└───sp
│ __init__.py
│
└───content
│ __init__.py
│
└───abilities
torpedo.py
__init__.py
When running the listed instantiation method, the moduleScope.GetVariable(className);
line fails with the following exception:
'ObjectDictionaryExpando' object has no attribute 'AbilityTorpedo'
When debugging, I've noticed that the ImportModule operation returned the module loaded from the first part of the name, while also loading the entirety of the module hierarchy (for example, the content
module marked in red)
Is there a proper way for me to load the module by it's full name instead of traversing down the scope to find the class I need?
I've managed to write an extension class which would provide a similar functionality to a regular python's "from X import Y". In the end I had to use the default ImportModule to import the very root of the module hierarchy and traverse down the structure to find the necessary module:
private static PythonModule TraverseDown(PythonModule module, string moduleName)
{
return (PythonModule) module.Get__dict__()[moduleName];
}
private static PythonModule TraverseToTarget(ScriptScope scope, string moduleName)
{
// The root of the module was already imported by the engine's import module call.
var moduleNameParts = moduleName.Split('.').Skip(count: 1).ToList();
var rootModule = scope.GetVariable<PythonModule>(moduleNameParts.First());
return moduleNameParts.Skip(count: 1).Aggregate(rootModule, TraverseDown);
}
public static dynamic ImportFromModule(this ScriptEngine engine, string moduleName, string targetImport)
{
var rootModuleScope = engine.ImportModule(moduleName);
var targetModule = TraverseToTarget(rootModuleScope, moduleName);
return targetModule.Get__dict__()[targetImport];
}
Here's how one is supposed to use this extension:
// Here's a sample input:
var moduleName = "sp.content.abilities.torpedo"
var className = "AbilityTorpedo"
// Here's my module loading and class instaitiation
var moduleScope = ScriptEngine.ImportFromModule(moduleName, className);
return ScriptEngine.Operations.CreateInstance(scriptClass);