← Back to team overview

configglue team mailing list archive

[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