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

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

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

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

Distributed remote tasks

14 June 2008 10:54

Following on from an earlier post investigating potential ways of distributing tasks across multiple hosts using py.execnet, here is a simple working example. It requires py.execnet's socketserver.py script to be running on the host/ports specified.

hosts = [('192.168.116.128',8888), ('127.0.0.1',8888)]

remoteCode = """
from time import sleep
from subprocess import Popen, PIPE
task = Popen('sleep 3', shell=True, stdout=PIPE)
print "Task started"
while task.poll() is None:
     channel.send(None)
     sleep(0.1)
channel.send('Data')
"""

from threading import Thread
from Queue import Queue
from time import sleep
from random import random

from py.execnet import SocketGateway

class Host:
    def __init__(self, address, port):
        self.address = address
        self.busy = False
        self.gateway = SocketGateway(address, port)

class DistributedTaskDispatcher(Thread):
    def __init__(self, hosts, task):
        self.task = task
        self.hosts = [Host(*h) for h in hosts]
        self.jobs = Queue()
        self.die = False
        Thread.__init__(self)
        self.start()

    def run(self):
        while not self.die:
            for h in self.hosts:
                if not h.busy:
                    if not self.jobs.empty():
                        print "Sending task %s to %s" % (self.jobs.get(), h.address)
                        h.busy = True
                        h.channel = h.gateway.remote_exec(self.task)
                else:
                    data = h.channel.receive()
                    if data is not None:
                        h.busy = False
                        h.channel.close()
                        print "Received data from",h.address,data
            sleep(0.1)

d = DistributedTaskDispatcher(hosts, remoteCode)

for i in range(10):
    sleep(random() * 2)
    print "Adding task %s to queue" % i
    d.jobs.put(i)

sleep(20)
d.die = True

To make the servers start listening again after the socket connection is closed, you will need to set this option to true in socketserver.py.

Serious health warning: by starting the socketserver script on your machine, you are allowing anyone who has access to that port to do anything they like. Be careful! Connecting over SSH with py.execnet.SshGateway is a much better option for many tasks.

Comments: 0

Adding a Simple Web Interface

14 June 2008 10:53

I had the idea to do this this morning. It is satisfyingly simple, and I spent about half my time trying to fix the bug caused by calling the thread's run() method instead of its start() method.

This is exactly the sort of thing paste was designed for: by making the web server into a well-designed library, it's really easy to use it whenever you need to. I've recently realised how much more powerful libraries are than frameworks, and this is a good example.

from paste.urlmap import URLMap
from paste.cascade import Cascade
from paste.urlparser import StaticURLParser
from paste.httpserver import serve
import time
import threading
from random import random

status = [random()]

def index(environ, start_response):
    start_response('200 OK', [('Content-type','text/html')])
    data = """
    <html><head>Status
    </head><body>%s
    </body></html>""" % status[0]
    return [data]

class WebServer(threading.Thread):
    def run(self):
        # Set up the URL map for the app
        app = URLMap()
        app['/'] = index

        httpd = serve(app, port=8003, host='0.0.0.0')
        httpd.serve_forever()

WebServer().start()

while 1:
    time.sleep(2)
    status[0] = random()
    print "Status is now",status[0]

Note that the shared data object between threads needs to be mutable for obvious reasons, so in this case I just wrap the float in a list.

Comments: 0