Click here to Skip to main content
12,758,323 members (34,543 online)
Click here to Skip to main content
Add your own
alternative version

Tagged as


15 bookmarked
Posted 9 Apr 2009

IronPython - A Configuration Language

, 9 Apr 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
IronPython - a configuration language

I had a requirement to create a state machine for a product at work. The product is a project management tool and therefore has the concept of a job which based on the users actions moves from one state to another. There was also the additional requirement that the state machine should be configurable by/for different customers. Essentially, the state machine is a large flow diagram but the implementation needed to allow for completely ripping up the first customers flow diagram and replacing it with a completely different one for the next customer.

My first thought was to implement this by modelling the state macine as meta data in the database. However, even just considering the first customers' state machine, it was apparent that the number of edge cases would make this difficult and probably require code changes as soon as the product was sold to different customers, based on their own edge cases. I had already coded part of the state machine in C# so inspired by a Dot Net Rocks podcast featuring Oren Eini I came up with the idea of coding the state machine in C#, storing the code in the database and then compiling it at runtime. However, I was further inspired by another Dot Net Rocks podcast featuring Michael Foord, talking about IronPython. Michael briefly mentioned embedding the IronPython interpreter in .NET code and on further investigation the use cases for doing this are pretty much my requirements of being able to write different code for different customers. Michael has also written IronPython in Action which I have bought and can recommend - it helped me get my head round IronPython language and how to go about using it with C#.

From the C# prototype I created, I had a number of helper methods for doing things to manipulate my job business entity and even create a project in project server! I didn't really want to throw this away, so I didn't - I created an abstract class and then implemented the subclass in IronPython. The abstract class is below:

namespace Sharpcoder.BusinessLogic
   public abstract class StateMachine
       public StateMachine()
            // I create various Dao's in here that are used by the helper methods

       /// <span class="code-SummaryComment"><summary>

As I said, the sub class is implemented in IronPython, to do this I needed to import the classes from my C# code which I will be using (such as Job) and then subclass my StateMachine abstract class and implement the Transition abstract method. You will notice the reference to self in the Transition methods parameter list which basically means that it is an instance method.

from Sharpcoder.BusinessEntities import (
   Job, JobTask
from Sharpcoder.BusinessLogic import StateMachine

class Customer1StateMachine(StateMachine):
   # Moves a job through the stage gate process.
   # param job 
   def Transition(self, job):
       return None
   # Transition - End

In order to be able to use the IronPython implementation of the state machine, I needed to read in the code, essentially compile it and then keep a handle to the state machine so that I could use it at some point in the future. There is a slight performance hit in doing all of this, so I decided to use a service locator, implemented as a singleton which due to what happens on start up of the application, gets created at start up. The interesting parts of this class - the bits that create the state machine object - are shown below. Because I need to reference some of the classes from the main part of my application (not least the StateMachine subclass), I need to load the assemblies where these are defined. I do this with the calls to runtime.LoadAssembly() - this code could definitely be refactored to be implemented in a nicer way. The code also assumes that the IronPython code will define a class that extends the StateMachine abstract class, instantiate an instance of this new class and then assign a reference to it, to a variable called machine - we can then use the machine variable to get a handle on the bespoke state machine.

namespace Sharpcoder.BusinessLogic
   public class StateMachineLocator
       // Thread-safe, lazy singleton

       /// <span class="code-SummaryComment"><summary>

GetStateMachineSource() can be implemented however you see fit. For my prototype, I put the IronPython code in a file which I set to be an embedded resource. Eventually I will change this to read the code from the database therefore facilitating the requirement of being able to have different state machines for different customers.

   /// <span class="code-SummaryComment"><summary>

At this point we have all of the code required to create the state machine and use it in the application. The only thing left, is to flesh out the state machine so that it actually does something useful! Part of the code for my state machine is below (this is still work in progress, you will notice hard-coded user ids, etc.):

class Customer1StateMachine(StateMachine):
   # Various constants

   # Transitions job to state 1.007
   # param job
   def _Event_1007(self, job):
       self.UpdateJobStage(job, self.JOB_STAGE_1_ID)
       self.UpdateJobState(job, self.JOB_STATE_1007_ID)
       self.UpdateJobStatus(job, self.JOB_STATUS_OPEN_AWAIT_CUST_QUAL_ID)

       # Hardcoded to be steff for now, will eventually need to do 
       # some lookup to find the
       # ???
       self.CreateJobTask(job, self.JOB_TASK_TYPE_CUST_PRE_QUAL_ID, self.STEFF_USER_ID)
   # _Event_1007 - End

   # Transitions job to state 1.010
   # param job
   def _TransitionToState_1010(self, job):
       self.UpdateJobState(job, self.JOB_STATE_1010_ID)
       self.UpdateJobStatus(job, self.JOB_STATUS_OPEN_AWAIT_QUAL_ID)

       # Hardcoded to steff for now, will eventually need to do some lookup to find the
       # manager
       self.CreateJobTask(job, self.JOB_TASK_TYPE_CUST_INFO_ID, self.STEFF_USER_ID)
   # _TransitionToState_1010 - End

   # Transitions job to state 1.010
   # param job
   def _Event_1009(self, job):
   # _Event_1009 - End

   # Transitions job to state 1.010
   # param job
   def _Event_1010(self, job):
       self.UpdateJobStage(job, self.JOB_STAGE_1_ID)
   # _Event_1009 - End

   # Performs the state transitions for a new job
   # param job - The new job.
   def _NewJobStateTransition(self, job):
       if job.JobType.Id is self.WINDFARMS_JOB_TYPE:
   # _NewJobStateTransition - End

   # Moves a job through the stage gate process.
   # param job 
   def Transition(self, job):
       if not isinstance(job, Job):
           raise Exception("Transition must be called with an object of type Job")

       if job.Id == 0:
       elif job.JobState == None:
           raise Exception("Job must have a state")
   # Transition - End

# Important!!! Stage Gate State Machine locator relies on the machine variable being set
machine = Customer1StateMachine()


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

United Kingdom United Kingdom
No Biography provided

You may also be interested in...

Comments and Discussions

GeneralThanks Pin
John C Wollner16-Jul-12 12:01
memberJohn C Wollner16-Jul-12 12:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170217.1 | Last Updated 9 Apr 2009
Article Copyright 2009 by S1mm0t
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid