More Clojure

26 February 2010 17:59

I've spent a few more days playing with Clojure. I've found it to be fun and challenging, but I can't really argue useful in any way yet.

I guess this isn't surprising. There are a few times when you would expect advantage from using Clojure: when you need a lot of heavy concurrency (so you can't just do things asynchronously), when you want to integrate with stuff on the JVM, and when you understand LISP enough for macros to make the language genuinely more powerful than other languages.

On the negative side, I've been confused by a few things.

Boxing

One of the expected ones was the boxing of low-level types. If you want to play with Java libraries, you might need to pass unboxed values around. But as soon as you touch anything in Clojure, it's automatically boxed. Calling libraries with values is fine: Clojure does some magic, but if you need to pass, say, an array of floats, you need to convert your Clojure vector explicitly. I made the mistake of expecting this to work:

(into-array [(double 1) 2.0 3.0])

Since into-array is a function which creates an array in which everything is the same type as the first item. However, what actually happens is that the double has to be boxed to be put into the Clojure vector, so you get an array of boxed doubles. The correct solution is:

(double-array [1 2 3])

Java libraries

In order to use ChartDirector with Clojure I had to download a couple of web libraries (servlet-api and jsp-api) and add them to my classpath. This wasn't too painful when I worked out what to get, but there are multiple jars out there containing the classes I needed, and I don't know what might be an official source of them.

Functional programming

I'm still to be convinced that the purists from either the functional or the OO world have a point. Generally I've liked the functional side of Clojure, but today I wanted to create an exponentially weighted moving average function. It took me a long while! Here's what I came up with:

(defn ema
([f values]
  (ema f (rest values) (first values)))
([f values running]
  (if (empty? values)
    (list running)
    (conj (ema f (rest values) (+ (* f (first values))
                                  (* (- 1 f) running))) running))))

Although this seems to work, it won't work with very large lists: I need to use recur since the JVM doesn't support tail-call optimisation. I also haven't thought very carefully about whether I should be using lists or vectors. Probably the performance is good enough with both, but it's another thing to think about.

After a while writing this, I wonder how much effort it would take in Python. Answer, about two minutes:

def ema(f, values):
   running = values[0]
   for v in values:
       running = f*v + (1-f)*running
       yield running

