Create plugins for .NET applications with IronPython via MEF, the Managed Extensibility Framework
This is a fork from a project by Bruno Lopes. Thanks, Bruno! This was just what I needed.
You want to write IronPython scripts to extend or create plugins for a .NET application. And, you want to Export types from IronPython / DLR to the CLR and Import types from the CLR. You've come to the right place. Keep reading.
IronPythonMef is the solution.
- Create a new C# console app
- You can download the code from here, or use NuGet to get the package.
- Replace the contents of
Program.cswith the code below. - Run it!
usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel.Composition;usingSystem.ComponentModel.Composition.Hosting;usingSystem.Reflection;usingIronPythonMef;namespaceIronPythonMefInAMinute{publicinterfaceIMessenger{stringGetMessage();}publicinterfaceIConfig{stringIntro{get;}}/// <summary>/// Gets exported from IronPython into the CLR Demo instance./// </summary>publicstaticclassPythonScript{publicstaticreadonlystringCode=@"@export(IMessenger)class PythonMessenger(IMessenger):def GetMessage(self): return self.config.Intro + ' from IronPython'@import_one(IConfig)def import_config(self, config): self.config = config";}/// <summary>/// Also gets exported into the Demo instance./// </summary>[Export(typeof(IMessenger))]publicclassClrMessenger:IMessenger{[Import(typeof(IConfig))]publicIConfigConfig{get;set;}publicstringGetMessage(){returnConfig.Intro+" from C#!";}}/// <summary>/// This will get imported into both the IronPython class and ClrMessenger./// </summary>[Export(typeof(IConfig))]publicclassConfig:IConfig{publicstringIntro{get{return"Hello";}}}publicclassDemo{[ImportMany(typeof(IMessenger))]publicIEnumerable<IMessenger>Messengers{get;set;}publicDemo(){// Extra types you might want to inject into the python script scopevartypesYouWantPythonToHaveAccessTo=new[]{typeof(IMessenger),typeof(IConfig)};// Create an IronPython script MEF Catalog using a default Python enginevarironpythonCatalog=newIronPythonScriptCatalog(newStringReader(PythonScript.Code),typesYouWantPythonToHaveAccessTo);// Compose with MEFvarcatalog=newAssemblyCatalog(Assembly.GetExecutingAssembly());varcontainer=newCompositionContainer(newAggregateCatalog(catalog,ironpythonCatalog));container.SatisfyImportsOnce(this);}publicstaticvoidMain(string[]args){vardemo=newDemo();foreach(varmessengerindemo.Messengers){Console.WriteLine(messenger.GetMessage());}Console.Read();}}}The output is simply:
Hello from IronPython
Hello from C#!
Code is here.
namespaceIronPythonMef.Tests.Example.Operations{publicinterfaceIOperation{objectExecute(paramsobject[]args);stringName{get;}stringUsage{get;}}}@export(IOperation)classFibonacci(IOperation):
defExecute(self, n):
n=int(n)
ifn==0:
return0elifn==1:
return1else:
returnself.Execute(n-1) +self.Execute(n-2)
@propertydefName(self):
return"fib"@propertydefUsage(self):
return"fib n -- calculates the nth Fibonacci number"@export(IOperation)classAbsolute(IOperation):
defExecute(self, n):
n=float(n)
if (n<0):
return-nreturnn@propertydefName(self):
return"abs"@propertydefUsage(self):
return"abs f -- calculates the absolute value of f"usingSystem;usingSystem.ComponentModel.Composition;namespaceIronPythonMef.Tests.Example.Operations{[Export(typeof(IOperation))]publicclassPower:IOperation{publicobjectExecute(paramsobject[]args){if(args.Length<2){thrownewArgumentException(Usage,"args");}varx=Convert.ToDouble(args[0]);vary=Convert.ToDouble(args[1]);returnMath.Pow(x,y);}publicstringName{get{return"pow";}}publicstringUsage{get{return"pow n, y -- calculates n to the y power";}}}[Export(typeof(IOperation))]publicclassFactorial:IOperation{publicobjectExecute(paramsobject[]args){if(args.Length<1){thrownewArgumentException(Usage,"args");}varn=Convert.ToInt32(args[0]);inti;longx=1;for(i=n;i>1;i--)x=x*i;returnx;}publicstringName{get{return"fac";}}publicstringUsage{get{return"fac n -- calculates n!";}}}}usingSystem.ComponentModel.Composition.Hosting;usingSystem.Linq;usingSystem.Reflection;usingIronPythonMef.Tests.Example.Operations;usingNUnit.Framework;namespaceIronPythonMef.Tests.Example{[TestFixture]publicclassMathWizardTests{[Test]publicvoidruns_script_with_operations_from_both_csharp_and_python(){varcurrentAssemblyCatalog=newAssemblyCatalog(Assembly.GetExecutingAssembly());varironPythonScriptCatalog=newIronPythonScriptCatalog(newCompositionHelper().GetResourceScript("Operations.Python.py"),typeof(IMathCheatSheet),typeof(IOperation));varmasterCatalog=newAggregateCatalog(currentAssemblyCatalog,ironPythonScriptCatalog);varcontainer=newCompositionContainer(masterCatalog);varmathWiz=container.GetExportedValue<MathWizard>();conststringmathScript=@"fib 6fac 6abs -99pow 2 4crc 3";varresults=mathWiz.ExecuteScript(mathScript).ToList();Assert.AreEqual(5,results.Count);Assert.AreEqual(8,results[0]);Assert.AreEqual(720,results[1]);Assert.AreEqual(99f,results[2]);Assert.AreEqual(16m,results[3]);Assert.AreEqual(9.4247782230377197d,results[4]);}}}How to load an IronPython script and inject .NET interfaces into it so that your .NET app can import its exports
Whoah, that sounds like crazy talk! Really? Not anymore! Here's how the unit test does it:
varironPythonScriptCatalog=newIronPythonScriptCatalog(newCompositionHelper().GetResourceScript("Operations.Python.py"),typeof(IMathCheatSheet),typeof(IOperation));We have a MEF catalog that can parse IronPython scripts (in this case it's an embedded resource called Python.Py), injecting extra items into the scope (in this case, two types that will be used for importing and exporting).
All other code is standard MEF code, now.
Note that Bruno Lopes has some more sophisticated examples in his code base, such as import-on-start, and recomposition when files change or are added. If I can find time or get the assistance to do so, I'll incorporate similar features into this.
This works too. It's not shown above, but the test cases and the MathWizard example has it.
Suppose you wanted to inject constants into IronPython, or other applications, to have a consistent approximation of Pi, or whatever.
namespaceIronPythonMef.Tests.Example.Operations{publicinterfaceIMathCheatSheet{floatPi{get;set;}}}usingSystem.ComponentModel.Composition;namespaceIronPythonMef.Tests.Example.Operations{[Export(typeof(IMathCheatSheet))]publicclassMathCheatSheet:IMathCheatSheet{publicMathCheatSheet(){Pi=3.141592653589793f;}publicfloatPi{get;set;}}}@export(IOperation)classCircumference(IOperation):
@import_one(IMathCheatSheet)defimport_cheatSheet(self, cheatSheet):
self.cheatSheet=cheatSheetdefExecute(self, d):
d=float(d)
returnself.cheatSheet.Pi*d@propertydefName(self):
return"crc"@propertydefUSage(self):
return"crc d -- calculaets the circumference of a circle with diameter d"So, this last example demonstrates that even though Circumference is itself exported from IronPython, it first gets its own import dependencies satisfied. Pretty awesome. All credit to the MEF team and Bruno on this.
- MEF home page
- Great slides and examples from Glenn Block
- IronPython home page
- Try IronPython inside your web browser, right now!