How to create a Zope resource package

Zenoss uses YUI as its JavaScript framework, and until now, we've always just committed the source to the main Zenoss source tree. Seeing as how we never actually modify it, version control seems a little overkill. Also, whenever we upgrade YUI, the changeset comprises the addition and removal of hundreds of files, which is completely unnecessary overhead. We don't need to track changes to YUI; Yahoo! already does that.

The solution was to package YUI into a Zope product that consisted of a single .egg; when the package is included, the YUI source directory is available as a resource in the browser. The same solution applies, of course, to any set of static files that merely need to be traversable—CSS, images, static data, whatever. It's trivially simple, but I couldn't find any clear tutorials. Plus I'm on a plane and don't feel like reading. Here's how to wrap any directory into a Zope product.

Throughout this, I'll be using the YUI package I created as an example. Obviously, you'd replace all instances of "zenoss" and "yui" with names appropriate to your package.


  1. Create the egg structure. Your best friend here is PasteScript (easy_install PasteScript if you don't have it already), which can generate skeleton structures from several different templates. In this case, you simply need an egg. You can either create the simplest kind of egg, or you can make a namespace package (for example, "yui" vs. "zenoss.yui"). I recommend the latter, if only to avoid conflicts with others who might have had the same idea:
    $ paster create -t basic_namespace zenoss.yui

    (If you just want a simple egg, use the basic_package template instead).

    Enter the namespace ("zenoss") and the package ("yui") and any other information you care to. You'll be asked for the basic egg metadata—license, description, author, etc. All is optional, but if you're going to distribute your package you should definitely provide this stuff. Don't worry, you can do it later by modifying setup.py.

    When you're done, you'll have an egg structure in the directory you specified (in this case, "zenoss.yui").

  2. Add the payload. Go down into the egg structure (In this example, zenoss.yui/zenoss/yui) and create the directory to hold your files:
    $ cd zenoss.yui/zenoss/yui
    $ mkdir src

    Copy any files you want available as resources into that directory. There's no need to create a yui directory inside the src directory; the entire resource directory will be available at /++resource++yui already (see below for a way to get rid of the ++resource++ cruft if so inclined), so if you created, for example, src/myfile.js, you would use the URL /++resource++yui/myfile.js to traverse to it.

    Now edit the new file zenoss.yui/MANIFEST.in (it'll be a sibling of setup.py) and add the following line:
    graft zenoss

    That tells setuptools to include every file underneath the directory zenoss; otherwise, it's likely to ignore most of the files you've added.

  3. Register src as a Zope resource directory. Edit the new file zenoss.yui/zenoss/yui/configure.zcml and add the ZCML necessary to wire the directory into Zope's publisher:

    <configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser"
    >

    <browser:resourceDirectory
    name="yui"
    directory="src"
    />

    </configure>


  4. You're done. Test. You can now build an egg for use in any Zope application. Go up to the root of the package, where setup.py is located. Build the egg:
    $ python setup.py bdist_egg

    That'll create an egg in dist. Install that in whatever way you like, as long as it ends up in the Python path of your Zope environment—manually, with easy_install, or by including it in a buildout recipe. Then, somewhere in the ZCML chain of your Zope application, include the package:
    <include package="zenoss.yui"/>

    Now fire up Zope and try the URL ++resource++yui/anyfile.js against the root object, where anyfile.js is some file directly under src in your package. As I explained earlier, you should be able to get to anything in the src directory by mirroring the filesystem path in the URL, replacing src with ++resource++yui.


I mentioned a technique for removing the necessity to include ++resource++ as part of the URL. You may not want to do this; I only did it because all my template files already referred to /yui and I didn't feel like changing them. In the same package in your Zope application in which you included zenoss.yui, create a file called YUIShortcut.py (you can put it anywhere, of course, but this keeps things organized). If you're using Five:
from Products.Five.browser import BrowserView

If Zope 3:
from zope.publisher.browser import BrowserView

Then make a view that passes traversal off to the zenoss.yui resourceDirectory:
class YUIShortcut(BrowserView):
def __getitem__(self, name):
return self.context.unrestrictedTraverse(
'++resource++yui')[name]

Then wire it to the /yui URL in configure.zcml:
<browser:page
for="*"
name="yui"
class=".YUIShortcut.YUIShortcut"
permission="zope2.Public"
>

Now you should be able to hit /yui/myfile.js instead of /++resource++yui/myfile.js.

0 comments

Post a Comment