Iron Python

About Iron Python

Iron Python is a dynamic language runtime (DLR) based implementation of Python. Spark scripting is based on assemblies in the 2.0 Release Candidate 1 binary zip. Other good resources are the Python documentation and the Python site itself.

The support for the DLR is very Asp.Net MVC centric. If there's an interest in expanded Castle MonoRail support, please speak up in the discussion group.

Creating a new project

A standard Asp.Net MVC web application is always a great starting place. First, add references to the following assemblies:

  • Spark.dll
  • Spark.Python.dll
  • Spark.Web.Mvc.dll
  • Spark.Web.Mvc.Python.dll

And the following from the IronPython release which are available in the Spark bin\dependencies folder. If you have problems with the these files from Codeplex try the ones in from the Spark distribution. You have the "works on my machine" guarantee.

  • Microsoft.Scripting.dll
  • Microsoft.Scripting.Core.dll
  • Microsoft.Scripting.ExtensionAttribute.dll
  • IronPython.dll
  • IronPython.Modules.dll

Yeah, I know. It's a lot of assemblies. You're going to love the next part though, adding the Spark view engine to an Asp.Net MVC web application has been simplified.

Add the following to your Global Application_Start method:

using Spark.Web.Mvc.Scripting;
...
protected void Application_Start(object sender, EventArgs e)
{
    SparkPythonEngineStarter.RegisterViewEngine();
}

This will create a view engine and add it to the engines collection.

The Spark engine starter also has several utility methods for more advanced initialization. You can, for example, provide a settings object from code. You can also use the ISparkServicesContainer to provide specific implementations of different services used by the Spark engine. For example:

// this example avoids adding <spark> to web.config
var settings = new SparkSettings();
 
// change settings, like
settings
    .SetDebug(true)
    .SetPageBaseType(typeof(MyBasePage));
 
var container = SparkPythonEngineStarter.CreateContainer(settings);
 
// change services, like
container.SetServiceBuilder<IViewActivatorFactory>(
    c => new MyViewActivatorFactory());
 
SparkPythonEngineStarter.RegisterViewEngine(
    ViewEngines.Engines, container);

Adding views

Spark views for IronPython look and act much like they would for csharp. They have a .spark extension and exist in the normal Views\<controllername>\<viewname>.spark path.

One of the most obvious differences would be the lack of a need to declare <viewdata/>. Other than that it's pretty darn straightforward. You can execute code inline with #statement and output information with ${expression} and all of the <content>, <macro>, support for underscored partials, imports, etc. is unchanged.

#import clr
<p>Methods available from the clr module</p>
<ul>
  <li each="x in dir(clr)">${x}</li>
</ul>
<p>Methods (and extension methods) available to the normal helpers</p>
<ul>
  <var underscores="'__'"/>
  <li each="x in dir(Html)" if="not x.startswith(underscores)">Html.${x}</li>
  <li each="x in dir(Ajax)" if="not x.startswith(underscores)">Ajax.${x}</li>
  <li each="x in dir(Url)" if="not x.startswith(underscores)">Url.${x}</li>
</ul>

So once you get past the subtle expression differences it's pretty straightforward. I relied very heavily on the Python language documentation figuring out the syntax to use. If you're comfortable with Python, or simply hate declaring your view data and mucking around with strong types and assembly references, using the DLR might be an attractive option.

<html>
  <head>
    <global Title="'Python View Language Sample'"/>
    <title>${Title}</title>
  </head>
  <body>
    <use:view/>
  </body>
</html>

#Title = "Products - " + Title
<p>There are ${len(products)} products available</p>
<ul>
  <li each="p in products">${Html.ActionLink(p.Name, "Show")}</li>
</ul>

Variable

Variable scope in Python is very different than in csharp because of the nature of the language itself. The entire view, and the entire master, are both single functions. All of the variables in those two functions are available to all of the views and partial files.

To share variables between view and master, use the global element, like <global title="'Default Title'"/>. Of course <content name="foo">...</content> and <use content="foo"/> can also be used to produce and consume material.

Other forms of variable declaration all end up meaning the same thing. <var x="5" hello="'world'"/>, <def x="5" hello="'world'"/>, and <set x="5" hello="'world'"/> are virtually interchangable - just remember to assign a variable before you use it.

Providing a fall-back value with <default x="1" hello="'(your name here)'"/> will work and is very convenient for assigning default values for optional arguments in partials.

ViewData

Viewdata does need to be declared because the contents of the dictionary will appear to be global symbols. Because they have a lower precedence than view class members you may need to use ViewData["name"] syntax to access information that collides with existing members.

public ActionResult Index()
{
  ViewData["foo"] = "bar";
  return View();
}

## no declaration needed
<p>Foo is ${foo}</p>

Looping and conditional

Iteration in Python doesn't require a type declaration on the looping variable. Otherwise it's very similar to Spark with csharp.

<for each="c in categories">
  <h3>${H(c.Name)}</h3>
  <ul>
    <li each="p in c.Products" class="alt?{pIndex%2}">${H(p.Name)}</li>
  </ul>
</for>

The conditional elements and attributes (test, if, elseif, else) in all their various forms work fine structurally once you take into account language differences. Python will use not, or, and and boolean keywords rather than punctuation operators. It will also infer the value of True for non-default values. So 0, False, and None (or null) all act as false. Which is why the condition ?{pIndex%2} above will give you even-odd striping by removing the alt css class as the result of 0 and 1 varies.

Inline code

The #statement<newline> format works especially well for adding Python code inline, but bear in mind how significant whitespace will be. The statement following the # character will be written at the correct indentation for the place it appears in scope, but there should only be whitespace between the # and the code if you're intentionally creating something scoped to a previous statement.

One use for that would be declaring a function.

<h3>Declaring a function</h3>
<ul>
  #import math
  ## Calculate the n-dimensional hypotenuse
  #def vectorlength(vector):
  #  dt = 0
  #  for d in vector:
  #    dt = dt + d * d
  #  return math.sqrt(dt)
  <li>3,4 : ${vectorlength([3,4])}</li>
  <li>12,13 : ${vectorlength([12,13])}</li>
  <li>0,5,3,2,0,6 : ${vectorlength([0,5,3,2,0,6])}</li>
</ul>

Note, since # is a comment in python ## can be used in Spark as a code-comment which won't appear in html output.

Invoking methods and helpers

All methods and properties of the base view class are available . If you're writing output explicitly there is a subtle problem with overloaded methods, like Output.Write(...), when the DLR determines which method to invoke. Writing a number for example will throw an exception because the several flavors of integer and floating point are ambiguous from the scripting point of view. Another method OutputWriteAdapter(object value) has been added to the scripting-specific spark view class, and that is the method ${expression} will invoke.

Some helper methods can be particularly tricky. It appears that native IronPython property-bearing reflect with put properties Keys and Values, so they can't be used for helper methods which take an anonymous type as a dictionary. The good news is you can typically call a different overload of the helper method by building the CLR dictionary classes.

<div>
  #import clr
  #clr.AddReference("System.Web.Routing")
  #from System.Web.Routing import RouteValueDictionary
  #routeData = RouteValueDictionary()
 
  #routeData["id"] = page.CurrentPage - 1
  <a href="${Url.Action(action, controller, routeData)}">&laquo; Previous</a>
 
  <for each="pageIndex in range(1, page.PageCount + 1)">
    #routeData["id"] = pageIndex
    <a href="${Url.Action(action, controller, routeData)}">${pageIndex}</a>
  </for>
 
  #routeData["id"] = page.CurrentPage + 1
  <a href="${Url.Action(action, controller, routeData)}">Next &raquo;</a>
</div>