[Zconfig] SVN: ZConfig/trunk/ add minimal implementation of schema-less parsing;

Fred L. Drake, Jr. fdrake at gmail.com
Thu Jun 21 13:01:40 EDT 2007


Log message for revision 76904:
  add minimal implementation of schema-less parsing;
  this is mostly intended for use from zc.buildout recipes, and contains
  a number of limitations (see ZConfig/schemaless.txt)
  

Changed:
  A   ZConfig/trunk/ZConfig/schemaless.py
  A   ZConfig/trunk/ZConfig/schemaless.txt
  A   ZConfig/trunk/ZConfig/tests/test_schemaless.py
  U   ZConfig/trunk/setup.py

-=-
Added: ZConfig/trunk/ZConfig/schemaless.py
===================================================================
--- ZConfig/trunk/ZConfig/schemaless.py	                        (rev 0)
+++ ZConfig/trunk/ZConfig/schemaless.py	2007-06-21 17:01:39 UTC (rev 76904)
@@ -0,0 +1,114 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""\
+Support for working with ZConfig data without a schema.
+
+"""
+__docformat__ = "reStructuredText"
+
+import ZConfig.cfgparser
+
+
+def loadConfigFile(file, url=None):
+    c = Context()
+    Parser(Resource(file, url), c).parse(c.top)
+    return c.top
+
+
+class Resource:
+
+    def __init__(self, file, url=''):
+        self.file, self.url = file, url
+
+
+class Section(dict):
+
+    imports = ()
+
+    def __init__(self, type='', name='', data=None, sections=None):
+        dict.__init__(self)
+        if data:
+            self.update(data)
+        self.sections = sections or []
+        self.type, self.name = type, name
+
+    def addValue(self, key, value, *args):
+        if key in self:
+            self[key].append(value)
+        else:
+            self[key] = [value]
+
+    def __str__(self, pre=''):
+        result = []
+
+        if self.imports:
+            for pkgname in self.imports:
+                result.append('%import ' + pkgname)
+            result.append('')
+
+        if self.type:
+            if self.name:
+                start = '%s<%s %s>' % (pre, self.type, self.name)
+            else:
+                start = '%s<%s>' % (pre, self.type)
+            result.append(start)
+            pre += '  '
+
+        for name, values in sorted(self.items()):
+            for value in values:
+                result.append('%s%s %s' % (pre, name, value))
+
+        if self.sections and self:
+            result.append('')
+
+        for section in self.sections:
+            result.append(section.__str__(pre))
+        
+        if self.type:
+            pre = pre[:-2]
+            result.append('%s</%s>' % (pre, self.type))
+            result.append('')
+
+        result = '\n'.join(result).rstrip()
+        if not pre:
+            result += '\n'
+        return result
+
+
+class Context:
+
+    def __init__(self):
+        self.top = Section()
+        self.sections = []
+
+    def startSection(self, container, type, name):
+        newsec = Section(type, name)
+        container.sections.append(newsec)
+        return newsec
+
+    def endSection(self, container, type, name, newsect):
+        pass
+
+    def importSchemaComponent(self, pkgname):
+        if pkgname not in self.top.imports:
+            self.top.imports += (pkgname, )
+
+    def includeConfiguration(self, section, newurl, defines):
+        raise NotImplementedError('includes are not supported')
+
+
+class Parser(ZConfig.cfgparser.ZConfigParser):
+
+    def handle_define(self, section, rest):
+        raise NotImplementedError('defines are not supported')


Property changes on: ZConfig/trunk/ZConfig/schemaless.py
___________________________________________________________________
Name: svn:mime-type
   + text/x-python
Name: svn:eol-style
   + native

Added: ZConfig/trunk/ZConfig/schemaless.txt
===================================================================
--- ZConfig/trunk/ZConfig/schemaless.txt	                        (rev 0)
+++ ZConfig/trunk/ZConfig/schemaless.txt	2007-06-21 17:01:39 UTC (rev 76904)
@@ -0,0 +1,290 @@
+=================================
+Using ZConfig data without schema
+=================================
+
+Sometimes it's useful to use ZConfig configuration data without a
+schema.  This is most interesting when assembling a configuration from
+fragments, as some buildout recipes do.  This is not recommended for
+general application use.
+
+The ``ZConfig.schemaless`` module provides some support for working
+without schema.  Something things are not (currently) supported,
+including the %define and %include directives.  The %import directive
+is supported.
+
+This module provides basic support for loading configuration,
+inspecting and modifying it, and re-serializing the result.
+
+  >>> from ZConfig import schemaless
+
+There is a single function which loads configuration data from a file
+open for reading.  Let's take a look at this, and what it returns::
+
+  >>> config_text = '''
+  ...
+  ... some-key some-value
+  ...
+  ... some-key another-value
+  ...
+  ... <section>
+  ...   key1 value1.1
+  ...   key1 value1.2
+  ...   key2 value2
+  ...
+  ...   <deeper>
+  ...     another key
+  ...     another value
+  ...   </deeper>
+  ... </section>
+  ...
+  ... another-key  whee!
+  ...
+  ...
+  ... <another named>
+  ...   nothing here
+  ... </another>
+  ...
+  ... '''
+
+  >>> import StringIO
+
+  >>> config = schemaless.loadConfigFile(StringIO.StringIO(config_text))
+
+The `config` object is a mapping from top-level keys to lists of
+values::
+
+  >>> config["some-key"]
+  ['some-value', 'another-value']
+
+  >>> config["another-key"]
+  ['whee!']
+
+  >>> config["no-such-key-in-the-config"]
+  Traceback (most recent call last):
+  KeyError: 'no-such-key-in-the-config'
+
+  >>> sorted(config)
+  ['another-key', 'some-key']
+
+There is also a ``sections`` attribute that lists child sections::
+
+  >>> len(config.sections)
+  2
+
+Let's take a look at one of the sections.  Like the top-level
+configuration, the section maps keys
+
+  >>> section = config.sections[0]
+
+  >>> section["key1"]
+  ['value1.1', 'value1.2']
+
+  >>> section["key2"]
+  ['value2']
+
+  >>> section["no-such-key-in-the-config"]
+  Traceback (most recent call last):
+  KeyError: 'no-such-key-in-the-config'
+
+  >>> sorted(section)
+  ['key1', 'key2']
+
+Child sections are again available via the ``sections`` attribute::
+
+  >>> len(section.sections)
+  1
+
+In addition, the section has ``type`` and ``name`` attributes that
+record the type and name of the section as ZConfig understands them::
+
+  >>> section.type
+  'section'
+
+  >>> print section.name
+  None
+
+Let's look at the named section from our example, so we can see the
+name::
+
+  >>> section = config.sections[1]
+  >>> section.type
+  'another'
+  >>> section.name
+  'named'
+
+We can also mutate the configuration, adding new keys and values as
+desired::
+
+  >>> config["new-key"] = ["new-value-1", "new-value-2"]
+  >>> config["some-key"].append("third-value")
+
+New sections can also be added::
+
+  >>> section = schemaless.Section("sectiontype", "my-name")
+  >>> section["key"] = ["value"]
+  >>> config.sections.insert(1, section)
+
+The configuration can be re-serialized using ``str()``::
+
+  >>> print str(config)
+  another-key whee!
+  new-key new-value-1
+  new-key new-value-2
+  some-key some-value
+  some-key another-value
+  some-key third-value
+  <BLANKLINE>
+  <section>
+    key1 value1.1
+    key1 value1.2
+    key2 value2
+  <BLANKLINE>
+    <deeper>
+      another key
+      another value
+    </deeper>
+  </section>
+  <BLANKLINE>
+  <sectiontype my-name>
+    key value
+  </sectiontype>
+  <BLANKLINE>
+  <another named>
+    nothing here
+  </another>
+  <BLANKLINE>
+
+Note that some adjustments have been made:
+
+- key/value pairs come before child sections
+
+- keys are sorted at each level
+
+- blank lines are removed, with new blank lines inserted to preserve
+  some semblance of readability
+
+These are all presentation changes, but not essential changes to the
+configuration data.  The ordering of sections is not modified in
+rendering, nor are the values for a single key re-ordered within a
+section or top-level configuration.
+
+
+Support for %import
+-------------------
+
+Imports are supported, and are re-ordered in much the same way that
+other elements of a configuration are::
+
+  >>> config_text = '''
+  ...
+  ... %import some.package
+  ...
+  ... <section>
+  ...
+  ...   %import another.package
+  ...
+  ...   <another>
+  ...     some value
+  ...   </another>
+  ...
+  ... </section>
+  ...
+  ... some-key some-value
+  ...
+  ... '''
+
+  >>> config = schemaless.loadConfigFile(StringIO.StringIO(config_text))
+
+  >>> print config
+  %import some.package
+  %import another.package
+  <BLANKLINE>
+  some-key some-value
+  <BLANKLINE>
+  <section>
+    <another>
+      some value
+    </another>
+  </section>
+  <BLANKLINE>
+
+The imports are also available as the ``imports`` attribute of the
+configuration object::
+
+  >>> config.imports
+  ('some.package', 'another.package')
+
+Multiple imports of the same name are removed::
+
+  >>> config_text = '''
+  ...
+  ... %import some.package
+  ... %import another.package
+  ... %import some.package
+  ...
+  ... '''
+
+  >>> config = schemaless.loadConfigFile(StringIO.StringIO(config_text))
+
+  >>> print config
+  %import some.package
+  %import another.package
+  <BLANKLINE>
+
+  >>> config.imports
+  ('some.package', 'another.package')
+
+
+Limitations
+-----------
+
+There are some limitations of handling ZConfig-based configurations
+using the ``ZConfig.schemaless`` module.  Some of these are
+implementation issues, and may be corrected in the future:
+
+- %define is not supported.
+
+- %include is not supported.
+
+Others are a function of not processing the schema, and can't easily
+be avoided:
+
+- normalization of keys based on keytypes specified in the <schema> or
+  <sectiontype> elements of the schema if not performed.
+
+  If the transformation of a key might affect the behavior controlled
+  by the resulting configuration, the generated configuration may not
+  be equivalent.  Examples of this are unusual, but exist.
+
+Limitations related to the non-processing of the schema cannot be
+detected by the ``ZConfig.schemaless``, so no errors are reported in
+these situations.
+
+For the strictly syntactic limitations, we do get errors when the
+input data requires they be supported.  Let's look at both the %define
+and %include handling.
+
+When %define is used in the input configuration, an exception is
+raised when loading the configuration::
+
+  >>> config_text = '''
+  ...
+  ... %define  somename  somevalue
+  ...
+  ... '''
+
+  >>> schemaless.loadConfigFile(StringIO.StringIO(config_text))
+  Traceback (most recent call last):
+  NotImplementedError: defines are not supported
+
+A similar exception is raised for %include::
+
+  >>> config_text = '''
+  ...
+  ... %include  some/other/file.conf
+  ...
+  ... '''
+
+  >>> schemaless.loadConfigFile(StringIO.StringIO(config_text))
+  Traceback (most recent call last):
+  NotImplementedError: includes are not supported


Property changes on: ZConfig/trunk/ZConfig/schemaless.txt
___________________________________________________________________
Name: svn:mime-type
   + text/plain
Name: svn:eol-style
   + native

Added: ZConfig/trunk/ZConfig/tests/test_schemaless.py
===================================================================
--- ZConfig/trunk/ZConfig/tests/test_schemaless.py	                        (rev 0)
+++ ZConfig/trunk/ZConfig/tests/test_schemaless.py	2007-06-21 17:01:39 UTC (rev 76904)
@@ -0,0 +1,27 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""\
+Test driver for ZConfig.schemaless.
+
+"""
+__docformat__ = "reStructuredText"
+
+try:
+    from zope.testing import doctest
+except ImportError:
+    import doctest
+
+
+def test_suite():
+    return doctest.DocFileSuite("schemaless.txt", package="ZConfig")


Property changes on: ZConfig/trunk/ZConfig/tests/test_schemaless.py
___________________________________________________________________
Name: svn:mime-type
   + text/x-python
Name: svn:eol-style
   + native

Modified: ZConfig/trunk/setup.py
===================================================================
--- ZConfig/trunk/setup.py	2007-06-21 16:26:38 UTC (rev 76903)
+++ ZConfig/trunk/setup.py	2007-06-21 17:01:39 UTC (rev 76904)
@@ -22,7 +22,7 @@
 
 setup(
     name = name,
-    version = "2.4a4",
+    version = "2.4a5",
     author = "Fred L. Drake, Jr.",
     author_email = "fred at zope.com",
     description = "Structured Configuration Library",



More information about the ZConfig mailing list