[Checkins] SVN: GenericSetup/branches/tseaver-bbq_sprint/ added support for metadata.xml file, read at profile registration time, which

Rob Miller ra at burningman.com
Thu Mar 29 09:02:38 EDT 2007


Log message for revision 73910:
  added support for metadata.xml file, read at profile registration time, which
  allows users to specify profile description, version, and dependencies
  

Changed:
  U   GenericSetup/branches/tseaver-bbq_sprint/CHANGES.txt
  A   GenericSetup/branches/tseaver-bbq_sprint/doc/configurators.txt
  A   GenericSetup/branches/tseaver-bbq_sprint/metadata.py
  U   GenericSetup/branches/tseaver-bbq_sprint/registry.py
  A   GenericSetup/branches/tseaver-bbq_sprint/tests/test_profile_metadata.py
  U   GenericSetup/branches/tseaver-bbq_sprint/utils.py

-=-
Modified: GenericSetup/branches/tseaver-bbq_sprint/CHANGES.txt
===================================================================
--- GenericSetup/branches/tseaver-bbq_sprint/CHANGES.txt	2007-03-29 12:57:53 UTC (rev 73909)
+++ GenericSetup/branches/tseaver-bbq_sprint/CHANGES.txt	2007-03-29 13:02:36 UTC (rev 73910)
@@ -2,6 +2,10 @@
 
   GenericSetup 1.3-beta (unreleased)
 
+    - Added support for 'metadata.xml' in the profile (read during
+      profile registration) to register profile description, version,
+      and dependencies.
+
     - Deprecated runImportStep and runAllImportSteps in favor of
       runImportStepFromProfile and runAllImportStepsFromProfile.
 

Added: GenericSetup/branches/tseaver-bbq_sprint/doc/configurators.txt
===================================================================
--- GenericSetup/branches/tseaver-bbq_sprint/doc/configurators.txt	2007-03-29 12:57:53 UTC (rev 73909)
+++ GenericSetup/branches/tseaver-bbq_sprint/doc/configurators.txt	2007-03-29 13:02:36 UTC (rev 73910)
@@ -0,0 +1,47 @@
+The Products.GenericSetup.utils.ImportConfiguratorBase class provides
+a convenient shortcut for defining how an XML file should be parsed
+and converted to a python dictionary.  To use this, create a subclass
+of ImportConfiguratorBase and implement a _getImportMapping method
+which returns a dictionary.  The returned dictionary should adhere to
+the following guidelines:
+
+- The utils module provides CONVERTER, DEFAULT, and KEY constants that
+  are to be used in the import mapping to define certain behaviours.
+
+- Any possible tag that you want your XML file format to support must
+  be listed as a top-level key in the import mapping dictionary.
+
+- The value of any key in the import mapping dictionary should be
+  another dictionary.
+
+- If an outer key represents a possible tag, then the nested
+  dictionary represents the possible "properties" of that tag, where a
+  property might be an attribute of the tag, the text content of the
+  tag, or possibly a nested tag.
+
+- If a key of the nested dictionary represents a nested tag, you will
+  also need a top-level key to represent that tag.  The nested
+  representation of the tag is where you make statements about how the
+  tag itself should be represented, while the top-level key allows you
+  to express information about how the data that is further nested
+  within the nested tag should be represented.
+
+- A CONVERTER can be registered on a an element to change the way that
+  the element is represented in the generated data structure.
+  self._convertToUnique will cause the element to be represented as a
+  single item and not a tuple of items, for instance.
+
+- A KEY can be specified for an element.  If it is specified then the
+  element's value will be stored in the resulting python dictionary
+  with the KEY value as the key.  If KEY is None, then the value will
+  be stored in a tuple rather than as a value in a nested dictionary.
+  If KEY is omitted, then the name of the element will be used as the
+  key.
+
+- A DEFAULT value can be specified on an element, which will be used
+  as the value on that element if the element doesn't actually exist
+  in the XML file.
+
+- Reference examples for this syntax can be found in the
+  metadata.ProfileMetadata and the rolemap.RolemapImportConfigurator
+  classes.

Added: GenericSetup/branches/tseaver-bbq_sprint/metadata.py
===================================================================
--- GenericSetup/branches/tseaver-bbq_sprint/metadata.py	2007-03-29 12:57:53 UTC (rev 73909)
+++ GenericSetup/branches/tseaver-bbq_sprint/metadata.py	2007-03-29 13:02:36 UTC (rev 73910)
@@ -0,0 +1,61 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+""" GenericSetup profile metadata
+
+$Id:$
+"""
+import os
+
+from utils import ImportConfiguratorBase
+from utils import CONVERTER, DEFAULT, KEY
+
+class ProfileMetadata( ImportConfiguratorBase ):
+    """ Extracts profile metadata from metadata.xml file.
+    """
+
+    def __init__( self, path, encoding=None ):
+
+        # don't call the base class __init__ b/c we don't have (or need)
+        # a site
+        self._path = path
+        self._encoding = encoding
+
+    def __call__( self ):
+        
+        full_path = os.path.join( self._path, 'metadata.xml' )
+        if not os.path.exists( full_path ):
+            return {}
+
+        file = open( full_path, 'r' )
+        return self.parseXML( file.read() )
+
+    def _getImportMapping( self ):
+
+        return {
+            'metadata':
+            {'description': { CONVERTER: self._convertToUnique },
+             'version': { CONVERTER: self._convertToUnique },
+             'dependencies': { CONVERTER: self._convertToUnique },
+             },
+            'description':
+            { '#text': { KEY: None, DEFAULT: '' },
+              },
+            'version':
+            { '#text': { KEY: None },
+              },
+            'dependencies':
+            {'dependency': { KEY: None },},
+            'dependency':
+            { '#text': { KEY: None },
+              },
+            }

