configglue team mailing list archive
-
configglue team
-
Mailing list archive
-
Message #00467
[Merge] lp:~ricardokirkner/configglue/from-future-remove-configparser into lp:configglue
Ricardo Kirkner has proposed merging lp:~ricardokirkner/configglue/from-future-remove-configparser into lp:configglue.
Commit message:
use compat module to define base for SchemaConfigParser that abstracts differences from Python 2.x and 3.x configparser modules
Requested reviews:
Barry Warsaw (barry)
Configglue developers (configglue)
For more details, see:
https://code.launchpad.net/~ricardokirkner/configglue/from-future-remove-configparser/+merge/173806
--
https://code.launchpad.net/~ricardokirkner/configglue/from-future-remove-configparser/+merge/173806
Your team Configglue developers is requested to review the proposed merge of lp:~ricardokirkner/configglue/from-future-remove-configparser into lp:configglue.
=== modified file 'configglue/_compat.py'
--- configglue/_compat.py 2013-05-27 00:56:38 +0000
+++ configglue/_compat.py 2013-07-09 20:10:35 +0000
@@ -4,12 +4,109 @@
PY2 = sys.version_info[0] == 2
if not PY2:
+ import builtins
+ import configparser
+
text_type = str
string_types = (str,)
- import builtins
iteritems = lambda d: iter(d.items())
+
+
+ class BaseConfigParser(configparser.SafeConfigParser):
+ def __init__(self, *args, **kwargs):
+ configparser.SafeConfigParser.__init__(self, *args, **kwargs)
+
+ self._KEYCRE = self._interpolation._KEYCRE
+
else:
+ import __builtin__ as builtins
+ import ConfigParser as configparser
+ import re
+
text_type = unicode
string_types = (str, unicode)
- import __builtin__ as builtins
iteritems = lambda d: d.iteritems()
+
+
+ # taken from Python 3.3's configparser module
+ class BasicInterpolation(object):
+ """Interpolation as implemented in the classic ConfigParser.
+
+ The option values can contain format strings which refer to other
+ values in the same section, or values in the special default section.
+
+ For example:
+
+ something: %(dir)s/whatever
+
+ would resolve the "%(dir)s" to the value of dir. All reference
+ expansions are done late, on demand. If a user needs to use a
+ bare % in a configuration file, she can escape it by writing %%.
+ Other % usage is considered a user error and
+ raises `InterpolationSyntaxError'."""
+
+ _KEYCRE = re.compile(r"%\(([^)]+)\)s")
+
+ def before_get(self, parser, section, option, value, defaults):
+ L = []
+ self._interpolate_some(parser, option, L, value, section,
+ defaults, 1)
+ return ''.join(L)
+
+ def before_set(self, parser, section, option, value):
+ tmp_value = value.replace('%%', '') # escaped percent signs
+ tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax
+ if '%' in tmp_value:
+ raise ValueError("invalid interpolation syntax in %r at "
+ "position %d" % (value, tmp_value.find('%')))
+ return value
+
+ def _interpolate_some(self, parser, option, accum, rest, section, map,
+ depth):
+ if depth > configparser.MAX_INTERPOLATION_DEPTH:
+ raise configparser.InterpolationDepthError(option, section,
+ rest)
+ while rest:
+ p = rest.find("%")
+ if p < 0:
+ accum.append(rest)
+ return
+ if p > 0:
+ accum.append(rest[:p])
+ rest = rest[p:]
+ # p is no longer used
+ c = rest[1:2]
+ if c == "%":
+ accum.append("%")
+ rest = rest[2:]
+ elif c == "(":
+ m = self._KEYCRE.match(rest)
+ if m is None:
+ raise configparser.InterpolationSyntaxError(option,
+ section,
+ "bad interpolation variable reference %r" % rest)
+ var = parser.optionxform(m.group(1))
+ rest = rest[m.end():]
+ try:
+ v = map[var]
+ except KeyError:
+ raise configparser.InterpolationMissingOptionError(
+ option, section, rest, var)
+ if "%" in v:
+ self._interpolate_some(parser, option, accum, v,
+ section, map, depth + 1)
+ else:
+ accum.append(v)
+ else:
+ raise configparser.InterpolationSyntaxError(
+ option, section,
+ "'%%' must be followed by '%%' or '(', "
+ "found: %r" % (rest,))
+ # end
+
+
+ class BaseConfigParser(configparser.SafeConfigParser):
+ def __init__(self, *args, **kwargs):
+ configparser.SafeConfigParser.__init__(self, *args, **kwargs)
+
+ self._interpolation = BasicInterpolation()
=== modified file 'configglue/glue.py'
--- configglue/glue.py 2013-05-25 15:21:04 +0000
+++ configglue/glue.py 2013-07-09 20:10:35 +0000
@@ -16,13 +16,10 @@
import os
import sys
-from configparser import (
- NoOptionError,
- NoSectionError,
-)
from optparse import OptionParser
from collections import namedtuple
+from ._compat import configparser
from .parser import SchemaConfigParser
@@ -68,7 +65,7 @@
kwargs['help'] = option.help
try:
kwargs['default'] = parser.get(section.name, option.name)
- except (NoSectionError, NoOptionError):
+ except (configparser.NoSectionError, configparser.NoOptionError):
pass
kwargs['action'] = option.action
args = ['--' + long_name(option)]
@@ -92,7 +89,7 @@
op_value = getattr(options, opt_name(option))
try:
parser_value = parser.get(section.name, option.name)
- except (NoSectionError, NoOptionError):
+ except (configparser.NoSectionError, configparser.NoOptionError):
parser_value = None
env_value = os.environ.get("CONFIGGLUE_{0}".format(
long_name(option).upper()))
=== modified file 'configglue/inischema/attributed.py'
--- configglue/inischema/attributed.py 2013-05-26 15:07:30 +0000
+++ configglue/inischema/attributed.py 2013-07-09 20:10:35 +0000
@@ -18,7 +18,8 @@
AttributtedConfigParser lives here.
"""
import re
-from configparser import RawConfigParser
+
+from configglue._compat import configparser
marker = object()
@@ -41,7 +42,7 @@
return self.value is marker
-class AttributedConfigParser(RawConfigParser, object):
+class AttributedConfigParser(configparser.RawConfigParser, object):
"""Handle attributed ini-style configuration files
"""
def optionxform(self, optionstr):
=== modified file 'configglue/parser.py'
--- configglue/parser.py 2013-07-08 20:10:06 +0000
+++ configglue/parser.py 2013-07-09 20:10:35 +0000
@@ -21,16 +21,9 @@
import os
import re
-from configparser import (
- DEFAULTSECT,
- SafeConfigParser as BaseConfigParser,
- InterpolationMissingOptionError,
- NoOptionError,
- NoSectionError,
-)
from functools import reduce
-from configglue._compat import text_type, string_types
+from ._compat import BaseConfigParser, configparser, text_type, string_types
__all__ = [
@@ -77,8 +70,6 @@
self._basedir = ''
self._dirty = collections.defaultdict(
lambda: collections.defaultdict(dict))
- # map to location in configparser
- self._KEYCRE = self._interpolation._KEYCRE
def is_valid(self, report=False):
"""Return if the state of the parser is valid.
@@ -123,7 +114,7 @@
section = self.schema.section(name)
try:
parsed_options = set(self.options(name))
- except NoSectionError:
+ except configparser.NoSectionError:
parsed_options = set([])
schema_options = set(section.options())
@@ -188,8 +179,8 @@
try:
d.update(self._sections[section])
except KeyError:
- if section != DEFAULTSECT:
- raise NoSectionError(section)
+ if section != configparser.DEFAULTSECT:
+ raise configparser.NoSectionError(section)
# Update with the entry specific variables
if vars:
for key, value in vars.items():
@@ -205,7 +196,7 @@
for option in options:
try:
value = self._interpolate(section, option, d[option], d)
- except InterpolationMissingOptionError as e:
+ except configparser.InterpolationMissingOptionError as e:
# interpolation failed, because key was not found in
# section. try other sections before bailing out
value = self._interpolate_value(section, option)
@@ -343,7 +334,7 @@
if section_obj is not None:
option_obj = getattr(section_obj, option, None)
else:
- raise NoSectionError(section)
+ raise configparser.NoSectionError(section)
if option_obj is not None:
kwargs = {}
@@ -373,7 +364,8 @@
for option in section.options():
try:
self.get(section.name, option.name, raw=option.raw)
- except (NoSectionError, NoOptionError):
+ except (configparser.NoSectionError,
+ configparser.NoOptionError):
if option.fatal:
raise
@@ -420,7 +412,7 @@
# we want the unparsed value
try:
value = self.get(section, key, parse=False)
- except (NoSectionError, NoOptionError):
+ except (configparser.NoSectionError, configparser.NoOptionError):
# value of key not found in config, so try in special
# sections
for section in ('__main__', '__noschema__'):
@@ -494,7 +486,7 @@
return value
# no default value found, raise an error
- raise NoOptionError(option, section)
+ raise configparser.NoOptionError(option, section)
def get(self, section, option, raw=False, vars=None, parse=True):
"""Return the parsed value of an option.
@@ -517,13 +509,13 @@
# value is defined entirely in current section
value = super(SchemaConfigParser, self).get(section, option,
raw=raw, vars=vars)
- except InterpolationMissingOptionError as e:
+ except configparser.InterpolationMissingOptionError as 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) as e:
+ except (configparser.NoSectionError, configparser.NoOptionError) as e:
# option not found in config, try to get its default value from
# schema
value = self._get_default(section, option)
@@ -569,7 +561,7 @@
self._fill_parser()
if self._defaults:
- fp.write("[%s]\n" % DEFAULTSECT)
+ fp.write("[%s]\n" % configparser.DEFAULTSECT)
for (key, value) in self._defaults.items():
fp.write("%s = %s\n" % (key, value.replace('\n', '\n\t')))
fp.write("\n")
@@ -609,7 +601,7 @@
else:
filename = self._last_location
- parser = BaseConfigParser()
+ parser = configparser.SafeConfigParser()
parser.read(filename)
for section, options in sections.items():
=== modified file 'configglue/schema.py'
--- configglue/schema.py 2013-05-26 19:07:46 +0000
+++ configglue/schema.py 2013-07-09 20:10:35 +0000
@@ -16,14 +16,11 @@
from __future__ import unicode_literals
import json
-from configparser import (
- NoSectionError,
- NoOptionError,
-)
from copy import deepcopy
from inspect import getmembers
-from configglue._compat import text_type, string_types
+from ._compat import configparser, text_type, string_types
+
__all__ = [
@@ -177,7 +174,7 @@
"""Return a Section by name"""
section = self._sections.get(name)
if section is None:
- raise NoSectionError(name)
+ raise configparser.NoSectionError(name)
return section
def sections(self):
@@ -248,7 +245,7 @@
"""Return a Option by name"""
opt = getattr(self, name, None)
if opt is None:
- raise NoOptionError(name, self.name)
+ raise configparser.NoOptionError(name, self.name)
return opt
def options(self):
=== modified file 'configglue/tests/inischema/test_attributed.py'
--- configglue/tests/inischema/test_attributed.py 2013-05-26 16:15:48 +0000
+++ configglue/tests/inischema/test_attributed.py 2013-07-09 20:10:35 +0000
@@ -19,9 +19,9 @@
# runner's output, so pylint: disable-msg=C0111
import unittest
-from configparser import RawConfigParser
from io import StringIO
+from configglue._compat import configparser
from configglue.inischema.attributed import AttributedConfigParser
@@ -44,7 +44,7 @@
class TestAttributed(BaseTest):
""" pretty basic tests of AttributedConfigParser """
def test_config_before_parsing_is_plain(self):
- rawConfig = RawConfigParser()
+ rawConfig = configparser.RawConfigParser()
rawConfig.readfp(StringIO(self.config_string))
self.assertEqual([(section, sorted(self.config.items(section)))
for section in self.config.sections()],
=== modified file 'configglue/tests/inischema/test_typed.py'
--- configglue/tests/inischema/test_typed.py 2013-05-26 16:15:48 +0000
+++ configglue/tests/inischema/test_typed.py 2013-07-09 20:10:35 +0000
@@ -21,8 +21,8 @@
import unittest
from io import StringIO
-from configparser import RawConfigParser
+from configglue._compat import configparser
from configglue.inischema.typed import TypedConfigParser
@@ -71,7 +71,7 @@
""" rather basic backwards compatibility checker
"""
def test_config_before_parse_is_plain(self):
- rawConfig = RawConfigParser()
+ rawConfig = configparser.RawConfigParser()
rawConfig.readfp(StringIO(self.config_string))
self.assertEqual([(section, sorted(self.config.items(section)))
for section in self.config.sections()],
=== modified file 'configglue/tests/test_parser.py'
--- configglue/tests/test_parser.py 2013-07-08 20:10:06 +0000
+++ configglue/tests/test_parser.py 2013-07-09 20:10:35 +0000
@@ -22,13 +22,6 @@
import tempfile
import textwrap
import unittest
-from configparser import (
- DEFAULTSECT,
- InterpolationDepthError,
- InterpolationMissingOptionError,
- InterpolationSyntaxError,
- NoSectionError,
-)
from io import BytesIO
from mock import (
@@ -37,10 +30,9 @@
patch,
)
-from configglue._compat import iteritems
+from configglue._compat import configparser, iteritems
from configglue.parser import (
CONFIG_FILE_ENCODING,
- NoOptionError,
SchemaConfigParser,
SchemaValidationError,
)
@@ -220,7 +212,7 @@
rawval = '%(baz)s'
vars = {}
parser = SchemaConfigParser(MySchema())
- self.assertRaises(InterpolationMissingOptionError,
+ self.assertRaises(configparser.InterpolationMissingOptionError,
parser._interpolate, section, option, rawval, vars)
def test_interpolate_too_deep(self):
@@ -234,7 +226,7 @@
rawval = '%(bar)s'
vars = {'foo': '%(bar)s', 'bar': '%(foo)s'}
parser = SchemaConfigParser(MySchema())
- self.assertRaises(InterpolationDepthError,
+ self.assertRaises(configparser.InterpolationDepthError,
parser._interpolate, section, option, rawval, vars)
def test_interpolate_incomplete_format(self):
@@ -248,8 +240,8 @@
rawval = '%(bar)'
vars = {'foo': '%(bar)s', 'bar': 'pepe'}
parser = SchemaConfigParser(MySchema())
- self.assertRaises(InterpolationSyntaxError, parser._interpolate,
- section, option, rawval, vars)
+ self.assertRaises(configparser.InterpolationSyntaxError,
+ parser._interpolate, section, option, rawval, vars)
def test_interpolate_across_sections(self):
"""Test interpolation across sections."""
@@ -263,7 +255,7 @@
config = BytesIO(b"[foo]\nbar=%(wham)s\n[baz]\nwham=42")
parser = SchemaConfigParser(MySchema())
parser.readfp(config)
- self.assertRaises(InterpolationMissingOptionError,
+ self.assertRaises(configparser.InterpolationMissingOptionError,
parser.get, 'foo', 'bar')
def test_interpolate_invalid_key(self):
@@ -278,8 +270,8 @@
config = BytesIO(b"[foo]\nbar=%(wham)s\n[baz]\nwham=42")
parser = SchemaConfigParser(MySchema())
parser.readfp(config)
- self.assertRaises(InterpolationMissingOptionError, parser.get,
- 'foo', 'bar')
+ self.assertRaises(configparser.InterpolationMissingOptionError,
+ parser.get, 'foo', 'bar')
@patch('configglue.parser.os')
def test_interpolate_environment_basic_syntax(self, mock_os):
@@ -592,7 +584,8 @@
self.assertEqual(set(items), set([('foo', 'bar')]))
def test_items_no_section(self):
- self.assertRaises(NoSectionError, self.parser.items, '__main__')
+ self.assertRaises(configparser.NoSectionError, self.parser.items,
+ '__main__')
def test_items_raw(self):
config = BytesIO(b'[__main__]\nfoo=%(baz)s')
@@ -624,8 +617,8 @@
def test_items_interpolate_error(self):
config = BytesIO(b'[__main__]\nfoo=%(bar)s')
self.parser.readfp(config)
- self.assertRaises(InterpolationMissingOptionError, self.parser.items,
- '__main__')
+ self.assertRaises(configparser.InterpolationMissingOptionError,
+ self.parser.items, '__main__')
def test_values_empty_parser(self):
values = self.parser.values()
@@ -690,7 +683,7 @@
self.assertEqual(value, expected_value)
def test_parse_invalid_section(self):
- self.assertRaises(NoSectionError, self.parser.parse,
+ self.assertRaises(configparser.NoSectionError, self.parser.parse,
'bar', 'baz', '1')
def test_default_values(self):
@@ -733,7 +726,7 @@
config = BytesIO(b"[__main__]\nbar=123")
parser = SchemaConfigParser(schema)
parser.readfp(config)
- self.assertRaises(NoOptionError, parser.values)
+ self.assertRaises(configparser.NoOptionError, parser.values)
def test_extra_sections(self):
"""Test extra_sections."""
@@ -820,12 +813,12 @@
self.assertEqual(default, expected)
def test_get_default_no_option(self):
- self.assertRaises(NoOptionError, self.parser._get_default,
- '__main__', 'bar')
+ self.assertRaises(configparser.NoOptionError,
+ self.parser._get_default, '__main__', 'bar')
def test_get_default_no_section(self):
- self.assertRaises(NoSectionError, self.parser._get_default,
- 'foo', 'bar')
+ self.assertRaises(configparser.NoSectionError,
+ self.parser._get_default, 'foo', 'bar')
def test_multi_file_dict_config(self):
"""Test parsing a dict option spanning multiple files."""
@@ -969,7 +962,7 @@
parser = SchemaConfigParser(MySchema())
expected = "[{0}]\nbaz = 2\n\n[__main__]\nfoo = bar".format(
- DEFAULTSECT)
+ configparser.DEFAULTSECT)
config = BytesIO(expected.encode(CONFIG_FILE_ENCODING))
parser.readfp(config)
@@ -1434,7 +1427,7 @@
parser.readfp(config)
parser.parse_all()
- self.assertRaises(NoSectionError, parser.values)
+ self.assertRaises(configparser.NoSectionError, parser.values)
# config is not valid
self.assertFalse(parser.is_valid())
=== modified file 'configglue/tests/test_schema.py'
--- configglue/tests/test_schema.py 2013-05-31 15:26:42 +0000
+++ configglue/tests/test_schema.py 2013-07-09 20:10:35 +0000
@@ -18,13 +18,9 @@
import textwrap
import unittest
-from configparser import (
- NoOptionError,
- NoSectionError,
-)
from io import BytesIO
-from configglue._compat import text_type
+from configglue._compat import configparser, text_type
from configglue.parser import (
SchemaConfigParser,
SchemaValidationError,
@@ -874,7 +870,7 @@
schema = MySchema()
parser = SchemaConfigParser(schema)
parser.readfp(config)
- self.assertRaises(NoSectionError, parser.values)
+ self.assertRaises(configparser.NoSectionError, parser.values)
def test_parse_dict_json_non_dict_json(self):
"""Test DictOption parse json not representing a dict."""
@@ -893,7 +889,7 @@
schema = MySchema()
parser = SchemaConfigParser(schema)
parser.readfp(config)
- self.assertRaises(NoSectionError, parser.values)
+ self.assertRaises(configparser.NoSectionError, parser.values)
def test_parse_dict_no_json_with_json(self):
"""Test DictOption parse json when json is disabled."""
@@ -915,7 +911,7 @@
schema = MySchema()
parser = SchemaConfigParser(schema)
parser.readfp(config)
- self.assertRaises(NoSectionError, parser.values)
+ self.assertRaises(configparser.NoSectionError, parser.values)
def test_parse_raw(self):
"""Test DictOption parse using raw=True."""
@@ -1265,7 +1261,7 @@
section.foo = IntOption()
self.assertEqual(section.option('foo'), section.foo)
- self.assertRaises(NoOptionError, section.option, 'bar')
+ self.assertRaises(configparser.NoOptionError, section.option, 'bar')
def test_options(self):
"""Test Section options method."""
=== modified file 'configglue/tests/test_schemaconfig.py'
--- configglue/tests/test_schemaconfig.py 2013-05-26 19:07:46 +0000
+++ configglue/tests/test_schemaconfig.py 2013-07-09 20:10:35 +0000
@@ -30,15 +30,12 @@
patch,
)
-from configglue._compat import PY2
+from configglue._compat import PY2, configparser
from configglue.glue import (
configglue,
schemaconfigglue,
)
-from configglue.parser import (
- NoSectionError,
- SchemaConfigParser,
-)
+from configglue.parser import SchemaConfigParser
from configglue.schema import (
DictOption,
IntOption,
@@ -194,7 +191,7 @@
parser.readfp(config)
# hitting the parser directly raises an exception
- self.assertRaises(NoSectionError, parser.values)
+ self.assertRaises(configparser.NoSectionError, parser.values)
self.assertFalse(parser.is_valid())
# which is nicely handled by the glue code, so as not to crash it
=== modified file 'setup.py'
--- setup.py 2013-05-27 00:42:51 +0000
+++ setup.py 2013-07-09 20:10:35 +0000
@@ -21,12 +21,9 @@
)
import configglue
-from configglue._compat import PY2
install_requires = ['pyxdg']
-if PY2:
- install_requires.extend(['configparser'])
setup(name='configglue',
Follow ups
-
[Merge] lp:~ricardokirkner/configglue/from-future-remove-configparser into lp:configglue
From: noreply, 2013-07-11
-
[Merge] lp:~ricardokirkner/configglue/from-future-remove-configparser into lp:configglue
From: Ricardo Kirkner, 2013-07-11
-
[Merge] lp:~ricardokirkner/configglue/from-future-remove-configparser into lp:configglue
From: Ricardo Kirkner, 2013-07-11
-
Re: lp:~ricardokirkner/configglue/from-future-remove-configparser into lp:configglue
From: Rodney Dawes, 2013-07-11
-
Re: lp:~ricardokirkner/configglue/from-future-remove-configparser into lp:configglue
From: Barry Warsaw, 2013-07-11
-
Re: lp:~ricardokirkner/configglue/from-future-remove-configparser into lp:configglue
From: Barry Warsaw, 2013-07-10
-
Re: lp:~ricardokirkner/configglue/from-future-remove-configparser into lp:configglue
From: Barry Warsaw, 2013-07-10
-
Re: lp:~ricardokirkner/configglue/from-future-remove-configparser into lp:configglue
From: Barry Warsaw, 2013-07-10
-
Re: lp:~ricardokirkner/configglue/from-future-remove-configparser into lp:configglue
From: Rodney Dawes, 2013-07-09
-
[Merge] lp:~ricardokirkner/configglue/from-future-remove-configparser into lp:configglue
From: Ricardo Kirkner, 2013-07-09