Thursday, March 31, 2005

A task-scheduling module

Part of the next release of Spyce will be the new scheduler module. This actually has no Spyce dependencies, so it may be useful in any (web or non-web) application that needs to run tasks at given times or intervals. Some things I have used it for:
  • Scan the log for errors and email me a summary
  • vacuum (pre-autovacuum daemon days...)
  • Purge stuff from the global cache
  • Send email to users whose accounts are about to be suspended
Why a python module instead of cron? The primary benefit in my mind is that scheduler runs as a thread of your application (or app server, in Spyce's case), so you can access your database connection pool, cache, or other global state without jumping through nasty hoops. This is a bigger deal the more complex your application is... Putting logic here also makes deploying to testing or release servers (or to a client) a matter of running "svn up" rather than having to mess with crontab.

You can browse the svn source here: http://svn-hosting.com/svn/spyce/trunk/spyce/scheduler.py

>>> import scheduler
>>> help(scheduler)
Help on module scheduler:

DESCRIPTION
    A module for scheduling arbitrary callables to run at given times
    or intervals, modeled on the naviserver API.  Scheduler runs in
    its own thread; callables run in this same thread, so if you have
    an unusually long callable to run you may wish to give it its own
    thread, for instance,
    
    schedule(lambda: threading.Thread(target=longcallable).start())
    
    Public functions are threadsafe.

CLASSES
    Task
    
    class Task
     |  Instantiated by the schedule methods.
     |  
     |  Instance variables:
     |    nextrun: epoch seconds at which to run next
     |    interval: seconds before repeating
     |    callable: function to invoke
     |    last: if True, will be unscheduled after nextrun
     |  
     |  (Note that by manually setting last on a Task instance, you
     |  can cause it to run an arbitrary number of times.)
     |  
     |  Methods defined here:
     |  
     |  __init__(self, firstrun, interval, callable, once)

FUNCTIONS
    pause()
        Temporarily suspend running scheduled tasks
    
    schedule(interval, callable, once=False)
        Schedules callable to be run every interval seconds.
        Returns the scheduled Task object.
    
    schedule_daily(hours, minutes, callable, once=False)
        Schedules callable to be run at hours:minutes every day.
        (Hours is a 24-hour format.)
        Returns the scheduled Task object.
    
    unpause()
        Resume running scheduled tasks.  If a task came due while
        it was paused, it will run immediately after unpausing.
    
    unschedule(task)
        Removes the given task from the scheduling queue.

Spyce subversion

As mentioned on the mailing list, Spyce development is going on in a subversion repository. We (well, I) got tired of waiting for sourceforge to leave the CVS dark ages... Get your bleeding edge spyce from http://svn-hosting.com/svn/spyce. The web site will be updated with this information eventually soon. (Incidently, I use svn-hosting.com for several projects and it's an excellent, reasonably priced service if you feel you have better things to do than learn how to admin a new source control system. I run the svn repository for my day job and I'd rather let someone else do it when I have the choice.)

Tuesday, March 29, 2005

When a tuple isn't enough

Using a tuple to pass aggregated date around is a Good Thing, but if you do it a lot or your tuple gets large then you should really use a class. Or should you?

NamedTuple lets you write code like this:

names = ("name", "age", "height")
person1 = NamedTuple(zip(names, ["James", "26", "185"]))
person2 = NamedTuple(zip(names, ["Sarah", "24", "170"]))

print person1.name
for i,name in enumerate(names): print name, ":", person2[i]
Okay, that's pretty ugly. But reading the comments of a related recipe led me to Ganesan Rajogpal's attrdict:
class attrdict(dict):
    def __getattr__(self, name):
        return self[name]

    def __setattr__(self, name, value):
        self[name] = value

data = attrdict(red=1, green=2, blue=3)
print data.red
data.black = 4
Much cleaner to use, as well as less code to implement. (Although not immutable, so you couldn't use an attrdict as a key in another dict.) Sort of a nice way to make an annonymous class-like structure for those times when you don't need the real thing. Sort of like a struct for those of you who cut your teeth on C.

There's more along these lines in the cookbook, but attrdict is the one I'm most likely to use.

Friday, March 25, 2005

Toward Spyce components

Making progress towards WebObjects/Tapestry/ASP.NET/JSF-style reusable components. As a first step, I've checked in code to svn trunk allowing the defining of handlers to run on form submissions. Working example:
[[\
def __init__(self):
   self.i = int(request.getpost1('i', 0))

def plusone(self):
   self.i += 1

def plustwo(self):
   self.i += 2

]]

<f:form>
   [[= self.i ]]
   <f:hidden name="i" value="=self.i">
  
   <f:submit handler="self.plusone" value="Add one">
  
   <f:submit handler="self.plustwo" value="Add two">
</f:form>

Thursday, March 24, 2005

How well do you know Python, part 1

Here is a stripped down script that tests a part of Spyce's code to "export" variables from an active tag to the main spyceProcess scope. Even without knowing the background, though, you should be able to figure out: What error will running this code produce?
class spyceImpl:
  def spyceProcess(self):
    ___tagexports={'y': 1, 'item': 0}
    for ___tagkey in ___tagexports:
      exec("%s = ___tagexports['%s']"%(___tagkey,___tagkey))

test = spyceImpl()
test.spyceProcess()