[CMF-checkins] CVS: CMF/CMFSetup - README.txt:1.1 __init__.py:1.1 registry.py:1.1 utils.py:1.1 version.txt:1.1

Tres Seaver tseaver at zope.com
Tue May 11 21:35:50 EDT 2004


Update of /cvs-repository/CMF/CMFSetup
In directory cvs.zope.org:/tmp/cvs-serv23126

Added Files:
	README.txt __init__.py registry.py utils.py version.txt 
Log Message:
 - Add refactored setup step registry and tests.


=== Added File CMF/CMFSetup/README.txt ===
CMFSetup Product README

  Overview

    This product provides a mini-framework for expressing the configured
    state of a CMF Site as a set of filesystem artifacts.  These artifacts
    consist of declarative XML files, which spell out the configuration
    settings for each tool, and supporting scripts / templates, in their
    "canonical" filesystem representations.

  Glossary

    Site -- 
      The instance in the Zope URL space which defines a "zone of service"
      for a set of CMF tools.

    Profile --
      A "preset" configuration of a site, defined on the filesystem

    Snapshot --
      "Frozen" site configuration, captured within the setup tool

    "dotted name" --
      The Pythonic representation of the "path" to a given function /
      module, e.g. 'Products.CMFCore.utils.getToolByName'.

  Extending The Tool

    Third-party products extend the tool by registering handlers for
    import / export of their unique tools.


=== Added File CMF/CMFSetup/__init__.py ===
""" CMFConfiguration product initialization

$Id: __init__.py,v 1.1 2004/05/12 01:35:47 tseaver Exp $
"""

#
#   Export API for third-party products (XXX: not yet!)
#
#from SiteConfiguration import registerSetupStep
#from SiteConfiguration import registerExportScript


def initialize(context):

    return # XXX comment out the rest

    from SiteConfiguration import SiteConfigurationTool
    from SiteConfiguration import addConfiguredSiteForm
    from SiteConfiguration import addConfiguredSite
    from SiteConfiguration import listPaths

    TOOLS_AND_ICONS = ( (SiteConfigurationTool, 'www/tool.png'),)

    # Add SiteConfiguration constructor.
    # We specify meta_type and interfaces because we don't actually register a
    # class here, only a constructor.
    #
    # Note that the 'listPaths' bit is a hack to get that
    # object added to the factory dispatcher, so that it will be available
    # within the 'addConfiguredSiteForm' template.
    #
    context.registerClass( meta_type='Configured CMF Site'
                         , permission='Create Configured CMF Site'
                         , constructors=( addConfiguredSiteForm
                                        , addConfiguredSite
                                        , listPaths #  WTF?
                                        )
                         , interfaces=None
                         )

    from Products.CMFCore.utils import ToolInit, registerIcon
    from Products.CMFCore.utils import ContentInit

    ToolInit( 'CMFSetup Tools'
            , tools=[ x[ 0 ] for x in TOOLS_AND_ICONS ]
            , product_name='Setup'
            , icon=None
            ).initialize( context )

    for tool, icon in TOOLS_AND_ICONS:
        registerIcon( tool, icon, globals() )


    # XXX:  This is *all* policy, and belongs in an XML file!

    # Install setup steps and export scripts
    from SetupSteps import installTools
    from SetupSteps import configureCookieCrumbler

    registerSetupStep('installTools', '2004/05/10-01',
                    installTools)

    registerSetupStep('configureCookieCrumbler', '2004/05/10-01',
                    configureCookieCrumbler)

    from CatalogIndexesConfig import configureCatalogIndexes
    from CatalogIndexesConfig import exportCatalogIndexes

    registerSetupStep('configureCatalogIndexes', '2004/05/10-01',
                      configureCatalogIndexes, ('installTools',))
    registerExportScript('exportCatalogIndexes', exportCatalogIndexes)

    from SkeletonBuilder import buildFolderStructure
    from SkeletonBuilder import generateSkeleton

    registerSetupStep('buildFolderStructure', '2004/05/10-01',
                      buildFolderStructure, ( 'installTypes'
                                            , 'configureSkins'
                                            , 'configureWorkflows'
                                            # Because the folder buildout will
                                            # need to know whomever is the
                                            # author of certain content:
                                            , 'installMembershipToolContent'
                                            ))
    registerExportScript('generateSkeleton', generateSkeleton)

    from ActionsConfig import configureToolGeneratedActions
    from ActionsConfig import exportToolGeneratedActions

    registerSetupStep('configureToolGeneratedActions', '2004/05/10-01',
                      configureToolGeneratedActions)
    registerExportScript('exportToolGeneratedActions',
                         exportToolGeneratedActions)

    from ContentTypesConfig import installTypes, exportTypes
    registerSetupStep('installTypes', '2004/05/10-01', installTypes)
    registerExportScript('exportTypes', exportTypes)

    from ActionIconsConfig import configureActionIcons, exportActionIcons
    registerSetupStep('configureActionIcons', '2004/05/10-02',
                      configureActionIcons, ('installTools',))
    registerExportScript('exportActionIcons', exportActionIcons)

    from SkinsConfig import configureSkins, exportSkins
    registerSetupStep('configureSkins', '2004/05/10-01', configureSkins)
    registerExportScript('exportSkins', exportSkins)

    from WorkflowConfig import configureWorkflows, exportWorkflows
    registerSetupStep('configureWorkflows', '2004/05/10-01',
                      configureWorkflows, ('installTypes',))
    registerExportScript('exportWorkflows', exportWorkflows)

    from RolesPermissionsConfig import configureRolesPermissions
    from RolesPermissionsConfig import exportRolesPermissions
    registerSetupStep('configureRolesPermissions', '2004/05/10-01',
                      configureRolesPermissions)
    registerExportScript('exportRolesPermissions', exportRolesPermissions)

    from MembershipConfig import installMembershipToolContent
    from MembershipConfig import exportMembershipToolContent
    registerSetupStep('installMembershipToolContent', '2004/05/10-01',
                      installMembershipToolContent, ('installTools',
                                                     'installTypes'))
    registerExportScript('exportMembershipToolContent',
                         exportMembershipToolContent)

    from RemoveTools import removeInstalledTools
    registerSetupStep('removeInstalledTools', '2004/05/10-1',
                      removeInstalledTools, ('installTools',))

    from MemberDataConfig import configureMemberDataTool
    from MemberDataConfig import exportMemberDataToolConfig
    registerSetupStep('configureMemberDataTool', '2004/05/10-01',
                      configureMemberDataTool, ())
    registerExportScript('exportMemberDataToolConfig',
                         exportMemberDataToolConfig)


