A first look at repoze.bfg - mrtopf.demrtopf.de

A first look at repoze.bfg

If you are a Zope developer you maybe have heard about repoze.bfg. Recently I was checking it out to see what one can do with it and if it’s useful for me.

What is repoze.bfg

In short repoze.bfg is an MVC framework like Pylons or Django but it’s based on Zope3 technologies, such as the Zope Component Architecture and z3c.pt. What I like about it is the fact that it also can do URL traversal while the others mostly can do some url pattern matching which is sometimes somewhat limiting. So a mix of both is sometimes what you need and you might have noticed that when you had the need to write some traverser for Zope2/Zope3 which is rather painful at least compared to using some url pattern matching like e.g. Django does it.

But here is how you start (I also noticed Chris did a screencast about it but I wanted to post this nevertheless as it was halfway finished).

So here is a little first example on what I tested with it.

Installing repoze.bfg in a sandbox

What you need is to have virtualenv installed. You then create a directory and make it a sandbox:

mkdir example
cd example
virtualenv . --no-site-packages

It will print something like this:

New python executable in ./bin/python
Please make sure you remove any previous custom paths from your /Users/cs/.pydistutils.cfg file.
Installing setuptools............done.

Now we need to install repoze.bfg itself:

bin/easy_install -i http://dist.repoze.org/lemonade/dev/simple repoze.bfg

Chris likes to use a custom pypi index with just the packages he needs which is why we have to put the -i parameter in there. It’s usually a good idea to set some index of your own up so you are not surprised at some point by some package update etc. Chris also explains how to do that.

While you read this the installation should have finished. If you are on a Mac and on Leopard for some reason some packages are missing though (if you use the shipped Python 2.5). You manually have to do this:

bin/easy_install -i http://dist.repoze.org/lemonade/dev/simple PasteDeploy
bin/easy_install -i http://dist.repoze.org/lemonade/dev/simple zope.proxy

Creating some application

This is simply done by a fellow paster command:

bin/paster create -t bfg bestappever

You then should have a new directory called bestappever in your virtualenv sandbox.

We need to develop it to make it actually be usable:

cd bestappever
../bin/python setup.py develop

And you guessed right, it’s an egg.

One note: you can also go to the root of your virtualenv sandbox and call source bin/activate in order to put the virtualenv python in front of your path so that you don’t have to type ../bin/python etc. but simply python. I will do this for the rest of this blogpost.

Running your application

Great, you have an application. Now run it. That’s simply done again by using paster:

$ paster serve bestappever.ini
Starting server in PID 883.
serving on 0.0.0.0:6543 view at http://127.0.0.1:6543

And you can now access this with your browser to get some hello world screen.

Looking at the code

As it’s an MVC framework you apparently have models, views and controllers. The meaning of those seems to be slightly different from framework to framework but in repoze.bfg you will find models in models.py, views in views.py and templates (which are used by a view) in templates/. To configure which view is used for which object there is a configure.zcml which binds this all together.

For more information about the structure you can also consult the documentation.

Testing URL traversal

One thing I wanted to test was url traversal. It’s pretty simple to enable this because you mainly need to implement __getitem__ which returns some new object for some name.

So let’s change bestappever/models.py which now looks like this:


from zope.interface import Interface
from zope.interface import implements

class IMyModel(Interface):
    pass

class MyModel(object):
    implements(IMyModel)
    pass

root = MyModel()

def get_root(environ):
    return root

We add a __getitem__ to MyModel:


class MyModel(object):
    implements(IMyModel)

    def __getitem__(self, name):
        return MyModel()

So we will have an endless chain of MyModel instances. Enough for an example.

Now we have to restart the server, this time with the reload option so we don’t have to do that all the time:

paster serve bestappever.ini --reload

Now if you access e.g. http://localhost:6543/foo/bar you will still get the hello world template instead of a 404 which you should have gotten before.

Let’s change the application to actually show the path and maybe implement something similar to absolute_url().

For this we need to change the model so it knows it’s name. Additionally we make it Location aware. This means that we need to implement ILocation which basically means we need to have a __name__ and __parent__ attribute on every node. repoze.bfg helps us here in that we only need to make both attributes None for the root node and it will dynamically assign these to every object derived from that.

We can still use MyModel as root node as well, we just have to change it slightly:

from zope.location.interfaces import ILocation
class MyModel(object):

    implements(IMyModel, ILocation)

    __name__ = None
    __parent__ = None

    def __getitem__(self, name):
        return MyModel()

That way we enable the dynamic assignment of __name__ and __parent__.

We also want to change the view so it actually shows the name of the last node. To do this we change views.py:

from repoze.bfg.template import render_template_to_response

def my_view(context, request):
    return render_template_to_response('templates/mytemplate.pt',
                                       project = 'bestappever',
                                       name = context.__name__)

We are now passing the name of the node (which is stored in context) to the template. We also have to change the template of course to display it. So edit templates/mytemplate.pt to look like this:


<html xmlns="http://www.w3.org/1999/xhtml"
     xmlns:tal="http://xml.zope.org/namespaces/tal">
<head></head>
<body>
  <h1>Welcome to ${project}</h1>
  <p> I am ${name}.  </p>
</body>
</html>

(Note that this is a special dialect of TAL and that’s why ${} is possible here.)

If you start it again you will then notice that it shows the last item in the URL as well.

So let’s finish the absolute_url functionality quickly by adding this method to MyModel:

def absolute_path(self):
    p = []
    node = self
    while node.__name__ is not None:
        p.append(node.__name__)
        node = node.__parent__
    p.reverse()
    return "/".join(p)

It simply iterates up the path until the name is not set anymore. Then it reverses the list of names and joins it together to form a path. This then needs to be passed to the template in views.py:


return render_template_to_response('templates/mytemplate.pt',
                                   project = 'bestappever',
                                   abspath = context.absolute_path(),
                                   name = context.__name__)

Notice that you cannot pass path to the template as this is used already internally.

Now just change the template in bestappever/templates/mytemplate.pt slightly:

<html xmlns="http://www.w3.org/1999/xhtml"
     xmlns:tal="http://xml.zope.org/namespaces/tal">
<head></head>
<body>
  <h1>Welcome to ${project}</h1>
  <p> I am ${name}.  </p>
  <p> Path: ${abspath}.  </p>
</body>
</html>

And that’s it. Restart your server (if you haven’t used the --reload option) and enter some path like http://localhost:6543/foo/bar and you will see the result on your screen.

And that’s it for this first experiment with bfg. Please read the full documentation here. I think it’s a nice and simple framework with lots of possibilities. And it’s well documented as well.

You can find the source code (and an egg of this application but I guess that’s not of much use) also on PyPI.

Update: As wiggy points out to me there is megrok.trails which implements url pattern matching for Zope3.

Update 2: Philikon points out that there is a tutorial for grok to pass a subpath into positional arguments.

2 Kommentare » Schreibe einen Kommentar

  1. The tutorial Philipp pointed out is rather a complicated edge case if you want to do sub-path traversal on *views* (which can also be done by the simpler views on views).

    If you want to override traversal in Grok it’s usually as simple as defining a ‚traverse‘ method on a model, or alternatively using a grok.Traverser, which is a special adapter.

  2. Thanks for the update, Martijn! :)

    I knew that grok directly can do traversal as I did that already and it’s good to know that with megrok.trails it also can do url pattern matching.