vendor/CMF/1.6-r41367/GenericSetup

view registry.py @ 0:d62b03aaa782

CMF 1.6 r41367 snapshot.
author fguillaume
date Thu, 19 Jan 2006 16:49:24 +0000
parents
children
line source
1 ##############################################################################
2 #
3 # Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
4 #
5 # This software is subject to the provisions of the Zope Public License,
6 # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10 # FOR A PARTICULAR PURPOSE.
11 #
12 ##############################################################################
13 """ Classes: ImportStepRegistry, ExportStepRegistry
15 $Id: registry.py 40339 2005-11-23 11:43:31Z yuppie $
16 """
18 from xml.sax import parseString
20 from AccessControl import ClassSecurityInfo
21 from Acquisition import Implicit
22 from Globals import InitializeClass
23 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
24 from zope.interface import implements
26 from interfaces import BASE
27 from interfaces import IImportStepRegistry
28 from interfaces import IExportStepRegistry
29 from interfaces import IToolsetRegistry
30 from interfaces import IProfileRegistry
31 from permissions import ManagePortal
32 from utils import HandlerBase
33 from utils import _xmldir
34 from utils import _getDottedName
35 from utils import _resolveDottedName
36 from utils import _extractDocstring
39 class ImportStepRegistry( Implicit ):
41 """ Manage knowledge about steps to create / configure site.
43 o Steps are composed together to define a site profile.
44 """
45 implements(IImportStepRegistry)
47 security = ClassSecurityInfo()
49 def __init__( self ):
51 self.clear()
53 security.declareProtected( ManagePortal, 'listSteps' )
54 def listSteps( self ):
56 """ Return a sequence of IDs of registered steps.
58 o Order is not significant.
59 """
60 return self._registered.keys()
62 security.declareProtected( ManagePortal, 'sortSteps' )
63 def sortSteps( self ):
65 """ Return a sequence of registered step IDs
67 o Sequence is sorted topologically by dependency, with the dependent
68 steps *after* the steps they depend on.
69 """
70 return self._computeTopologicalSort()
72 security.declareProtected( ManagePortal, 'checkComplete' )
73 def checkComplete( self ):
75 """ Return a sequence of ( node, edge ) tuples for unsatisifed deps.
76 """
77 result = []
78 seen = {}
80 graph = self._computeTopologicalSort()
82 for node in graph:
84 dependencies = self.getStepMetadata( node )[ 'dependencies' ]
86 for dependency in dependencies:
88 if seen.get( dependency ) is None:
89 result.append( ( node, dependency ) )
91 seen[ node ] = 1
93 return result
95 security.declareProtected( ManagePortal, 'getStepMetadata' )
96 def getStepMetadata( self, key, default=None ):
98 """ Return a mapping of metadata for the step identified by 'key'.
100 o Return 'default' if no such step is registered.
102 o The 'handler' metadata is available via 'getStep'.
103 """
104 result = {}
106 info = self._registered.get( key )
108 if info is None:
109 return default
111 return info.copy()
113 security.declareProtected( ManagePortal, 'listStepMetadata' )
114 def listStepMetadata( self ):
116 """ Return a sequence of mappings describing registered steps.
118 o Mappings will be ordered alphabetically.
119 """
120 step_ids = self.listSteps()
121 step_ids.sort()
122 return [ self.getStepMetadata( x ) for x in step_ids ]
124 security.declareProtected( ManagePortal, 'generateXML' )
125 def generateXML( self ):
127 """ Return a round-trippable XML representation of the registry.
129 o 'handler' values are serialized using their dotted names.
130 """
131 return self._exportTemplate()
133 security.declarePrivate( 'getStep' )
134 def getStep( self, key, default=None ):
136 """ Return the IImportPlugin registered for 'key'.
138 o Return 'default' if no such step is registered.
139 """
140 marker = object()
141 info = self._registered.get( key, marker )
143 if info is marker:
144 return default
146 return _resolveDottedName( info[ 'handler' ] )
148 security.declarePrivate( 'registerStep' )
149 def registerStep( self
150 , id
151 , version
152 , handler
153 , dependencies=()
154 , title=None
155 , description=None
156 ):
157 """ Register a setup step.
159 o 'id' is a unique name for this step,
161 o 'version' is a string for comparing versions, it is preferred to
162 be a yyyy/mm/dd-ii formatted string (date plus two-digit
163 ordinal). when comparing two version strings, the version with
164 the lower sort order is considered the older version.
166 - Newer versions of a step supplant older ones.
168 - Attempting to register an older one after a newer one results
169 in a KeyError.
171 o 'handler' should implement IImportPlugin.
173 o 'dependencies' is a tuple of step ids which have to run before
174 this step in order to be able to run at all. Registration of
175 steps that have unmet dependencies are deferred until the
176 dependencies have been registered.
178 o 'title' is a one-line UI description for this step.
179 If None, the first line of the documentation string of the handler
180 is used, or the id if no docstring can be found.
182 o 'description' is a one-line UI description for this step.
183 If None, the remaining line of the documentation string of
184 the handler is used, or default to ''.
185 """
186 already = self.getStepMetadata( id )
188 if already and already[ 'version' ] > version:
189 raise KeyError( 'Existing registration for step %s, version %s'
190 % ( id, already[ 'version' ] ) )
192 if title is None or description is None:
194 t, d = _extractDocstring( handler, id, '' )
196 title = title or t
197 description = description or d
199 info = { 'id' : id
200 , 'version' : version
201 , 'handler' : _getDottedName( handler )
202 , 'dependencies' : dependencies
203 , 'title' : title
204 , 'description' : description
205 }
207 self._registered[ id ] = info
209 security.declarePrivate( 'parseXML' )
210 def parseXML( self, text, encoding=None ):
212 """ Parse 'text'.
213 """
214 reader = getattr( text, 'read', None )
216 if reader is not None:
217 text = reader()
219 parser = _ImportStepRegistryParser( encoding )
220 parseString( text, parser )
222 return parser._parsed
224 security.declarePrivate( 'clear' )
225 def clear( self ):
227 self._registered = {}
229 #
230 # Helper methods
231 #
232 security.declarePrivate( '_computeTopologicalSort' )
233 def _computeTopologicalSort( self ):
235 result = []
237 graph = [ ( x[ 'id' ], x[ 'dependencies' ] )
238 for x in self._registered.values() ]
240 for node, edges in graph:
242 after = -1
244 for edge in edges:
246 if edge in result:
247 after = max( after, result.index( edge ) )
249 result.insert( after + 1, node )
251 return result
253 security.declarePrivate( '_exportTemplate' )
254 _exportTemplate = PageTemplateFile( 'isrExport.xml', _xmldir )
256 InitializeClass( ImportStepRegistry )
259 class ExportStepRegistry( Implicit ):
261 """ Registry of known site-configuration export steps.
263 o Each step is registered with a unique id.
265 o When called, with the portal object passed in as an argument,
266 the step must return a sequence of three-tuples,
267 ( 'data', 'content_type', 'filename' ), one for each file exported
268 by the step.
270 - 'data' is a string containing the file data;
272 - 'content_type' is the MIME type of the data;
274 - 'filename' is a suggested filename for use when downloading.
276 """
277 implements(IExportStepRegistry)
279 security = ClassSecurityInfo()
281 def __init__( self ):
283 self.clear()
285 security.declareProtected( ManagePortal, 'listSteps' )
286 def listSteps( self ):
288 """ Return a list of registered step IDs.
289 """
290 return self._registered.keys()
292 security.declareProtected( ManagePortal, 'getStepMetadata' )
293 def getStepMetadata( self, key, default=None ):
295 """ Return a mapping of metadata for the step identified by 'key'.
297 o Return 'default' if no such step is registered.
299 o The 'handler' metadata is available via 'getStep'.
300 """
301 info = self._registered.get( key )
303 if info is None:
304 return default
306 return info.copy()
308 security.declareProtected( ManagePortal, 'listStepMetadata' )
309 def listStepMetadata( self ):
311 """ Return a sequence of mappings describing registered steps.
313 o Steps will be alphabetical by ID.
314 """
315 step_ids = self.listSteps()
316 step_ids.sort()
317 return [ self.getStepMetadata( x ) for x in step_ids ]
319 security.declareProtected( ManagePortal, 'generateXML' )
320 def generateXML( self ):
322 """ Return a round-trippable XML representation of the registry.
324 o 'handler' values are serialized using their dotted names.
325 """
326 return self._exportTemplate()
328 security.declarePrivate( 'getStep' )
329 def getStep( self, key, default=None ):
331 """ Return the IExportPlugin registered for 'key'.
333 o Return 'default' if no such step is registered.
334 """
335 marker = object()
336 info = self._registered.get( key, marker )
338 if info is marker:
339 return default
341 return _resolveDottedName( info[ 'handler' ] )
343 security.declarePrivate( 'registerStep' )
344 def registerStep( self, id, handler, title=None, description=None ):
346 """ Register an export step.
348 o 'id' is the unique identifier for this step
350 o 'step' should implement IExportPlugin.
352 o 'title' is a one-line UI description for this step.
353 If None, the first line of the documentation string of the step
354 is used, or the id if no docstring can be found.
356 o 'description' is a one-line UI description for this step.
357 If None, the remaining line of the documentation string of
358 the step is used, or default to ''.
359 """
360 if title is None or description is None:
362 t, d = _extractDocstring( handler, id, '' )
364 title = title or t
365 description = description or d
367 info = { 'id' : id
368 , 'handler' : _getDottedName( handler )
369 , 'title' : title
370 , 'description' : description
371 }
373 self._registered[ id ] = info
375 security.declarePrivate( 'parseXML' )
376 def parseXML( self, text, encoding=None ):
378 """ Parse 'text'.
379 """
380 reader = getattr( text, 'read', None )
382 if reader is not None:
383 text = reader()
385 parser = _ExportStepRegistryParser( encoding )
386 parseString( text, parser )
388 return parser._parsed
390 security.declarePrivate( 'clear' )
391 def clear( self ):
393 self._registered = {}
395 #
396 # Helper methods
397 #
398 security.declarePrivate( '_exportTemplate' )
399 _exportTemplate = PageTemplateFile( 'esrExport.xml', _xmldir )
401 InitializeClass( ExportStepRegistry )
403 class ToolsetRegistry( Implicit ):
405 """ Track required / forbidden tools.
406 """
407 implements(IToolsetRegistry)
409 security = ClassSecurityInfo()
410 security.setDefaultAccess( 'allow' )
412 def __init__( self ):
414 self.clear()
416 #
417 # Toolset API
418 #
419 security.declareProtected( ManagePortal, 'listForbiddenTools' )
420 def listForbiddenTools( self ):
422 """ See IToolsetRegistry.
423 """
424 result = list( self._forbidden )
425 result.sort()
426 return result
428 security.declareProtected( ManagePortal, 'addForbiddenTool' )
429 def addForbiddenTool( self, tool_id ):
431 """ See IToolsetRegistry.
432 """
433 if tool_id in self._forbidden:
434 return
436 if self._required.get( tool_id ) is not None:
437 raise ValueError, 'Tool %s is required!' % tool_id
439 self._forbidden.append( tool_id )
441 security.declareProtected( ManagePortal, 'listRequiredTools' )
442 def listRequiredTools( self ):
444 """ See IToolsetRegistry.
445 """
446 result = list( self._required.keys() )
447 result.sort()
448 return result
450 security.declareProtected( ManagePortal, 'getRequiredToolInfo' )
451 def getRequiredToolInfo( self, tool_id ):
453 """ See IToolsetRegistry.
454 """
455 return self._required[ tool_id ]
457 security.declareProtected( ManagePortal, 'listRequiredToolInfo' )
458 def listRequiredToolInfo( self ):
460 """ See IToolsetRegistry.
461 """
462 return [ self.getRequiredToolInfo( x )
463 for x in self.listRequiredTools() ]
465 security.declareProtected( ManagePortal, 'addRequiredTool' )
466 def addRequiredTool( self, tool_id, dotted_name ):
468 """ See IToolsetRegistry.
469 """
470 if tool_id in self._forbidden:
471 raise ValueError, "Forbidden tool ID: %s" % tool_id
473 self._required[ tool_id ] = { 'id' : tool_id
474 , 'class' : dotted_name
475 }
477 security.declareProtected( ManagePortal, 'generateXML' )
478 def generateXML( self ):
480 """ Pseudo API.
481 """
482 return self._toolsetConfig()
484 security.declareProtected( ManagePortal, 'parseXML' )
485 def parseXML( self, text, encoding=None ):
487 """ Pseudo-API
488 """
489 reader = getattr( text, 'read', None )
491 if reader is not None:
492 text = reader()
494 parser = _ToolsetParser( encoding )
495 parseString( text, parser )
497 for tool_id in parser._forbidden:
498 self.addForbiddenTool( tool_id )
500 for tool_id, dotted_name in parser._required.items():
501 self.addRequiredTool( tool_id, dotted_name )
503 security.declarePrivate( 'clear' )
504 def clear( self ):
506 self._forbidden = []
507 self._required = {}
509 #
510 # Helper methods.
511 #
512 security.declarePrivate( '_toolsetConfig' )
513 _toolsetConfig = PageTemplateFile( 'tscExport.xml'
514 , _xmldir
515 , __name__='toolsetConfig'
516 )
518 InitializeClass( ToolsetRegistry )
520 class ProfileRegistry( Implicit ):
522 """ Track registered profiles.
523 """
524 implements(IProfileRegistry)
526 security = ClassSecurityInfo()
527 security.setDefaultAccess( 'allow' )
529 def __init__( self ):
531 self.clear()
533 security.declareProtected( ManagePortal, '' )
534 def getProfileInfo( self, profile_id, for_=None ):
536 """ See IProfileRegistry.
537 """
538 result = self._profile_info[ profile_id ]
539 if for_ is not None:
540 if not issubclass( for_, result['for'] ):
541 raise KeyError, profile_id
542 return result.copy()
544 security.declareProtected( ManagePortal, 'listProfiles' )
545 def listProfiles( self, for_=None ):
547 """ See IProfileRegistry.
548 """
549 result = []
550 for profile_id in self._profile_ids:
551 info = self.getProfileInfo( profile_id )
552 if for_ is None or issubclass( for_, info['for'] ):
553 result.append( profile_id )
554 return tuple( result )
556 security.declareProtected( ManagePortal, 'listProfileInfo' )
557 def listProfileInfo( self, for_=None ):
559 """ See IProfileRegistry.
560 """
561 candidates = [ self.getProfileInfo( id )
562 for id in self.listProfiles() ]
563 return [ x for x in candidates if for_ is None or x['for'] is None or
564 issubclass( for_, x['for'] ) ]
566 security.declareProtected( ManagePortal, 'registerProfile' )
567 def registerProfile( self
568 , name
569 , title
570 , description
571 , path
572 , product=None
573 , profile_type=BASE
574 , for_=None
575 ):
576 """ See IProfileRegistry.
577 """
578 profile_id = '%s:%s' % (product or 'other', name)
579 if self._profile_info.get( profile_id ) is not None:
580 raise KeyError, 'Duplicate profile ID: %s' % profile_id
582 self._profile_ids.append( profile_id )
584 info = { 'id' : profile_id
585 , 'title' : title
586 , 'description' : description
587 , 'path' : path
588 , 'product' : product
589 , 'type': profile_type
590 , 'for': for_
591 }
593 self._profile_info[ profile_id ] = info
595 security.declarePrivate( 'clear' )
596 def clear( self ):
598 self._profile_info = {}
599 self._profile_ids = []
601 InitializeClass( ProfileRegistry )
603 _profile_registry = ProfileRegistry()
605 class _ImportStepRegistryParser( HandlerBase ):
607 security = ClassSecurityInfo()
608 security.declareObjectPrivate()
609 security.setDefaultAccess( 'deny' )
611 def __init__( self, encoding ):
613 self._encoding = encoding
614 self._started = False
615 self._pending = None
616 self._parsed = []
618 def startElement( self, name, attrs ):
620 if name == 'import-steps':
622 if self._started:
623 raise ValueError, 'Duplicated setup-steps element: %s' % name
625 self._started = True
627 elif name == 'import-step':
629 if self._pending is not None:
630 raise ValueError, 'Cannot nest setup-step elements'
632 self._pending = dict( [ ( k, self._extract( attrs, k ) )
633 for k in attrs.keys() ] )
635 self._pending[ 'dependencies' ] = []
637 elif name == 'dependency':
639 if not self._pending:
640 raise ValueError, 'Dependency outside of step'
642 depended = self._extract( attrs, 'step' )
643 self._pending[ 'dependencies' ].append( depended )
645 else:
646 raise ValueError, 'Unknown element %s' % name
648 def characters( self, content ):
650 if self._pending is not None:
651 content = self._encode( content )
652 self._pending.setdefault( 'description', [] ).append( content )
654 def endElement(self, name):
656 if name == 'import-steps':
657 pass
659 elif name == 'import-step':
661 if self._pending is None:
662 raise ValueError, 'No pending step!'
664 deps = tuple( self._pending[ 'dependencies' ] )
665 self._pending[ 'dependencies' ] = deps
667 desc = ''.join( self._pending[ 'description' ] )
668 self._pending[ 'description' ] = desc
670 self._parsed.append( self._pending )
671 self._pending = None
673 InitializeClass( _ImportStepRegistryParser )
675 class _ExportStepRegistryParser( HandlerBase ):
677 security = ClassSecurityInfo()
678 security.declareObjectPrivate()
679 security.setDefaultAccess( 'deny' )
681 def __init__( self, encoding ):
683 self._encoding = encoding
684 self._started = False
685 self._pending = None
686 self._parsed = []
688 def startElement( self, name, attrs ):
690 if name == 'export-steps':
692 if self._started:
693 raise ValueError, 'Duplicated export-steps element: %s' % name
695 self._started = True
697 elif name == 'export-step':
699 if self._pending is not None:
700 raise ValueError, 'Cannot nest export-step elements'
702 self._pending = dict( [ ( k, self._extract( attrs, k ) )
703 for k in attrs.keys() ] )
705 else:
706 raise ValueError, 'Unknown element %s' % name
708 def characters( self, content ):
710 if self._pending is not None:
711 content = self._encode( content )
712 self._pending.setdefault( 'description', [] ).append( content )
714 def endElement(self, name):
716 if name == 'export-steps':
717 pass
719 elif name == 'export-step':
721 if self._pending is None:
722 raise ValueError, 'No pending step!'
724 desc = ''.join( self._pending[ 'description' ] )
725 self._pending[ 'description' ] = desc
727 self._parsed.append( self._pending )
728 self._pending = None
730 InitializeClass( _ExportStepRegistryParser )
733 class _ToolsetParser( HandlerBase ):
735 security = ClassSecurityInfo()
736 security.declareObjectPrivate()
737 security.setDefaultAccess( 'deny' )
739 def __init__( self, encoding ):
741 self._encoding = encoding
742 self._required = {}
743 self._forbidden = []
745 def startElement( self, name, attrs ):
747 if name == 'tool-setup':
748 pass
750 elif name == 'forbidden':
752 tool_id = self._extract( attrs, 'tool_id' )
754 if tool_id not in self._forbidden:
755 self._forbidden.append( tool_id )
757 elif name == 'required':
759 tool_id = self._extract( attrs, 'tool_id' )
760 dotted_name = self._extract( attrs, 'class' )
761 self._required[ tool_id ] = dotted_name
763 else:
764 raise ValueError, 'Unknown element %s' % name
767 InitializeClass( _ToolsetParser )