=== Added File CMF/CMFSetup/registry.py ===
""" Classes:  SetupStepRegistry

$Id: registry.py,v 1.1 2004/05/12 01:35:47 tseaver Exp $
"""
import re
from xml.sax import parseString
from xml.sax.handler import ContentHandler

from AccessControl import ClassSecurityInfo
from Acquisition import Implicit
from Globals import InitializeClass
from Products.PageTemplates.PageTemplateFile import PageTemplateFile

from permissions import ManagePortal
from utils import _xmldir
from utils import _getDottedName
from utils import _resolveDottedName

class SetupStepRegistry( Implicit ):

    """ Manage knowledge about steps to create / configure site.

    o Steps are composed together to define a site profile.
    """
    security = ClassSecurityInfo()

    def __init__( self ):

        self._clear()

    security.declareProtected( ManagePortal, 'listSteps' )
    def listSteps( self ):

        """ Return a sequence of IDs of registered steps.

        o Order is not significant.
        """
        return self._steps.keys()

    security.declareProtected( ManagePortal, 'sortSteps' )
    def sortSteps( self ):

        """ Return a sequence of step IDs, sorted topologically by dependency.
        """
        return self._computeTopologicalSort()

    security.declareProtected( ManagePortal, 'checkComplete' )
    def checkComplete( self ):

        """ Return a sequence of ( node, edge ) tuples for unsatisifed deps.
        """
        result = []
        seen = {}

        graph = self._computeTopologicalSort()

        for node in graph:

            dependencies = self.getStepMetadata( node )[ 'dependencies' ]

            for dependency in dependencies:

                if seen.get( dependency ) is None:
                    result.append( ( node, dependency ) )

            seen[ node ] = 1

        return result

    security.declareProtected( ManagePortal, 'getStepMetadata' )
    def getStepMetadata( self, key, default=None ):

        """ Return a mapping of metadata for the step identified by 'key'.

        o Return 'default' if no such step is registered.

        o The 'callable' metadata is available via 'getStep'.
        """
        result = {}

        info = self._steps.get( key )

        if info is None:
            return default

        for key, value in info.items():

            if key == 'callable':
                result[ key ] = _getDottedName( value )
            else:
                result[ key ] = value

        return result

    security.declareProtected( ManagePortal, 'listStepMetadata' )
    def listStepMetadata( self ):

        """ Return a sequence of mappings describing registered steps.

        o Mappings will be ordered topologically (most-dependent last).
        """
        return [ self.getStepMetadata( x ) for x in self.sortSteps() ]

    security.declareProtected( ManagePortal, 'exportAsXML' )
    def exportAsXML( self ):

        """ Return a round-trippable XML representation of the registry.

        o 'callable' values are serialized using their dotted names.
        """
        return self._exportTemplate()

    security.declarePrivate( 'getStep' )
    def getStep( self, key, default=None ):

        """ Return the callable for the step identified by 'key'.

        o Return 'default' if no such step is registered.
        """
        marker = object()
        info = self._steps.get( key, marker )

        if info is marker:
            return default

        return info[ 'callable' ]
    
    security.declarePrivate( 'registerStep' )
    def registerStep( self
                    , id
                    , version
                    , callable
                    , dependencies=()
                    , description=None
                    ):
        """ Register a setup step.

        o 'id' is a unique name for this step,

        o 'version' is a string for comparing versions, it is preferred to
          be a yyyy/mm/dd-ii formatted string (date plus two-digit
          ordinal).  when comparing two version strings, the version with
          the lower sort order is considered the older version.
          
          - Newer versions of a step supplant older ones.

          - Attempting to register an older one after a newer one results
            in a KeyError.

        o 'callable' is the setup code, which is passed a context object when
          called, and is expected to return a user-friendly message as to
          what happened.  The context object provides access to data files
          and the portal object.

        o 'dependencies' is a tuple of step ids which have to run before
          this step in order to be able to run at all. Registration of
          steps that have unmet dependencies are deferred until the
          dependencies have been registered.

        o 'description' defaults to the first line of the function doc
          string, and can be used in a display enumerating steps. If the
          docstring is also empty, the id of the step is used as a final
          fallback.
        """
        already = self.getStepMetadata( id )

        if already and already[ 'version' ] >= version:
            raise KeyError( 'Existing registration for step %s, version %s'
                          % ( id, already[ 'version' ] ) )

        info = { 'id'           : id
               , 'version'      : version
               , 'callable'     : callable
               , 'dependencies' : dependencies
               , 'description'  : description
               }

        self._steps[ id ] = info

    security.declarePrivate( 'importFromXML' )
    def importFromXML( self, text ):

        """ Parse 'text' into a clean registry.
        """
        self._clear()

        reader = getattr( text, 'read', None )

        if reader is not None:
            text = reader()

        parseString( text, _SetupStepRegistryParser( self ) )

    #
    #   Helper methods
    #
    security.declarePrivate( '_clear' )
    def _clear( self ):

        self._steps = {}

    security.declarePrivate( '_computeTopologicalSort' )
    def _computeTopologicalSort( self ):

        result = []

        graph = [ ( x[ 'id' ], x[ 'dependencies' ] )
                    for x in self._steps.values() ]

        for node, edges in graph:

            after = -1

            for edge in edges:

                if edge in result:
                    after = max( after, result.index( edge ) )

            result.insert( after + 1, node )

        return result

    security.declarePrivate( '_exportTemplate' )
    _exportTemplate = PageTemplateFile( 'ssrExport.xml', _xmldir )

