[Zope3-dev] ZCML alternative

Shane Hathaway shane@zope.com
Sun, 02 Jun 2002 02:06:54 -0400


This is a multi-part message in MIME format.
--------------050302030707050301020605
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit

I'm proposing a ZCML alternative that I'd like feedback on before 
wikifying.  But first some background.

I've been thinking about the difficulty Itamar had recently.  Several 
months ago, he integrated Twisted (an Internet protocol framework) into 
Zope 3 with very little effort.  It was an exciting thing to be able to 
do.  But after we converted the startup procedure to use ZCML, Itamar 
tried again to integrate Twisted and failed, because the startup 
mechanisms were too hard to decipher.  I tried to help, but I couldn't 
wrap my head around what it would take to add a different kind of server 
either.  Itamar blamed ZCML, and I tried to convince him otherwise, but 
now I'm having second thoughts.

ZCML fulfills an important role.  As the Wiki says, it separates 
component configuration from component software.  That's a very 
powerful, important concept.  But maybe the separation shouldn't be 
enforced by having a separate language.

Some specific problems with ZCML:

- It's hard to find the code that processes a given directive.

- It's hard to tell what a ZCML file depends on without reading every line.

- You can't easily use debugging tools, like "print" statements, pdb, or 
the interactive interpreter.  We have tracebacks, but they aren't very 
clear.

- We've had to invent syntax to cover things that XML does not address, 
like Python imports and lists.

I'd like to suggest that configurations are objects.  Object 
encapsulation gives you separation.  Besides being objects, 
configurations are also components, so they implement an interface.

With that in mind, I took the JobBoard ZCML file and rewrote it as a 
Python class.  I liked the results.  I'm sure there are typos.

The approach provided easy answers to a lot of the ZCML syntax issues. 
register() is the one method required.  Namespaces weren't really 
needed, so I didn't use them.  There are groupings in the ZCML using 
comments, so I turned those into separate methods.  I used Python 
imports instead of ".xxx.".  I used lists.  I avoided using Python 
keywords as argument names through the use of positional arguments.

Comments?

Shane

--------------050302030707050301020605
Content-Type: text/plain;
 name="JobBoardConfiguration.py"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="JobBoardConfiguration.py"


from Zope.Configuration import Configuration

import IJob
import Job
import JobView
import JobList
import JobListView
import ApproveJobs


class JobBoardConfiguration (Configuration):

    def register(self):
        self.zmi = self.getConfigurator('zmi')
        self.security = self.getConfigurator('security')
        self.browser = self.getConfigurator('browser')

        self.registerContentClasses()
        self.registerViews()
        self.registerActions()


    def registerContentClasses(self):
        # JobList
        self.zmi.factoryFromClass(
            JobList.JobList, name='JobList', permission='Zope.ManageContent',
            title='JobList', description='A Job Board')
        self.security.protectClass(JobList.JobList, permission='Zope.Public')

        # Job
        self.zmi.factoryFromClass(
            Job.Job, name='Job', permission='Zope.Public',
            title="A job to be placed within a JobList",
            creation_markers=IJob.IJobCreator)
        self.security.protectClass(Job.Job, permission='Zope.Public')


    def registerViews(self):
        # pre-creation job view
        self.browser.defaultView(IJob.IJobCreator, name='create',
                                 factory=JobView.CreateJobView)
        self.security.protectClass(
            JobView.CreateJobView, permission='Zope.Public',
            names=['index', 'action', 'preview', 'cancel'])

        # JobListTraverser
        self.browser.view(IJobList.IJobList, name='_traverse',
                          factory=JobListView.JobListTraverser)

        # JobListSummaryView
        self.browser.defaultView(IJobList.IJobList, name="summary",
                                 factory=JobListView.JobListSummaryView)
        self.security.protectClass(JobListView.JobListSummaryView,
                                   permission="Zope.Public",
                                   names=['index', 'getApprovedJobs'])

        # JobView
        self.browser.view(IJob.IJob, name="JobView",
                          factory=JobView.JobView)
        self.browser.defaultView(IJob.IJob, name="display",
                                 factory=JobView.JobView)
        self.security.protectClass(
            JobView.JobView, permission="Zope.View",
            names=["index", "getSubmitter", "getSummary", "getDescription",
                   "getContact"])
        
        # JobEditView
        self.browser.view(IJob.IJob, name="edit", factory=JobView.JobEditView)
        self.security.protectClass(
            JobView.JobEditView, permission="Zope.Public",
            names=["index", "preview", "cancel", "edit", "submit"])


    def registerActions(self):
        # ApproveJobs
        self.browser.view(IJobList.IJobList, name="ApproveJobs",
                          factory=ApproveJobs.ApproveJobs)
        self.security.protectClass(
            ApproveJobs.ApproveJobs, permission="Zope.View",
            names=["index", "back", "submit", "getPendingJobs"])


--------------050302030707050301020605--