There are two basic types of components: Utilities, and Adapters.  Other component types are specialisations of these.

Utilities

Utilities are simple plug points identified by an interface.  For example, the ZTK (zope.schema.vocabulary) defines IVocabularyFactory as a kind of utility.  When you define an instance of this interface, you give it a name- for example "fruit".  The implementation of the factory returns a list of terms, each being a tuple consisting of a value, a token and a title.  For example, ('orange', str(0), u'An Orange').  Later, when the framework needs a list of "fruit" options, perhaps whilst rendering options for a select tag, it can look for an IVocabularyFactory utility with a name of "fruit".

class Fruit(grok.GlobalUtility)
  grok.implements(IVocabularyFactory)
  grok.name('fruit')

  def __call__(self, context):
    typesOfFruit = [('orange', u'An Orange'),
                    ('pear', u"A Pear"),
                    ('banana', u"A Banana")]
    terms = [SimpleVocabulary.createTerm(value, str(token), title)
             for token, (val, title) in enumerate(typesOfFruit)]
    return SimpleVocabulary(terms)

Of course we can have all sorts of vocabularies defined and registered, but since they all implement the same interface, we can treat them in precisely the same way.  So, for example, we can have a component which renders a vocabulary as a dropdown list, and that component will work for all vocabularies.  Another component might render vocabularies as a horizontal radio button list.

Utilities are singletons.  For global utilities, there is only ever one instance of the object.  Local utilities are object instances installed within a site manager, and will be specifically available for the currently active site.

Python class advisors

You will notice an unfamiliar syntax being used to specify that the Fruit class implements the IVocabularyFactory  interface. 

grok.implements(IVocabularyFactory)
grok.name('fruit')

Such directives are Grok's way of removing the need for registering utilities and views in ZCML (Zope Configuration Markup Language).  Instead of maintaining configuration outside of Python, it becomes part of specifying your classes.  Another way Grok manages the ZCA configuration is through decorators, although this usage may be minimal.

There are a good number of such Grok directives registered, and the way they work is based upon some trickery implemented in the zope.interface.advice module.  You can read more about class advisors in a rather old blog on the subject.

NOTE: Decorators are often used by other Python frameworks for route configuration.  ZCML for configuration is reminiscent of the "old way" for ASP route configuration, while decorators are similar to the "new way" for configuring ASP routes.  This similarity is misleading.  Zope & Grok do not configure routes in a comparable way using decorators, but may use decorators to configure Zope adapters and utilities.

Adapters

Adapters are things which make one interface look like another.  Another way of saying it, is that an Adapter is a Python callable which takes one thing as an argument, and returns another kind of thing.

MultiAdapter is a Python callable which... wait for it ... takes more than one kind of thing and turns it into another kind of thing.

The concept is so simple as to be almost stupid. This may be the reason some programmers think the ZCA is complicated: they keep saying to themselves "it can't be that simple!" and overthinking it.

The difference though, is that the ZCA provides a way to register such callable objects, by name if necessary, and to find them later according to argument type and/or name.  That is where the power and utility of the ZCA comes from.

The ZCA documentation goes on about UK wall sockets and USA wall sockets and a physical adapter that makes a UK socket look like a USA one.  Good enough as analogies go.

In the case of the ZCA, ZTK and Grok, a real world example may be better than an analogy.

A Zope schema is a kind of Interface which defines fields as attributes.  These fields have a type such as IntTextBoolean etc.  Schemas may be used to automatically generate add, edit or view forms for classes which implement the schema.  So for example,

class ImySchema(Interface):
  name = TextLine(title=u'Name:')
  surname = TextLine(title=u'Surname:')
  ...

We can then define a model which implements the schema:

class MyModel(grok.Model):
  grok.implements(ImySchema)
  ...

A default view for this model could be a form:

class Index(grok.EditForm):
  grok.context(MyModel)

Now when we navigate to an instance of MyModel, we will see an automatically generated view of the fields in the schema. 

Yes, it really is that simple.

In Detail

The model above implements ImySchema, and because ImySchema defines the name and surname fields, the view can automatically generate a form for editing an object of that type.

There are other approches as well.  Sometimes a model does not directly implement the interface you need, but an object can be derived from the model instance which does.  For example:

class MyModel(grok.Model):

    def getObj(self):
        return db.ANewObjectInstanceOfImySchema()
    ...

Then we could use an adapter to make MyModel actually look like an ImySchema:

class MyModelSchema(grok.Adapter):
  grok.context(MyModel)
  grok.implements(ImySchema)
  name = u''
  surname = u''

  def __init__(self, context):
    super(MyModelSchema, self).__init__(context)
    ob = context.getObj() 
    self.name, self.surname = ob.name, ob.surname

The above adapter adapts MyModel, which means that the context argument in the __init__(self, context) is an instance of MyModel.  The adapter calls context.getObj() to retrieve an instance of the ImySchema, and then replaces it's own attributes with the ones from the model. The adapter implements ImySchema by defining the name and surname attributes. One could define these attributes as python properties in order to handle updates/changes to these fields.

A more transparent way for the adapter to work would be to implement __new__() instead of __init__()- a factory.

class MyModelSchema(grok.Adapter):
  grok.context(MyModel)
  grok.implements(ImySchema)

  def __new__(cls, context):
    return context.getObj() 

Doing it this way exposes the object (as returned by context.getObj()directly to the view, where in the previous example the view would have seen an instance of the adapter class.  There is a subtle difference.

If we then define Index() as follows:

class Index(grok.DisplayForm):
  grok.context(MyModel)
  form_fields = grok.Fields(ImySchema)

the framework will recognise that the fields defined don't match the context, and will search for a suitable adapter which can make a MyModel instance look like an ImySchema.  If it finds such an adapter, the form fields will be mapped to the attributes of whichever object the adapter returns.

Grok 4 Noobs

What types of components are defined by the ZCA?