IronPython is your friend - Part 1 - IronPython and delegates….

I felt like posting some code snippets... so I was looking around
and thought perhaps some IronPython might be interesting.... Some
of you may find this useful, possibly not.  I tend to
explore functionality using unit tests, so all my code will be
test cases - I might make this part of a small series on looking
at python code with a view to integrating it into an existing
system as a scripting engine.  Lets call the series
"IronPython is your friend"... because it really is.



Part 1 - IronPython and delegates....

So you have downloaded the IronPython runtime dll's, and you
would like to start integrating it into some project your working
on... first off lets create a python engine, and make sure it
goes....


[Test]
public void GetGoing()
{
PythonEngine engine = new PythonEngine();
engine.Execute("myString = 'hello'");
Assert.AreEqual("hello reader", engine.Evaluate("myString + ' reader'"));
}


That was pretty easy; we executed some python code to assign the
value hello to myString, and then evaluated the expression - sweet
you might say - no effort required.



Now, you could write your scripting engine so that it passed the
strings to the engine for evaluation every time you wanted to
evaluate them - but this strings malarkey isn't very pleasant...
what we really want is a decent god fearing .Net delegate, so we
can call this without having to think about the python engine....
Lets check out our first destination, method delegates.


Method Delegates

Given a converter delegate like this:



   
Converter
int,
string>



(Which by the way, if you haven't stumbled across the generic
ConverterTInput,TOutput> delegate yet,
effectively you end up with a delegate method of the
definition"TOutput Converter(TInput)" - pretty
handy actually)

 

We could do this:


[Test]
public void StatementsToStronglyTypedDelegate()
{
PythonEngine engine = new PythonEngine();
List parameters = new List(new string[] {"age"});
Converter converter =
engine.CreateMethod<>>("return str(age+1)", parameters);
Assert.AreEqual("11", converter(10));
}


Using a strongly typed overload for the engine's
CreateMethod method, we're able to evaluate statements using
our new delegate, which in turn uses our IronPython method we
created earlier... looks promising!


Events

Now delegates are cool,  but it's only half the story for
scripting in an application - depending on your model, you may be
aiming to produce results similar to a microsoft's VBA in
applications like Excel or Word - and for this we need to be able
to assign python code to events on our own classes... we can do
this using delegates of course, or we could let the python (user
code) do this itself, and save some time.... So lets have a look at
the latter:


[Test]
public void HookingToEvents()
{
PythonEngine engine = new PythonEngine();

ManualResetEvent waitHandle = new ManualResetEvent(false);
WebClient client = new WebClient();

List results = new List();

engine.Globals["client"] = client;
engine.Globals["results"] = results;

engine.Execute(
@"def storeResult(sender, e):
results.Add(e.Result)
e.UserState.Set()

# assign out storeResult function as an event handler for the client class
client.DownloadStringCompleted += storeResult
");

client.DownloadStringAsync(
new Uri(
"http://api.feedburner.com/awareness/1.0/GetFeedData?uri=http://feeds.feedburner.com/BitterCoder"),
waitHandle);

Assert.IsTrue(waitHandle.WaitOne(10000, false), "timeout occured after 10 seconds");

Assert.AreEqual(1, results.Count);

Assert.IsTrue(results[0].StartsWith("<>
}

     

This test is a bit clunky, but you get the idea... we are:
  • Creating a web client, for downloading some xml from a web
    site.
  • Set the client as a global variable for the default module in
    the python engine.
  • Execute some python, which creates a function, and assigns
    that function as a handle for the web client's
    DownloadStringCompleted event (take note at how concise the code
    is, this would be a lot uglier in C#)

  • start the async download
  • Make sure we got a response, and that the python code did
    what it should.

All in all, with a little finesse you can recreate a model
similar to VBA, driving your extension points from events - and
this certainly has some merit - obviously you want to create the
code in their own modules, as opposed to using the default module
for the python engine as we are.



We haven't quite finished with delegates yet though, I'll leave
that for part 2 - which I'll post shortly.


Written on September 30, 2006