Modified: GenericSetup/branches/tseaver-bbq_sprint/registry.py
===================================================================
--- GenericSetup/branches/tseaver-bbq_sprint/registry.py	2007-03-29 12:57:53 UTC (rev 73909)
+++ GenericSetup/branches/tseaver-bbq_sprint/registry.py	2007-03-29 13:02:36 UTC (rev 73910)
@@ -14,12 +14,14 @@
 
 $Id$
 """
-
+import os
 from xml.sax import parseString
 
 from AccessControl import ClassSecurityInfo
 from Acquisition import Implicit
 from Globals import InitializeClass
+from App.FactoryDispatcher import ProductDispatcher
+import App.Product
 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
 from zope.interface import implements
 
@@ -29,13 +31,13 @@
 from interfaces import IToolsetRegistry
 from interfaces import IProfileRegistry
 from permissions import ManagePortal
+from metadata import ProfileMetadata
 from utils import HandlerBase
 from utils import _xmldir
 from utils import _getDottedName
 from utils import _resolveDottedName
 from utils import _extractDocstring
 
-
 class ImportStepRegistry( Implicit ):
 
     """ Manage knowledge about steps to create / configure site.
@@ -610,6 +612,31 @@
                , 'for': for_
                }
 
+        metadata = ProfileMetadata( path )()
+
+        version = metadata.get( 'version', None )
+        if version is None and product is not None:
+            prod_module = getattr(App.Product.Products, product, None)
+            if prod_module is not None:
+                prod_path = prod_module.__path__[0]
+
+                # Retrieve version number from any suitable version.txt
+                for fname in ('version.txt', 'VERSION.txt', 'VERSION.TXT'):
+                    try:
+                        fpath = os.path.join( prod_path, fname )
+                        fhandle = open( fpath, 'r' )
+                        version = fhandle.read().strip()
+                        fhandle.close()
+                        break
+                    except IOError:
+                        continue
+
+            if version is not None:
+                metadata[ 'version' ] = version
+
+        # metadata.xml description trumps ZCML description... awkward
+        info.update( metadata )
+
         self._profile_info[ profile_id ] = info
 
     security.declarePrivate( 'clear' )

Added: GenericSetup/branches/tseaver-bbq_sprint/tests/test_profile_metadata.py
===================================================================
--- GenericSetup/branches/tseaver-bbq_sprint/tests/test_profile_metadata.py	2007-03-29 12:57:53 UTC (rev 73909)
+++ GenericSetup/branches/tseaver-bbq_sprint/tests/test_profile_metadata.py	2007-03-29 13:02:36 UTC (rev 73910)
@@ -0,0 +1,80 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+""" Unit tests for ProfileMetadata.
+
+$Id:$
+"""
+import unittest
+import os
+
+from Testing.ZopeTestCase import ZopeTestCase
+from Testing.ZopeTestCase import installProduct
+
+from Products.GenericSetup import profile_registry
+from Products.GenericSetup.metadata import ProfileMetadata
+
+desc = 'DESCRIPTION TEXT'
+version = 'VERSION'
+dep1 = 'DEPENDENCY 1'
+dep2 = 'DEPENDENCY 2'
+
+_METADATA_XML = """\
+<?xml version="1.0"?>
+<metadata>
+  <description>%s</description>
+  <version>%s</version>
+  <dependencies>
+    <dependency>%s</dependency>
+    <dependency>%s</dependency>
+  </dependencies>
+</metadata>
+""" % (desc, version, dep1, dep2)
+
+_METADATA_MAP = {
+    'description': desc,
+    'version': version,
+    'dependencies': (dep1, dep2),
+    }
+
+class ProfileMetadataTests( ZopeTestCase ):
+
+    installProduct('GenericSetup')
+
+    def test_parseXML(self):
+        metadata = ProfileMetadata( '' )
+        parsed = metadata.parseXML( _METADATA_XML )
+        self.assertEqual(parsed, _METADATA_MAP)
+
+    def test_versionFromProduct(self):
+        profile_id = 'dummy_profile'
+        product_name = 'GenericSetup'
+        directory = os.path.split(__file__)[0]
+        path = os.path.join(directory, 'default_profile')
+        profile_registry.registerProfile( profile_id,
+                                          'Dummy Profile',
+                                          'This is a dummy profile',
+                                          path,
+                                          product=product_name)
+        profile_info = profile_registry.getProfileInfo('%s:%s' % (product_name,
+                                                                  profile_id))
+        product = getattr(self.app.Control_Panel.Products, product_name)
+        self.assertEqual(profile_info['version'], product.version)
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite( ProfileMetadataTests ),
+        ))
+
+if __name__ == '__main__':
+    from Products.GenericSetup.testing import run
+    run(test_suite())

Modified: GenericSetup/branches/tseaver-bbq_sprint/utils.py
===================================================================
--- GenericSetup/branches/tseaver-bbq_sprint/utils.py	2007-03-29 12:57:53 UTC (rev 73909)
+++ GenericSetup/branches/tseaver-bbq_sprint/utils.py	2007-03-29 13:02:36 UTC (rev 73910)
@@ -181,7 +181,9 @@
         return self._extractNode(root)
 
     def _extractNode(self, node):
-
+        """ Please see docs/configurator.txt for information about the
+        import mapping syntax.
+        """
         nodes_map = self._getImportMapping()
         if node.nodeName not in nodes_map:
             nodes_map = self._getSharedImportMapping()



More information about the Checkins mailing list