[Grok-dev] Bonked

Kevin Teague kevin at bud.ca
Sat Dec 13 18:17:06 EST 2008


I've made an attempt at a plug-in system for the GUM app that I wrote (http://www.bcgsc.ca/platform/bioinfo/software/gum 
). It's possible to create what you describe - while my plug-in system  
meets my needs, I can't promise it's the cleanest way of  doing  
things, but I'll describe what I wanted and how I did it ...

Goal:

Allow add-ons to be managed as seperate Python projects, packagable as  
eggs, etc. When an add-on package is grokked, then the application  
presents a UI for installing/uninstalling that add-on.

Initial use-case:

When users are added to specific LDAP Groups using GUM, then a JIRA  
web service should be invoked which adds that user account to JIRA  
(not actually sure if this is 100% necessary with newer JIRA installs  
now, but our JIRA deployment was done when it's integration with LDAP  
was quite limited).

Application support:

The GUM app provides an 'extensions' container which contains the  
installed add-on instances. This part was fairly straight-forward, the  
Extensions container class contains a method to query if a given add- 
on is installed or not, so that the UI can present a list of installed  
and uninstalled extensions.

class Extensions(grok.Container):

     def installed(self):
         "List of installed extensions"
         return self.values()

     def is_installed(self, ext):
         "Return True if an extension is already installed"
         for installed_ext in self.values():
             if type(installed_ext) == type(ext):
                 return True
         return False

     def available(self):
         "List of uninstalled extensions"
         return [
             ext_maker[1].new()
             for ext_maker in component.getUtilitiesFor(IExtensionMaker)
             if not self.is_installed(ext_maker[1].new())
         ]

Then, in a Python project seperate from GUM one can write an  
extension. Since available add-ons are discovered using  
"component.getUtilitiesFor(IExtensionMaker)" to make an add-on  
available, the add-on package just needs to register a global utility  
that provides the IExtensionMaker interface. In the add-on package  
this looks something like:

import grok
from gum.interfaces import IExtension, IExtensionMaker

class ExtensionMaker(grok.GlobalUtility):
     grok.implements(IExtensionMaker)
     grok.name('jira')

     def new(self):
         return Extension()

     def load(self):
         return grok.getSite()['extensions']['jira']

class Extension(grok.Container):
     grok.implements(IExtensionSchema)
     grok.name('jira')

     title = u'JIRA Account Integrator'
     service_url = u''
     ldap_groupname = u''
     username = u''
     password = u''

     def __init__(self):
         self.changelog = PersistentList()

     def add_change(self, change_type, message):
         "Appends to the changelog, and also limits size to 50 changes"
         self.changelog.append(Change(change_type, message))
         if len(self.changelog) > 50:
             self.changelog = self.changelog[1:]

Finally, because I wanted add-ons to be able to provide their own  
Views for custom UI features, I also depend upon the master layout of  
the GUM application in the templates directories for the add-ons:

<html metal:use-macro="context/@@layout/macros/page">
  ... custom template
</html>

In order to make this work between Python projects, you need to use a  
DirectoryResource, which allows you to share static elements such as  
JavaScript, images and CSS. This was added to grokcore.view 1.2 (which  
is in trunk and will be in Grok 1.0), I've pulled it into Grok 0.14  
with:

[versions]
grokcore.view = 1.2 # DirectoryResource - to be included in next Grok  
release

And created a directoryresource in gum/layout.py:

class Resources(grokcore.view.DirectoryResource):
     grokcore.view.name('resources')
     grokcore.view.path('resources')

Finally, the question of deployment. In a normal Grok application,  
your app provides it's own buildout.cfg file, and specifies that it  
depends upon Grok and other Python projects. An add-on depends upon  
your application though. But you can't put a custom buildout.cfg in  
each add-ons package, since what you want is to just maintain a list  
of installed add-ons as part of the main application deployment. For a  
GUM deployment I just have a custom buildout.cfg file which extends  
the basic one with:

[buildout]
develop = . src/gum-gsc-systems
extends = base.cfg

[app]
eggs = gum
              gum-gsc-systems

This lives in the root of GUM deployment, but since it's specific to a  
particular deployment I don't store this in version control. I just  
name the file 'production.cfg" and run './bin/buildout -c  
production.cfg".

So that's pretty much how I approached the add-on problem. The key  
being that the main application has an IExtensionMaker and IExtension  
interfaces, and each add-on then implements these interfaces as global  
utilities. Then I query the component architecture to see what's being  
registered via grokking and build the UI based on that.

Again, I can't promise that I've got the cleanest or most robust  
solution, but I did write something that worked for me :)





More information about the Grok-dev mailing list