Настраиваемое разрешение импорта IronPython
Я загружаю скрипт IronPython из базы данных и выполняю его. Это хорошо работает для простых сценариев, но импорт является проблемой. Как я могу перехватить эти вызовы импорта и затем загрузить соответствующие сценарии из базы данных?
РЕДАКТИРОВАТЬ: мое основное приложение написано на C#, и я хотел бы перехватывать вызовы на стороне C# без редактирования скриптов Python.
РЕДАКТИРОВАТЬ: Из исследования, которое я сделал, похоже, что создание собственного PlatformAdaptationLayer - это способ, которым вы должны реализовать это, но в этом случае это не работает. Я создал свой собственный PAL и в моем тестировании мой FileExsists
метод вызывается для каждого импорта в сценарии. Но по какой-то причине это никогда не вызывает перегрузки OpenInputFileStream
метод. Перебирая источник IronPython, когда FileExists возвращает true, он пытается найти сам файл по пути. Так что это похоже на тупик.
4 ответа
После долгих проб и ошибок я пришел к решению. Мне так и не удалось получить PlatformAdaptationLayer
подход к работе правильно. Он никогда не перезванивал PAL при попытке загрузить модули.
Поэтому я решил заменить встроенную функцию импорта с помощью метода SetVariable, как показано ниже (Engine и Scope - это защищенные элементы, раскрывающие ScriptEngine
а также ScriptScope
для родительского скрипта):
delegate object ImportDelegate(CodeContext context, string moduleName, PythonDictionary globals, PythonDictionary locals, PythonTuple tuple);
protected void OverrideImport()
{
ScriptScope scope = IronPython.Hosting.Python.GetBuiltinModule(Engine);
scope.SetVariable("__import__", new ImportDelegate(DoDatabaseImport));
}
protected object DoDatabaseImport(CodeContext context, string moduleName, PythonDictionary globals, PythonDictionary locals, PythonTuple tuple)
{
if (ScriptExistsInDb(moduleName))
{
string rawScript = GetScriptFromDb(moduleName);
ScriptSource source = Engine.CreateScriptSourceFromString(rawScript);
ScriptScope scope = Engine.CreateScope();
Engine.Execute(rawScript, scope);
Microsoft.Scripting.Runtime.Scope ret = Microsoft.Scripting.Hosting.Providers.HostingHelpers.GetScope(scope);
Scope.SetVariable(moduleName, ret);
return ret;
}
else
{ // fall back on the built-in method
return IronPython.Modules.Builtin.__import__(context, moduleName);
}
}
Надеюсь, это поможет кому-то!
Я просто пытался сделать то же самое, за исключением того, что я хотел сохранить свои скрипты как встроенные ресурсы. Я создаю библиотеку, которая представляет собой смесь C# и IronPython и хотела распространять ее как одну DLL. Я написал PlatformAdaptationLayer, который работает, сначала он ищет ресурсы для загружаемого скрипта, но затем возвращается к базовой реализации, которая просматривает файловую систему. Три части к этому:
Часть 1, пользовательский PlatformAdaptationLayer
namespace ZenCoding.Hosting
{
internal class ResourceAwarePlatformAdaptationLayer : PlatformAdaptationLayer
{
private readonly Dictionary<string, string> _resourceFiles = new Dictionary<string, string>();
private static readonly char Seperator = Path.DirectorySeparatorChar;
private const string ResourceScriptsPrefix = "ZenCoding.python.";
public ResourceAwarePlatformAdaptationLayer()
{
CreateResourceFileSystemEntries();
}
#region Private methods
private void CreateResourceFileSystemEntries()
{
foreach (string name in Assembly.GetExecutingAssembly().GetManifestResourceNames())
{
if (!name.EndsWith(".py"))
{
continue;
}
string filename = name.Substring(ResourceScriptsPrefix.Length);
filename = filename.Substring(0, filename.Length - 3); //Remove .py
filename = filename.Replace('.', Seperator);
_resourceFiles.Add(filename + ".py", name);
}
}
private Stream OpenResourceInputStream(string path)
{
string resourceName;
if (_resourceFiles.TryGetValue(RemoveCurrentDir(path), out resourceName))
{
return Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName);
}
return null;
}
private bool ResourceDirectoryExists(string path)
{
return _resourceFiles.Keys.Any(f => f.StartsWith(RemoveCurrentDir(path) + Seperator));
}
private bool ResourceFileExists(string path)
{
return _resourceFiles.ContainsKey(RemoveCurrentDir(path));
}
private static string RemoveCurrentDir(string path)
{
return path.Replace(Directory.GetCurrentDirectory() + Seperator, "").Replace("." + Seperator, "");
}
#endregion
#region Overrides from PlatformAdaptationLayer
public override bool FileExists(string path)
{
return ResourceFileExists(path) || base.FileExists(path);
}
public override string[] GetFileSystemEntries(string path, string searchPattern, bool includeFiles, bool includeDirectories)
{
string fullPath = Path.Combine(path, searchPattern);
if (ResourceFileExists(fullPath) || ResourceDirectoryExists(fullPath))
{
return new[] { fullPath };
}
if (!ResourceDirectoryExists(path))
{
return base.GetFileSystemEntries(path, searchPattern, includeFiles, includeDirectories);
}
return new string[0];
}
public override bool DirectoryExists(string path)
{
return ResourceDirectoryExists(path) || base.DirectoryExists(path);
}
public override Stream OpenInputFileStream(string path)
{
return OpenResourceInputStream(path) ?? base.OpenInputFileStream(path);
}
public override Stream OpenInputFileStream(string path, FileMode mode, FileAccess access, FileShare share)
{
return OpenResourceInputStream(path) ?? base.OpenInputFileStream(path, mode, access, share);
}
public override Stream OpenInputFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize)
{
return OpenResourceInputStream(path) ?? base.OpenInputFileStream(path, mode, access, share, bufferSize);
}
#endregion
}
}
Вам нужно изменить константу ResourceScriptsPrefix так, чтобы ваше базовое пространство имен находилось там, где вы хранили скрипты Python.
Часть 2, пользовательский ScriptHost
namespace ZenCoding.Hosting
{
internal class ResourceAwareScriptHost : ScriptHost
{
private readonly PlatformAdaptationLayer _layer = new ResourceAwarePlatformAdaptationLayer();
public override PlatformAdaptationLayer PlatformAdaptationLayer
{
get { return _layer; }
}
}
}
Часть 3, наконец, как получить движок Python, используя ваши собственные вещи:
namespace ZenCoding.Hosting
{
internal static class ResourceAwareScriptEngineSetup
{
public static ScriptEngine CreateResourceAwareEngine()
{
var setup = Python.CreateRuntimeSetup(null);
setup.HostType = typeof(ResourceAwareScriptHost);
var runtime = new ScriptRuntime(setup);
return runtime.GetEngineByTypeName(typeof(PythonContext).AssemblyQualifiedName);
}
}
}
Было бы легко изменить это, чтобы загружать скрипты из какого-то другого места, например из базы данных. Просто измените методы OpenResourceStream, ResourceFileExists и ResourceDirectoryExists.
Надеюсь это поможет.
Вы можете перенаправить весь ввод-вывод в базу данных, используя PlatformAdaptationLayer. Для этого вам нужно реализовать ScriptHost, который предоставляет PAL. Затем, когда вы создаете ScriptRuntime, вы устанавливаете HostType в тип вашего хоста, и он будет использоваться для среды выполнения. На PAL вы затем переопределяете OpenInputFileStream и возвращаете объект потока, который имеет содержимое из базы данных (вы можете просто использовать MemoryStream здесь после чтения из БД).
Если вы все еще хотите предоставить доступ к файловому вводу / выводу, вы всегда можете обратиться к FileStream за "файлами", которые вы не можете найти.
Вам нужно реализовать хуки импорта. Вот SO вопрос с указателями: Пример PEP 302: новые импортные хуки