2010-11-05 14 views
7

Sto caricando uno script IronPython da un database ed eseguendolo. Funziona bene per gli script semplici, ma le importazioni sono un problema. Come posso intercettare queste chiamate di importazione e quindi caricare gli script appropriati dal database?Risoluzione di importazione IronPython personalizzata

MODIFICA: la mia applicazione principale è scritta in C# e vorrei intercettare le chiamate sul lato C# senza modificare gli script Python.

EDIT: Dalla ricerca che ho fatto, sembra che creare il proprio PlatformAdaptationLayer è il modo in cui si è supposto per implementare questo, ma non funziona in questo caso. Ho creato il mio PAL e nei miei test, il mio metodo FileExsists viene chiamato per ogni importazione nello script. Ma per qualche motivo non chiama mai un sovraccarico del metodo OpenInputFileStream. Scavando nell'origine IronPython, una volta che FileExists restituisce true, prova a individuare il file stesso sul percorso. Quindi questo sembra un vicolo cieco.

risposta

11

Dopo una lunga serie di tentativi ed errori, sono arrivato a una soluzione. Non sono mai riuscito a far funzionare correttamente l'approccio PlatformAdaptationLayer. Non ha mai richiamato il PAL durante il tentativo di caricare i moduli.

Quindi quello che ho deciso di fare era sostituire la funzione di importazione built-in utilizzando il metodo SetVariable come illustrato di seguito (motore e ambito di applicazione sono protetti membri espongono il ScriptEngine e ScriptScope per lo script genitore):

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

Spero che questo aiuti qualcuno!

+0

Grazie! Ho esattamente lo stesso caso d'uso come il tuo –

0

È necessario implementare i ganci di importazione. Ecco una domanda SO con i puntatori: PEP 302 Example: New Import Hooks

+0

Non sono sicuro se fossi abbastanza chiaro nel mio fraseggio originale della domanda. L'applicazione che chiama lo script Python è scritta in C# e vorrei trattare il più possibile lo script Python come una scatola nera, quindi mi piacerebbe essere in grado di intercettare le importazioni sul lato C# se possibile. Ho modificato la domanda originale per riflettere queste informazioni aggiuntive. – Dan

+0

Vedi sopra. Sembra che dovrebbe funzionare, ma non è così. Forse l'implementazione dell'Importazione IronPython rompe l'approccio standard? – Dan

1

È possibile reindirizzare tutti gli I/O al database utilizzando PlatformAdaptationLayer. Per fare questo è necessario implementare uno ScriptHost che fornisce il PAL. Quindi, quando si crea ScriptRuntime, si imposta HostType sul proprio tipo di host e verrà utilizzato per il runtime. Sul PAL, quindi, si ignora OpenInputFileStream e si restituisce un oggetto stream che ha il contenuto dal database (si potrebbe semplicemente utilizzare un MemoryStream qui dopo aver letto dal DB).

Se si desidera fornire ancora l'accesso all'I/O di file, è sempre possibile tornare a FileStream per "file" che non si riescono a trovare.

+0

Sto lavorando a una soluzione che utilizza questo come punto di partenza (ho trovato alcune cose utili sparse su (http://efreedom.com/Question/1-3264029/Can-Set-Dynamic-Imports-Hosting-IronPython e http : //www.mail-archive.com/[email protected]/msg06080.html). Il problema attuale sembra essere la decifrazione del modo in cui vengono richiamati i vari metodi Platform Adaptation Layer. Non riesco a trovare alcuna documentazione – Dan

+0

L'host Silverlight potrebbe essere un esempio ragionevole di come eseguire questa operazione se si desidera vedere un'implementazione esistente. –

+0

Vedere la mia risposta per un PlatformAdaptationLayer abbastanza semplice –

9

Stavo solo cercando di fare la stessa cosa, tranne che volevo memorizzare i miei script come risorse incorporate. Sto creando una libreria che è una miscela di C# e IronPython e volevo distribuirla come una singola DLL. Ho scritto un PlatformAdaptationLayer che funziona, prima controlla le risorse per lo script che viene caricato, ma poi ricade sull'implementazione di base che appare nel filesystem. Tre parti di questo:

Parte 1, La PlatformAdaptationLayer personalizzato

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

Lei avrebbe bisogno di cambiare il ResourceScriptsPrefix costante qualunque sia il vostro spazio dei nomi di base è dove è stato memorizzato gli script Python.

parte 2, l'abitudine ScriptHost

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

Parte 3, infine, come ottenere un motore di Python utilizzando la tua roba personalizzato:

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

Sarebbe facile cambiare questo per caricare gli script da qualche altra posizione, come un database. Basta cambiare i metodi OpenResourceStream, ResourceFileExists e ResourceDirectoryExists.

Spero che questo aiuti.

+0

co dovresti aggiungere a quale versione di IronPython era questo? Penso che questo fosse per una versione precedente di IronPython 2.7? –

+0

Sì, questo era due anni fa, quindi qualsiasi versione fosse quella attuale. Probabilmente 2.6, ma non ne sono sicuro. –

+0

C'è un modo migliore in 2.7+? Quanto sopra funziona ancora btw. –

Problemi correlati