自定义IronPython导入解析

时间:2010-11-05 12:25:24

标签: import ironpython

我正在从数据库加载IronPython脚本并执行它。这适用于简单的脚本,但导入是一个问题。如何拦截这些导入调用,然后从数据库加载适当的脚本?

编辑:我的主应用程序是用C#编写的,我想在没有编辑Python脚本的情况下拦截C#端的调用。

编辑:从我所做的研究来看,创建自己的PlatformAdaptationLayer就像你所希望的那样来实现它,但在这种情况下它不起作用。我已经创建了自己的PAL,在我的测试中,我的FileExsists方法会在脚本中的每次导入时被调用。但由于某种原因,它永远不会调用OpenInputFileStream方法的任何重载。通过IronPython源,一旦FileExists返回true,它就会尝试在路径上找到文件本身。所以这看起来像死路一条。

4 个答案:

答案 0 :(得分:11)

经过大量的反复试验后,我找到了解决方案。我从未设法让PlatformAdaptationLayer方法正常工作。尝试加载模块时,它从未回调过PAL。

所以我决定做的是使用SetVariable方法替换内置的import函数,如下所示(Engine和Scope是受保护的成员,为父脚本公开ScriptEngineScriptScope

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);
     }
}

希望这有助于某人!

答案 1 :(得分:9)

我只是想做同样的事情,除了我想将我的脚本存储为嵌入式资源。我正在创建一个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方法。

希望这有帮助。

答案 2 :(得分:1)

您可以使用PlatformAdaptationLayer将所有I / O重定向到数据库。为此,您需要实现提供PAL的ScriptHost。然后,当您创建ScriptRuntime时,将HostType设置为主机类型,它将用于运行时。然后在PAL上覆盖OpenInputFileStream并返回一个流对象,该对象具有数据库中的内容(您可以在从DB读取后在此处使用MemoryStream)。

如果您仍想提供对文件I / O的访问权限,您可以随时回到FileStream中找不到的“文件”。

答案 3 :(得分:0)

您需要实现导入挂钩。这是一个带有指针的SO问题:PEP 302 Example: New Import Hooks