2010-11-05 7 views
7

Estoy cargando un script de IronPython desde una base de datos y ejecutándolo. Esto funciona bien para scripts simples, pero las importaciones son un problema. ¿Cómo puedo interceptar estas llamadas de importación y luego cargar las secuencias de comandos apropiadas desde la base de datos?Resolución personalizada de importación de IronPython

EDITAR: Mi aplicación principal está escrita en C# y me gustaría interceptar las llamadas en el lado C# sin editar las secuencias de comandos de Python.

EDITAR: A partir de la investigación que he hecho, parece que la creación de su propia PlatformAdaptationLayer es la forma en que está supuesto para implementar esto, pero no funciona en este caso. Creé mi propia PAL y en mis pruebas, se llama a mi método FileExsists para cada importación en la secuencia de comandos. Pero por alguna razón nunca llama a ninguna sobrecarga del método OpenInputFileStream. Excavando a través del origen de IronPython, una vez FileExists devuelve verdadero, intenta ubicar el archivo en la ruta. Entonces esto parece un callejón sin salida.

Respuesta

11

Después de una gran cantidad de prueba y error, llegué a una solución. Nunca logré que el enfoque PlatformAdaptationLayer funcione correctamente. Nunca volvió a llamar al PAL cuando intentaba cargar los módulos.

Así que lo que decidí hacer fue reemplazar la función de importación incorporada utilizando el método SetVariable como se muestra a continuación (motor y alcance son miembros exponiendo la ScriptEngine y ScriptScope para el script padre protegidos):

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

Espero que esto ayude a alguien!

+0

Gracias! Tengo exactamente el mismo caso de uso que el suyo. –

0

Debe implementar ganchos de importación. Aquí hay una pregunta ASÍ CON punteros: PEP 302 Example: New Import Hooks

+0

No estoy seguro si fui lo suficientemente claro en mi redacción original de la pregunta. La aplicación que llama al script de Python está escrita en C# y me gustaría tratar el script de Python como un cuadro negro tanto como sea posible, así que me gustaría poder interceptar las importaciones en el lado C# si es posible. He editado la pregunta original para reflejar esta información adicional. – Dan

+0

Ver arriba. Parece que debería funcionar, pero no es así. ¿Tal vez la implementación de Importar IronPython rompe el enfoque estándar? – Dan

1

Puede redirigir todas las E/S a la base de datos utilizando PlatformAdaptationLayer. Para hacer esto, necesitará implementar un ScriptHost que proporcione el PAL. Luego, cuando crea ScriptRuntime, configura el HostType a su tipo de host y se usará para el tiempo de ejecución. En el PAL, usted anula OpenInputFileStream y devuelve un objeto de transmisión que tiene el contenido de la base de datos (puede usar un MemoryStream aquí después de leer desde el DB).

Si aún desea proporcionar acceso a E/S de archivos, siempre puede recurrir a FileStream para obtener "archivos" que no puede encontrar.

+0

Estoy trabajando en una solución que usa esto como punto de partida (encontré algunas cosas útiles diseminadas sobre (http://efreedom.com/Question/1-3264029/Can-Set-Dynamic-Imports-Hosting-IronPython y http : //www.mail-archive.com/[email protected]/msg06080.html). El problema actual parece ser descifrar cómo se llaman los diversos métodos de Capa de Adaptación de Plataforma. Parece que no puedo encontrar ninguna documentación – Dan

+0

El host de Silverlight puede ser un ejemplo razonable de cómo hacer esto si desea ver una implementación existente. –

+0

Consulte mi respuesta para una plataforma PlatformAdaptationLayer bastante simple –

9

Solo intentaba hacer lo mismo, excepto que quería almacenar mis scripts como recursos incrustados. Estoy creando una biblioteca que es una mezcla de C# e IronPython y quería distribuirlo como un dll único. Escribí PlatformAdaptationLayer que funciona, primero busca en los recursos para el script que se está cargando, pero luego vuelve a la implementación base que se ve en el sistema de archivos. Tres partes en este:

Parte 1, La PlatformAdaptationLayer encargo

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

Usted tendría que cambiar el ResourceScriptsPrefix constante a lo que su espacio de nombres es la base en la que almacenó los scripts de Python.

Parte 2, la aduana ScriptHost

namespace ZenCoding.Hosting 
{ 
    internal class ResourceAwareScriptHost : ScriptHost 
    { 
     private readonly PlatformAdaptationLayer _layer = new ResourceAwarePlatformAdaptationLayer(); 
     public override PlatformAdaptationLayer PlatformAdaptationLayer 
     { 
      get { return _layer; } 
     } 
    } 
} 

parte 3, por último, cómo conseguir un motor de Python usando su material personalizado:

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

Sería fácil de cambiar esta situación para cargar secuencias de comandos desde otra ubicación, como una base de datos. Simplemente cambie los métodos OpenResourceStream, ResourceFileExists y ResourceDirectoryExists.

Espero que esto ayude.

+0

co ¿Cómo agregas para qué versión de IronPython fue esto? Creo que esto fue para una versión anterior a IronPython 2.7. –

+0

Sí, esto fue hace dos años, por lo que cualquiera versión era la actual entonces. Probablemente 2.6, pero no estoy seguro. –

+0

¿Hay una manera mejor en 2.7+? Lo anterior todavía funciona por cierto. –