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: 3

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 <http://www.purpletech.com/articles/mvc/mvc-and-beyond.html>.

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: 2

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: 3

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: 4

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: 3

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: 3

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: 3

AppJet

14 June 2008 10:57

Zimki

About a year ago, a small firm called Fotango launched a server-side JavaScript web development environment called Zimki. At the time, they were offering a MacBook Pro for the best application developed using Zimki, so I had a play with it. It was missing some critical features including database transactions, but I managed to make some simple stuff work with it.

Part of the Fotango's plan was to open-source the code powering Zimki, so that other providers could also host applications developed with it, making it a safer choice for development. But whilst this was the CTO's wish, the owners eventually decided they did not want to release the code, and at this point the CTO left. Soon after Zimki announced that they would be shutting down their service.

Javascript

JavaScript as a server-side language is an interesting choice. It has the obvious advantage that it's well-known, standard and familiar to the vast majority of web developers. That's not true of any other language. And it's not as bad as you might think, since the latest version of JavaScript, as was being used for Zimki, is much better than the elderly versions which have to be used for client-side programming. But at the same time, that means that in many ways it's not the same language that developers know (and hate). In fact, the latest version of JavaScript feels like it's nearly half way to being Python from the early versions.

But the real weakness of using JavaScript is not the language itself but the selection of libraries available for it. Whereas Python and PHP have a vast set of libraries available for web development, JavaScript has relatively few. And writing web applications without libraries for common tasks such as form validation is a pain.

Web-based development

When Zimki first appeared, my first thought was that it was about time, and wasn't through-the-web the obvious way to do web development? I often spend about half my time on system administration, just to create another environment that's basically the same as that thousands of other developers have set up. And by having a hosted setup, data backups are automatically made, and the application can automatically scale across multiple servers. The advantages are certainly significant.

But then, the flexibility of configuring my own system started to become apparent. Installing image libraries, running a mail server, and many more things are going to cause problems if you can't do them. And, perhaps most critically, the development environment is probably never going to be as flexible as developing on your own machine.

After a little while, I wondered how Zimki could possibly be used for anything significant. The lack of libraries and configurability was too limiting. And no-one was going to choose a framework which they couldn't be sure was going to provide the flexibility they needed. So it wasn't a big surprise to me when Zimki announced that they were stopping their service.

AppJet

But now there's a new company in the same game, AppJet. Doomed to go the same way? Well, I'd have thought so, but they are funded by Paul Graham, so I thought I'd look a bit more closely before drawing any conclusions.

So, what does AppJet offer? Does it address Zimki's shortcomings in a lack of mail and image processing libraries? What about form handling and database transactions? Ability to host your site at your own domain?

Well, there's no sign of transactions, making the data storage of dubious value. Interestingly, there is no templating language in appjet. You can print strings, or you can generate tags like this:

print(UL(LI("Ice cream"),
        LI("The sound of the ocean"),
        LI("That song by Bobby McFerrin"),
        LI("The movie ",I("Gone with the Wind")),
        LI("Elegant code"),
        LI("The color ",SPAN({style:"color:green"},"green"))));

which looks a bit smelly. Seaside (Smalltalk) and Uncommon Web (LISP) both have XML tree generation like this, but it doesn't seem right here. There's also something similar in Python called Breve, which I've been intending to look at for a while.

Request dispatch is an interesting topic. The Pylons web framework uses a port of Rails' routes, which defaults to a /controller/action/id style scheme. Zope uses the URL for object traversal, and when that matches your application it's excellent. TurboGears uses a similar object traversal dispatch mechanism from CherryPy. Django uses regular expression to match incoming requests.

AppJet? Well, if you call the dispatch() function, then the url /name will call the function get_name. So there's a way to go there. Similarly the form library doesn't do validation.

So, does AppJet have anything that might make it attractive for some kind of web development? Well, there's a library for accessing the Facebook API. So it might make it easy to produce pointless Facebook applications. But I can't see them making their authors much money, so there won't be much to come back to AppJet.

