A couple of weeks ago I decided to start a code kata using IronPython, with the objective of learning how C# and IronPython code could be integrated and used together. I had previously used Python (my favorite choice for University assignments), but this was my first try with the DLR version.
(If you just can't wait to get to the code, just scroll to the bottom of this post).
The first step is to get IronPython. As the project is hosted at codeplex, you can simply access ironpython.codeplex.com and select the Downloads tab. I chose to download the latest stable release (v2.6.1).
I created some simple classes to wrap around Python's e-mail libraries and begun research on how to use them from C# code in a WPF application. The following were some of the most useful resources I found:
- Using Dynamic Languages to Build Scriptable Applications (Dino Viehland's talk at PDC 2009)
- Running IronPython Scripts from a C# 4.0 Program
- IronPython’s documentation from IronPython.net
After the initial spikes, I created a simple application with a single V-VM pair, which let’s you send e-mails using either a Gmail or Hotmail account (disregard the poor-man’s UI).
Let's go over the pythonic parts of the ViewModel.
First I defined the following four attributes:
private ScriptEngine engine; private ScriptRuntime python; private dynamic emailWrapper; private dynamic emailDispatcher;
The ScriptEngine is used to customize the paths to search for Python modules. Unless the paths are configured correctly, you will get errors when importing modules that are part of Python’s standard library. As the following code shows, the path for the Python standard library and the relative path for my Python modules are being added to the list of paths:
private void SetPythonLibraryPath() { ICollection<string> paths = engine.GetSearchPaths(); paths.Add(@"C:\Program Files (x86)\IronPython 2.6 for .NET 4.0\Lib"); paths.Add(Path.Combine(Directory.GetCurrentDirectory(), "Python Code")); engine.SetSearchPaths(paths); }
Then, I get the ScriptRuntime from the ScriptEngine using its Runtime property. The ScriptRuntime is used to load the python modules into the dynamic fields mentioned above. The emailWrapper and emailDispatcher fields will store the modules with the same name.
private void LoadPythonModules() { string wrapperPath = Path.Combine("Python Code", "emailWrapper.py"); string dispatcherPath = Path.Combine("Python Code", "emailDispatcher.py"); emailDispatcher = python.UseFile(dispatcherPath); emailWrapper = python.UseFile(wrapperPath); }
Now that I have access to the modules, I can simply instance classes or call functions from them. This is because the dynamic type resolves the operations at runtime.
private void SendEmail() { ... string providerAccount = String.Format("{0}@{1}.com", this.UserAccount, this.Provider); dynamic email = emailWrapper.Email(this.Subject, this.ToAddress, providerAccount); email.appendContent(this.Body); dynamic dispatcher = emailDispatcher.EmailDispatcher(providerAccount, this.Password, this.Provider); dispatcher.send(email); ... }
private void PopulateProviders() { this.Providers = new ObservableCollection<string>(); foreach (string provider in emailDispatcher.getProviders()) { this.Providers.Add(provider); } }
Note: A way to use python objects while maintaining compile time checking is having Python objects implement CLR interfaces. There might be scenarios in which type safety brings more benefits, but in this case I thought interface implementation was un-pythonic. |
That's pretty much it. I'll probably continue working on this small application, to show a couple more things that can be achieved with this kind of integration. Things that come to mind are:
- Sharing variables between Python and C# scopes
- Changing the application's behavior by updating Python modules without the need to rebuild the application.
Downloading the sample
You can download the sample code from here. The code is provided "AS IS" with no warranties and confers no rights.
I hope this small introductions is useful for you and I'd love to get your thoughts on this approach.
Acknowledgements
Thanks to Martin Salias, Iron Python expert (among other technologies), for reviewing the code and contributing with ideas, and to Fer, Mati and Diego who witnessed the first demo of the application and also provided useful ideas.