Настраиваемое разрешение импорта IronPython - PullRequest
8 голосов
/ 05 ноября 2010

Я загружаю скрипт IronPython из базы данных и выполняю его. Это хорошо работает для простых скриптов, но проблемы с импортом. Как я могу перехватить эти вызовы импорта и затем загрузить соответствующие сценарии из базы данных?

РЕДАКТИРОВАТЬ: мое основное приложение написано на C #, и я хотел бы перехватывать вызовы на стороне C # без редактирования скриптов Python.

РЕДАКТИРОВАТЬ: Исходя из проведенного мною исследования, похоже, что создание собственного PlatformAdaptationLayer - это способ, которым вы предполагали реализовать это, но в этом случае это не работает. Я создал свой собственный PAL, и в моем тестировании мой метод FileExsists вызывался для каждого импорта в сценарии. Но по какой-то причине он никогда не вызывает перегрузки метода OpenInputFileStream. Перебирая источник IronPython, когда FileExists возвращает true, он пытается найти сам файл по пути. Так что это похоже на тупик.

Ответы [ 4 ]

11 голосов
/ 08 ноября 2010

После долгих проб и ошибок я пришел к решению. Мне никогда не удавалось заставить подход 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);
     }
}

Надеюсь, это кому-нибудь поможет!

9 голосов
/ 10 января 2011

Я просто пытался сделать то же самое, за исключением того, что я хотел сохранить свои скрипты как встроенные ресурсы. Я создаю библиотеку, которая представляет собой смесь 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.

Надеюсь, это поможет.

1 голос
/ 06 ноября 2010

Вы можете перенаправить все операции ввода-вывода в базу данных, используя PlatformAdaptationLayer. Для этого вам нужно реализовать ScriptHost, который предоставляет PAL. Затем, когда вы создаете ScriptRuntime, вы устанавливаете HostType в тип вашего хоста, и он будет использоваться для среды выполнения. В PAL вы затем переопределяете OpenInputFileStream и возвращаете объект потока, который имеет содержимое из базы данных (вы можете просто использовать MemoryStream здесь после чтения из БД).

Если вы все еще хотите предоставить доступ к файловому вводу / выводу, вы всегда можете обратиться к FileStream за «файлами», которые вы не можете найти.

0 голосов
/ 05 ноября 2010

Вам необходимо реализовать импортные хуки. Вот SO вопрос с указателями: PEP 302 Пример: новые хуки импорта

...