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.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").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.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>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.