InitializeClass( SetupStepRegistry )

class _SetupStepRegistryParser( ContentHandler ):

    security = ClassSecurityInfo()
    security.declareObjectPrivate()
    security.setDefaultAccess( 'deny' )

    _WHITESPACE = re.compile( r'\s*' )

    def __init__( self, registry, encoding='latin-1' ):

        self._registry = registry
        self._encoding = encoding
        self._started = False
        self._pending = None

    def startElement( self, name, attrs ):

        if name == 'setup-steps':

            if self._started:
                raise ValueError, 'Duplicated setup-steps element: %s' % name

            self._started = True

        elif name == 'setup-step':

            if self._pending is not None:
                raise ValueError, 'Cannot nest setup-step elements'

            self._pending = dict( [ ( k, v.encode( self._encoding ) )
                                    for k, v in attrs.items() ] )

        elif name == 'dependency':

            if not self._pending:
                raise ValueError, 'Dependency outside of step'

            depended = attrs['step'].encode('latin-1')
            self._pending.setdefault( 'dependencies', [] ).append( depended )

        else:
            raise ValueError, 'Unknown element %s' % name

    def characters( self, content ):

        if self._pending is not None:
            content = content.encode( self._encoding )
            self._pending.setdefault( 'description', [] ).append( content )

    def endElement(self, name):

        if name == 'setup-steps':
            pass

        elif name == 'setup-step':

            if self._pending is None:
                raise ValueError, 'No pending step!'

            id = self._pending[ 'id' ]
            version = self._pending[ 'version' ]
            callable = _resolveDottedName( self._pending[ 'callable' ] )

            dependencies = tuple( self._pending.get( 'dependencies', () ) )
            description = ''.join( self._pending.get( 'description', [] ) )

            self._registry.registerStep( id=id
                                       , version=version
                                       , callable=callable
                                       , dependencies=dependencies
                                       , description=description
                                       )
            self._pending = None

InitializeClass( _SetupStepRegistryParser )


=== Added File CMF/CMFSetup/utils.py ===
""" CMFSetup product utilities

$Id: utils.py,v 1.1 2004/05/12 01:35:47 tseaver Exp $
"""
import os

from Globals import package_home

_pkgdir = package_home( globals() )
_wwwdir = os.path.join( _pkgdir, 'www' )
_datadir = os.path.join( _pkgdir, 'data' )
_xmldir = os.path.join( _pkgdir, 'xml' )

def _getDottedName( callable ):

    return '%s.%s' % ( callable.__module__, callable.__name__ )

def _resolveDottedName( dotted ):

    parts = dotted.split( '.' )

    if not parts:
        raise ValueError, "incomplete dotted name: %s" % dotted

    parts_copy = parts[:]

    while parts_copy:
        try:
            module = __import__( '.'.join( parts_copy ) )
            break

        except ImportError:

            del parts_copy[ -1 ]

            if not parts_copy:
                raise

    parts = parts[ 1: ] # Funky semantics of __import__'s return value

    obj = module

    for part in parts:
        obj = getattr( obj, part )

    return obj



=== Added File CMF/CMFSetup/version.txt ===
0.9




More information about the CMF-checkins mailing list