configglue team mailing list archive
-
configglue team
-
Mailing list archive
-
Message #00165
[Merge] lp:~ricardokirkner/configglue/django-configglue-inspired-fixes into lp:configglue
Ricardo Kirkner has proposed merging lp:~ricardokirkner/configglue/django-configglue-inspired-fixes into lp:configglue.
Requested reviews:
Configglue developers (configglue)
For more details, see:
https://code.launchpad.net/~ricardokirkner/configglue/django-configglue-inspired-fixes/+merge/65594
Overview
========
Fixes to issues raised while working on getting django-configglue compatible with the latest configglue (revno 59)
Details
=======
While updating configglue to work with the latest configglue, some issues were detected. Those issue are fixed in this branch so that django-configglue can be updated to work with the latest configglue version.
--
https://code.launchpad.net/~ricardokirkner/configglue/django-configglue-inspired-fixes/+merge/65594
Your team Configglue developers is requested to review the proposed merge of lp:~ricardokirkner/configglue/django-configglue-inspired-fixes into lp:configglue.
=== modified file 'configglue/app/tests/test_base.py'
--- configglue/app/tests/test_base.py 2011-06-18 15:48:15 +0000
+++ configglue/app/tests/test_base.py 2011-06-22 23:11:36 +0000
@@ -1,5 +1,5 @@
import user
-from ConfigParser import NoSectionError
+from ConfigParser import NoOptionError
from unittest import TestCase
from mock import (
@@ -64,7 +64,7 @@
def test_glue_invalid_config(self):
class MySchema(Schema):
foo = IntOption(fatal=True)
- self.assertRaises(NoSectionError, make_app, schema=MySchema)
+ self.assertRaises(SystemExit, make_app, schema=MySchema)
def test_get_config_files(self):
app = make_app()
=== modified file 'configglue/pyschema/glue.py'
--- configglue/pyschema/glue.py 2011-06-18 20:20:34 +0000
+++ configglue/pyschema/glue.py 2011-06-22 23:11:36 +0000
@@ -17,6 +17,10 @@
import os
import sys
+from ConfigParser import (
+ NoOptionError,
+ NoSectionError,
+)
from optparse import OptionParser
from collections import namedtuple
@@ -63,7 +67,8 @@
kwargs = {}
if option.help:
kwargs['help'] = option.help
- kwargs['default'] = parser.get(section.name, option.name)
+ if not option.fatal:
+ 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)
@@ -81,7 +86,10 @@
# 3. op value == parser value == env value or not env value
op_value = getattr(options, opt_name(option))
- parser_value = parser.get(section.name, option.name)
+ try:
+ parser_value = parser.get(section.name, option.name)
+ except (NoSectionError, NoOptionError):
+ parser_value = None
env_value = os.environ.get("CONFIGGLUE_{0}".format(
long_name(option).upper()))
@@ -111,5 +119,5 @@
parser, opts, args = schemaconfigglue(scp, op=op)
is_valid, reasons = scp.is_valid(report=True)
if not is_valid:
- parser.error(reasons[0])
+ parser.error('\n'.join(reasons))
return SchemaGlue(scp, parser, opts, args)
=== modified file 'configglue/pyschema/parser.py'
--- configglue/pyschema/parser.py 2011-06-19 16:38:52 +0000
+++ configglue/pyschema/parser.py 2011-06-22 23:11:36 +0000
@@ -89,13 +89,19 @@
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)
+ if len(unmatched_sections) > 0:
+ error_msg = "Sections in configuration are missing from schema: %s"
+ error_value = ', '.join(unmatched_sections)
+ errors.append(error_msg % error_value)
+ unmatched_sections = list(schema_sections - parsed_sections)
+ if len(unmatched_sections) > 0:
+ error_msg = "Sections in schema are missing from configuration: %s"
+ error_value = ', '.join(unmatched_sections)
+ errors.append(error_msg % error_value)
valid &= sections_match
- for name in parsed_sections:
+ for name in list(parsed_sections.union(schema_sections)):
if name not in skip_sections:
if not self.schema.has_section(name):
# this should have been reported before
@@ -103,7 +109,10 @@
continue
section = self.schema.section(name)
- parsed_options = set(self.options(name))
+ try:
+ parsed_options = set(self.options(name))
+ except:
+ parsed_options = set([])
schema_options = set(section.options())
fatal_options = set(opt.name for opt in schema_options
@@ -139,9 +148,8 @@
self.parse_all()
except Exception, e:
- if valid:
- errors.append(e)
- valid = False
+ errors.append(str(e))
+ valid = False
if report:
return valid, errors
@@ -382,17 +390,18 @@
"""Return the location (file) where the option was last defined."""
return self._location.get(option)
+ def _extract_interpolation_keys(self, item):
+ if isinstance(item, (list, tuple)):
+ keys = map(self._extract_interpolation_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
+
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:
@@ -401,7 +410,7 @@
except:
value = rawval
- keys = extract_keys(value)
+ keys = self._extract_interpolation_keys(value)
return rawval, keys
def _interpolate_value(self, section, option):
@@ -439,42 +448,33 @@
def interpolate_environment(self, rawval, raw=False):
if raw:
return rawval
+
# interpolate environment variables
pattern = re.sub(r'\${([A-Z_]+)}', r'%(\1)s', rawval)
pattern = re.sub(r'\$([A-Z_]+)', r'%(\1)s', pattern)
+
+ keys = self._extract_interpolation_keys(pattern)
+ if not keys:
+ # interpolation keys are not valid
+ return rawval
+
interpolated = pattern % os.environ
return interpolated
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__':
+ if 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:
- return value
+ return value
+
+ # any other section
+ opt = self.schema.section(section).option(option)
+ if not opt.fatal:
+ value = opt.default
+ return value
+
+ # no default value found, raise an error
+ raise NoOptionError(option, section)
def get(self, section, option, raw=False, vars=None, parse=True):
"""Return the parsed value of an option.
@@ -507,27 +507,21 @@
# 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)
# interpolate environment variables
if isinstance(value, basestring):
try:
value = self.interpolate_environment(value, raw=raw)
+ if parse:
+ value = self.parse(section, option, value)
except KeyError:
# interpolation failed, fallback to default value
value = self._get_default(section, option)
- if parse:
- value = self.parse(section, option, value)
return value
def _get_option(self, section, option):
@@ -545,6 +539,10 @@
# cast value to a string because SafeConfigParser only allows
# strings to be set
str_value = option_obj.to_string(value)
+ if not self.has_section(section):
+ # Don't call .add_section here because 2.6 complains
+ # about sections called '__main__'
+ self._sections[section] = {}
super(SchemaConfigParser, self).set(section, option, str_value)
filename = self.locate(option)
self._dirty[filename][section][option] = str_value
=== modified file 'configglue/pyschema/schema.py'
--- configglue/pyschema/schema.py 2011-06-19 16:46:01 +0000
+++ configglue/pyschema/schema.py 2011-06-22 23:11:36 +0000
@@ -15,6 +15,10 @@
#
###############################################################################
+from ConfigParser import (
+ NoSectionError,
+ NoOptionError,
+)
from copy import deepcopy
from inspect import getmembers
from warnings import warn
@@ -175,7 +179,8 @@
def section(self, name):
"""Return a Section by name"""
section = self._sections.get(name)
- assert section is not None, "Invalid Section name '%s'" % name
+ if section is None:
+ raise NoSectionError(name)
return section
def sections(self):
@@ -242,7 +247,8 @@
def option(self, name):
"""Return a Option by name"""
opt = getattr(self, name, None)
- assert opt is not None, "Invalid Option name '%s'" % name
+ if opt is None:
+ raise NoOptionError(name, self.name)
return opt
def options(self):
@@ -478,10 +484,12 @@
return result
def to_string(self, value):
+ if value is None and self.null:
+ return 'None'
return value
def validate(self, value):
- return isinstance(value, basestring)
+ return (self.null and value is None) or isinstance(value, basestring)
class TupleOption(Option):
=== modified file 'configglue/tests/pyschema/test_parser.py'
--- configglue/tests/pyschema/test_parser.py 2011-06-19 17:38:47 +0000
+++ configglue/tests/pyschema/test_parser.py 2011-06-22 23:11:36 +0000
@@ -705,14 +705,12 @@
self.assertEqual(default, expected)
def test_get_default_no_option(self):
- expected = None
- default = self.parser._get_default('__main__', 'bar')
- self.assertEqual(default, expected)
+ self.assertRaises(NoOptionError, self.parser._get_default,
+ '__main__', 'bar')
def test_get_default_no_section(self):
- expected = None
- default = self.parser._get_default('foo', 'bar')
- self.assertEqual(default, expected)
+ self.assertRaises(NoSectionError, self.parser._get_default,
+ 'foo', 'bar')
def test_multi_file_dict_config(self):
"""Test parsing a dict option spanning multiple files."""
@@ -1023,8 +1021,8 @@
valid, errors = self.parser.is_valid(report=True)
self.assertFalse(valid)
- self.assertEqual(errors,
- [u'Sections in configuration do not match schema: bar'])
+ self.assertEqual(errors[0],
+ u'Sections in configuration are missing from schema: bar')
def test_different_sections(self):
config = StringIO("[__main__]\nfoo=1\n[bar]\nbaz=2")
=== modified file 'configglue/tests/pyschema/test_schema.py'
--- configglue/tests/pyschema/test_schema.py 2011-06-19 16:46:01 +0000
+++ configglue/tests/pyschema/test_schema.py 2011-06-22 23:11:36 +0000
@@ -17,6 +17,7 @@
###############################################################################
import unittest
+from ConfigParser import NoOptionError
from StringIO import StringIO
from configglue.pyschema.parser import (
@@ -351,6 +352,24 @@
"""Test OptionString validate a non-string value."""
self.assertEqual(self.opt.validate(0), False)
+ def test_validate_null_string(self):
+ """Test OptionString validate a null string."""
+ opt = StringOption(null=True)
+ self.assertEqual(opt.validate(None), True)
+
+ def test_to_string_for_null_string(self):
+ """Test OptionString to_string for a null string."""
+ opt = StringOption(null=True)
+ self.assertEqual(opt.to_string(None), 'None')
+ self.assertEqual(self.opt.to_string(''), '')
+ self.assertEqual(self.opt.to_string('foo'), 'foo')
+
+ def test_to_string_for_non_null_string(self):
+ """Test OptionString to_string for non-null string."""
+ self.assertEqual(self.opt.to_string(None), None)
+ self.assertEqual(self.opt.to_string(''), '')
+ self.assertEqual(self.opt.to_string('foo'), 'foo')
+
class TestStringConfigOption(TestStringOption):
cls = StringConfigOption
@@ -962,7 +981,7 @@
section.foo = IntOption()
self.assertEqual(section.option('foo'), section.foo)
- self.assertRaises(AssertionError, section.option, 'bar')
+ self.assertRaises(NoOptionError, section.option, 'bar')
def test_options(self):
"""Test Section options method."""
=== modified file 'configglue/tests/pyschema/test_schemaconfig.py'
--- configglue/tests/pyschema/test_schemaconfig.py 2011-06-19 17:38:47 +0000
+++ configglue/tests/pyschema/test_schemaconfig.py 2011-06-22 23:11:36 +0000
@@ -263,6 +263,28 @@
output = stdout.read()
self.assertTrue(output.startswith('Usage:'))
+ def test_help_with_fatal(self):
+ """Test schemaconfigglue with --help and an undefined fatal option."""
+ class MySchema(Schema):
+ foo = IntOption(fatal=True)
+
+ self.parser = SchemaConfigParser(MySchema())
+
+ # 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:'))
+
def test_parser_set_with_encoding(self):
"""Test schemaconfigglue override an option with a non-ascii value."""
class MySchema(Schema):
Follow ups