You are here: Home Articles Grok (and martian) rocks
OpenID Log in


Grok (and martian) rocks

by Martin Aspeli last modified Jul 02, 2008 05:57 AM

Even if you're writing Plone code

The fine folks over at the Grok project have taken it upon themselves to release the basic components of the Grok framework - those that "grok" your package for components and things to register - so that they can be used in other systems. The library that supports writing grokkers is called Martian. A package called grokcore.component contains the wiring for using Martian in Zope 2 land.

For grokcore.component to be able to grok your package, you need to have the following in your configure.zcml:


   <grok:grok package="." />


The idea is that you shouldn't need too much else in you configure.zcml after this if your code is self-describing. Here is an example of an adapter registration, for example:

import grokcore.component

class MyAdapter(grokcore.component.Adapter):

    # No need to implement __init__, it's already
    # provided by the base class.

    def do_something(self):

When the grokker sees this, it knows that is is an adapter (because of the base class) and it knows that it adapts a context of type IAdaptedContext and provide an interface IMyInterface. There is no need for a separate ZCML line to tell the Component Architecture about this component.

grokcore.component provides grokkers for the basic components, like adapters, utilities and subscribers. It works in Zope 3 as well as in Zope 2. All you have to do is depend on it, make sure that its meta.zcml file is loaded, and use it as above.

Writing custom grokkers

What got me really excited, though, is how easy it is to write your own grokkers. For example, in Dexterity we wanted to be able to initialise a class with fields declared in a schema interface if those were not already provided by the class. The syntax we were aiming for was:

from zope.interface import implements, Interface
from zope import schema

from plone.dexterity import api

class IMyType(api.Schema):
    title = schema.TextLine(title=u"Title")
    body = schema.Text(title=u"Body text",
                       default=u"Body text goes here")

class MyType(api.Item):

After grokking, MyType should be registered as a Zope 2 content class (in the same way that the <five:registerClass /> ZCML directive would do) with the given meta type and add permission, and it should have the attributes 'title' and 'body', even if these did not exist already, initialised to default values.

The grokker looks like this:

import martian

from zope.interface import implementedBy
from zope.schema import getFieldsInOrder

from plone.supermodel.directives import Schema
from plone.dexterity.content import DexterityContent

from Products.Five.fiveconfigure import registerClass

class meta_type(martian.Directive):
    """Directive used to specify the meta type of an 
    scope = martian.CLASS
    store = martian.ONCE
    default = None
    validate = martian.validateText
    def factory(self, meta_type):
        return meta_type

class add_permission(martian.Directive):
    """Directive used to specify the add permission of 
       an object
    scope = martian.CLASS
    store = martian.ONCE
    default = u"cmf.AddPortalContent"
    validate = martian.validateText
    def factory(self, permission):
        return permission

class ContentGrokker(martian.ClassGrokker):

    def execute(self, class_, config, meta_type, add_permission, **kw):
        # 1. Register class if a meta type was specified. Most types
        # will probably not need this.
        if meta_type is not None:
            registerClass(config, class_, meta_type, add_permission)
        # 2. Initialise properties from schema to their default values if 
        # they are not already on the class.
        for iface in implementedBy(class_).flattened():
            if iface.extends(Schema):
                for name, field in getFieldsInOrder(iface):
                    if not hasattr(class_, name):
                        setattr(class_, name, field.default)

        return True

We first define to two directives, meta_type() and add_permission(). Martian has base classes to make this easy.

We then declare the grokker itself, specifying that it should work on classes deriving from DexterityContent, and that it supports the aforementioned meta_type() and add_permission() directives. When the class is grokked (at ZCML parse time), the execute() method is called, and is passed the class we grokked, the zope.configuration config object, and the value of our two directives. We then do our work and return True if the class was successfully grokked.

For this to work, we need to Grok the package providing the custom grokker (grokkit?). That is done in a meta.zcml file for plone.dexterity, which looks like this:


    <!-- Load ZCML from Grok and grok our grokkers -->
    <include package="grokcore.component" file="meta.zcml" />
    <grok:grok package=".directives" />


Martian has decent documentation on its PyPI page if you want to learn more.

Obviously, Martian is a tool for framework authors. Most people will never write a grokker. But I'm really excited about how easy it is to write them. There is a definite risk of over-use leading to too many things happening implicitly, but used wisely, the patterns that Grok has espoused could help make Plone easier to code for.

Some grokkers that I'd like to see include:

  • Views with and without templates (mostly done, I think)
  • Viewlets (ditto)
  • Portlets (ditto)
  • Archetypes content classes (to facilitate the type registration and setting security)
This, plus the basic Zope 3 component types already supported by grokcore.component, should cover the bulk of custom code that people write in their Plone products.
Document Actions
Plone Book
Professional Plone 4 Development

I am the author of a book called Professional Plone Development. You can read more about it here.

About this site

This Plone site is kindly hosted by: 

Six Feet Up