A lot simpler. (I'm not arguing that Python is always this much clearer; I'm just making the point that some things don't seem to be suited to the functional approach).

Maybe the Clojure code could be more like the Python by using the lazy-seq macro. I couldn't see how immediately, though.

Brain size

I'm not sure whether Clojure could ever fit in my brain. There are sooo many functions and macros. And often they are synonyms which do different things. For example vector returns a vector containing its arguments, whilst vec takes a collection as an argument and returns a vector containing its contents. I haven't found the equivalent of vec for lists yet!

Inlining

I spent ages early on trying to figure out why some code (which wasn't correct) behaved in inconsistent ways. It turned out to be related to function inlining. I haven't looked into this in depth, but I'm not the only person who's been confused by it. Looking at the source for + you can see the inline option:

(defn +
 "Returns the sum of nums. (+) returns 0."
 {:inline (fn [x y] `(. clojure.lang.Numbers (add ~x ~y)))
  :inline-arities #{2}}
 ([] 0)
 ([x] (cast Number x))
 ([x y] (. clojure.lang.Numbers (add x y)))
 ([x y & more]
  (reduce + (+ x y) more)))
Comments: 0

First experience with Clojure

7 February 2010 01:22

This weekend I played with Clojure for the first time. There's no doubt it's pretty cool. I did a little bit of work with LISP before, and I do believe Clojure fixes a lot of the warts, as well as enabling use of all the Java libraries. And that's not mentioning the concurrency stuff which was the primary reason for Clojure's development.

For now, here's what I've written. I expect this code to get improved a lot, but so far it just fetches stock prices from Yahoo and maps the result into the correct data types.

(import '(java.net URL)
        '(java.lang StringBuilder)
        '(java.io BufferedReader InputStreamReader)
        '(java.util.regex Pattern)
        '(java.util Date)
        '(java.text SimpleDateFormat))

(defn fetch-url
  [address]
  (let [url (URL. address)]
       (java.io.BufferedReader. (InputStreamReader. (. url (openStream))))))

(defn yahoo-data-url
  [code from-year]
  (str "http://ichart.finance.yahoo.com/table.csv?s="
       code "&a=00&b=01&c=" from-year "&d=01&e=6&f=2010&g=d&ignore=.csv")))

(defn split ([#^Pattern re #^String s] (seq (.split re s))))

(defn str-to-date [date] (. (SimpleDateFormat. "yyyy-MM-dd") parse date))
(defn toint [s] (Integer. s))
(defn tofloat [s] (Float. s))

(defn map-values
  [line]
  (for [i (map vector [str-to-date tofloat tofloat tofloat tofloat toint tofloat] line)]
               (apply (first i) (rest i))))

(defn map-line [s] (map-values (split #"," s)))

(map map-line (rest (line-seq (fetch-url (yahoo-data-url "GOOG" 2000)))))

I can't believe how much I love syntax highlighting.

Comments: 1

Application deployment

3 January 2009 02:56

We have a number of Linux machines onto which we want to deploy a number of applications which will run as services. There will likely need to be some amount of data transfer between the applications at some point, too, and possibly some shared configuration.

The applications which we are deploying need to be started at boot-up, restarted if they crash (and preferably also if they misbehave, such as by using too many system resources), and need to be manually controllable. We want it to be possible to easily install new versions of any of the services we have running on any of the systems.

Of course, it's also important that it's easy to install versions of the software for development and testing. In particular, it would be nice if installing on my Mac worked too.

Our standard existing deployment platform is Fedora Core 8. It would be good to have a solution that works on later versions of Fedora, but also Debian, Ubuntu and other distributions. Working on the custom Linux distributions found on some SCCs which we would like to use as embedded devices would also be an advantage.

Installing the software

Here, I'm going to assume that we're going to use zc.buildout. This is mainly because I have familiarity with it, it's extensible, it does what I want roughly how I think it should be done, and if there was anything better out there I suspect Jim Fulton would have found it.

Running the services

There are a few tools out there to run a program and monitor it to ensure that it keeps running. The two main ones I considered were zdaemon and supervisor. However, to confuse matters there is also D J Bernstein's daemontools, which covers pretty similar ground, but also provides a start-up system which works across most unix-based systems. Once you get there you also come across runit, which is meant to be an enhanced daemontools. Runit would really like to be run as process 1 and replace init, but it's not necessary in order to use it. There's a good article explaining the whats, whys and wherefores of runit.

Configuring boot-up scripts

This is possibly the trickiest bit. In our existing set-up, all custom things to be started on boot-up are in /etc/rc.local. In order to make it easy to install and upgrade and install different applications separately, we would ideally like to just place a start-up script in a directory and know that it will be run.

When daemontools installs itself it makes sure that it gets started by appending a line to /etc/inittab if it exists. From Fedora Core 9 this file still exists, but is not automatically executed on boot-up; Fedora has moved to the new upstart system. OS X has its own startup system called launchd.

zdaemon compared to supervisor

zdaemon and supervisor fill almost the same rĂ´le, so it makes sense to compare them. This thread is a good comparison.

Puppet

On my way to getting all this working, I took a look at Puppet. Puppet is a tool for managing systems. It will create files, install packages, configure services and all those other things that one usually writes flakey scripts to do. A Puppet configuration, called a manifest, can be run repeatedly and will update the necessary components. Components can depend on one another, and it's all cool. Puppet is written in Ruby. The test coverage is high, and they use Trac and Buildbot. I can't help having a very positive feel about the project.

Puppet will also do some level of supervision. All that is required is a process which daemonises itself, and a set of commands to start/stop/status it. There are buildout recipes to install both zdaemon and supervisor with an application. However, following slightly the philosophy of Daemontools, I decided it made more sense to install the application and install something to daemonise it entirely separately.

I used zdaemon to do daemonise my process, as it produces an executable with start, stop and status commands. Supervisor doesn't install an init-style script. There's one for Debian in the respository here, but that's all, and that doesn't include a status command. There was a slight snag; the zdaemon status command returns exit status 0 even if it's not running. Since this is how Puppet tells whether the process is running, I hacked zdaemon to return 1 if the supervisor process is not running.

I should add that Puppet can either check the status of a process every time it updates itself (by default on boot-up and then every half hour), and start the process if it's not running, or it can configure the process to be started by init. It doesn't really make that much sense to do both, and I opted for the former since it makes it cleaner if I decide that I don't want the process running.

Not doing any of that

You might think that it would make sense for Puppet to do roughly what zdaemon does, so that you can just install an application and tell Puppet to make in into a service. But I have something which works, so I'm happy.

Another improvement at some point may be to move to using Supervisor instead of zdaemon. Supervisor is much more full-featured, including being able to configure notifications on failure, clever restarting logic and memory monitoring. But until I need one of these features, I'm quite happy.

Puppet Introduction

Puppet manifests are written in a custom language which borrows heavily from Ruby syntax. The code below demonstrates a lot of the features of Puppet, and I concluded that there are too many to attempt to explain them all!

But as a quick overview, I have defined a, erm, definition which takes a buildout config file and installs the application. I also created a definition to take an executable and make in into a service. (Unfortunately this only works under linux as it puts a script in /etc/init.d/. It would also be possible to write the control script to the application directory and then specify the start stop and status command explicitly in the service resource.)

Below is the Puppet configuration file which controls all the above. It requires Puppet to be configured to serve some files, including the application buildout file and a separate buildout which just installs zdaemon. (zdaemon could have just been installed globally, but this felt a little cleaner).

Exec { path => "/usr/bin:/usr/sbin" }

define setuptools($executable) {
    file { ezsetup:
        name => '/tmp/ez_setup.py',
        source => 'puppet://server.example.net/files/ez_setup.py',
    }

    exec { install:
        require => File[ezsetup],
        command => "$executable /tmp/ez_setup.py",
    }
}

class setuptools25 {
    setuptools { setuptools25:
        executable => 'python2.5',
    }
}

class zdaemon25 {
    buildout { zdaemon:
        require => File['/cwd'],
        path => '/cwd/zdaemon/',
        config_file => "puppet://server.example.net/files/zdaemon.cfg",
        python => 'python2.5',
    }
}


define service_daemon($exe, $dir, $service_name) {
    include zdaemon25

    $python = 'python2.5'

    $init_script = "#!/bin/sh
/cwd/zdaemon/bin/zdaemon -C ${dir}zdaemon.conf \$1
"

    $zdaemon_conf = "<runner>
 directory $dir
 program $exe
 transcript ${dir}log/transcript.log
</runner>

<eventlog>
 <logfile>
  path ${dir}log/zdaemon.log
 </logfile>
</eventlog>
"

    file { "${dir}log":
        ensure => directory,
    }

    file { "/etc/init.d/$service_name":
        content => $init_script,
        mode => 755,
    }

    file { "${dir}zdaemon.conf":
        content => $zdaemon_conf,
    }
}

define buildout($path, $config_file, $python) {
    include setuptools25

    file { $path:
        ensure => directory,
    }
    file { "$path/bootstrap.py":
        source => "puppet://server.example.net/files/bootstrap.py"
    }
    file { "$path/buildout.cfg":
        source => $config_file
    }
    exec { "bootstrap_$path":
        require => [File["$path/bootstrap.py"], File["$path/buildout.cfg"]],
        command => "cd $path; python2.5 bootstrap.py",
        unless => "test -d $path/bin",
    }
    exec { "build_$path":
        require => Exec["bootstrap_$path"],
        command => "cd $path; ./bin/buildout",
        subscribe => File["$path/buildout.cfg"],
        refreshonly => true,
    }
}

file { "/cwd":
    ensure => directory,
}

$python25headers = $operatingsystem ? {
    fedora => "python-devel",
    default => "python2.5-dev"
}

package { $python25headers:
    alias => pydev,
}

#Buildout, wrap with daemoniser, start myapp
buildout { myapp:
    require => [File['/cwd'], Package[pydev]],
    path => '/cwd/myapp/',
    config_file => "puppet://server.example.net/files/myapp.cfg",
    python => 'python2.5',
}

service_daemon { myapp:
    service_name => myapp,
    dir => '/cwd/myapp/',
    exe => '/cwd/myapp/bin/myapp',
}

service { myapp:
    require => [Buildout[myapp], Service_daemon[myapp]],
    ensure => running,
}

The unsolved problems

In some ways you might argue that the above fails to get anywhere near solving the problem that I set out to solve. In particular, there is still no simple way to decide which version of myapp is deployed. You would need to find a buildout configuration file for the correct version and copy it to the Puppet file serving directory. That's not too bad for performing an upgrade, but it's pretty messy for performing a downgrade or, more commonly, seeing what version is currently deployed.

It would be much better if I could just specify the version of myapp in the Puppet configuration. Hopefully that will happen soon!

Comments: 1

Refactoring a PostgreSQL database

22 September 2008 10:49

When investigating legacy systems, all sorts of interesting things can appear. Whilst it's easy to change code around a bit, historical warts in database structure can be a bit harder to change.

In this instance, a datetime was stored in the database as a string representation of the number of seconds since the epoch. Based on the approach given in "Refactoring Databases", I set up a new TIMESTAMP column and set up triggers to keep the two columns syncronised. Since I couldn't find any other examples of using triggers for column synchronisation in PostgreSQL, I thought I'd publish the working code, as it's a bit different to the Oracle version.

I started by creating functions to map between timestamps and strings. These definitions are in PL/pgSQL (Procedural Language/PostgreSQL Structured Query Language), a procedural language supported by the PostgreSQL RDBMS. It closely resembles Oracle's PL/SQL language. Thanks, Wikipedia.

So, the function definitions:

CREATE OR REPLACE FUNCTION string_to_timestamp (dtstring text) RETURNS TIMESTAMP AS $$
BEGIN
IF dtstring = '' THEN
  RETURN NULL;
END IF;
  RETURN TIMESTAMP WITH TIME ZONE 'EPOCH' + to_number(dtstring, 9999999999) * INTERVAL '1 SECOND';
END;
$$ LANGUAGE plpgsql;


CREATE OR REPLACE FUNCTION timestamp_to_string (dt timestamp) RETURNS text AS $$
BEGIN
IF dt IS NULL THEN
  RETURN '';
END IF;
  RETURN EXTRACT(EPOCH FROM dt);
END;
$$ LANGUAGE plpgsql;

Let's test these functions:

postgres=# SELECT timestamp_to_string(NULL) = '';
 ?column?
----------
 t
(1 row)

postgres=# SELECT string_to_timestamp('') IS NULL;
 ?column?
----------
 t
(1 row)

postgres=# SELECT string_to_timestamp('1222222222');
 string_to_timestamp
---------------------
 2008-09-24 12:10:22
(1 row)

postgres=# SELECT timestamp_to_string(TIMESTAMP '2008-9-24 12:10:22');
 timestamp_to_string
---------------------
 1222222222
(1 row)

Looks quite convincing, though we should assure ourselves that it handles timezones how we want it to.

Now we can add a new column, and update it to correlate with the existing column we want it to mirror:

ALTER TABLE spio ADD redtime TIMESTAMP;
UPDATE spio SET redtime=string_to_timestamp(RETT);

Finally, we define trigger functions and configure them as triggers on insert and update operations to keep the two columns synchronised:

CREATE OR REPLACE FUNCTION InitialiseRedemptionTime() RETURNS trigger AS $$
BEGIN
  IF NEW.RETT IS NULL THEN
    NEW.RETT := timestamp_to_string(NEW.REDTIME);
  END IF;
  IF NEW.REDTIME IS NULL THEN
    NEW.REDTIME := string_to_timestamp(NEW.RETT);
  END IF;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER InitialiseRedemptionTime BEFORE INSERT ON spio
  FOR EACH ROW EXECUTE PROCEDURE InitialiseRedemptionTime();

CREATE OR REPLACE FUNCTION SynchroniseRedemptionTime() RETURNS trigger AS $$
BEGIN
  IF NOT(NEW.RETT=OLD.RETT) THEN
    NEW.REDTIME := string_to_timestamp(NEW.RETT);
  END IF;

  IF NOT(NEW.REDTIME=OLD.REDTIME) THEN
    NEW.RETT := timestamp_to_string(NEW.REDTIME);
  END IF;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER SynchroniseRedemptionTime BEFORE UPDATE ON spio
  FOR EACH ROW EXECUTE PROCEDURE SynchroniseRedemptionTime();

So if we create a new record with RETT set:

postgres=# insert into spio(RETT) VALUES (1212121212);
INSERT 0 1
postgres=# select uniq, RETT, redtime from spio order by   uniq desc limit 1;
 uniq  |    rett    |       redtime
-------+------------+---------------------
 17381 | 1212121212 | 2008-05-30 14:20:12
(1 row)

Or if we just set REDTIME:

postgres=# insert into spio(REDTIME) VALUES (TIMESTAMP '2008-05-30 14:20:12');
INSERT 0 1
postgres=# select uniq, RETT, redtime from spio order by  uniq desc limit 1;
 uniq  |    rett    |       redtime
-------+------------+---------------------
 17382 | 1212121212 | 2008-05-30 14:20:12
(1 row)

And if we update RETT:

postgres=# UPDATE spio SET RETT=1212312341 WHERE uniq=17382;
UPDATE 1
postgres=# select uniq, RETT, redtime from spio order by uniq desc limit 1;
 uniq  |    rett    |       redtime
-------+------------+---------------------
 17382 | 1212312341 | 2008-06-01 19:25:41
(1 row)

Or if we update REDTIME:

postgres=# UPDATE spio SET REDTIME=TIMESTAMP '2008-06-01 19:25:41' WHERE uniq=17381;
UPDATE 1
postgres=# select uniq, RETT, redtime from spio order by uniq desc limit 1 OFFSET 1;
 uniq  |    rett    |       redtime
-------+------------+---------------------
 17381 | 1212312341 | 2008-06-01 19:25:41
(1 row)

Of course, if you were doing this on an active database you'd need to set up the triggers before doing the one-time synchronisation.

Comments: 0

Is Zope the Answer?

9 September 2008 12:29

For writing web applications, I am increasingly convinced that Zope is the best way forward for a large majority of tasks. It's reliable, well-tested, documented, and uses unicode throughout. And it currently has the most appalling website of any framework. So, what makes it so good?

Python

Python rocks. I'm not sure whether I could stand programming in any other language. Everything else is so redundant. Python is simple: I don't have to think about how to code something. I don't need a massive reference manual next to me just for the language itself. It makes me happy. Well, less annoyed, anyway.

So, recursing slightly, what's so good about Python?

Interactive interpreter

Yes, some people still code in languages which require a compile step and which you can't experiment with interactively. Strange, huh? But, web development is usually done using dynamic languages, so what sets Python apart?

Significant whitespace

Your code does what it looks like it does, and it's not littered with curly braces/'end' statements. What are you complaining about?

doctest

I really think the single best thing about Python is doctest. Both in docstrings and in separate files, it really makes life much better. It means I write tests which always catch lots of bugs and make refactoring pretty much painless. I often write a doctest as a design document, which makes me think about how I want the API to work before I start writing the code. The code coverage reports from z3c.coverage are the icing on the cake for Python testing.

In the face of ambiguity, refuse the temptation to guess

Python prioritises making it hard to make mistakes without realising it over being able to do things really fast. Well, nearly. Python 3 will get rid of a few more including the ability to compare arbitrary objects (which actually works on memory location). Ew.

Choice of programming style

When dealing with lists I often write in a functional style; when I have complex data structures I write very object oriented code. Python lets me do whatever I feel like at the time. Sometimes all you want is a script with a couple of procedures, and that's all I want to have to write.

So, back to the best of Zope...

Component Architecture

Zope 3's component architecture has lead to a level of cleanliness, reusability and testability of code that I would not have thought possible. The component architecture forces you to think about how your code works, without restricting you in any way. I increasingly notice that I write better code through trying to take advantage of the component architecture. What does that mean? Well, usually it means adding new functionality without modifying existing code at all. How? Use an adapter.

Object Publishing

I have to admit, I really don't get this MVC business for web applications, and I don't see how the standard /model/controller/id url scheme is going to make sense very often. Choosing a URL structure for a site forces it to be made into a hierarchy of some sort, and this fits with having some tree of objects (not all of which are necessarily persistent). I often don't have many views per object, often just a view and an edit view, making my applications really quite ReST-like (which isn't necessarily good, but is fashionable at the moment).

I also like having a separate object created for every view and viewlet; it feels right to me. I guess it is more like multi-model MVC.

zc.buildout

zc.buildout is unfortunately quite tricky to get started with, but is well worth a lot of investment. It enables the construction of repeatable environments for development, testing or deployment, without affecting the existing Python installation. How we got by without it I'll never know.

The ZODB

In my opinion, the ZODB is Zope's biggest asset. Change your classes to inherit from persistent.Persistent, use persistent lists and mappings, and that's it. You can completely ignore the problem of data storage. People continue to claim great things of object-relational mappers, and they continue to fail to live up to the promise, mainly due to the fundamental impedance mismatch.

I'm encouraged to see that new tools are under development for making the ZODB less scary, for example a new object browser.

And the bad bits

Okay, so what's not so good? Well, all the experimentation which goes on to develop all these great technologies also leads to a lot of less good ideas, and it can be very difficult to keep ahead and to know which ones to go for. It makes life interesting, but can be pretty frustrating when you just want to get a job done.

Python 3 is probably not that far away now, and is rather scary. There is no significant common subset between Python 2 and Python 3 (mainly due to the change in string type, which will default to unicode instead of raw bytes). What the transition will do to the Zope world remains to be seen, but there is certainly a question as to whether the cost of the change is really worth it.

Finally, Zope uses some C-level code to implement some of its security, which I believe is the sticking point getting it to run on PyPy/Jython/Google App Engine. Hopefully these problems will be navigated somehow. Zope on App Engine sounds like a very attractive proposition!

Comments: 0

z3c.tutorialtest

19 July 2008 01:10

z3c.tutorialtest was written to solve a problem: I had a complex internal web application for which I needed to write a tutorial for the users. One nice way to do this might have been to make a screen cast, but that would have been very time consuming and was likely to become outdated quickly.

What I wanted to do instead was to write a doctest and have the user watch the doctest being played in their browser. This package is a first attempt at that.

Whilst the package contains a lot of stuff to integrate TutorialTests into Zope applications, it's intended that the core functionality should be usable outside of Zope, though I haven't tested this yet.

Getting it

Currently, z3c.tutorialtest is only available from Assembla:

svn co http://svn.assembla.com/svn/z3c_tutorialtest/trunk z3c.tutorialtest

If other people use it I will move it to svn.zope.org and make a release.

Using it

z3c.tutorialtest defines the http://namespaces.zope.org/tutorialtest zcml namespace with a single directive, test. The test directive registers an existing doctest as a TutorialTest, for example:

<tutorialtest:test
    id="businessdevelopment"
    title="Business development introduction"
    test_path="web/ftests/businessdev.txt"
    />

The TutorialTest test runner will set a browser object in the test namespace very similar to an instance of zc.testbrowser.real.Browser. To run the same test as part of your test suite, you'll therefore likely to want to create an instance a different browser class in your setUp code:

from zope.testbrowser.testing import Browser
from dsrscheduler.testing import FunctionalDocFileSuite

def setUp(test):
    test.globs = {'browser':Browser()}

def test_suite():
    return FunctionalDocFileSuite('web/ftests/businessdev.txt', setUp=setUp)

This package creates a new traversal namespace for tests. To view an index of all the available tests, visit /++tutorialtest++index Individual tests can be accessed at /++tutorialtest++<testid>.

To run a test, you will first need to start MozRepl (MozLab 0.1.9 on Firefox 3 works) on port 4242, and open a browser window. If the web application you are testing is running on a different machine, you will need to select "Allow outside connections" before starting MozRepl. Be aware of the security implications of this: if you're not behind a firewall, don't do it!

Then, navigate to ++tutorialtest++index and choose the test you want. The test should now play in your browser. Comments from the doctest are turned into Javascript alert boxes. (Obviously there's room for improvement there!)

Because z3c.tutorialtest runs tests against a live application, it uses the live ZODB and modifies real data. So far I've got round this by creating a new instance of the application, but that's only easy since I'm using Grok. I'm not sure what might be considered good practice here; it certainly requires some thought.

Comments: 0

Object lists with Grok and formlib

16 June 2008 15:10

Today I needed an attribute on an object to be a list of contacts. Something like this:

from zope.interface import Interface
from zope import schema
from persistent import Persistent
from persistent.list import PersistentList
import grok

class IFacilityContact(Interface):
    name = schema.TextLine(title=u"Contact name")
    email = schema.TextLine(title=u"Email")
    phone = schema.TextLine(title=u"Phone")

class IFacility(Interface):
    name = schema.TextLine(title=u"Facility name")
    contacts = schema.List(title=u"Contacts",
                           value_type=schema.Object(schema=IFacilityContact),
                           default=[])

I don't understand why that default=[] is required. If I render the form as a view it isn't, but when I render it as a viewlet (with a very slightly customised template) it is, otherwise I get this error:

- Expression: <PathExpr standard:u'widget'>
TypeError: iteration over non-sequence

Anyway, on with the object definition:

class FacilityContact(Persistent):
    grok.implements(IFacilityContact)
    name = ''
    phone = ''
    email = ''

class Facility(grok.Container):
    grok.implements(IFacility)

    def __init__(self, name=''):
       super(Facility, self).__init__()
       self.name = name
       self.contacts = PersistentList()

I wanted to use formlib to automatically generate the edit form. So, a standard grok edit form:

from zope.formlib import form
import zope.event
from zope.component import getMultiAdapter
from zope.lifecycleevent import ObjectModifiedEvent

class EditFacility(grok.Viewlet):
    grok.viewletmanager(Edit)
    grok.name("Edit facility details")

    def update(self):
        self.form = getMultiAdapter((self.context, self.request),
                                    name='editfacilityform')
        self.form.update_form()

    def render(self):
        return self.form.render()

class EditFacilityForm(grok.EditForm):
    form_fields = grok.AutoFields(IFacility)

    template = subpage_edit_form #A template without html headers for viewlets

    @form.action("Apply", condition=form.haveInputWidgets)
    def handle_edit_action(self, action, data):
        if form.applyChanges(self.context, self.form_fields, data, self.adapters):
            zope.event.notify(ObjectModifiedEvent(self.context))
        self.request.response.redirect('.')

Now no widgets have been registered for Object yet, so we get a component lookup error:

ComponentLookupError: ((<zope.schema._field.Object object at 0x2c91530>, <zope.publisher.browser.BrowserRequest instance URL=http://localhost:8080/test2/edit>), <InterfaceClass zope.app.form.interfaces.IInputWidget>, u'')

Let's register an adapter for object. I can't remember where I stole this code from...

from zope.app import zapi
from zope.app.form.interfaces import IInputWidget
from zope.interface import implements
from zope.schema.interfaces import IObject
from zope.publisher.interfaces.browser import IBrowserRequest
import grok


class ObjectInputWidgetView(grok.MultiAdapter):
    grok.adapts(IObject, IBrowserRequest)
    grok.implements(IInputWidget)

    def __new__(self, context, request):
        """Dispatch widget for Object schema field to a widget that is
        registered for (IObject, schema, IBrowserRequest) where schema
        is the schema of the object."""
        class Obj(object):
            implements(context.schema)
        widget=zapi.getMultiAdapter((context, Obj(), request), IInputWidget)
        return widget

This adapter looks up another adapter based on the context, the schema and the request. So we now need to register an adapter for our schema:

from zope.app.form.browser import ObjectWidget

class ContactWidget(grok.MultiAdapter):
    grok.adapts(IObject, IFacilityContact, IBrowserRequest)
    grok.implements(IInputWidget)

    def __new__(self, context, obj, request):
        return ObjectWidget(context, request, FacilityContact)

And now, things should finally work.

Comments: 1

Assorted Revision Control Systems

14 June 2008 11:00

I've recently moved to using git as my standard revision control system. I honestly don't know how it compares with most of its competitors, but I appreciate the ideas behind distributed revision control.

Today I had a project which I wanted to check into a subversion repository, so I thought I'd try to do things the hard way: use git-svn. It turned out not to be too bad to get the code checked in. First, I added this to .git/config:

[svn-remote "assembla/trunk"]
  url = http://svn.assembla.com/svn/grokstar/trunk
  fetch = :refs/remotes/assembla/trunk

Then the following did the work:

$ git-svn fetch assembla/trunk
W: +empty_dir: trunk
r1 = f59e10be7c3662088211b97cdfc7c8bf32fb0db0  (assembla/trunk)

$ git branch -a
* master
  assembla/trunk

$ git checkout -b local-svn/trunk assembla/trunk
Switched to a new branch "local-svn/trunk"

$ git merge master
Merge made by recursive.

$ git-svn dcommit

And finally switch back to the master branch:

$ git checkout master

To commit further changes, commit to the master branch, switch to the local svn branch, merge, push and switch back:

$ git-commit -a -m"Fix the one test"
$ git checkout local-svn/trunk
$ git merge master
$ git-svn dcommit
$ git checkout master

Showing the current branch in the prompt

To show the current git branch in your prompt, edit it to look something like this:

export PS1='\[\033[1;36m\]\u@\h:\w\[\033[0m\]$(__git_ps1 "(%s)")$ '

Autocompletion

git has proper command completion which you'll need to enable, like this:

ln -s /opt/local/var/macports/build/_opt_local_var_macports_sources_rsync.macports.org_release_ports_devel_git-core/work/git-1.5.3.7/contrib/completion/git-completion.bash ~/
. ~/.git-completion.bash

And whilst we're on the completion theme, here's how to get sudo to do something sensible:

complete -c -f command sudo
Comments: 0

Moving an Ubuntu VMware image

14 June 2008 10:59

I downloaded a VMware virtual machine installed with Ubuntu 8.04. On boot, the networking didn't work. After restarting networking, eth0 gave errors, and afterwards only lo and eth1 (down) were returned by ifconfig -a. I couldn't get eth1 to start with various types of coaxing.

I remembered that VMware assigns a new MAC address to the network interface on virtual machines when they are copied.

I even remembered the file to edit: /etc/iftab. Unfortunately, this file didn't exist. Hmmm.

The mapping of device names to MAC addresses now seems to be stored in /etc/udev/rules.d/70-persistent-net.rules. I copied the MAC address for eth1 to that for eth0 and deleted the entry for eth1.

I ran /etc/init.d/networking restart. Still no dice. How does the system remember what networking devices are registered? No idea. So I took the Windows solution, and now it's up and running. Which should save me a bit of embarassment next week, since I claimed setting up virtual machines was trivial.

Comments: 0

Google App Engine

14 June 2008 10:58

A while back I wrote that I thought AppJet were a long way off the mark when it comes to providing a useable hosted web application service. Now Google have stepped into the marketplace, and it seems that they have addressed most of the points I was concerned about. There are still a few pretty major blockers, with the lack of any image processing library being a killer for quite a few projects. But Google also bring scalability and reliability to the table. However big my budget, I wouldn't expect to me able make a system with the scalability and reliability that I would expect from Google. They pretty much immediately got 10000 developers subscribed, and so far I haven't come across any complaints of problems. That doesn't surprise me, but it probably would from any other company.

Whilst I can't imagine Google wanting to get involved in installing a lot of C extensions, I guess there's a chance that they will decide the need to make an exception for PIL. A lot of web applications need to be able to process images in some way, even if it is just scaling them. Alternatively they might choose to add their own image processing API which can be used from other languages when they become supported. An alternative for developer is that a third party makes a web service for such functionality, but that could result in a lot of images being transferred round the world.

Desktop development

Google are providing an SDK which enables you to run the same platform on your desktop in order to develop your app. I haven't looked into this is detail yet. Can you download you DB to test new changes? How do you set up a staging environment? What about running tests?

The datastore

I'm glad to see Google using something which is basically an Object database with simple queries on object types and back-references. Hopefully this will start to spread the concept of object databases into the mainstream, though I suspect that a lot of people are going to complain about the lack of an RDB. In fact, quite a few already are doing.

Google's stated reason for not using an RDB (my feeling is that 'this is better' would be good enough) is that relational databases can't be made to scale in the same way as the datastore. I can't find any reference to how the datastore deals with conflicts, so this doesn't really make any sense. If a user in Australia and a user in the UK make changes to the data, something still needs to check that those changes are not in conflict before both parties can see the results of the changes they made. Maybe instead of a having a single master server App Engine allows for multiple master servers each responsible for part of the database. For a discussion of the issues of distributed RDBs, I highly recommend Scalable Internet Architectures (and the chapter on databases is available for preview), which does what it says on the tin, though it also covers reliability and redundancy, whilst wisely giving optimisation a wide berth.

Effect on the industry

There's an interesting question as to how big an effect App Engine will have on the industry. I can imagine that this changes the landscape significantly for Paul Graham-style web start-ups (without even considering AppJet, who I think it leaves in a significantly worse situation than they were already in). App Engine significantly reduces the complexity of launching a web start-up, and possibly removes the need for investment from the business model completely.

A few commentators are suggesting that this is going to be the start of the PaaS (Platform as a Service) wars. Having control of someone's database is a really good way to tie people to service.

I see that within a few hours of the launch, someone had already produced an OpenID provider application, using the authentication against Google accounts to produce an OpenID provider based on Google logins. Very neat.

Interestingly (to me), Google have included an yaml parser and use yml as the configuration language for applications. When I encountered yaml in my brief visit to the world of Ruby I quite liked it and thought it was quite Pythonic, and I continue to be surprised that it isn't supported by the standard library. Okay, I just looked the the yaml specs. Now I understand.

In summary, I think Google did everything right; including PIL in App Engine would make it really attractive, and overall I'm quite happy. As ever, though, I can't immediately think of any uses for it!

Comments: 0