Commit f33a0c1e authored by Ricardo Kirkner's avatar Ricardo Kirkner Committed by Tarmac

[r=ricardokirkner,mfoord] made code Python 3.x compatible

parents f4595930 e74bf631
......@@ -14,4 +14,4 @@
#
###############################################################################
__version__ = '1.0.3'
__version__ = '1.1.0'
import sys
PY2 = sys.version_info[0] == 2
if not PY2:
text_type = str
string_types = (str,)
import builtins
iteritems = lambda d: iter(d.items())
else:
text_type = unicode
string_types = (str, unicode)
import __builtin__ as builtins
iteritems = lambda d: d.iteritems()
......@@ -16,7 +16,7 @@
import os
import sys
from ConfigParser import (
from configparser import (
NoOptionError,
NoSectionError,
)
......
......@@ -18,7 +18,7 @@
AttributtedConfigParser lives here.
"""
import re
from ConfigParser import RawConfigParser
from configparser import RawConfigParser
marker = object()
......
......@@ -18,9 +18,9 @@
"""
from __future__ import absolute_import
import __builtin__
from collections import namedtuple
from configglue._compat import builtins
from configglue.inischema import parsers
from configglue.inischema.attributed import AttributedConfigParser
from configglue.glue import schemaconfigglue
......@@ -78,7 +78,7 @@ def ini2schema(fd, p=None):
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)
parser_fun = getattr(builtins, parser, None)
if parser_fun is None:
parser_fun = lambda x: x
......
......@@ -15,10 +15,11 @@
###############################################################################
""" TypedConfigParser lives here """
from __future__ import absolute_import
from __future__ import absolute_import, unicode_literals
import os
from configglue._compat import text_type
from . import parsers
from .attributed import AttributedConfigParser
......@@ -34,7 +35,7 @@ class TypedConfigParser(AttributedConfigParser):
'float': float,
'int': int,
'lines': parsers.lines,
'unicode': unicode,
'unicode': text_type,
'getenv': os.getenv,
None: lambda x: x}
......
......@@ -20,15 +20,18 @@ import copy
import logging
import os
import re
import string
from io import TextIOWrapper
from ConfigParser import (
from configparser import (
DEFAULTSECT,
SafeConfigParser as BaseConfigParser,
InterpolationMissingOptionError,
NoOptionError,
NoSectionError,
)
from functools import reduce
from configglue._compat import text_type, string_types
__all__ = [
......@@ -75,6 +78,8 @@ class SchemaConfigParser(BaseConfigParser, object):
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.
......@@ -155,8 +160,8 @@ class SchemaConfigParser(BaseConfigParser, object):
# structure validates, validate content
self.parse_all()
except Exception, e:
errors.append(str(e))
except Exception as e:
errors.append(text_type(e))
valid = False
if report:
......@@ -201,7 +206,7 @@ class SchemaConfigParser(BaseConfigParser, object):
for option in options:
try:
value = self._interpolate(section, option, d[option], d)
except InterpolationMissingOptionError, e:
except InterpolationMissingOptionError as e:
# interpolation failed, because key was not found in
# section. try other sections before bailing out
value = self._interpolate_value(section, option)
......@@ -243,7 +248,7 @@ class SchemaConfigParser(BaseConfigParser, object):
"""Like ConfigParser.read, but consider files we've already read."""
if already_read is None:
already_read = set()
if isinstance(filenames, basestring):
if isinstance(filenames, string_types):
filenames = [filenames]
read_ok = []
for filename in filenames:
......@@ -280,7 +285,7 @@ class SchemaConfigParser(BaseConfigParser, object):
old_basedir, self._basedir = self._basedir, os.path.dirname(
fpname)
includes = self.get('__main__', 'includes')
filenames = map(string.strip, includes)
filenames = [text_type.strip(x) for x in includes]
self.read(filenames, already_read=already_read)
self._basedir = old_basedir
......@@ -300,7 +305,7 @@ class SchemaConfigParser(BaseConfigParser, object):
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())
option_names = [x.name for x in self.schema.options()]
# new values
sections = self._sections
......@@ -348,7 +353,7 @@ class SchemaConfigParser(BaseConfigParser, object):
try:
value = option_obj.parse(value, **kwargs)
except ValueError, e:
except ValueError as e:
raise ValueError("Invalid value '%s' for %s '%s' in"
" section '%s'. Original exception was: %s" %
(value, option_obj.__class__.__name__, option,
......@@ -379,7 +384,7 @@ class SchemaConfigParser(BaseConfigParser, object):
def _extract_interpolation_keys(self, item):
if isinstance(item, (list, tuple)):
keys = map(self._extract_interpolation_keys, item)
keys = [self._extract_interpolation_keys(x) for x in item]
keys = reduce(set.union, keys, set())
else:
keys = set(self._KEYCRE.findall(item))
......@@ -390,7 +395,7 @@ class SchemaConfigParser(BaseConfigParser, object):
def _get_interpolation_keys(self, section, option):
rawval = super(SchemaConfigParser, self).get(section, option, True)
rawval = super(SchemaConfigParser, self).get(section, option, raw=True)
try:
opt = self.schema.section(section).option(option)
value = opt.parse(rawval, raw=True)
......@@ -400,6 +405,10 @@ class SchemaConfigParser(BaseConfigParser, object):
keys = self._extract_interpolation_keys(value)
return rawval, keys
def _interpolate(self, *args, **kwargs):
"""Helper method for transition to configparser."""
return self._interpolation.before_get(self, *args, **kwargs)
def _interpolate_value(self, section, option):
rawval, keys = self._get_interpolation_keys(section, option)
if not keys:
......@@ -429,7 +438,7 @@ class SchemaConfigParser(BaseConfigParser, object):
# replace holders with values
result = rawval % values
assert isinstance(result, basestring)
assert isinstance(result, string_types)
return result
def interpolate_environment(self, rawval, raw=False):
......@@ -508,20 +517,20 @@ class SchemaConfigParser(BaseConfigParser, object):
pass
# value is defined entirely in current section
value = super(SchemaConfigParser, self).get(section, option,
raw, vars)
except InterpolationMissingOptionError, e:
raw=raw, vars=vars)
except 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), e:
except (NoSectionError, NoOptionError) as e:
# option not found in config, try to get its default value from
# schema
value = self._get_default(section, option)
# interpolate environment variables
if isinstance(value, basestring):
if isinstance(value, string_types):
try:
value = self.interpolate_environment(value, raw=raw)
if parse:
......@@ -582,11 +591,12 @@ class SchemaConfigParser(BaseConfigParser, object):
"""
if fp is not None:
if isinstance(fp, basestring):
if isinstance(fp, string_types):
fp = open(fp, 'w')
# write to a specific file
encoded_fp = codecs.getwriter(CONFIG_FILE_ENCODING)(fp)
self.write(encoded_fp)
if not isinstance(fp, TextIOWrapper):
# write to a specific file
fp = codecs.getwriter(CONFIG_FILE_ENCODING)(fp)
self.write(fp)
else:
# write to the original files
for filename, sections in self._dirty.items():
......
......@@ -13,15 +13,18 @@
# For bug reports, support, and new releases: http://launchpad.net/configglue
#
###############################################################################
from __future__ import unicode_literals
import json
from ConfigParser import (
from configparser import (
NoSectionError,
NoOptionError,
)
from copy import deepcopy
from inspect import getmembers
from configglue._compat import text_type, string_types
__all__ = [
'BoolOption',
......@@ -156,6 +159,9 @@ class Schema(object):
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return id(self)
def is_valid(self):
"""Return whether the schema has a valid structure."""
explicit_default_section = isinstance(getattr(self, '__main__', None),
......@@ -186,7 +192,7 @@ class Schema(object):
To get options from the default section, specify section='__main__'
"""
if isinstance(section, basestring):
if isinstance(section, string_types):
section = self.section(section)
if section is None:
options = []
......@@ -222,6 +228,9 @@ class Section(object):
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return id(self)
def __repr__(self):
if self.name:
name = " %s" % self.name
......@@ -316,6 +325,9 @@ class Option(object):
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return id(self)
def __repr__(self):
extra = ' raw' if self.raw else ''
extra += ' fatal' if self.fatal else ''
......@@ -341,7 +353,7 @@ class Option(object):
def to_string(self, value):
"""Return a string representation of the value."""
return str(value)
return text_type(value)
class BoolOption(Option):
......@@ -432,6 +444,9 @@ class ListOption(Option):
return equal
def __hash__(self):
return id(self)
def _get_default(self):
return []
......@@ -501,6 +516,9 @@ class StringOption(Option):
return equal
def __hash__(self):
return id(self)
def _get_default(self):
return '' if not self.null else None
......@@ -514,7 +532,7 @@ class StringOption(Option):
result = value
elif self.null:
result = None if value in (None, 'None') else value
elif isinstance(value, basestring):
elif isinstance(value, string_types):
result = value
else:
result = repr(value)
......@@ -526,7 +544,7 @@ class StringOption(Option):
return value
def validate(self, value):
return (self.null and value is None) or isinstance(value, basestring)
return (self.null and value is None) or isinstance(value, string_types)
class TupleOption(Option):
......@@ -552,6 +570,9 @@ class TupleOption(Option):
return equal
def __hash__(self):
return id(self)
def _get_default(self):
return ()
......@@ -620,6 +641,9 @@ class DictOption(Option):
return equal
def __hash__(self):
return id(self)
def _get_default(self):
default = {}
for key, value in self.spec.items():
......@@ -683,7 +707,7 @@ class DictOption(Option):
if not raw:
value = option.default
else:
value = unicode(option.default)
value = text_type(option.default)
result[key] = value
return result
......
......@@ -14,7 +14,6 @@
#
###############################################################################
import os
import user
from optparse import OptionParser
from unittest import TestCase
......@@ -68,8 +67,8 @@ def make_config(app=None):
class ConfigTestCase(TestCase):
def get_xdg_config_dirs(self):
xdg_config_home = os.environ.get('XDG_CONFIG_HOME',
os.path.join(user.home, '.config'))
xdg_config_dirs = ([xdg_config_home] +
os.path.join(os.path.expanduser('~'), '.config'))
xdg_config_dirs = ([xdg_config_home] +
os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg').split(':'))
return xdg_config_dirs
......
......@@ -13,13 +13,14 @@
# For bug reports, support, and new releases: http://launchpad.net/configglue
#
###############################################################################
from __future__ import unicode_literals
# in testfiles, putting docstrings on methods messes up with the
# runner's output, so pylint: disable-msg=C0111
import unittest
from ConfigParser import RawConfigParser
from StringIO import StringIO
from configparser import RawConfigParser
from io import StringIO
from configglue.inischema.attributed import AttributedConfigParser
......
......@@ -13,21 +13,25 @@
# For bug reports, support, and new releases: http://launchpad.net/configglue
#
###############################################################################
from __future__ import unicode_literals
# in testfiles, putting docstrings on methods messes up with the
# runner's output, so pylint: disable-msg=C0111
import sys
import unittest
from StringIO import StringIO
from io import BytesIO, StringIO, TextIOWrapper
from mock import patch
from configglue._compat import PY2
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.file = TextIOWrapper(BytesIO(self.ini))
self.old_sys_argv = sys.argv
sys.argv = ['']
......@@ -36,7 +40,7 @@ class TestBase(unittest.TestCase):
class TestGlue(TestBase):
ini = '''
ini = b'''
[blah]
foo.help = yadda yadda yadda
yadda
......@@ -60,17 +64,20 @@ foo = 2
{self.opt: '5'})
def test_help_is_displayed(self):
sys.stdout = StringIO()
try:
configglue(self.file, args=['', '--help'])
except SystemExit:
output = sys.stdout.getvalue()
sys.stdout = sys.__stdout__
new_callable = StringIO
if PY2:
new_callable = BytesIO
with patch('sys.stdout', new_callable=new_callable) as mock_stdout:
try:
configglue(self.file, args=['', '--help'])
except SystemExit:
output = mock_stdout.getvalue()
self.assertTrue('yadda yadda yadda yadda' in output)
class TestCrazyGlue(TestGlue):
ini = '''
ini = b'''
[bl-ah]
foo.default = 3
foo.help = yadda yadda yadda
......@@ -84,7 +91,7 @@ foo = 2
class TestNoValue(TestGlue):
ini = '''
ini = b'''
[blah]
foo.help = yadda yadda yadda
yadda
......@@ -96,7 +103,7 @@ foo = 3
class TestGlue2(TestBase):
ini = '[__main__]\na=1\n'
ini = b'[__main__]\na=1\n'
def test_main(self):
parser, options, args = configglue(self.file)
......@@ -104,7 +111,7 @@ class TestGlue2(TestBase):
class TestGlue3(TestBase):
ini = '[x]\na.help=hi\n'
ini = b'[x]\na.help=hi\n'
def test_empty(self):
parser, options, args = configglue(self.file)
......@@ -117,7 +124,7 @@ class TestGlue3(TestBase):
class TestGlueBool(TestBase):
ini = '''[__main__]
ini = b'''[__main__]
foo.parser=bool
foo.action=store_true
......@@ -136,7 +143,7 @@ bar.action = store_false
class TestGlueLines(TestBase):
ini = '''[__main__]
ini = b'''[__main__]
foo.parser = lines
foo.action = append
......
......@@ -14,11 +14,12 @@
# For bug reports, support, and new releases: http://launchpad.net/configglue
#
###############################################################################
from __future__ import unicode_literals
import sys
import textwrap
import unittest
from StringIO import StringIO
from io import StringIO
from configglue.inischema.glue import (
configglue,
......@@ -67,8 +68,6 @@ class TestGlueConvertor(unittest.TestCase):
s = textwrap.dedent("""
[__main__]
bar = zátrapa
bar.parser = unicode
bar.parser.args = utf-8
""")
_, cg, _ = configglue(StringIO(s))
_, sg, _ = schemaconfigglue(ini2schema(StringIO(s)))
......
# -*- encoding: utf-8 -*-
###############################################################################
#
# configglue -- glue for your apps' configuration
......@@ -13,13 +14,14 @@
# For bug reports, support, and new releases: http://launchpad.net/configglue
#
###############################################################################
from __future__ import unicode_literals
# in testfiles, putting docstrings on methods messes up with the
# runner's output, so pylint: disable-msg=C0111
import unittest
from StringIO import StringIO
from ConfigParser import RawConfigParser
from io import StringIO
from configparser import RawConfigParser
from configglue.inischema.typed import TypedConfigParser
......@@ -52,14 +54,8 @@ baz = marker
baz2.parser = more.parser
baz2 = -1
meep = \xe1rbol
meep = árbol
meep.parser = unicode
meep.parser.args = latin1
quux = \xe1rbol
quux.parser = unicode
quux.parser.args = utf-8
replace
thud.help = this is the help for thud
......@@ -119,9 +115,7 @@ class TestParserd(BaseTest):
('baz', marker),
('baz2', None),
('foo', 1j),
('meep', u'\xe1rbol'),
('quux', unicode('\xe1rbol', 'utf-8',
'replace')),
('meep', '\xe1rbol'),
('thud', None),
('woof', True),
])])
......
This diff is collapsed.
This diff is collapsed.
......@@ -14,11 +14,12 @@
# For bug reports, support, and new releases: http://launchpad.net/configglue
#
###############################################################################
from __future__ import unicode_literals
import unittest
import os
import sys
from StringIO import StringIO
from io import BytesIO, StringIO
from optparse import (
OptionConflictError,
OptionParser,
......@@ -29,6 +30,7 @@ from mock import (
patch,
)
from configglue._compat import PY2
from configglue.glue import (
configglue,
schemaconfigglue,
......@@ -146,7 +148,7 @@ class TestSchemaConfigGlue(unittest.TestCase):
def test_glue_no_op(self):
"""Test schemaconfigglue with the default OptionParser value."""
config = StringIO("[__main__]\nbaz=1")
config = BytesIO(b"[__main__]\nbaz=1")
self.parser.readfp(config)
self.assertEqual(self.parser.values(),
{'foo': {'bar': 0}, '__main__': {'baz': 1}})
......@@ -157,7 +159,7 @@ class TestSchemaConfigGlue(unittest.TestCase):
def test_glue_no_argv(self):
"""Test schemaconfigglue with the default argv value."""
config = StringIO("[__main__]\nbaz=1")
config = BytesIO(b"[__main__]\nbaz=1")
self.parser.readfp(config)
self.assertEqual(self.parser.values(),
{'foo': {'bar': 0}, '__main__': {'baz': 1}})
......@@ -172,7 +174,7 @@ class TestSchemaConfigGlue(unittest.TestCase):
def test_glue_section_option(self):
"""Test schemaconfigglue overriding one option."""
config = StringIO("[foo]\nbar=1")
config = BytesIO(b"[foo]\nbar=1")
self.parser.readfp(config)
self.assertEqual(self.parser.values(),
{'foo': {'bar': 1}, '__main__': {'baz': 0}})
......@@ -187,7 +189,7 @@ class TestSchemaConfigGlue(unittest.TestCase):
class MySchema(Schema):
foo = DictOption()
config = StringIO("[__main__]\nfoo = bar")
config = BytesIO(b"[__main__]\nfoo = bar")
parser = SchemaConfigParser(MySchema())
parser.readfp(config)
......@@ -216,7 +218,7 @@ class TestSchemaConfigGlue(unittest.TestCase):
@patch('configglue.glue.os')
def test_glue_environ(self, mock_os):
mock_os.environ = {'CONFIGGLUE_FOO_BAR': '42', 'CONFIGGLUE_BAZ': 3}
config = StringIO("[foo]\nbar=1")
config = BytesIO(b"[foo]\nbar=1")
self.parser.readfp(config)
_argv, sys.argv = sys.argv, ['prognam']
......@@ -230,7 +232,7 @@ class TestSchemaConfigGlue(unittest.TestCase):
@patch('configglue.glue.os')
def test_glue_environ_bad_name(self, mock_os):
mock_os.environ = {'FOO_BAR': 2, 'BAZ': 3}
config = StringIO("[foo]\nbar=1")
config = BytesIO(b"[foo]\nbar=1")
self.parser.readfp(config)
_argv, sys.argv = sys.argv, ['prognam']
......@@ -245,7 +247,7 @@ class TestSchemaConfigGlue(unittest.TestCase):
with patch.object(os, 'environ',
{'CONFIGGLUE_FOO_BAR': '42', 'BAR': '1'}):
config = StringIO("[foo]\nbar=$BAR")
config = BytesIO(b"[foo]\nbar=$BAR")
self.parser.readfp(config)
_argv, sys.argv = sys.argv, ['prognam']
......@@ -306,7 +308,7 @@ class TestSchemaConfigGlue(unittest.TestCase):
class bar(Section):
baz = IntOption()
config = StringIO("[foo]\nbaz=1")
config = BytesIO(b"[foo]\nbaz=1")
parser = SchemaConfigParser(MySchema())
parser.readfp(config)
self.assertEqual(parser.values('foo'), {'baz': 1})
......@@ -319,24 +321,22 @@ class TestSchemaConfigGlue(unittest.TestCase):
def test_help(self):
"""Test schemaconfigglue with --help."""
config = StringIO("[foo]\nbar=1")
config = BytesIO(b"[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
new_callable = StringIO
if PY2:
new_callable = BytesIO
with patch('sys.stdout', new_callable=new_callable) as mock_stdout:
# call the method and assert its value
self.assertRaises(SystemExit, schemaconfigglue, self.parser,
argv=['--help'])
# assert the value of stdout is correct
stdout.seek(0)
output = stdout.read()
output = mock_stdout.getvalue()
self.assertTrue(output.startswith('Usage:'))
def test_help_with_fatal(self):
......@@ -347,18 +347,16 @@ class TestSchemaConfigGlue(unittest.TestCase):
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
new_callable = StringIO
if PY2:
new_callable = BytesIO
with patch('sys.stdout', new_callable=new_callable) as mock_stdout:
# call the method and assert its value
self.assertRaises(SystemExit, schemaconfigglue, self.parser,
argv=['--help'])
# assert the value of stdout is correct
stdout.seek(0)
output = stdout.read()