configglue team mailing list archive
-
configglue team
-
Mailing list archive
-
Message #00000
[Merge] lp:~configglue/configglue/schemaconfig-integration-fixes into lp:configglue
Ricardo Kirkner has proposed merging lp:~configglue/configglue/schemaconfig-integration-fixes into lp:configglue.
Requested reviews:
Configglue developers (configglue)
fixes most unit tests (the unicode test is left failing for further discussion, as it seems to fail on lucid but work on maverick)
--
https://code.launchpad.net/~configglue/configglue/schemaconfig-integration-fixes/+merge/31790
Your team Configglue developers is requested to review the proposed merge of lp:~configglue/configglue/schemaconfig-integration-fixes into lp:configglue.
=== modified file 'LICENSE'
--- LICENSE 2009-06-15 18:23:08 +0000
+++ LICENSE 2010-08-04 21:21:41 +0000
@@ -1,26 +1,29 @@
-Copyright (c) 2009 Canonical Ltd.
-All rights reserved.
+Copyright 2009, 2010 Canonical Ltd. All rights reserved.
Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-1. Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-2. Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
-3. Neither the name of the University nor the names of its contributors
- may be used to endorse or promote products derived from this software
- without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGE.
+modification, are permitted provided that the following conditions are
+met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY CANONICAL LTD. ``AS IS'' AND ANY EXPRESS
+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL CANONICAL LTD. OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation
+are those of the authors and should not be interpreted as representing
+official policies, either expressed or implied, of Canonical Ltd.
=== added file 'configglue/__init__.py'
--- configglue/__init__.py 1970-01-01 00:00:00 +0000
+++ configglue/__init__.py 2010-08-04 21:21:41 +0000
@@ -0,0 +1,17 @@
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
+# Released under the BSD License (see the file LICENSE)
+#
+# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
+
=== added directory 'configglue/inischema'
=== renamed file 'configglue/__init__.py' => 'configglue/inischema/__init__.py'
--- configglue/__init__.py 2009-03-19 20:01:36 +0000
+++ configglue/inischema/__init__.py 2010-08-04 21:21:41 +0000
@@ -1,7 +1,19 @@
-# This file is part of configglue, by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
-# (C) 2009 by Canonical Ltd.
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
# Released under the BSD License (see the file LICENSE)
+#
# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
"""configglue -- glue for your apps' configuration
=== renamed file 'configglue/attributed.py' => 'configglue/inischema/attributed.py'
--- configglue/attributed.py 2009-03-19 20:01:36 +0000
+++ configglue/inischema/attributed.py 2010-08-04 21:21:41 +0000
@@ -1,7 +1,19 @@
-# This file is part of configglue, by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
-# (C) 2009 by Canonical Ltd.
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
# Released under the BSD License (see the file LICENSE)
+#
# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
"""
AttributtedConfigParser lives here.
=== renamed file 'configglue/glue.py' => 'configglue/inischema/glue.py'
--- configglue/glue.py 2009-03-19 20:01:36 +0000
+++ configglue/inischema/glue.py 2010-08-04 21:21:41 +0000
@@ -1,7 +1,19 @@
-# This file is part of configglue, by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
-# (C) 2009 by Canonical Ltd.
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
# Released under the BSD License (see the file LICENSE)
+#
# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
"""configglue lives here
"""
@@ -20,7 +32,7 @@
return parser.optionxform(section +'-'+ option)
-def configglue(fileobj, *filenames, **kwargs):
+def _configglue(fileobj, *filenames, **kwargs):
"""Populate an OptionParser with options and defaults taken from a
series of files.
@@ -76,3 +88,8 @@
setattr(options, optname, optval.parser(value))
return op, options, args
+
+from configglue.pyschema import schemaconfigglue, ini2schema
+def configglue(fileobj, *filenames, **kwargs):
+ args = kwargs.pop('args', None)
+ return schemaconfigglue(ini2schema(fileobj), argv=args)
=== renamed file 'configglue/parsers.py' => 'configglue/inischema/parsers.py'
--- configglue/parsers.py 2009-03-19 20:01:36 +0000
+++ configglue/inischema/parsers.py 2010-08-04 21:21:41 +0000
@@ -1,7 +1,19 @@
-# This file is part of configglue, by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
-# (C) 2009 by Canonical Ltd.
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
# Released under the BSD License (see the file LICENSE)
+#
# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
"""Parsers used by TypedConfigParser live here
"""
=== renamed file 'configglue/typed.py' => 'configglue/inischema/typed.py'
--- configglue/typed.py 2009-11-19 19:59:28 +0000
+++ configglue/inischema/typed.py 2010-08-04 21:21:41 +0000
@@ -1,7 +1,19 @@
-# This file is part of configglue, by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
-# (C) 2009 by Canonical Ltd.
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
# Released under the BSD License (see the file LICENSE)
+#
# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
""" TypedConfigParser lives here """
from __future__ import absolute_import
=== added directory 'configglue/pyschema'
=== added file 'configglue/pyschema/__init__.py'
--- configglue/pyschema/__init__.py 1970-01-01 00:00:00 +0000
+++ configglue/pyschema/__init__.py 2010-08-04 21:21:41 +0000
@@ -0,0 +1,256 @@
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
+# Released under the BSD License (see the file LICENSE)
+#
+# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
+
+import __builtin__
+from optparse import OptionParser
+import sys
+
+from configglue.inischema import AttributedConfigParser, parsers
+
+def ini2schema(fd):
+ """
+ Turn a fd that refers to a INI-style schema definition into a
+ SchemaConfigParser object
+ """
+ p = AttributedConfigParser()
+ p.readfp(fd)
+ p.parse_all()
+
+ parser2option = {'unicode': options.StringConfigOption,
+ 'int': options.IntConfigOption,
+ 'bool': options.BoolConfigOption,
+ 'lines': options.LinesConfigOption}
+
+ class MySchema(Schema):
+ pass
+
+ for section_name in p.sections():
+ if section_name == '__main__':
+ section = MySchema
+ else:
+ section = ConfigSection()
+ setattr(MySchema, section_name, section)
+ for option_name in p.options(section_name):
+ option = p.get(section_name, option_name)
+
+ parser = option.attrs.pop('parser', 'unicode')
+ parser_args = option.attrs.pop('parser.args', '').split()
+ parser_fun = getattr(parsers, parser, None)
+ if parser_fun is None:
+ parser_fun = getattr(__builtin__, parser, None)
+ if parser_fun is None:
+ parser_fun = lambda x: x
+
+ attrs = {}
+ option_help = option.attrs.pop('help', None)
+ if option_help is not None:
+ attrs['help'] = option_help
+ if not option.is_empty:
+ attrs['default'] = parser_fun(option.value, *parser_args)
+ option_action = option.attrs.pop('action', None)
+ if option_action is not None:
+ attrs['action'] = option_action
+
+ klass = parser2option.get(parser, options.StringConfigOption)
+ if parser == 'lines':
+ instance = klass(options.StringConfigOption(), **attrs)
+ else:
+ instance = klass(**attrs)
+ setattr(section, option_name, instance)
+
+ return SchemaConfigParser(MySchema())
+
+
+def schemaconfigglue(parser, op=None, argv=None):
+ """Populate an OptionParser with options and defaults taken from a
+ fully loaded SchemaConfigParser.
+ """
+
+ def long_name(option):
+ if option.section.name == '__main__':
+ return option.name
+ return option.section.name + '_' + option.name
+
+ def opt_name(option):
+ return long_name(option).replace('-', '_')
+
+ if op is None:
+ op = OptionParser()
+ if argv is None:
+ argv = sys.argv[1:]
+ schema = parser.schema
+
+ for section in schema.sections():
+ if section.name == '__main__':
+ og = op
+ else:
+ og = op.add_option_group(section.name)
+ for option in section.options():
+ kwargs = {}
+ if option.help:
+ kwargs['help'] = option.help
+ kwargs['default'] = parser.get(section.name, option.name)
+ kwargs['action'] = option.action
+ og.add_option('--' + long_name(option), **kwargs)
+ options, args = op.parse_args(argv)
+
+ for section in schema.sections():
+ for option in section.options():
+ value = getattr(options, opt_name(option))
+ if parser.get(section.name, option.name) != value:
+ # the value has been overridden by an argument;
+ # update it.
+ parser.set(section.name, option.name, value)
+
+ return op, options, args
+
+def super_vars(obj):
+ """An extended version of vars() that walks all base classes."""
+ items = {}
+ if hasattr(obj, '__mro__'):
+ bases = map(vars, obj.__mro__)
+ map(items.update, bases)
+ else:
+ items = vars(obj)
+ return items
+
+NO_DEFAULT = object()
+
+
+class ConfigOption(object):
+ """Base class for Config Options.
+
+ ConfigOptions are never bound to a particular conguration file, and
+ simply describe one particular available option.
+
+ They also know how to parse() the content of a config file in to the right
+ type of object.
+
+ If self.raw == True, then variable interpolation will not be carried out
+ for this config option.
+
+ If self.require_parser == True, then the parse() method will have a second
+ argument, parser, that should receive the whole SchemaConfigParser to
+ do the parsing. This is needed for config options that need to look at
+ other parts of the config file to be able to carry out their parsing,
+ like DictConfigOptions.
+
+ If self.fatal == True, SchemaConfigParser's parse_all will raise an
+ exception if no value for this option is provided in the configuration
+ file. Otherwise, the self.default value will be used if the option is
+ omitted.
+
+ In runtime, after instantiating the Schema, each ConfigOption will also
+ know its own name and to which section it belongs.
+ """
+
+ require_parser = False
+
+ def __init__(self, name='', raw=False, default=NO_DEFAULT, fatal=False, help='',
+ section=None, action='store'):
+ self.name = name
+ self.raw = raw
+ self.fatal = fatal
+ if default is NO_DEFAULT:
+ default = self._get_default()
+ self.default = default
+ self.help = help
+ self.section = section
+ self.action = action
+
+ def __eq__(self, other):
+ try:
+ equal = (self.name == other.name and
+ self.raw == other.raw and
+ self.fatal == other.fatal and
+ self.default == other.default and
+ self.help == other.help)
+ if self.section is not None and other.section is not None:
+ # only test for section name to avoid recursion
+ equal &= self.section.name == other.section.name
+ else:
+ equal &= (self.section is None and other.section is None)
+ except AttributeError:
+ equal = False
+
+ return equal
+
+ def __repr__(self):
+ extra = ' raw' if self.raw else ''
+ extra += ' fatal' if self.fatal else ''
+ section = self.section.name if self.section is not None else None
+ if section is not None:
+ name = " %s.%s" % (section, self.name)
+ elif self.name:
+ name = " %s" % self.name
+ else:
+ name = ''
+ value = "<ConfigOption%s%s>" % (name, extra)
+ return value
+
+ def _get_default(self):
+ return None
+
+ def parse(self, value):
+ raise NotImplementedError()
+
+
+class ConfigSection(object):
+ """A group of options.
+
+ This class is just a bag you can dump ConfigOptions in.
+
+ After instantiating the Schema, each ConfigSection will know its own
+ name.
+ """
+ def __init__(self, name=''):
+ self.name = name
+
+ def __eq__(self, other):
+ return (self.name == other.name and
+ self.options() == other.options())
+
+ def __repr__(self):
+ if self.name:
+ name = " %s" % self.name
+ else:
+ name = ''
+ value = "<ConfigSection%s>" % name
+ return value
+
+ def has_option(self, name):
+ """Return True if a ConfigOption with the given name is available"""
+ opt = getattr(self, name, None)
+ return isinstance(opt, ConfigOption)
+
+ def option(self, name):
+ """Return a ConfigOption by name"""
+ assert hasattr(self, name), "Invalid ConfigOption name '%s'" % name
+ return getattr(self, name)
+
+ def options(self):
+ """Return a list of all available ConfigOptions within this section"""
+ return [getattr(self, att) for att in vars(self)
+ if isinstance(getattr(self, att), ConfigOption)]
+
+
+# usability tweak -- put everything in the base namespace to make import lines
+# shorter
+from options import (BoolConfigOption, DictConfigOption, IntConfigOption,
+ LinesConfigOption, StringConfigOption, TupleConfigOption)
+from parser import SchemaConfigParser
+from schema import Schema
=== added file 'configglue/pyschema/options.py'
--- configglue/pyschema/options.py 1970-01-01 00:00:00 +0000
+++ configglue/pyschema/options.py 2010-08-04 21:21:41 +0000
@@ -0,0 +1,245 @@
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
+# Released under the BSD License (see the file LICENSE)
+#
+# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
+
+from configglue.pyschema import ConfigOption, NO_DEFAULT
+
+
+class BoolConfigOption(ConfigOption):
+ """A ConfigOption that is parsed into a bool"""
+
+ def _get_default(self):
+ return False
+
+ def parse(self, value, raw=False):
+ if raw:
+ return value
+
+ if value.lower() in ['y', '1', 'yes', 'on', 'true']:
+ return True
+ elif value.lower() in ['n', '0', 'no', 'off', 'false']:
+ return False
+ else:
+ raise ValueError("Unable to determine boolosity of %r" % value)
+
+
+class IntConfigOption(ConfigOption):
+ """A ConfigOption that is parsed into an int"""
+
+ def _get_default(self):
+ return 0
+
+ def parse(self, value, raw=False):
+ if raw:
+ return value
+
+ return int(value)
+
+
+class LinesConfigOption(ConfigOption):
+ """A ConfigOption that is parsed into a list of objects
+
+ All items in the list need to be of the same type. The 'item' constructor
+ argument determines the type of the list items. item should be another
+ child of ConfigOption.
+
+ self.require_parser will be True if the item provided in turn has
+ require_parser == True.
+
+ if remove_duplicates == True, duplicate elements in the lines will be
+ removed. Only the first occurrence of any item will be kept,
+ otherwise the general order of the list will be preserved.
+ """
+
+ def _get_default(self):
+ return []
+
+ def parse(self, value, parser=None, raw=False):
+ def _parse_item(value):
+ if self.require_parser:
+ value = self.item.parse(value, parser=parser, raw=raw)
+ else:
+ value = self.item.parse(value, raw=raw)
+ return value
+ items = [_parse_item(x) for x in value.split('\n') if len(x)]
+ if self.remove_duplicates:
+ filtered_items = []
+ for item in items:
+ if not item in filtered_items:
+ filtered_items.append(item)
+ items = filtered_items
+ return items
+
+ def __init__(self, item, raw=False, default=NO_DEFAULT, fatal=False,
+ help='', action='store', remove_duplicates=False):
+ super(LinesConfigOption, self).__init__(raw=raw, default=default,
+ fatal=fatal, help=help, action=action)
+ self.item = item
+ self.require_parser = item.require_parser
+ self.raw = item.raw
+ self.remove_duplicates = remove_duplicates
+
+class StringConfigOption(ConfigOption):
+ """A ConfigOption that is parsed into a string.
+
+ If null==True, a value of 'None' will be parsed in to None instead of
+ just leaving it as the string 'None'.
+ """
+
+ def _get_default(self):
+ return '' if not self.null else None
+
+ def parse(self, value, raw=False):
+ if raw:
+ return value
+
+ return unicode(value)
+
+ def __init__(self, raw=False, default=NO_DEFAULT, fatal=False, null=False,
+ help='', action='store'):
+ self.null = null
+ super(StringConfigOption, self).__init__(raw=raw, default=default,
+ fatal=fatal, help=help, action=action)
+
+
+class TupleConfigOption(ConfigOption):
+ """A ConfigOption that is parsed into a fixed-size tuple of strings.
+
+ The number of items in the tuple should be specified with the 'length'
+ constructor argument.
+ """
+
+ def __init__(self, length=0, raw=False, default=NO_DEFAULT, fatal=False,
+ help='', action='store'):
+ super(TupleConfigOption, self).__init__(raw=raw, default=default,
+ fatal=fatal, help=help, action=action)
+ self.length = length
+
+ def _get_default(self):
+ return ()
+
+ def parse(self, value, raw=False):
+ parts = [part.strip() for part in value.split(',')]
+ if parts == ['()']:
+ result = ()
+ elif self.length:
+ # length is not 0, so length validation
+ if len(parts) == self.length:
+ result = tuple(parts)
+ else:
+ raise ValueError("Tuples need to be %d items long" % self.length)
+ else:
+ result = tuple(parts)
+ # length is 0, so no length validation
+ return result
+
+
+class DictConfigOption(ConfigOption):
+ """A ConfigOption that is parsed into a dictionary.
+
+ In the configuration file you'll need to specify the name of a section,
+ and all that section's items will be parsed as a dictionary.
+
+ The available keys for the dict are specified with the 'spec' constructor
+ argument, that should be in turn a dictionary. spec's keys are the
+ available keys for the config file, and spec's values should be
+ ConfigOptions that will be used to parse the values in the config file.
+ """
+ require_parser = True
+
+ def __init__(self, spec=None, strict=False, raw=False,
+ default=NO_DEFAULT, fatal=False, help='', action='store',
+ item=None):
+ if spec is None:
+ spec = {}
+ if item is None:
+ item = StringConfigOption()
+ self.spec = spec
+ self.strict = strict
+ self.item = item
+ super(DictConfigOption, self).__init__(raw=raw, default=default,
+ fatal=fatal, help=help, action=action)
+
+ def _get_default(self):
+ default = {}
+ for key, value in self.spec.items():
+ default[key] = value.default
+ return default
+
+ def parse(self, section, parser=None, raw=False):
+ parsed = dict(parser.items(section))
+ result = {}
+
+ # parse config items according to spec
+ for key, value in parsed.items():
+ if self.strict and not key in self.spec:
+ raise ValueError("Invalid key %s in section %s" % (key,
+ section))
+ option = self.spec.get(key, None)
+ if option is None:
+ # option not part of spec, but we are in non-strict mode
+ # parse it using the default item parser
+ option = self.item
+
+ # parse option
+ kwargs = {}
+ if option.require_parser:
+ kwargs['parser'] = parser
+ if not raw:
+ value = option.parse(value, **kwargs)
+ result[key] = value
+
+ # fill in missing items with default values
+ for key in self.spec:
+ if not key in parsed:
+ option = self.spec[key]
+ if option.fatal:
+ raise ValueError("No option '%s' in section '%s'" %
+ (key, section))
+ else:
+ if not raw:
+ value = option.default
+ else:
+ value = unicode(option.default)
+ result[key] = value
+ return result
+
+ def get_extra_sections(self, section, parser):
+ sections = []
+ for option in parser.options(section):
+ option_obj = self.spec.get(option, self.item)
+ is_dict_item = isinstance(option_obj, DictConfigOption)
+ is_dict_lines_item = (hasattr(option_obj, 'item') and
+ isinstance(option_obj.item, DictConfigOption))
+
+ if is_dict_item:
+ base = option_obj
+ elif is_dict_lines_item:
+ base = option_obj.item
+ else:
+ continue
+
+ value = parser.get(section, option, parse=False)
+ names = value.split()
+ sections.extend(names)
+
+ # recurse
+ for name in names:
+ extra = base.get_extra_sections(name, parser)
+ sections.extend(extra)
+
+ return sections
+
=== added file 'configglue/pyschema/parser.py'
--- configglue/pyschema/parser.py 1970-01-01 00:00:00 +0000
+++ configglue/pyschema/parser.py 2010-08-04 21:21:41 +0000
@@ -0,0 +1,504 @@
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
+# Released under the BSD License (see the file LICENSE)
+#
+# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
+
+import codecs
+import collections
+import copy
+import os
+import string
+
+from ConfigParser import (NoSectionError, DEFAULTSECT,
+ InterpolationMissingOptionError, NoOptionError,
+ ConfigParser as BaseConfigParser)
+
+from configglue.pyschema.options import DictConfigOption
+
+
+CONFIG_FILE_ENCODING = 'utf-8'
+
+
+class SchemaValidationError(Exception):
+ pass
+
+
+class SchemaConfigParser(BaseConfigParser, object):
+ """A ConfigParser that validates against a Schema
+
+ The way to use this class is:
+
+ config = SchemaConfigParser(MySchema())
+ config.read('mysystemconfig.cfg', 'mylocalconfig.cfg', ...)
+ config.parse_all()
+ ...
+ profit!
+ """
+ def __init__(self, schema):
+ super(SchemaConfigParser, self).__init__()
+ # validate schema
+ if not schema.is_valid():
+ # TODO: add error details
+ raise SchemaValidationError()
+ self.schema = schema
+ self._location = {}
+ self.extra_sections = set()
+ self._basedir = ''
+ self._dirty = collections.defaultdict(
+ lambda: collections.defaultdict(dict))
+
+ def is_valid(self, report=False):
+ valid = True
+ errors = []
+ try:
+ # validate structure
+ parsed_sections = set(self.sections())
+ schema_sections = set(s.name for s in self.schema.sections())
+ skip_sections = self.extra_sections
+ if '__noschema__' in parsed_sections:
+ skip_sections.add('__noschema__')
+ schema_sections.update(skip_sections)
+ sections_match = (parsed_sections == schema_sections)
+ if not sections_match:
+ error_msg = "Sections in configuration do not match schema: %s"
+ unmatched_sections = list(parsed_sections - schema_sections)
+ error_value = ', '.join(unmatched_sections)
+ errors.append(error_msg % error_value)
+ valid &= sections_match
+
+ for name in parsed_sections:
+ if name not in skip_sections:
+ if not self.schema.has_section(name):
+ # this should have been reported before
+ # so skip bogus section
+ continue
+
+ section = self.schema.section(name)
+ parsed_options = set(self.options(name))
+ schema_options = set(section.options())
+
+ fatal_options = set(opt.name for opt in schema_options
+ if opt.fatal)
+ # all fatal options are included
+ fatal_included = parsed_options.issuperset(fatal_options)
+ if not fatal_included:
+ error_msg = ("Configuration missing required options"
+ " for section '%s': %s")
+ error_value = ', '.join(list(fatal_options -
+ parsed_options))
+ errors.append(error_msg % (name, error_value))
+ valid &= fatal_included
+
+ # remaining parsed options are valid schema options
+ other_options = parsed_options - fatal_options
+ schema_opt_names = set(opt.name for opt in schema_options)
+
+ # add the default section special includes option
+ if name == '__main__':
+ schema_opt_names.add('includes')
+
+ schema_options = other_options.issubset(schema_opt_names)
+ if not schema_options:
+ error_msg = ("Configuration includes invalid options"
+ " for section '%s': %s")
+ error_value = ', '.join(list(other_options -
+ schema_opt_names))
+ errors.append(error_msg % (name, error_value))
+ valid &= schema_options
+
+ # structure validates, validate content
+ self.parse_all()
+
+ except Exception, e:
+ if valid:
+ errors.append(e)
+ valid = False
+
+ if report:
+ return valid, errors
+ else:
+ return valid
+
+ def items(self, section, raw=False, vars=None):
+ """Return a list of tuples with (name, value) for each option
+ in the section.
+
+ All % interpolations are expanded in the return values, based on the
+ defaults passed into the constructor, unless the optional argument
+ `raw' is true. Additional substitutions may be provided using the
+ `vars' argument, which must be a dictionary whose contents overrides
+ any pre-existing defaults.
+
+ The section DEFAULT is special.
+ """
+ d = self._defaults.copy()
+ try:
+ d.update(self._sections[section])
+ except KeyError:
+ if section != DEFAULTSECT:
+ raise NoSectionError(section)
+ # Update with the entry specific variables
+ if vars:
+ for key, value in vars.items():
+ d[self.optionxform(key)] = value
+ options = d.keys()
+ if "__name__" in options:
+ options.remove("__name__")
+ if raw:
+ return [(option, d[option])
+ for option in options]
+ else:
+ items = []
+ for option in options:
+ try:
+ value = self._interpolate(section, option, d[option], d)
+ except InterpolationMissingOptionError, e:
+ # interpolation failed, because key was not found in
+ # section. try other sections before bailing out
+ value = self._interpolate_value(section, option)
+ if value is None:
+ # this should be a string, so None indicates an error
+ raise e
+ items.append((option, value))
+ return items
+
+ def values(self, section=None, parse=True):
+ """Returns multiple values, in a dict.
+
+ This method can return the value of multiple options in a single call,
+ unlike get() that returns a single option's value.
+
+ If section=None, return all options from all sections.
+ If section is specified, return all options from that section only.
+
+ Section is to be specified *by name*, not by
+ passing in real ConfigSection objects.
+ """
+ values = collections.defaultdict(dict)
+ if section is None:
+ sections = self.schema.sections()
+ else:
+ sections = [self.schema.section(section)]
+
+ for sect in sections:
+ for opt in sect.options():
+ values[sect.name][opt.name] = self.get(
+ sect.name, opt.name, parse=parse)
+ if section is not None:
+ return values[section]
+ else:
+ return values
+
+ def read(self, filenames, already_read=None):
+ """Like ConfigParser.read, but consider files we've already read."""
+ if already_read is None:
+ already_read = set()
+ if isinstance(filenames, basestring):
+ filenames = [filenames]
+ read_ok = []
+ for filename in filenames:
+ path = os.path.join(self._basedir, filename)
+ if path in already_read:
+ continue
+ try:
+ fp = codecs.open(path, 'r', CONFIG_FILE_ENCODING)
+ except IOError:
+ continue
+ self._read(fp, path, already_read=already_read)
+ fp.close()
+ read_ok.append(path)
+ return read_ok
+
+ def readfp(self, fp, filename=None):
+ # wrap the StringIO so it can read encoded text
+ decoded_fp = codecs.getreader(CONFIG_FILE_ENCODING)(fp)
+ self._read(decoded_fp, filename)
+
+ def _read(self, fp, fpname, already_read=None):
+ # read file content
+ self._update(fp, fpname)
+
+ if already_read is None:
+ already_read = set()
+ already_read.add(fpname)
+
+ if self.has_option('__main__', 'includes'):
+ old_basedir, self._basedir = self._basedir, os.path.dirname(fpname)
+ includes = self.get('__main__', 'includes')
+ filenames = map(string.strip, includes)
+ for filename in filenames:
+ self.read(filename, already_read=already_read)
+ self._basedir = old_basedir
+
+ if filenames:
+ # re-read the file to override included options with
+ # local values
+ fp.seek(0)
+ self._update(fp, fpname)
+
+ def _update(self, fp, fpname):
+ # remember current values
+ old_sections = copy.deepcopy(self._sections)
+ # read in new file
+ super(SchemaConfigParser, self)._read(fp, fpname)
+ # update location of changed values
+ self._update_location(old_sections, fpname)
+
+ def _update_location(self, old_sections, filename):
+ # keep list of valid options to include locations for
+ option_names = map(lambda x: x.name, self.schema.options())
+
+ # new values
+ sections = self._sections
+
+ # update locations
+ for section, options in sections.items():
+ old_section = old_sections.get(section)
+ if old_section is not None:
+ # update options in existing section
+ for option, value in options.items():
+ valid_option = option in option_names
+ option_changed = (option not in old_section or
+ value != old_section[option])
+ if valid_option and option_changed:
+ self._location[option] = filename
+ else:
+ # complete section is new
+ for option, value in options.items():
+ valid_option = option in option_names
+ if valid_option:
+ self._location[option] = filename
+
+ def parse(self, section, option, value):
+ if section == '__main__':
+ option_obj = getattr(self.schema, option, None)
+ else:
+ section_obj = getattr(self.schema, section, None)
+ if section_obj is not None:
+ option_obj = getattr(section_obj, option, None)
+ else:
+ raise NoSectionError(section)
+
+ if option_obj is not None:
+ kwargs = {}
+ if option_obj.require_parser:
+ kwargs = {'parser': self}
+
+ # hook to save extra sections
+ is_dict_option = isinstance(option_obj, DictConfigOption)
+ is_dict_lines_option = (hasattr(option_obj, 'item') and
+ isinstance(option_obj.item, DictConfigOption))
+ is_default_value = unicode(option_obj.default) == value
+
+ # avoid adding implicit sections for dict default value
+ if ((is_dict_option or is_dict_lines_option) and
+ not is_default_value):
+ sections = value.split()
+ self.extra_sections.update(set(sections))
+
+ if is_dict_option:
+ base = option_obj
+ else:
+ base = option_obj.item
+
+ for name in sections:
+ nested = base.get_extra_sections(name, self)
+ self.extra_sections.update(set(nested))
+
+ if is_default_value:
+ value = option_obj.default
+ else:
+ try:
+ value = option_obj.parse(value, **kwargs)
+ except ValueError, e:
+ raise ValueError("Invalid value '%s' for %s '%s' in"
+ " section '%s'. Original exception was: %s" %
+ (value, option_obj.__class__.__name__, option,
+ section, e))
+ return value
+
+ def parse_all(self):
+ """Go through all sections and options attempting to parse each one.
+
+ If any options are omitted from the config file, provide the
+ default value from the schema. If the option has fatal=True, raise
+ an exception.
+ """
+ for section in self.schema.sections():
+ for option in section.options():
+ try:
+ self.get(section.name, option.name, raw=option.raw)
+ except (NoSectionError, NoOptionError):
+ if option.fatal:
+ raise
+
+ def locate(self, option=None):
+ return self._location.get(option)
+
+ def _get_interpolation_keys(self, section, option):
+ def extract_keys(item):
+ if isinstance(item, (list, tuple)):
+ keys = map(extract_keys, item)
+ keys = reduce(set.union, keys, set())
+ else:
+ keys = set(self._KEYCRE.findall(item))
+ # remove invalid key
+ if '' in keys:
+ keys.remove('')
+ return keys
+
+ rawval = super(SchemaConfigParser, self).get(section, option, True)
+ try:
+ opt = self.schema.section(section).option(option)
+ value = opt.parse(rawval, raw=True)
+ except:
+ value = rawval
+
+ keys = extract_keys(value)
+ return rawval, keys
+
+ def _interpolate_value(self, section, option):
+ rawval, keys = self._get_interpolation_keys(section, option)
+ if not keys:
+ # interpolation keys are not valid
+ return
+
+ values = {}
+ # iterate over the other sections
+ for key in keys:
+ # we want the unparsed value
+ try:
+ value = self.get(section, key, parse=False)
+ except (NoSectionError, NoOptionError):
+ # value of key not found in config, so try in special
+ # sections
+ for section in ('__main__', '__noschema__'):
+ try:
+ value = super(SchemaConfigParser, self).get(section,
+ key)
+ break
+ except:
+ continue
+ else:
+ return
+ values[key] = value
+
+ # replace holders with values
+ result = rawval % values
+
+ assert isinstance(result, basestring)
+ return result
+
+ def _get_default(self, section, option):
+ # mark the value as not initialized to be able to have a None default
+ marker = object()
+ value = marker
+
+ # cater for 'special' sections
+ if section == '__main__':
+ opt = getattr(self.schema, option, None)
+ if opt is not None and not opt.fatal:
+ value = opt.default
+ elif section == '__noschema__':
+ value = super(SchemaConfigParser, self).get(section, option)
+ else:
+ try:
+ opt = self.schema.section(section).option(option)
+ if not opt.fatal:
+ value = opt.default
+ except Exception:
+ pass
+
+ if value is marker:
+ # value was not set, so either section or option was not found
+ # or option was required (fatal set to True)
+ #if self.schema.has_section(section):
+ # raise NoOptionError(option, section)
+ #else:
+ # raise NoSectionError(section)
+ return None
+ else:
+ # we want to return a non-parsed value
+ # a unicode of the value is the closest we can get
+ return unicode(value)
+
+ def get(self, section, option, raw=False, vars=None, parse=True):
+ try:
+ # get option's raw mode setting
+ try:
+ section_obj = self.schema.section(section)
+ option_obj = section_obj.option(option)
+ raw = option_obj.raw or raw
+ except:
+ pass
+ # value is defined entirely in current section
+ value = super(SchemaConfigParser, self).get(section, option,
+ raw, vars)
+ except InterpolationMissingOptionError, e:
+ # interpolation key not in same section
+ value = self._interpolate_value(section, option)
+ if value is None:
+ # this should be a string, so None indicates an error
+ raise e
+ except (NoSectionError, NoOptionError), e:
+ # option not found in config, try to get its default value from
+ # schema
+ value = self._get_default(section, option)
+ if value is None:
+ raise
+
+ # value found, so section and option exist
+ # add it to the config
+ if not self.has_section(section):
+ # Don't call .add_section here because 2.6 complains
+ # about sections called '__main__'
+ self._sections[section] = {}
+ self.set(section, option, value)
+
+ if parse:
+ value = self.parse(section, option, value)
+ return value
+
+ def set(self, section, option, value):
+ super(SchemaConfigParser, self).set(section, option, value)
+ filename = self.locate(option)
+ self._dirty[filename][section][option] = value
+
+ def save(self, fp=None):
+ if fp is not None:
+ if isinstance(fp, basestring):
+ fp = open(fp, 'w')
+ # write to a specific file
+ encoded_fp = codecs.getwriter(CONFIG_FILE_ENCODING)(fp)
+ self.write(encoded_fp)
+ else:
+ # write to the original files
+ for filename, sections in self._dirty.items():
+
+ parser = BaseConfigParser()
+ parser.read(filename)
+
+ for section, options in sections.items():
+ for option, value in options.items():
+ parser.set(section, option, value)
+
+ # write to new file
+ parser.write(open("%s.new" % filename, 'w'))
+ # rename old file
+ if os.path.exists(filename):
+ os.rename(filename, "%s.old" % filename)
+ # rename new file
+ os.rename("%s.new" % filename, filename)
+
=== added file 'configglue/pyschema/schema.py'
--- configglue/pyschema/schema.py 1970-01-01 00:00:00 +0000
+++ configglue/pyschema/schema.py 2010-08-04 21:21:41 +0000
@@ -0,0 +1,106 @@
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
+# Released under the BSD License (see the file LICENSE)
+#
+# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
+
+from configglue.pyschema import ConfigOption, ConfigSection, super_vars
+from configglue.pyschema.options import LinesConfigOption, StringConfigOption
+
+
+class Schema(object):
+ """A complete description of a system configuration.
+
+ To define your own configuration schema you should:
+ 1- Inherit from Schema
+ 2- Add ConfigOptions and ConfigSections as class attributes.
+
+ With that your whole configuration schema is defined, and you can now
+ load configuration files.
+
+ ConfigOptions that don't go in a ConfigSection will belong in the
+ '__main__' section of the configuration files.
+
+ One ConfigOption comes already defined in Schema, 'includes' in the
+ '__main__' section, that allows configuration files to include other
+ configuration files.
+ """
+ def __init__(self):
+ self.includes = LinesConfigOption(item=StringConfigOption())
+ self._sections = {}
+ defaultSection = None
+ for attname in super_vars(self.__class__):
+ att = getattr(self, attname)
+ if isinstance(att, ConfigSection):
+ att.name = attname
+ self._sections[attname] = att
+ for optname in super_vars(att):
+ opt = getattr(att, optname)
+ if isinstance(opt, ConfigOption):
+ opt.name = optname
+ opt.section = att
+ elif isinstance(att, ConfigOption):
+ if defaultSection is None:
+ defaultSection = ConfigSection()
+ defaultSection.name = '__main__'
+ self._sections['__main__'] = defaultSection
+ att.name = attname
+ att.section = defaultSection
+ setattr(defaultSection, attname, att)
+
+ def __eq__(self, other):
+ return (self._sections == other._sections and
+ self.includes == other.includes)
+
+ def is_valid(self):
+ explicit_default_section = isinstance(getattr(self, '__main__', None),
+ ConfigSection)
+ is_valid = not explicit_default_section
+ return is_valid
+
+ def has_section(self, name):
+ """Return True if a ConfigSection with the given name is available"""
+ return name in self._sections.keys()
+
+ def section(self, name):
+ """Return a ConfigSection by name"""
+ section = self._sections.get(name)
+ assert section is not None, "Invalid ConfigSection name '%s'" % name
+ return section
+
+ def sections(self):
+ """Returns the list of available ConfigSections"""
+ return self._sections.values()
+
+ def options(self, section=None):
+ """Return all the ConfigOptions within a given section.
+
+ If section is omitted, returns all the options in the configuration
+ file, flattening out any sections.
+ To get options from the default section, specify section='__main__'
+ """
+ if isinstance(section, basestring):
+ section = self.section(section)
+ if section is None:
+ options = []
+ for s in self.sections():
+ options += self.options(s)
+ elif section.name == '__main__':
+ options = [getattr(self, att) for att in super_vars(self.__class__)
+ if isinstance(getattr(self, att), ConfigOption)]
+ else:
+ options = section.options()
+ return options
+
+
=== modified file 'setup.py'
--- setup.py 2009-06-15 18:23:08 +0000
+++ setup.py 2010-08-04 21:21:41 +0000
@@ -1,7 +1,20 @@
-# This file is part of configglue, by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
-# (C) 2009 by Canonical Ltd.
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
# Released under the BSD License (see the file LICENSE)
+#
# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
+
from setuptools import setup, find_packages
import sys, os
@@ -29,5 +42,5 @@
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
include_package_data=True,
zip_safe=True,
- test_suite='configglue.tests',
+ test_suite='tests',
)
=== renamed directory 'configglue/tests' => 'tests'
=== added file 'tests/__init__.py'
--- tests/__init__.py 1970-01-01 00:00:00 +0000
+++ tests/__init__.py 2010-08-04 21:21:41 +0000
@@ -0,0 +1,17 @@
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
+# Released under the BSD License (see the file LICENSE)
+#
+# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
+
=== added directory 'tests/inischema'
=== renamed file 'configglue/tests/__init__.py' => 'tests/inischema/__init__.py'
--- configglue/tests/__init__.py 2009-03-19 20:01:36 +0000
+++ tests/inischema/__init__.py 2010-08-04 21:21:41 +0000
@@ -1,7 +1,19 @@
-# This file is part of configglue, by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
-# (C) 2009 by Canonical Ltd.
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
# Released under the BSD License (see the file LICENSE)
+#
# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
"""Tests! Who woulda said"""
# Two use cases so far for this file:
=== renamed file 'configglue/tests/test_attributed.py' => 'tests/inischema/test_attributed.py'
--- configglue/tests/test_attributed.py 2009-03-19 20:01:36 +0000
+++ tests/inischema/test_attributed.py 2010-08-04 21:21:41 +0000
@@ -1,7 +1,19 @@
-# This file is part of configglue, by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
-# (C) 2009 by Canonical Ltd.
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
# Released under the BSD License (see the file LICENSE)
+#
# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
# in testfiles, putting docstrings on methods messes up with the
# runner's output, so pylint: disable-msg=C0111
@@ -10,7 +22,7 @@
from ConfigParser import RawConfigParser
from StringIO import StringIO
-from configglue.attributed import AttributedConfigParser
+from configglue.inischema.attributed import AttributedConfigParser
class BaseTest(unittest.TestCase):
""" Base class to keep common set-up """
=== renamed file 'configglue/tests/test_glue.py' => 'tests/inischema/test_glue.py'
--- configglue/tests/test_glue.py 2009-03-19 20:01:36 +0000
+++ tests/inischema/test_glue.py 2010-08-04 21:21:41 +0000
@@ -1,7 +1,19 @@
-# This file is part of configglue, by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
-# (C) 2009 by Canonical Ltd.
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
# Released under the BSD License (see the file LICENSE)
+#
# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
# in testfiles, putting docstrings on methods messes up with the
# runner's output, so pylint: disable-msg=C0111
@@ -10,29 +22,33 @@
import unittest
from StringIO import StringIO
-from configglue.glue import configglue
+from configglue.inischema.glue import configglue
class TestBase(unittest.TestCase):
""" Base class to keep common set-up """
def setUp(self):
self.file = StringIO(self.ini)
+ self.old_sys_argv = sys.argv
+ sys.argv = ['']
+
+ def tearDown(self):
+ sys.argv = self.old_sys_argv
class TestGlue(TestBase):
ini = '''
[blah]
-foo.default = 3
foo.help = yadda yadda yadda
yadda
foo.metavar = FOO
foo.parser = int
foo = 2
'''
- arg = '--blah-foo'
+ arg = '--blah_foo'
opt = 'blah_foo'
val = 2
def test_ini_file_wins_when_no_args(self):
- parser, options, args = configglue(self.file)
+ parser, options, args = configglue(self.file, args=[])
self.assertEqual(vars(options),
{self.opt: self.val})
@@ -40,7 +56,7 @@
parser, options, args = configglue(self.file,
args=['', self.arg + '=5'])
self.assertEqual(vars(options),
- {self.opt: 5})
+ {self.opt: '5'})
def test_help_is_displayed(self):
sys.stdout = StringIO()
try:
@@ -60,17 +76,17 @@
foo.parser = int
foo = 2
'''
- arg = '--bl-ah-foo'
+ arg = '--bl-ah_foo'
opt = 'bl_ah_foo'
class TestNoValue(TestGlue):
ini = '''
[blah]
-foo.default = 3
foo.help = yadda yadda yadda
yadda
foo.metavar = FOO
foo.parser = int
+foo = 3
'''
val = 3
@@ -84,11 +100,11 @@
ini = '[x]\na.help=hi\n'
def test_empty(self):
parser, options, args = configglue(self.file)
- self.assertEqual(options.x_a, None)
+ self.assertEqual(options.x_a, '')
def test_accepts_args_and_filenames(self):
parser, options, args = configglue(self.file, 'dummy',
- args=['', '--x-a=1'])
+ args=['', '--x_a=1'])
self.assertEqual(options.x_a, '1')
class TestGlueBool(TestBase):
@@ -121,7 +137,7 @@
'''
def test_nothing(self):
parser, options, args = configglue(self.file)
- self.assertEqual(options.foo, None)
+ self.assertEqual(options.foo, [])
def test_no_append(self):
parser, options, args = configglue(self.file)
=== renamed file 'configglue/tests/test_parsers.py' => 'tests/inischema/test_parsers.py'
--- configglue/tests/test_parsers.py 2009-03-19 20:01:36 +0000
+++ tests/inischema/test_parsers.py 2010-08-04 21:21:41 +0000
@@ -1,14 +1,26 @@
-# This file is part of configglue, by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
-# (C) 2009 by Canonical Ltd.
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
# Released under the BSD License (see the file LICENSE)
+#
# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
# in testfiles, putting docstrings on methods messes up with the
# runner's output, so pylint: disable-msg=C0111
import unittest
-from configglue import parsers
+from configglue.inischema import parsers
class TestParsers(unittest.TestCase):
def test_bool(self):
=== renamed file 'configglue/tests/test_typed.py' => 'tests/inischema/test_typed.py'
--- configglue/tests/test_typed.py 2010-07-29 13:13:21 +0000
+++ tests/inischema/test_typed.py 2010-08-04 21:21:41 +0000
@@ -1,7 +1,19 @@
-# This file is part of configglue, by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
-# (C) 2009 by Canonical Ltd.
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
# Released under the BSD License (see the file LICENSE)
+#
# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
# in testfiles, putting docstrings on methods messes up with the
# runner's output, so pylint: disable-msg=C0111
@@ -10,7 +22,7 @@
from StringIO import StringIO
from ConfigParser import RawConfigParser
-from configglue.typed import TypedConfigParser
+from configglue.inischema.typed import TypedConfigParser
marker = object()
def some_parser(value):
=== added directory 'tests/pyschema'
=== added file 'tests/pyschema/__init__.py'
--- tests/pyschema/__init__.py 1970-01-01 00:00:00 +0000
+++ tests/pyschema/__init__.py 2010-08-04 21:21:41 +0000
@@ -0,0 +1,16 @@
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
+# Released under the BSD License (see the file LICENSE)
+#
+# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
=== added file 'tests/pyschema/test_glue2glue.py'
--- tests/pyschema/test_glue2glue.py 1970-01-01 00:00:00 +0000
+++ tests/pyschema/test_glue2glue.py 2010-08-04 21:21:41 +0000
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
+# Released under the BSD License (see the file LICENSE)
+#
+# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
+
+import sys
+import unittest
+from StringIO import StringIO
+
+from configglue.inischema import configglue
+from configglue.pyschema import schemaconfigglue, ini2schema
+
+
+class TestGlueConvertor(unittest.TestCase):
+ def setUp(self):
+ # make sure we have a clean sys.argv so as not to have unexpected test
+ # results
+ self.old_argv = sys.argv
+ sys.argv = []
+
+ def tearDown(self):
+ # restore old sys.argv
+ sys.argv = self.old_argv
+
+ def test_empty(self):
+ s = ""
+ _, cg, _ = configglue(StringIO(s))
+ _, sg, _ = schemaconfigglue(ini2schema(StringIO(s)))
+ self.assertEqual(vars(cg), vars(sg))
+
+ def test_simple(self):
+ s = "[foo]\nbar = 42\n"
+ _, cg, _ = configglue(StringIO(s))
+ _, sg, _ = schemaconfigglue(ini2schema(StringIO(s)))
+ self.assertEqual(vars(cg), vars(sg))
+
+ def test_main(self):
+ s = "[__main__]\nbar = 42\n"
+ _, cg, _ = configglue(StringIO(s))
+ _, sg, _ = schemaconfigglue(ini2schema(StringIO(s)))
+ self.assertEqual(vars(cg), vars(sg))
+
+ def test_parser_none(self):
+ s = "[__main__]\nbar = meeeeh\nbar.parser = none"
+ _, cg, _ = configglue(StringIO(s),
+ extra_parsers=[('none', str)])
+ _, sg, _ = schemaconfigglue(ini2schema(StringIO(s)))
+ self.assertEqual(vars(cg), vars(sg))
+
+ def test_parser_unicode(self):
+ s = "[__main__]\nbar = zátrapa\nbar.parser = unicode\nbar.parser.args = utf-8"
+ _, cg, _ = configglue(StringIO(s))
+ _, sg, _ = schemaconfigglue(ini2schema(StringIO(s)))
+ self.assertEqual(vars(cg), vars(sg))
+
+ def test_parser_int(self):
+ s = "[__main__]\nbar = 42\nbar.parser = int\n"
+ _, cg, _ = configglue(StringIO(s))
+ _, sg, _ = schemaconfigglue(ini2schema(StringIO(s)))
+ self.assertEqual(vars(cg), vars(sg))
+
+ def test_parser_bool(self):
+ s = "[__main__]\nbar = true\nbar.parser = bool \n"
+ _, cg, _ = configglue(StringIO(s))
+ _, sg, _ = schemaconfigglue(ini2schema(StringIO(s)))
+ self.assertEqual(vars(cg), vars(sg))
+
+if __name__ == '__main__':
+
+ unittest.main()
=== added file 'tests/pyschema/test_options.py'
--- tests/pyschema/test_options.py 1970-01-01 00:00:00 +0000
+++ tests/pyschema/test_options.py 2010-08-04 21:21:41 +0000
@@ -0,0 +1,511 @@
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
+# Released under the BSD License (see the file LICENSE)
+#
+# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
+
+import unittest
+from StringIO import StringIO
+
+from configglue.pyschema.options import (BoolConfigOption, IntConfigOption,
+ StringConfigOption, LinesConfigOption, TupleConfigOption, DictConfigOption)
+from configglue.pyschema.parser import SchemaConfigParser
+from configglue.pyschema.schema import Schema
+
+
+class TestStringConfigOption(unittest.TestCase):
+ def test_init_no_args(self):
+ opt = StringConfigOption()
+ self.assertFalse(opt.null)
+
+ def test_init_null(self):
+ opt = StringConfigOption(null=True)
+ self.assertTrue(opt.null)
+
+ def test_parse_string(self):
+ class MySchema(Schema):
+ foo = StringConfigOption(null=True)
+ config = StringIO("[__main__]\nfoo = 42")
+ expected_values = {'__main__': {'foo': '42'}}
+ schema = MySchema()
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+ config = StringIO("[__main__]\nfoo = ")
+ expected_values = {'__main__': {'foo': ''}}
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+ config = StringIO("[__main__]\nfoo = None")
+ expected_values = {'__main__': {'foo': None}}
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+ class MySchema(Schema):
+ foo = StringConfigOption()
+ config = StringIO("[__main__]\nfoo = None")
+ expected_values = {'__main__': {'foo': 'None'}}
+ schema = MySchema()
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+ def test_default(self):
+ opt = StringConfigOption()
+ self.assertEqual(opt.default, '')
+
+ def test_default_null(self):
+ opt = StringConfigOption(null=True)
+ self.assertEqual(opt.default, None)
+
+
+class TestIntConfigOption(unittest.TestCase):
+ def test_parse_int(self):
+ class MySchema(Schema):
+ foo = IntConfigOption()
+ config = StringIO("[__main__]\nfoo = 42")
+ expected_values = {'__main__': {'foo': 42}}
+ schema = MySchema()
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+ config = StringIO("[__main__]\nfoo =")
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertRaises(ValueError, parser.values)
+
+ config = StringIO("[__main__]\nfoo = bla")
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertRaises(ValueError, parser.values)
+
+ def test_default(self):
+ opt = IntConfigOption()
+ self.assertEqual(opt.default, 0)
+
+
+class TestBoolConfigOption(unittest.TestCase):
+ def test_parse_bool(self):
+ class MySchema(Schema):
+ foo = BoolConfigOption()
+ config = StringIO("[__main__]\nfoo = Yes")
+ expected_values = {'__main__': {'foo': True}}
+ schema = MySchema()
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+ config = StringIO("[__main__]\nfoo = tRuE")
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+ config = StringIO("[__main__]\nfoo =")
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertRaises(ValueError, parser.values)
+
+ config = StringIO("[__main__]\nfoo = bla")
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertRaises(ValueError, parser.values)
+
+ def test_default(self):
+ opt = BoolConfigOption()
+ self.assertEqual(opt.default, False)
+
+
+class TestLinesConfigOption(unittest.TestCase):
+ def test_parse_int_lines(self):
+ class MySchema(Schema):
+ foo = LinesConfigOption(item=IntConfigOption())
+
+ config = StringIO("[__main__]\nfoo = 42\n 43\n 44")
+ expected_values = {'__main__': {'foo': [42, 43, 44]}}
+ schema = MySchema()
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+ def test_parse_bool_lines(self):
+ class MySchema(Schema):
+ foo = LinesConfigOption(item=BoolConfigOption())
+ schema = MySchema()
+ config = StringIO("[__main__]\nfoo = tRuE\n No\n 0\n 1")
+ expected_values = {'__main__': {'foo': [True, False, False, True]}}
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEqual(expected_values, parser.values())
+
+ def test_parse_bool_empty_lines(self):
+ class MySchema(Schema):
+ foo = LinesConfigOption(item=BoolConfigOption())
+ schema = MySchema()
+ config = StringIO("[__main__]\nfoo =")
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ expected_values = {'__main__': {'foo': []}}
+ self.assertEqual(expected_values, parser.values())
+
+ def test_parse_bool_invalid_lines(self):
+ class MySchema(Schema):
+ foo = LinesConfigOption(item=BoolConfigOption())
+ schema = MySchema()
+ config = StringIO("[__main__]\nfoo = bla")
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertRaises(ValueError, parser.values)
+
+ config = StringIO("[__main__]\nfoo = True\n bla")
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertRaises(ValueError, parser.values)
+
+ def test_default(self):
+ opt = LinesConfigOption(item=IntConfigOption())
+ self.assertEqual(opt.default, [])
+
+ def test_remove_duplicates(self):
+ class MySchema(Schema):
+ foo = LinesConfigOption(item=StringConfigOption(),
+ remove_duplicates=True)
+ schema = MySchema()
+ config = StringIO("[__main__]\nfoo = bla\n blah\n bla")
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEquals({'__main__': {'foo': ['bla', 'blah']}},
+ parser.values())
+
+ def test_remove_dict_duplicates(self):
+ class MyOtherSchema(Schema):
+ foo = LinesConfigOption(item=DictConfigOption(),
+ remove_duplicates=True)
+ schema = MyOtherSchema()
+ config = StringIO("[__main__]\nfoo = bla\n bla\n[bla]\nbar = baz")
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEquals({'__main__': {'foo': [{'bar': 'baz'}]}},
+ parser.values())
+
+class TestTupleConfigOption(unittest.TestCase):
+ def test_init(self):
+ opt = TupleConfigOption(2)
+ self.assertEqual(opt.length, 2)
+
+ def test_init_no_length(self):
+ opt = TupleConfigOption()
+ self.assertEqual(opt.length, 0)
+ self.assertEqual(opt.default, ())
+
+ def test_parse_no_length(self):
+ class MySchema(Schema):
+ foo = TupleConfigOption()
+
+ config = StringIO('[__main__]\nfoo=1,2,3,4')
+ expected_values = {'__main__': {'foo': ('1', '2', '3', '4')}}
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+ def test_parse_tuple(self):
+ class MySchema(Schema):
+ foo = TupleConfigOption(length=4)
+ config = StringIO('[__main__]\nfoo = 1, 2, 3, 4')
+ expected_values = {'__main__': {'foo': ('1', '2', '3', '4')}}
+ schema = MySchema()
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+ config = StringIO('[__main__]\nfoo = 1, 2, 3')
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertRaises(ValueError, parser.values)
+
+ config = StringIO('[__main__]\nfoo = ')
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertRaises(ValueError, parser.values)
+
+ def test_default(self):
+ opt = TupleConfigOption(2)
+ self.assertEqual(opt.default, ())
+
+
+class TestDictConfigOption(unittest.TestCase):
+ def test_init(self):
+ opt = DictConfigOption()
+ self.assertEqual(opt.spec, {})
+ self.assertEqual(opt.strict, False)
+
+ spec = {'a': IntConfigOption(), 'b': BoolConfigOption()}
+ opt = DictConfigOption(spec)
+ self.assertEqual(opt.spec, spec)
+ self.assertEqual(opt.strict, False)
+
+ opt = DictConfigOption(spec, strict=True)
+ self.assertEqual(opt.spec, spec)
+ self.assertEqual(opt.strict, True)
+
+ def test_get_extra_sections(self):
+ class MySchema(Schema):
+ foo = DictConfigOption(item=DictConfigOption())
+
+ config = StringIO("""
+[__main__]
+foo=dict1
+[dict1]
+bar=dict2
+[dict2]
+baz=42
+""")
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ expected = ['dict2']
+
+ opt = DictConfigOption(item=DictConfigOption())
+ extra = opt.get_extra_sections('dict1', parser)
+ self.assertEqual(extra, expected)
+
+ def test_parse_dict(self):
+ class MySchema(Schema):
+ foo = DictConfigOption({'bar': StringConfigOption(),
+ 'baz': IntConfigOption(),
+ 'bla': BoolConfigOption(),
+ })
+ config = StringIO("""[__main__]
+foo = mydict
+[mydict]
+bar=baz
+baz=42
+bla=Yes
+""")
+ expected_values = {
+ '__main__': {
+ 'foo': {'bar': 'baz', 'baz': 42, 'bla': True}
+ }
+ }
+
+ schema = MySchema()
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+ def test_parse_raw(self):
+ class MySchema(Schema):
+ foo = DictConfigOption({'bar': StringConfigOption(),
+ 'baz': IntConfigOption(),
+ 'bla': BoolConfigOption(),
+ })
+ config = StringIO("""[__main__]
+foo = mydict
+[mydict]
+baz=42
+""")
+ expected = {'bar': '', 'baz': '42', 'bla': 'False'}
+
+ schema = MySchema()
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ parsed = schema.foo.parse('mydict', parser, True)
+ self.assertEqual(parsed, expected)
+
+ def test_parse_invalid_key_in_parsed(self):
+ class MySchema(Schema):
+ foo = DictConfigOption({'bar': IntConfigOption()})
+
+ config = StringIO("[__main__]\nfoo=mydict\n[mydict]\nbaz=2")
+ expected_values = {'__main__': {'foo': {'bar': 0, 'baz': '2'}}}
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+ def test_parse_invalid_key_in_spec(self):
+ class MySchema(Schema):
+ foo = DictConfigOption({'bar': IntConfigOption(),
+ 'baz': IntConfigOption(fatal=True)})
+
+ config = StringIO("[__main__]\nfoo=mydict\n[mydict]\nbar=2")
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ self.assertRaises(ValueError, parser.parse_all)
+
+ def test_default(self):
+ opt = DictConfigOption({})
+ self.assertEqual(opt.default, {})
+
+ def test_parse_no_strict_missing_args(self):
+ class MySchema(Schema):
+ foo = DictConfigOption({'bar': IntConfigOption()})
+
+ config = StringIO("[__main__]\nfoo=mydict\n[mydict]")
+ expected_values = {'__main__': {'foo': {'bar': 0}}}
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+ def test_parse_no_strict_extra_args(self):
+ class MySchema(Schema):
+ foo = DictConfigOption()
+
+ config = StringIO("[__main__]\nfoo=mydict\n[mydict]\nbar=2")
+ expected_values = {'__main__': {'foo': {'bar': '2'}}}
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+ def test_parse_no_strict_with_item(self):
+ class MySchema(Schema):
+ foo = DictConfigOption(
+ item=DictConfigOption(
+ item=IntConfigOption()))
+ config = StringIO("""
+[__main__]
+foo = mydict
+[mydict]
+bar = baz
+[baz]
+wham=42
+""")
+ expected_values = {'__main__': {'foo': {'bar': {'wham': 42}}}}
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+ def test_parse_strict(self):
+ class MySchema(Schema):
+ spec = {'bar': IntConfigOption()}
+ foo = DictConfigOption(spec, strict=True)
+
+ config = StringIO("[__main__]\nfoo=mydict\n[mydict]\nbar=2")
+ expected_values = {'__main__': {'foo': {'bar': 2}}}
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+ def test_parse_strict_missing_vars(self):
+ class MySchema(Schema):
+ spec = {'bar': IntConfigOption(),
+ 'baz': IntConfigOption()}
+ foo = DictConfigOption(spec, strict=True)
+
+ config = StringIO("[__main__]\nfoo=mydict\n[mydict]\nbar=2")
+ expected_values = {'__main__': {'foo': {'bar': 2, 'baz': 0}}}
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+ def test_parse_strict_extra_vars(self):
+ class MySchema(Schema):
+ spec = {'bar': IntConfigOption()}
+ foo = DictConfigOption(spec, strict=True)
+
+ config = StringIO("[__main__]\nfoo=mydict\n[mydict]\nbar=2\nbaz=3")
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ self.assertRaises(ValueError, parser.parse_all)
+
+
+class TestLinesOfDictConfigOption(unittest.TestCase):
+ def test_parse_lines_of_dict(self):
+ class MySchema(Schema):
+ foo = LinesConfigOption(
+ DictConfigOption({'bar': StringConfigOption(),
+ 'baz': IntConfigOption(),
+ 'bla': BoolConfigOption(),
+ }))
+ config = StringIO("""[__main__]
+foo = mylist0
+ mylist1
+[mylist0]
+bar=baz
+baz=42
+bla=Yes
+[mylist1]
+bar=zort
+baz=123
+bla=0
+""")
+ expected_values = {
+ '__main__': {'foo': [{'bar': 'baz', 'baz': 42, 'bla': True},
+ {'bar': 'zort', 'baz': 123, 'bla': False},
+ ]}}
+
+ schema = MySchema()
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+
+class TestDictWithDicts(unittest.TestCase):
+ def test_parse_dict_with_dicts(self):
+ innerspec = {'bar': StringConfigOption(),
+ 'baz': IntConfigOption(),
+ 'bla': BoolConfigOption(),
+ }
+ spec = {'name': StringConfigOption(),
+ 'size': IntConfigOption(),
+ 'options': DictConfigOption(innerspec)}
+ class MySchema(Schema):
+ foo = DictConfigOption(spec)
+ config = StringIO("""[__main__]
+foo = outerdict
+[outerdict]
+options = innerdict
+[innerdict]
+bar = something
+baz = 42
+""")
+ expected_values = {
+ '__main__': {'foo': {'name': '', 'size': 0,
+ 'options': {'bar': 'something', 'baz': 42,
+ 'bla': False}}}}
+ schema = MySchema()
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEqual(parser.values(), expected_values)
+
+
+class TestListOfTuples(unittest.TestCase):
+ def setUp(self):
+ class MySchema(Schema):
+ foo = LinesConfigOption(item=TupleConfigOption(length=3))
+ schema = MySchema()
+ self.parser = SchemaConfigParser(schema)
+
+ def test_parse_list_of_tuples(self):
+ config = StringIO('[__main__]\nfoo = a, b, c\n d, e, f')
+ expected_values = {
+ '__main__': {'foo': [('a', 'b', 'c'), ('d', 'e', 'f')]}}
+ self.parser.readfp(config)
+ self.assertEqual(self.parser.values(), expected_values)
+
+ def test_parse_wrong_tuple_size(self):
+ config = StringIO('[__main__]\nfoo = a, b, c\n d, e')
+ self.parser.readfp(config)
+ self.assertRaises(ValueError, self.parser.values)
+
+ def test_parse_empty_tuple(self):
+ config = StringIO('[__main__]\nfoo=()')
+ expected_values = {'__main__': {'foo': [()]}}
+ self.parser.readfp(config)
+ self.assertEqual(self.parser.values(), expected_values)
+
+
+if __name__ == '__main__':
+ unittest.main()
=== added file 'tests/pyschema/test_parser.py'
--- tests/pyschema/test_parser.py 1970-01-01 00:00:00 +0000
+++ tests/pyschema/test_parser.py 2010-08-04 21:21:41 +0000
@@ -0,0 +1,1009 @@
+# -*- coding: utf-8 -*-
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
+# Released under the BSD License (see the file LICENSE)
+#
+# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
+
+import os
+import shutil
+import tempfile
+import unittest
+from StringIO import StringIO
+
+from ConfigParser import (InterpolationMissingOptionError,
+ InterpolationDepthError, NoSectionError)
+
+from configglue.pyschema import ConfigSection
+from configglue.pyschema.options import (BoolConfigOption, DictConfigOption,
+ IntConfigOption, StringConfigOption, LinesConfigOption, TupleConfigOption)
+from configglue.pyschema.schema import Schema
+from configglue.pyschema.parser import (NoOptionError, SchemaConfigParser,
+ SchemaValidationError, CONFIG_FILE_ENCODING)
+
+
+class TestIncludes(unittest.TestCase):
+ def setUp(self):
+ class MySchema(Schema):
+ foo = StringConfigOption()
+ self.schema = MySchema()
+ fd, self.name = tempfile.mkstemp(suffix='.cfg')
+ os.write(fd, '[__main__]\nfoo=bar\n')
+ os.close(fd)
+
+ def tearDown(self):
+ os.remove(self.name)
+
+ def test_basic_include(self):
+ config = StringIO('[__main__]\nincludes=%s' % self.name)
+ parser = SchemaConfigParser(self.schema)
+ parser.readfp(config, 'my.cfg')
+ self.assertEquals({'__main__': {'foo': 'bar'}}, parser.values())
+
+ def test_locate(self):
+ config = StringIO("[__main__]\nincludes=%s" % self.name)
+ parser = SchemaConfigParser(self.schema)
+ parser.readfp(config, 'my.cfg')
+
+ location = parser.locate(option='foo')
+ expected_location = self.name
+ self.assertEqual(expected_location, location)
+
+ def test_read_ioerror(self):
+ def mock_open(filename, mode='r', encoding='ascii'):
+ raise IOError
+ _open = __builtins__['open']
+ __builtins__['open'] = mock_open
+
+ parser = SchemaConfigParser(self.schema)
+ read_ok = parser.read(self.name)
+ self.assertEqual(read_ok, [])
+
+ __builtins__['open'] = _open
+
+ def test_relative_include(self):
+ def setup_config():
+ folder = tempfile.mkdtemp()
+
+ f = open("%s/first.cfg" % folder, 'w')
+ f.write("[__main__]\nfoo=1\nincludes=second.cfg")
+ f.close()
+
+ f = open("%s/second.cfg" % folder, 'w')
+ f.write("[__main__]\nbar=2\nincludes=sub/third.cfg")
+ f.close()
+
+ os.mkdir("%s/sub" % folder)
+ f = open("%s/sub/third.cfg" % folder, 'w')
+ f.write("[__main__]\nincludes=../fourth.cfg")
+ f.close()
+
+ f = open("%s/fourth.cfg" % folder, 'w')
+ f.write("[__main__]\nbaz=3")
+ f.close()
+
+ config = StringIO("[__main__]\nincludes=%s/first.cfg" % folder)
+ return config, folder
+
+ class MySchema(Schema):
+ foo = IntConfigOption()
+ bar = IntConfigOption()
+ baz = IntConfigOption()
+
+ config, folder = setup_config()
+ expected_values = {'__main__': {'foo': 1, 'bar': 2, 'baz': 3}}
+ parser = SchemaConfigParser(MySchema())
+ # make sure we start on a clean basedir
+ self.assertEqual(parser._basedir, '')
+ parser.readfp(config, 'my.cfg')
+ self.assertEqual(parser.values(), expected_values)
+ # make sure we leave the basedir clean
+ self.assertEqual(parser._basedir, '')
+
+ # silently remove any created files
+ try:
+ shutil.rmtree(folder)
+ except:
+ pass
+
+ def test_local_override(self):
+ def setup_config():
+ folder = tempfile.mkdtemp()
+
+ f = open("%s/first.cfg" % folder, 'w')
+ f.write("[__main__]\nfoo=1\nbar=2\nincludes=second.cfg")
+ f.close()
+
+ f = open("%s/second.cfg" % folder, 'w')
+ f.write("[__main__]\nbaz=3")
+ f.close()
+
+ config = StringIO("[__main__]\nfoo=4\nincludes=%s/first.cfg" % folder)
+ return config, folder
+
+ class MySchema(Schema):
+ foo = IntConfigOption()
+ bar = IntConfigOption()
+ baz = IntConfigOption()
+
+ config, folder = setup_config()
+ expected_values = {'__main__': {'foo': 4, 'bar': 2, 'baz': 3}}
+ parser = SchemaConfigParser(MySchema())
+ # make sure we start on a clean basedir
+ self.assertEqual(parser._basedir, '')
+ parser.readfp(config, 'my.cfg')
+ self.assertEqual(parser.values(), expected_values)
+ # make sure we leave the basedir clean
+ self.assertEqual(parser._basedir, '')
+
+ # silently remove any created files
+ try:
+ shutil.rmtree(folder)
+ except:
+ pass
+
+
+class TestInterpolation(unittest.TestCase):
+ def test_basic_interpolate(self):
+ class MySchema(Schema):
+ foo = StringConfigOption()
+ bar = BoolConfigOption()
+ config = StringIO('[__main__]\nbar=%(foo)s\nfoo=True')
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config, 'my.cfg')
+ self.assertEquals({'__main__': {'foo': 'True', 'bar': True}},
+ parser.values())
+
+ def test_interpolate_missing_option(self):
+ class MySchema(Schema):
+ foo = StringConfigOption()
+ bar = BoolConfigOption()
+
+ section = '__main__'
+ option = 'foo'
+ rawval = '%(baz)s'
+ vars = {}
+ parser = SchemaConfigParser(MySchema())
+ self.assertRaises(InterpolationMissingOptionError,
+ parser._interpolate, section, option, rawval, vars)
+
+ def test_interpolate_too_deep(self):
+ class MySchema(Schema):
+ foo = StringConfigOption()
+ bar = BoolConfigOption()
+
+ section = '__main__'
+ option = 'foo'
+ rawval = '%(bar)s'
+ vars = {'foo': '%(bar)s', 'bar': '%(foo)s'}
+ parser = SchemaConfigParser(MySchema())
+ self.assertRaises(InterpolationDepthError,
+ parser._interpolate, section, option, rawval, vars)
+
+ def test_interpolate_incomplete_format(self):
+ class MySchema(Schema):
+ foo = StringConfigOption()
+ bar = BoolConfigOption()
+
+ section = '__main__'
+ option = 'foo'
+ rawval = '%(bar)'
+ vars = {'foo': '%(bar)s', 'bar': 'pepe'}
+ parser = SchemaConfigParser(MySchema())
+ self.assertRaises(ValueError, parser._interpolate, section, option,
+ rawval, vars)
+
+ def test_interpolate_across_sections(self):
+ class MySchema(Schema):
+ foo = ConfigSection()
+ foo.bar = IntConfigOption()
+
+ baz = ConfigSection()
+ baz.wham = IntConfigOption()
+
+ config = StringIO("[foo]\nbar=%(wham)s\n[baz]\nwham=42")
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ self.assertRaises(InterpolationMissingOptionError,
+ parser.get, 'foo', 'bar')
+
+ def test_interpolate_invalid_key(self):
+ class MySchema(Schema):
+ foo = ConfigSection()
+ foo.bar = IntConfigOption()
+
+ baz = ConfigSection()
+ baz.wham = IntConfigOption()
+
+ config = StringIO("[foo]\nbar=%(wham)\n[baz]\nwham=42")
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ self.assertRaises(InterpolationMissingOptionError, parser.get,
+ 'foo', 'bar')
+
+ def test_get_interpolation_keys_string(self):
+ class MySchema(Schema):
+ foo = StringConfigOption()
+ config = StringIO("[__main__]\nfoo=%(bar)s")
+ expected = ('%(bar)s', set(['bar']))
+
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ result = parser._get_interpolation_keys('__main__', 'foo')
+ self.assertEqual(result, expected)
+
+ def test_get_interpolation_keys_int(self):
+ class MySchema(Schema):
+ foo = IntConfigOption()
+ config = StringIO("[__main__]\nfoo=%(bar)s")
+ expected = ('%(bar)s', set(['bar']))
+
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ result = parser._get_interpolation_keys('__main__', 'foo')
+ self.assertEqual(result, expected)
+
+ def test_get_interpolation_keys_bool(self):
+ class MySchema(Schema):
+ foo = BoolConfigOption()
+ config = StringIO("[__main__]\nfoo=%(bar)s")
+ expected = ('%(bar)s', set(['bar']))
+
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ result = parser._get_interpolation_keys('__main__', 'foo')
+ self.assertEqual(result, expected)
+
+ def test_get_interpolation_keys_tuple(self):
+ class MySchema(Schema):
+ foo = TupleConfigOption(2)
+ config = StringIO("[__main__]\nfoo=%(bar)s,%(baz)s")
+ expected = ('%(bar)s,%(baz)s', set(['bar', 'baz']))
+
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ result = parser._get_interpolation_keys('__main__', 'foo')
+ self.assertEqual(result, expected)
+
+ def test_get_interpolation_keys_lines(self):
+ class MySchema(Schema):
+ foo = LinesConfigOption(item=StringConfigOption())
+ config = StringIO("[__main__]\nfoo=%(bar)s\n %(baz)s")
+ expected = ('%(bar)s\n%(baz)s', set(['bar', 'baz']))
+
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ result = parser._get_interpolation_keys('__main__', 'foo')
+ self.assertEqual(result, expected)
+
+ def test_get_interpolation_keys_tuple_lines(self):
+ class MySchema(Schema):
+ foo = LinesConfigOption(item=TupleConfigOption(2))
+ config = StringIO("[__main__]\nfoo=%(bar)s,%(bar)s\n %(baz)s,%(baz)s")
+ expected = ('%(bar)s,%(bar)s\n%(baz)s,%(baz)s',
+ set(['bar', 'baz']))
+
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ result = parser._get_interpolation_keys('__main__', 'foo')
+ self.assertEqual(result, expected)
+
+ def test_get_interpolation_keys_dict(self):
+ class MySchema(Schema):
+ foo = DictConfigOption({'a': IntConfigOption()})
+ config = StringIO("[__noschema__]\nbar=4\n[__main__]\nfoo=mydict\n[mydict]\na=%(bar)s")
+ expected = ('mydict', set([]))
+
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ result = parser._get_interpolation_keys('__main__', 'foo')
+ self.assertEqual(result, expected)
+
+ def test_interpolate_value_duplicate_key(self):
+ class MySchema(Schema):
+ foo = TupleConfigOption(2)
+ config = StringIO("[__noschema__]\nbar=4\n[__main__]\nfoo=%(bar)s,%(bar)s")
+ expected_value = '4,4'
+
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ value = parser._interpolate_value('__main__', 'foo')
+ self.assertEqual(value, expected_value)
+
+ def test_interpolate_value_invalid_key(self):
+ class MySchema(Schema):
+ foo = TupleConfigOption(2)
+ config = StringIO("[other]\nbar=4\n[__main__]\nfoo=%(bar)s,%(bar)s")
+ expected_value = None
+
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ value = parser._interpolate_value('__main__', 'foo')
+ self.assertEqual(value, expected_value)
+
+ def test_get_with_raw_value(self):
+ class MySchema(Schema):
+ foo = StringConfigOption(raw=True)
+ config = StringIO('[__main__]\nfoo=blah%(asd)##$@@dddf2kjhkjs')
+ expected_value = 'blah%(asd)##$@@dddf2kjhkjs'
+
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ value = parser.get('__main__', 'foo')
+ self.assertEqual(value, expected_value)
+
+ def test_interpolate_parse_dict(self):
+ class MySchema(Schema):
+ foo = DictConfigOption({'a': IntConfigOption()})
+ config = StringIO("[__noschema__]\nbar=4\n[__main__]\nfoo=mydict\n[mydict]\na=%(bar)s")
+ expected = {'__main__': {'foo': {'a': 4}}}
+
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ result = parser.values()
+ self.assertEqual(result, expected)
+
+
+class TestSchemaConfigParser(unittest.TestCase):
+ def setUp(self):
+ class MySchema(Schema):
+ foo = StringConfigOption()
+ self.schema = MySchema()
+ self.parser = SchemaConfigParser(self.schema)
+ self.config = StringIO("[__main__]\nfoo = bar")
+
+ def test_init_no_args(self):
+ self.assertRaises(TypeError, SchemaConfigParser)
+
+ def test_init_valid_schema(self):
+ self.assertEqual(self.parser.schema, self.schema)
+
+ def test_init_invalid_schema(self):
+ class MyInvalidSchema(Schema):
+ __main__ = ConfigSection()
+
+ self.assertRaises(SchemaValidationError, SchemaConfigParser,
+ MyInvalidSchema())
+
+ def test_items(self):
+ self.parser.readfp(self.config)
+ items = self.parser.items('__main__')
+ self.assertEqual(set(items), set([('foo', 'bar')]))
+
+ def test_items_no_section(self):
+ self.assertRaises(NoSectionError, self.parser.items, '__main__')
+
+ def test_items_raw(self):
+ config = StringIO('[__main__]\nfoo=%(baz)s')
+ self.parser.readfp(config)
+ items = self.parser.items('__main__', raw=True)
+ self.assertEqual(set(items), set([('foo', '%(baz)s')]))
+
+ def test_items_vars(self):
+ config = StringIO('[__main__]\nfoo=%(baz)s')
+ self.parser.readfp(config)
+ items = self.parser.items('__main__', vars={'baz': '42'})
+ self.assertEqual(set(items), set([('foo', '42'), ('baz', '42')]))
+
+ def test_items_interpolate(self):
+ class MySchema(Schema):
+ foo = StringConfigOption()
+ baz = ConfigSection()
+ baz.bar = StringConfigOption()
+
+ parser = SchemaConfigParser(MySchema())
+ config = StringIO('[__main__]\nfoo=%(bar)s\n[baz]\nbar=42')
+ parser.readfp(config)
+ # test interpolate
+ items = parser.items('baz')
+ self.assertEqual(items, {'bar': '42'}.items())
+
+ def test_items_interpolate_error(self):
+ config = StringIO('[__main__]\nfoo=%(bar)s')
+ self.parser.readfp(config)
+ self.assertRaises(InterpolationMissingOptionError, self.parser.items,
+ '__main__')
+
+ def test_values_empty_parser(self):
+ values = self.parser.values()
+ self.assertEqual(values, {'__main__': {'foo': ''}})
+
+ def test_values_full_parser(self):
+ expected_values = {'__main__': {'foo': 'bar'}}
+
+ self.parser.readfp(self.config)
+ values = self.parser.values()
+ self.assertEqual(expected_values, values)
+ values = self.parser.values(section='__main__')
+ self.assertEqual(expected_values['__main__'], values)
+
+ def test_values_many_sections_same_option(self):
+ class MySchema(Schema):
+ foo = ConfigSection()
+ foo.bar = IntConfigOption()
+
+ baz = ConfigSection()
+ baz.bar = IntConfigOption()
+
+ config = StringIO("[foo]\nbar=3\n[baz]\nbar=4")
+ expected_values = {'foo': {'bar': 3}, 'baz': {'bar': 4}}
+
+ schema = MySchema()
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ values = parser.values()
+ self.assertEqual(values, expected_values)
+
+ def test_values_many_sections_different_options(self):
+ class MySchema(Schema):
+ foo = ConfigSection()
+ foo.bar = IntConfigOption()
+
+ bar = ConfigSection()
+ bar.baz = IntConfigOption()
+
+ config = StringIO("[foo]\nbar=3\n[bar]\nbaz=4")
+ expected_values = {'foo': {'bar': 3}, 'bar': {'baz': 4}}
+
+ schema = MySchema()
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ values = parser.values()
+ self.assertEqual(values, expected_values)
+
+ def test_parse_option(self):
+ class MyOtherSchema(Schema):
+ foo = ConfigSection()
+ foo.bar = StringConfigOption()
+
+ expected_value = 'baz'
+ config = StringIO("[foo]\nbar = baz")
+ parser = SchemaConfigParser(MyOtherSchema())
+ parser.readfp(config)
+ value = parser.get('foo', 'bar')
+ self.assertEqual(value, expected_value)
+
+ def test_parse_invalid_section(self):
+ self.assertRaises(NoSectionError, self.parser.parse, 'bar', 'baz', '1')
+
+ def test_default_values(self):
+ class MySchema(Schema):
+ foo = BoolConfigOption(default=True)
+ bar = ConfigSection()
+ bar.baz = IntConfigOption()
+ bar.bla = StringConfigOption(default='hello')
+ schema = MySchema()
+ config = StringIO("[bar]\nbaz=123")
+ expected_values = {'__main__': {'foo': True},
+ 'bar': {'baz': 123, 'bla': 'hello'}}
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEquals(expected_values, parser.values())
+
+ config = StringIO("[bar]\nbla=123")
+ expected = {'__main__': {'foo': True}, 'bar': {'baz': 0, 'bla': '123'}}
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEquals(expected, parser.values())
+
+ def test_fatal_options(self):
+ class MySchema(Schema):
+ foo = IntConfigOption(fatal=True)
+ bar = IntConfigOption()
+ schema = MySchema()
+ config = StringIO("[__main__]\nfoo=123")
+ expected = {'__main__': {'foo': 123, 'bar': 0}}
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertEquals(expected, parser.values())
+
+ config = StringIO("[__main__]\nbar=123")
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+ self.assertRaises(NoOptionError, parser.values)
+
+ def test_extra_sections(self):
+ class MySchema(Schema):
+ foo = DictConfigOption({'bar': IntConfigOption()})
+
+ config = StringIO("[__main__]\nfoo=mydict\n[mydict]\nbar=1")
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ parser.parse_all()
+
+ expected_sections = set(['mydict'])
+ extra_sections = parser.extra_sections
+ self.assertEqual(expected_sections, extra_sections)
+
+ def test_multiple_extra_sections(self):
+ class MySchema(Schema):
+ foo = LinesConfigOption(
+ item=DictConfigOption({'bar': IntConfigOption()}))
+
+ config = StringIO('[__main__]\nfoo=d1\n d2\n d3\n'
+ '[d1]\nbar=1\n[d2]\nbar=2\n[d3]\nbar=3')
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ parser.parse_all()
+
+ expected_sections = set(['d1', 'd2', 'd3'])
+ extra_sections = parser.extra_sections
+ self.assertEqual(expected_sections, extra_sections)
+
+ def test_get_default(self):
+ config = StringIO("[__main__]\n")
+ expected = ''
+ self.parser.readfp(config)
+ default = self.parser._get_default('__main__', 'foo')
+ self.assertEqual(default, expected)
+
+ def test_get_default_noschema(self):
+ config = StringIO("[__noschema__]\nbar=1\n[__main__]\n")
+ expected = '1'
+ self.parser.readfp(config)
+ default = self.parser._get_default('__noschema__', 'bar')
+ self.assertEqual(default, expected)
+
+ def test_get_default_from_section(self):
+ class MySchema(Schema):
+ foo = ConfigSection()
+ foo.bar = IntConfigOption()
+ config = StringIO("[__main__]\n")
+ expected = '0'
+
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ default = parser._get_default('foo', 'bar')
+ self.assertEqual(default, expected)
+
+ def test_get_default_no_option(self):
+ expected = None
+ default = self.parser._get_default('__main__', 'bar')
+ self.assertEqual(default, expected)
+
+ def test_get_default_no_section(self):
+ expected = None
+ default = self.parser._get_default('foo', 'bar')
+ self.assertEqual(default, expected)
+
+ def test_multi_file_dict_config(self):
+ class MySchema(Schema):
+ foo = DictConfigOption({'bar': IntConfigOption(),
+ 'baz': IntConfigOption()},
+ strict=True)
+ config1 = StringIO('[__main__]\nfoo=mydict\n[mydict]\nbar=1\nbaz=1')
+ config2 = StringIO('[mydict]\nbaz=2')
+ expected_values = {'__main__': {'foo': {'bar': 1, 'baz': 2}}}
+
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config1)
+ parser.readfp(config2)
+ self.assertEqual(parser.values(), expected_values)
+
+ def test_multi_file_dict_list_config(self):
+ class MySchema(Schema):
+ foo = LinesConfigOption(
+ item=DictConfigOption({'bar': IntConfigOption(),
+ 'baz': IntConfigOption()},
+ strict=True))
+
+ config1 = StringIO('[__main__]\nfoo=mydict\n[mydict]\nbar=1\nbaz=1')
+ expected_values = {'__main__': {'foo': [{'bar': 1, 'baz': 1}]}}
+
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config1)
+ self.assertEqual(parser.values(), expected_values)
+
+ # override used dictionaries
+ config2 = StringIO('[__main__]\nfoo=otherdict\n[otherdict]\nbar=2')
+ expected_values = {'__main__': {'foo': [{'bar': 2, 'baz': 0}]}}
+ parser.readfp(config2)
+ self.assertEqual(parser.values(), expected_values)
+
+ # override existing dictionaries
+ config3 = StringIO('[otherdict]\nbaz=3')
+ expected_values = {'__main__': {'foo': [{'bar': 2, 'baz': 3}]}}
+ parser.readfp(config3)
+ self.assertEqual(parser.values(), expected_values)
+
+ # reuse existing dict
+ config4 = StringIO('[__main__]\nfoo=mydict\n otherdict')
+ expected_values = {'__main__': {'foo': [{'bar': 1, 'baz': 1},
+ {'bar': 2, 'baz': 3}]}}
+ parser.readfp(config4)
+ self.assertEqual(parser.values(), expected_values)
+
+ def test_read_multiple_files(self):
+ def setup_config():
+ folder = tempfile.mkdtemp()
+
+ f = open("%s/first.cfg" % folder, 'w')
+ f.write("[__main__]\nfoo=foo")
+ f.close()
+
+ f = open("%s/second.cfg" % folder, 'w')
+ f.write("[__main__]\nfoo=bar")
+ f.close()
+
+ files = ["%s/first.cfg" % folder, "%s/second.cfg" % folder]
+ return files, folder
+
+ files, folder = setup_config()
+ self.parser.read(files)
+ self.assertEqual(self.parser.values(), {'__main__': {'foo': 'bar'}})
+
+ # silently remove any created files
+ try:
+ shutil.rmtree(folder)
+ except:
+ pass
+
+ def test_read_utf8_encoded_file(self):
+ # create config file
+ fp, filename = tempfile.mkstemp()
+
+ try:
+ f = open(filename, 'w')
+ f.write(u'[__main__]\nfoo=â¬'.encode(CONFIG_FILE_ENCODING))
+ f.close()
+
+ self.parser.read(filename)
+ self.assertEqual(self.parser.values(), {'__main__': {'foo': u'â¬'}})
+ finally:
+ # destroy config file
+ os.remove(filename)
+
+ def test_readfp_with_utf8_encoded_text(self):
+ config = StringIO(u'[__main__]\nfoo=â¬'.encode(CONFIG_FILE_ENCODING))
+ self.parser.readfp(config)
+ self.assertEqual(self.parser.values(), {'__main__': {'foo': u'â¬'}})
+
+ def test_set(self):
+ with tempfile.NamedTemporaryFile() as f:
+ f.write('[__main__]\nfoo=1')
+ f.flush()
+
+ self.parser.read(f.name)
+ self.assertEqual(self.parser._dirty, {})
+ self.assertEqual(self.parser.get('__main__', 'foo'), '1')
+ self.parser.set('__main__', 'foo', '2')
+ self.assertEqual(self.parser.get('__main__', 'foo'), '2')
+ self.assertEqual(self.parser._dirty,
+ {f.name: {'__main__': {'foo': '2'}}})
+
+ def test_save_config(self):
+ expected_output = u'[__main__]\nfoo = 42\n\n'
+ config = StringIO(u'[__main__]\nfoo=42')
+ self.parser.readfp(config)
+
+ # create config file
+ fp, filename = tempfile.mkstemp()
+ self.parser.save(open(filename, 'w'))
+ self.assertEqual(open(filename, 'r').read(), expected_output)
+
+ self.parser.save(filename)
+ self.assertEqual(open(filename, 'r').read(), expected_output)
+
+ # remove the file
+ os.unlink(filename)
+
+ def test_save_config_same_files(self):
+ def setup_config():
+ folder = tempfile.mkdtemp()
+
+ f = open("%s/first.cfg" % folder, 'w')
+ f.write("[__main__]\nfoo=1")
+ f.close()
+
+ f = open("%s/second.cfg" % folder, 'w')
+ f.write("[__main__]\nbar=2")
+ f.close()
+
+ files = ["%s/first.cfg" % folder, "%s/second.cfg" % folder]
+ return files, folder
+
+ class MySchema(Schema):
+ foo = StringConfigOption()
+ bar = StringConfigOption()
+
+ self.parser = SchemaConfigParser(MySchema())
+
+ files, folder = setup_config()
+ self.parser.read(files)
+ self.parser.set('__main__', 'foo', '42')
+ self.parser.set('__main__', 'bar', '42')
+ self.parser.save()
+
+ # test the changes were correctly saved
+ data = open("%s/first.cfg" % folder).read()
+ self.assertTrue('foo = 42' in data)
+ self.assertFalse('bar = 42' in data)
+ data = open("%s/second.cfg" % folder).read()
+ self.assertFalse('foo = 42' in data)
+ self.assertTrue('bar = 42' in data)
+
+ # silently remove any created files
+ try:
+ shutil.rmtree(folder)
+ except:
+ pass
+
+
+class TestParserIsValid(unittest.TestCase):
+ def setUp(self):
+ class MySchema(Schema):
+ foo = StringConfigOption()
+ self.schema = MySchema()
+ self.parser = SchemaConfigParser(self.schema)
+ self.config = StringIO("[__main__]\nfoo = bar")
+
+ def test_basic_is_valid(self):
+ class MySchema(Schema):
+ foo = IntConfigOption()
+
+ schema = MySchema()
+ config = StringIO("[__main__]\nfoo = 5")
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+
+ self.assertTrue(parser.is_valid())
+
+ def test_basic_is_valid_with_report(self):
+ class MySchema(Schema):
+ foo = IntConfigOption()
+
+ config = StringIO("[__main__]\nfoo=5")
+ expected = (True, [])
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ valid, errors = parser.is_valid(report=True)
+ self.assertEqual((valid, errors), expected)
+
+ def test_basic_is_not_valid(self):
+ class MySchema(Schema):
+ foo = IntConfigOption()
+
+ schema = MySchema()
+ config = StringIO("[__main__]\nfoo = 5\nbar = 6")
+ parser = SchemaConfigParser(schema)
+ parser.readfp(config)
+
+ self.assertFalse(parser.is_valid())
+
+ def test_basic_is_not_valid_with_report(self):
+ class MySchema(Schema):
+ foo = IntConfigOption()
+
+ config = StringIO("[__main__]\nfoo=5\nbar=6")
+ errors = ["Configuration includes invalid options for section '__main__': bar"]
+ expected = (False, errors)
+
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ valid, errors = parser.is_valid(report=True)
+ self.assertEqual((valid, errors), expected)
+
+ def test_is_not_valid_parser_error(self):
+ class MySchema(Schema):
+ foo = IntConfigOption()
+
+ def mock_parse_all(self):
+ assert False
+
+ schema = MySchema()
+ config = StringIO("[__main__]\nfoo = 5")
+ parser = SchemaConfigParser(schema)
+ parser.parse_all = mock_parse_all
+ parser.readfp(config)
+
+ self.assertFalse(parser.is_valid())
+
+ def test_parse_invalid_section(self):
+ config = StringIO("[bar]\nbaz=foo")
+ self.parser.readfp(config)
+
+ self.assertFalse(self.parser.is_valid())
+
+ def test_different_sections(self):
+ config = StringIO("[__main__]\nfoo=1\n[bar]\nbaz=2")
+ self.parser.readfp(config)
+
+ self.assertFalse(self.parser.is_valid())
+
+ def test_missing_fatal_options(self):
+ class MySchema(Schema):
+ foo = IntConfigOption()
+ bar = IntConfigOption(fatal=True)
+
+ config = StringIO("[__main__]\nfoo=1")
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+
+ self.assertFalse(parser.is_valid())
+
+ def test_missing_nonfatal_options(self):
+ class MySchema(Schema):
+ foo = IntConfigOption()
+ bar = IntConfigOption(fatal=True)
+
+ config = StringIO("[__main__]\nbar=2")
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+
+ self.assertTrue(parser.is_valid())
+
+ def test_extra_sections(self):
+ class MySchema(Schema):
+ foo = DictConfigOption({'bar': IntConfigOption()})
+
+ config = StringIO("[__main__]\nfoo=mydict\n[mydict]\nbar=1")
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ parser.parse_all()
+
+ self.assertTrue(parser.is_valid())
+
+ def test_extra_sections_when_dict_with_nested_dicts(self):
+ class MySchema(Schema):
+ foo = DictConfigOption(item=DictConfigOption())
+
+ config = StringIO("""
+[__main__]
+foo=dict1
+[dict1]
+bar=dict2
+[dict2]
+baz=42
+""")
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ parser.parse_all()
+
+ self.assertEqual(parser.values(),
+ {'__main__': {'foo': {'bar': {'baz': '42'}}}})
+ self.assertTrue(parser.is_valid())
+
+ def test_extra_sections_with_nested_dicts_strict(self):
+ class MySchema(Schema):
+ foo = DictConfigOption({'bar': DictConfigOption()}, strict=True)
+
+ config = StringIO("""
+[__main__]
+foo=dict1
+[dict1]
+bar=dict2
+[dict2]
+baz=42
+""")
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ parser.parse_all()
+
+ self.assertEqual(parser.values(),
+ {'__main__': {'foo': {'bar': {'baz': '42'}}}})
+ self.assertTrue(parser.is_valid())
+
+ def test_extra_sections_when_lines_dict_with_nested_dicts(self):
+ class MySchema(Schema):
+ foo = LinesConfigOption(
+ item=DictConfigOption(item=DictConfigOption()))
+
+ config = StringIO("""
+[__main__]
+foo = dict1
+ dict2
+[dict1]
+bar = dict3
+[dict2]
+baz = dict4
+[dict3]
+wham = 1
+[dict4]
+whaz = 2
+""")
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ parser.parse_all()
+
+ self.assertEqual(parser.values(),
+ {'__main__': {'foo': [
+ {'bar': {'wham': '1'}},
+ {'baz': {'whaz': '2'}}
+ ]}})
+ self.assertTrue(parser.is_valid())
+
+ def test_extra_sections_when_dict_with_nested_lines_dicts(self):
+ class MySchema(Schema):
+ foo = DictConfigOption(
+ item=LinesConfigOption(item=DictConfigOption()))
+
+ config = StringIO("""
+[__main__]
+foo = dict1
+[dict1]
+bar = dict2
+ dict3
+[dict2]
+baz = 1
+[dict3]
+wham = 2
+""")
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ parser.parse_all()
+
+ self.assertEqual(parser.values(),
+ {'__main__': {'foo': {'bar': [{'baz': '1'}, {'wham': '2'}]}}})
+ self.assertTrue(parser.is_valid())
+
+ def test_extra_sections_when_lines_dict_with_nested_lines_dicts(self):
+ class MySchema(Schema):
+ foo = LinesConfigOption(
+ item=DictConfigOption(
+ item=LinesConfigOption(item=DictConfigOption())))
+
+ config = StringIO("""
+[__main__]
+foo = dict1
+ dict2
+[dict1]
+bar = dict3
+ dict4
+[dict2]
+baz = dict5
+ dict6
+[dict3]
+wham = 1
+[dict4]
+whaz = 2
+[dict5]
+whoosh = 3
+[dict6]
+swoosh = 4
+""")
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ parser.parse_all()
+
+ self.assertEqual(parser.values(),
+ {'__main__': {'foo': [
+ {'bar': [{'wham': '1'}, {'whaz': '2'}]},
+ {'baz': [{'whoosh': '3'}, {'swoosh': '4'}]}
+ ]}})
+ self.assertTrue(parser.is_valid())
+
+ def test_multiple_extra_sections(self):
+ class MySchema(Schema):
+ foo = LinesConfigOption(
+ item=DictConfigOption({'bar': IntConfigOption()}))
+
+ config = StringIO('[__main__]\nfoo=d1\n d2\n d3\n'
+ '[d1]\nbar=1\n[d2]\nbar=2\n[d3]\nbar=3')
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ parser.parse_all()
+
+ self.assertTrue(parser.is_valid())
+
+ def test_noschema_section(self):
+ config = StringIO("[__main__]\nfoo=%(bar)s\n[__noschema__]\nbar=hello")
+ parser = SchemaConfigParser(self.schema)
+ parser.readfp(config)
+ parser.parse_all()
+
+ self.assertTrue(parser.is_valid())
+
+
+if __name__ == '__main__':
+ unittest.main()
=== added file 'tests/pyschema/test_schema.py'
--- tests/pyschema/test_schema.py 1970-01-01 00:00:00 +0000
+++ tests/pyschema/test_schema.py 2010-08-04 21:21:41 +0000
@@ -0,0 +1,136 @@
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
+# Released under the BSD License (see the file LICENSE)
+#
+# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
+
+import unittest
+
+from configglue.pyschema import ConfigSection
+from configglue.pyschema.options import (BoolConfigOption,
+ IntConfigOption, LinesConfigOption)
+from configglue.pyschema.schema import Schema
+
+
+class TestSchema(unittest.TestCase):
+ def test_sections(self):
+ class MySchema(Schema):
+ foo = BoolConfigOption()
+
+ class MyOtherSchema(Schema):
+ web = ConfigSection()
+ web.bar = IntConfigOption()
+ froo = ConfigSection()
+ froo.twaddle = LinesConfigOption(item=BoolConfigOption())
+
+ class MyThirdSchema(Schema):
+ bar = IntConfigOption()
+ froo = ConfigSection()
+ froo.twaddle = LinesConfigOption(item=BoolConfigOption())
+
+ schema = MySchema()
+ names = set(s.name for s in schema.sections())
+ self.assertEquals(set(['__main__']), names)
+
+ schema = MyOtherSchema()
+ names = set(s.name for s in schema.sections())
+ self.assertEquals(set(['web', 'froo']), names)
+
+ schema = MyThirdSchema()
+ names = set(s.name for s in schema.sections())
+ self.assertEquals(set(['__main__', 'froo']), names)
+
+ def test_schema_validation(self):
+ class BorkenSchema(Schema):
+ __main__ = ConfigSection()
+ __main__.foo = BoolConfigOption()
+
+ class SomeSchema(Schema):
+ mysection = ConfigSection()
+
+ schema = BorkenSchema()
+ self.assertFalse(schema.is_valid())
+
+ schema = SomeSchema()
+ self.assertTrue(schema.is_valid())
+
+ def test_names(self):
+ class MySchema(Schema):
+ foo = BoolConfigOption()
+ bar = ConfigSection()
+ bar.baz = IntConfigOption()
+
+ schema = MySchema()
+ self.assertEquals('foo', schema.foo.name)
+ self.assertEquals('__main__', schema.foo.section.name)
+ self.assertEquals('bar', schema.bar.name)
+ self.assertEquals('baz', schema.bar.baz.name)
+ self.assertEquals('bar', schema.bar.baz.section.name)
+
+ def test_options(self):
+ class MySchema(Schema):
+ foo = BoolConfigOption()
+ bar = ConfigSection()
+ bar.baz = IntConfigOption()
+
+ schema = MySchema()
+ names = set(s.name for s in schema.options())
+ self.assertEquals(set(['foo', 'baz']), names)
+ names = set(s.name for s in schema.options('__main__'))
+ self.assertEquals(set(['foo']), names)
+ names = set(s.name for s in schema.options('bar'))
+ self.assertEquals(set(['baz']), names)
+
+ def test_include(self):
+ schema = Schema()
+ self.assertTrue(hasattr(schema, 'includes'))
+
+ def test_equal(self):
+ class MySchema(Schema):
+ foo = IntConfigOption()
+ class OtherSchema(Schema):
+ bar = IntConfigOption()
+
+ self.assertEqual(MySchema(), MySchema())
+ self.assertNotEqual(MySchema(), OtherSchema())
+
+
+class TestSchemaInheritance(unittest.TestCase):
+ def setUp(self):
+ class SchemaA(Schema):
+ foo = ConfigSection()
+ foo.bar = IntConfigOption()
+ class SchemaB(SchemaA):
+ baz = ConfigSection()
+ baz.wham = IntConfigOption()
+
+ self.schema = SchemaB()
+
+ def test_basic_inheritance(self):
+ names = [('foo', ['bar']), ('baz', ['wham'])]
+ for section, options in names:
+ section_obj = getattr(self.schema, section)
+ self.assertTrue(isinstance(section_obj, ConfigSection))
+ for option in options:
+ option_obj = getattr(section_obj, option)
+ self.assertTrue(isinstance(option_obj, IntConfigOption))
+
+ def test_inherited_sections(self):
+ names = set(s.name for s in self.schema.sections())
+ self.assertEqual(set(['foo', 'baz']), names)
+
+ def test_inherited_options(self):
+ names = set(s.name for s in self.schema.options())
+ self.assertEqual(set(['bar', 'wham']), names)
+
=== added file 'tests/pyschema/test_schemaconfig.py'
--- tests/pyschema/test_schemaconfig.py 1970-01-01 00:00:00 +0000
+++ tests/pyschema/test_schemaconfig.py 2010-08-04 21:21:41 +0000
@@ -0,0 +1,181 @@
+###############################################################################
+#
+# configglue -- glue for your apps' configuration
+#
+# A library for simple, DRY configuration of applications
+#
+# (C) 2009--2010 by Canonical Ltd.
+# originally by John R. Lenton <john.lenton@xxxxxxxxxxxxx>
+# incorporating schemaconfig as configglue.pyschema
+# schemaconfig originally by Ricardo Kirkner <ricardo.kirkner@xxxxxxxxxxxxx>
+#
+# Released under the BSD License (see the file LICENSE)
+#
+# For bug reports, support, and new releases: http://launchpad.net/configglue
+#
+###############################################################################
+
+import unittest
+import sys
+from StringIO import StringIO
+
+from configglue.pyschema import ConfigOption, ConfigSection, schemaconfigglue
+from configglue.pyschema.options import IntConfigOption
+from configglue.pyschema.parser import SchemaConfigParser
+from configglue.pyschema.schema import Schema
+
+
+class TestConfigOption(unittest.TestCase):
+ def test_repr_name(self):
+ opt = ConfigOption()
+ expected = "<ConfigOption>"
+ self.assertEqual(repr(opt), expected)
+
+ opt = ConfigOption(name='name')
+ expected = "<ConfigOption name>"
+ self.assertEqual(repr(opt), expected)
+
+ sect = ConfigSection(name='sect')
+ opt = ConfigOption(name='name', section=sect)
+ expected = "<ConfigOption sect.name>"
+ self.assertEqual(repr(opt), expected)
+
+ def test_repr_extra(self):
+ opt = ConfigOption(name='name', raw=True)
+ expected = "<ConfigOption name raw>"
+ self.assertEqual(repr(opt), expected)
+
+ opt = ConfigOption(name='name', fatal=True)
+ expected = "<ConfigOption name fatal>"
+ self.assertEqual(repr(opt), expected)
+
+ opt = ConfigOption(name='name', raw=True, fatal=True)
+ expected = "<ConfigOption name raw fatal>"
+ self.assertEqual(repr(opt), expected)
+
+ def test_parse(self):
+ opt = ConfigOption()
+ self.assertRaises(NotImplementedError, opt.parse, '')
+
+ def test_equal(self):
+ opt1 = ConfigOption()
+ opt2 = ConfigOption(name='name', raw=True)
+
+ self.assertEqual(opt1, ConfigOption())
+ self.assertEqual(opt2, ConfigOption(name='name', raw=True))
+ self.assertNotEqual(opt1, opt2)
+ self.assertNotEqual(opt1, None)
+
+
+class TestConfigSection(unittest.TestCase):
+ def test_repr_name(self):
+ sect = ConfigSection()
+ expected = "<ConfigSection>"
+ self.assertEqual(repr(sect), expected)
+
+ sect = ConfigSection(name='sect')
+ expected = "<ConfigSection sect>"
+ self.assertEqual(repr(sect), expected)
+
+ def test_equal(self):
+ sec1 = ConfigSection()
+ sec2 = ConfigSection(name='sec2')
+
+ self.assertEqual(sec1, ConfigSection())
+ self.assertEqual(sec2, ConfigSection(name='sec2'))
+ self.assertNotEqual(sec1, sec2)
+
+ def test_has_option(self):
+ sec1 = ConfigSection()
+ sec1.foo = IntConfigOption()
+
+ self.assertTrue(sec1.has_option('foo'))
+ self.assertFalse(sec1.has_option('bar'))
+
+
+class TestSchemaConfigGlue(unittest.TestCase):
+ def setUp(self):
+ class MySchema(Schema):
+ foo = ConfigSection()
+ foo.bar = IntConfigOption()
+
+ baz = IntConfigOption(help='The baz option')
+
+ self.parser = SchemaConfigParser(MySchema())
+
+ def test_glue_no_op(self):
+ config = StringIO("[__main__]\nbaz=1")
+ self.parser.readfp(config)
+ self.assertEqual(self.parser.values(),
+ {'foo': {'bar': 0}, '__main__': {'baz': 1}})
+
+ op, options, args = schemaconfigglue(self.parser, argv=['--baz', '2'])
+ self.assertEqual(self.parser.values(),
+ {'foo': {'bar': 0}, '__main__': {'baz': 2}})
+
+ def test_glue_no_argv(self):
+ config = StringIO("[__main__]\nbaz=1")
+ self.parser.readfp(config)
+ self.assertEqual(self.parser.values(),
+ {'foo': {'bar': 0}, '__main__': {'baz': 1}})
+
+ _argv = sys.argv
+ sys.argv = []
+
+ op, options, args = schemaconfigglue(self.parser)
+ self.assertEqual(self.parser.values(),
+ {'foo': {'bar': 0}, '__main__': {'baz': 1}})
+
+ sys.argv = _argv
+
+ def test_glue_section_option(self):
+ config = StringIO("[foo]\nbar=1")
+ self.parser.readfp(config)
+ self.assertEqual(self.parser.values(),
+ {'foo': {'bar': 1}, '__main__': {'baz': 0}})
+
+ op, options, args = schemaconfigglue(self.parser,
+ argv=['--foo_bar', '2'])
+ self.assertEqual(self.parser.values(),
+ {'foo': {'bar': 2}, '__main__': {'baz': 0}})
+
+ def test_ambiguous_option(self):
+ class MySchema(Schema):
+ foo = ConfigSection()
+ foo.baz = IntConfigOption()
+
+ bar = ConfigSection()
+ bar.baz = IntConfigOption()
+
+ config = StringIO("[foo]\nbaz=1")
+ parser = SchemaConfigParser(MySchema())
+ parser.readfp(config)
+ self.assertEqual(parser.values('foo'), {'baz': 1})
+ self.assertEqual(parser.values('bar'), {'baz': 0})
+
+ op, options, args = schemaconfigglue(
+ parser, argv=['--bar_baz', '2'])
+ self.assertEqual(parser.values('foo'), {'baz': 1})
+ self.assertEqual(parser.values('bar'), {'baz': 2})
+
+ def test_help(self):
+ config = StringIO("[foo]\nbar=1")
+ self.parser.readfp(config)
+ self.assertEqual(self.parser.values(),
+ {'foo': {'bar': 1}, '__main__': {'baz': 0}})
+
+ # replace stdout to capture its value
+ stdout = StringIO()
+ _stdout = sys.stdout
+ sys.stdout = stdout
+ # call the method and assert its value
+ self.assertRaises(SystemExit, schemaconfigglue, self.parser,
+ argv=['--help'])
+ # replace stdout again to cleanup
+ sys.stdout = _stdout
+
+ # assert the value of stdout is correct
+ stdout.seek(0)
+ output = stdout.read()
+ self.assertTrue(output.startswith('Usage:'))
+