Conclusion

I'm quite surprised that there isn't anything about AppJet that makes me optimistic. I'm still not convinced that the idea of a hosted application development environment can't work. But AppJet seems to be no nearer than Zimki was.

Roll-your-own

I've been wondering how hard it would be to set up a Python-based TTW environment. IMO the whole hosting environment must be installable on your local machine so that standard development tools can be used. Editor wars are still fun, after all! At this point, what's the point in a hosted system? Well, as soon as you move to a live server --- maybe by just running a script --- then you could have the scalability and backup issues automatically addressed. But then if you changed the data strucutres? What would happen to the database? Hmmm. I still think that there ought to be mileage in this general idea.

The primary reason that I think the system needs to be available locally is so that people can develop and test the libraries they want to use. Maybe if it is possible to install any eggs needed for your application on the server, that would be enough. That might start to address what in my opinion is the biggest issue facing hosted application development.

Hosted systems are always going to be more restricted than having full control over a system, but it is interesting to see how flexible they might manage to become.

Rails?

A few days after writing the above, I was watching a screen-cast on database migrations in Rails. It was quite impressive, but I was as ever intimidated by the need to remember all the conventions (which avoid the configurations) in Rails. But it made me think that maybe Rails is the obvious technology to be offering hosted development for. The vast majority of Rails developers don't try to use their own ORM/templating system/form library. In my opinion, there's much more mileage in hosted Rails development than in hosted JS development. As far as I can make out, no one is doing so as yet. I'm tempted. [Note: I'm not going to develop it in Rails, though. Rails is shockingly slow in some of the screen-casts.]

Comments: 3

Move a Plone site to zc.buildout

14 June 2008 10:56

I've been foolish enough to say I'll do some maintenance work on a site I created a while back. Since deployment is, as ever, a nightmare, and some impressive work has been done to use zc.buildout with Plone, I decided to try it.

In the end, it wasn't too bad. After copying the ZODB from the existing tarball-based site, I got some errors because the products were not found at Product.ProductName...., so the ZODB unpickling failed. I fixed this by moving the products from parts/productdistros to products. Unfortunately plone.recipe.distros has no option for putting things there directly.

[buildout]
parts = plone zope2 zmysqlda patch_zmysqlda productdistros instance
eggs = elementtree
develop =

[plone]
recipe = plone.recipe.plone

[zope2]
recipe = plone.recipe.zope2install
url = ${plone:zope2-url}

[zmysqlda]
recipe = cns.recipe.zmysqlda
target = ${buildout:directory}/products

[patch_zmysqlda]
recipe = plone.recipe.command
command = patch --quiet -p1 -d ${buildout:directory}/products/ZMySQLDA < patches/zmysqlda-imagefile.patch

[productdistros]
recipe = plone.recipe.distros
urls =
    http://plone.org/products/sqlpasplugin/releases/1.0/SQLPASPlugin-1.0.tar.gz
    http://plone.org/products/cachefu/releases/1.1.1/CacheFu-1.1.1.tgz
nested-packages =
    CacheFu-1.1.1.tgz

[instance]
recipe = plone.recipe.zope2instance
zope2-location = ${zope2:location}
user = admin:admin
http-port = 8080
debug-mode = on
verbose-security = on
eggs =
    ${buildout:eggs}
    ${plone:eggs}
    Products.Ploneboard
    MySQL-Python
zcml =

products =
    ${plone:products}
    ${buildout:directory}/products
Comments: 7

One-liner

14 June 2008 10:55

Sometimes list comprehensions seem to be the right way at every step, resulting in code like this:

return [z for x, y, z in sorted(
            [(len([j for j in scores if all(
                    [(f-e)*s>0 for (e, f, s) in zip(i, j, signs)]
            )]), random(), i) for i in scores])]

But since I did write a doctest for the function I don't feel too guilty!

Comments: 3