[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