← Back to team overview

configglue team mailing list archive

[Merge] lp:~ricardokirkner/configglue/py3k into lp:configglue

 

Ricardo Kirkner has proposed merging lp:~ricardokirkner/configglue/py3k into lp:configglue.

Commit message:
made code Python 3.x compatible

Requested reviews:
  Configglue developers (configglue)

For more details, see:
https://code.launchpad.net/~ricardokirkner/configglue/py3k/+merge/165793

This branch adds compatibility for Python 2.6, 2.7, 3.2 and 3.3

There is one backwards incompatible change, which is that the source file is expected to be read as a unicode string, which means it cannot contain strings using multiple encodings, but all strings within the config file have to be expressed in the same encoding.
-- 
https://code.launchpad.net/~ricardokirkner/configglue/py3k/+merge/165793
Your team Configglue developers is requested to review the proposed merge of lp:~ricardokirkner/configglue/py3k into lp:configglue.
=== modified file 'configglue/__init__.py'
--- configglue/__init__.py	2012-05-04 19:22:39 +0000
+++ configglue/__init__.py	2013-05-27 01:13:29 +0000
@@ -14,4 +14,4 @@
 #
 ###############################################################################
 
-__version__ = '1.0.3'
+__version__ = '1.1.0'

=== added file 'configglue/_compat.py'
--- configglue/_compat.py	1970-01-01 00:00:00 +0000
+++ configglue/_compat.py	2013-05-27 01:13:29 +0000
@@ -0,0 +1,15 @@
+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()

=== modified file 'configglue/glue.py'
--- configglue/glue.py	2011-07-29 22:16:30 +0000
+++ configglue/glue.py	2013-05-27 01:13:29 +0000
@@ -16,7 +16,7 @@
 
 import os
 import sys
-from ConfigParser import (
+from configparser import (
     NoOptionError,
     NoSectionError,
 )

=== modified file 'configglue/inischema/attributed.py'
--- configglue/inischema/attributed.py	2011-07-17 22:32:16 +0000
+++ configglue/inischema/attributed.py	2013-05-27 01:13:29 +0000
@@ -18,7 +18,7 @@
 AttributtedConfigParser lives here.
 """
 import re
-from ConfigParser import RawConfigParser
+from configparser import RawConfigParser
 
 
 marker = object()

=== modified file 'configglue/inischema/glue.py'
--- configglue/inischema/glue.py	2011-07-17 22:32:16 +0000
+++ configglue/inischema/glue.py	2013-05-27 01:13:29 +0000
@@ -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 @@
             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
 

=== modified file 'configglue/inischema/typed.py'
--- configglue/inischema/typed.py	2011-07-17 22:32:16 +0000
+++ configglue/inischema/typed.py	2013-05-27 01:13:29 +0000
@@ -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 @@
                         'float': float,
                         'int': int,
                         'lines': parsers.lines,
-                        'unicode': unicode,
+                        'unicode': text_type,
                         'getenv': os.getenv,
                         None: lambda x: x}
 

=== modified file 'configglue/parser.py'
--- configglue/parser.py	2013-05-08 15:20:47 +0000
+++ configglue/parser.py	2013-05-27 01:13:29 +0000
@@ -20,15 +20,18 @@
 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 @@
         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 @@
             # 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 @@
             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 @@
         """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 @@
             old_basedir, self._basedir = self._basedir, os.path.dirname(
                 fpname)
             includes = self.get('__main__', 'includes')
-            filenames = map(string.strip, includes)
+            filenames = list(map(text_type.strip, includes))
             self.read(filenames, already_read=already_read)
             self._basedir = old_basedir
 
@@ -300,7 +305,7 @@
 
     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 = list(map(lambda x: x.name, self.schema.options()))
 
         # new values
         sections = self._sections
@@ -348,7 +353,7 @@
 
             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 @@
 
     def _extract_interpolation_keys(self, item):
         if isinstance(item, (list, tuple)):
-            keys = map(self._extract_interpolation_keys, item)
+            keys = list(map(self._extract_interpolation_keys, item))
             keys = reduce(set.union, keys, set())
         else:
             keys = set(self._KEYCRE.findall(item))
@@ -390,7 +395,7 @@
 
     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 @@
         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 @@
         # 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 @@
                 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 @@
 
         """
         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():

=== modified file 'configglue/schema.py'
--- configglue/schema.py	2011-09-30 19:30:46 +0000
+++ configglue/schema.py	2013-05-27 01:13:29 +0000
@@ -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 @@
     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 @@
         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 @@
     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 @@
     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 @@
 
     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 @@
 
         return equal
 
+    def __hash__(self):
+        return id(self)
+
     def _get_default(self):
         return []
 
@@ -501,6 +516,9 @@
 
         return equal
 
+    def __hash__(self):
+        return id(self)
+
     def _get_default(self):
         return '' if not self.null else None
 
@@ -514,7 +532,7 @@
             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 @@
         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 @@
 
         return equal
 
+    def __hash__(self):
+        return id(self)
+
     def _get_default(self):
         return ()
 
@@ -620,6 +641,9 @@
 
         return equal
 
+    def __hash__(self):
+        return id(self)
+
     def _get_default(self):
         default = {}
         for key, value in self.spec.items():
@@ -683,7 +707,7 @@
                     if not raw:
                         value = option.default
                     else:
-                        value = unicode(option.default)
+                        value = text_type(option.default)
                     result[key] = value
         return result
 

=== modified file 'configglue/tests/app/test_base.py'
--- configglue/tests/app/test_base.py	2011-07-23 20:26:54 +0000
+++ configglue/tests/app/test_base.py	2013-05-27 01:13:29 +0000
@@ -14,7 +14,6 @@
 #
 ###############################################################################
 import os
-import user
 from optparse import OptionParser
 from unittest import TestCase
 
@@ -68,8 +67,8 @@
 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
 

=== modified file 'configglue/tests/inischema/test_attributed.py'
--- configglue/tests/inischema/test_attributed.py	2011-07-17 22:32:16 +0000
+++ configglue/tests/inischema/test_attributed.py	2013-05-27 01:13:29 +0000
@@ -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
 

=== modified file 'configglue/tests/inischema/test_glue.py'
--- configglue/tests/inischema/test_glue.py	2011-07-17 22:32:16 +0000
+++ configglue/tests/inischema/test_glue.py	2013-05-27 01:13:29 +0000
@@ -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 TestGlue(TestBase):
-    ini = '''
+    ini = b'''
 [blah]
 foo.help = yadda yadda yadda
          yadda
@@ -60,17 +64,20 @@
                          {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 @@
 
 
 class TestNoValue(TestGlue):
-    ini = '''
+    ini = b'''
 [blah]
 foo.help = yadda yadda yadda
          yadda
@@ -96,7 +103,7 @@
 
 
 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 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 TestGlueBool(TestBase):
-    ini = '''[__main__]
+    ini = b'''[__main__]
 foo.parser=bool
 foo.action=store_true
 
@@ -136,7 +143,7 @@
 
 
 class TestGlueLines(TestBase):
-    ini = '''[__main__]
+    ini = b'''[__main__]
 foo.parser = lines
 foo.action = append
 

=== modified file 'configglue/tests/inischema/test_glue2glue.py'
--- configglue/tests/inischema/test_glue2glue.py	2011-07-17 22:32:16 +0000
+++ configglue/tests/inischema/test_glue2glue.py	2013-05-27 01:13:29 +0000
@@ -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 @@
         s = textwrap.dedent("""
             [__main__]
             bar = zátrapa
-            bar.parser = unicode
-            bar.parser.args = utf-8
         """)
         _, cg, _ = configglue(StringIO(s))
         _, sg, _ = schemaconfigglue(ini2schema(StringIO(s)))

=== modified file 'configglue/tests/inischema/test_typed.py'
--- configglue/tests/inischema/test_typed.py	2011-07-17 22:32:16 +0000
+++ configglue/tests/inischema/test_typed.py	2013-05-27 01:13:29 +0000
@@ -1,3 +1,4 @@
+# -*- 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 @@
 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 @@
                                      ('baz', marker),
                                      ('baz2', None),
                                      ('foo', 1j),
-                                     ('meep', u'\xe1rbol'),
-                                     ('quux', unicode('\xe1rbol', 'utf-8',
-                                                      'replace')),
+                                     ('meep', '\xe1rbol'),
                                      ('thud', None),
                                      ('woof', True),
                                      ])])

=== modified file 'configglue/tests/test_parser.py'
--- configglue/tests/test_parser.py	2013-05-08 15:20:47 +0000
+++ configglue/tests/test_parser.py	2013-05-27 01:13:29 +0000
@@ -14,20 +14,22 @@
 # For bug reports, support, and new releases: http://launchpad.net/configglue
 #
 ###############################################################################
+from __future__ import unicode_literals
 
+import codecs
 import os
 import shutil
 import tempfile
 import textwrap
 import unittest
-from ConfigParser import (
+from configparser import (
     DEFAULTSECT,
     InterpolationDepthError,
     InterpolationMissingOptionError,
     InterpolationSyntaxError,
     NoSectionError,
 )
-from StringIO import StringIO
+from io import BytesIO
 
 from mock import (
     MagicMock,
@@ -35,6 +37,7 @@
     patch,
 )
 
+from configglue._compat import PY2, iteritems
 from configglue.parser import (
     CONFIG_FILE_ENCODING,
     NoOptionError,
@@ -66,20 +69,22 @@
             foo = StringOption()
         self.schema = MySchema()
         fd, self.name = tempfile.mkstemp(suffix='.cfg')
-        os.write(fd, '[__main__]\nfoo=bar\n')
+        os.write(fd, b'[__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)
+        config = '[__main__]\nincludes=%s' % self.name
+        config = BytesIO(config.encode(CONFIG_FILE_ENCODING))
         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)
+        config = "[__main__]\nincludes=%s" % self.name
+        config = BytesIO(config.encode(CONFIG_FILE_ENCODING))
         parser = SchemaConfigParser(self.schema)
         parser.readfp(config, 'my.cfg')
 
@@ -122,7 +127,8 @@
             f.write("[__main__]\nbaz=3")
             f.close()
 
-            config = StringIO("[__main__]\nincludes=%s/first.cfg" % folder)
+            config = "[__main__]\nincludes=%s/first.cfg" % folder
+            config = BytesIO(config.encode(CONFIG_FILE_ENCODING))
             return config, folder
 
         class MySchema(Schema):
@@ -159,8 +165,8 @@
             f.write("[__main__]\nbaz=3")
             f.close()
 
-            config = StringIO(
-                "[__main__]\nfoo=4\nincludes=%s/first.cfg" % folder)
+            config = "[__main__]\nfoo=4\nincludes=%s/first.cfg" % folder
+            config = BytesIO(config.encode(CONFIG_FILE_ENCODING))
             return config, folder
 
         class MySchema(Schema):
@@ -191,7 +197,7 @@
         class MySchema(Schema):
             foo = StringOption()
             bar = BoolOption()
-        config = StringIO('[__main__]\nbar=%(foo)s\nfoo=True')
+        config = BytesIO(b'[__main__]\nbar=%(foo)s\nfoo=True')
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config, 'my.cfg')
         self.assertEquals({'__main__': {'foo': 'True', 'bar': True}},
@@ -248,7 +254,7 @@
             class baz(Section):
                 wham = IntOption()
 
-        config = StringIO("[foo]\nbar=%(wham)s\n[baz]\nwham=42")
+        config = BytesIO(b"[foo]\nbar=%(wham)s\n[baz]\nwham=42")
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
         self.assertRaises(InterpolationMissingOptionError,
@@ -263,7 +269,7 @@
             class baz(Section):
                 wham = IntOption()
 
-        config = StringIO("[foo]\nbar=%(wham)s\n[baz]\nwham=42")
+        config = BytesIO(b"[foo]\nbar=%(wham)s\n[baz]\nwham=42")
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
         self.assertRaises(InterpolationMissingOptionError, parser.get,
@@ -354,7 +360,7 @@
             pythonpath = StringOption()
             path = StringOption()
 
-        config = StringIO("[__main__]\npythonpath=${PYTHONPATH}\npath=$PATH")
+        config = BytesIO(b"[__main__]\npythonpath=${PYTHONPATH}\npath=$PATH")
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
         self.assertEqual(parser.values('__main__'),
@@ -372,7 +378,7 @@
         class MySchema(Schema):
             foo = IntOption()
 
-        config = StringIO("[__main__]\nfoo=$FOO")
+        config = BytesIO(b"[__main__]\nfoo=$FOO")
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
         self.assertEqual(parser.get('__main__', 'foo'), 42)
@@ -381,7 +387,7 @@
         class MySchema(Schema):
             foo = IntOption()
 
-        config = StringIO("[__main__]\nfoo=$FOO")
+        config = BytesIO(b"[__main__]\nfoo=$FOO")
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
         self.assertEqual(parser.get('__main__', 'foo'), 0)
@@ -390,7 +396,7 @@
         """Test get_interpolation_keys for a string."""
         class MySchema(Schema):
             foo = StringOption()
-        config = StringIO("[__main__]\nfoo=%(bar)s")
+        config = BytesIO(b"[__main__]\nfoo=%(bar)s")
         expected = ('%(bar)s', set(['bar']))
 
         parser = SchemaConfigParser(MySchema())
@@ -402,7 +408,7 @@
         """Test get_interpolation_keys for an integer."""
         class MySchema(Schema):
             foo = IntOption()
-        config = StringIO("[__main__]\nfoo=%(bar)s")
+        config = BytesIO(b"[__main__]\nfoo=%(bar)s")
         expected = ('%(bar)s', set(['bar']))
 
         parser = SchemaConfigParser(MySchema())
@@ -414,7 +420,7 @@
         """Test get_interpolation_keys for a boolean."""
         class MySchema(Schema):
             foo = BoolOption()
-        config = StringIO("[__main__]\nfoo=%(bar)s")
+        config = BytesIO(b"[__main__]\nfoo=%(bar)s")
         expected = ('%(bar)s', set(['bar']))
 
         parser = SchemaConfigParser(MySchema())
@@ -426,7 +432,7 @@
         """Test get_interpolation_keys for a tuple."""
         class MySchema(Schema):
             foo = TupleOption(2)
-        config = StringIO("[__main__]\nfoo=%(bar)s,%(baz)s")
+        config = BytesIO(b"[__main__]\nfoo=%(bar)s,%(baz)s")
         expected = ('%(bar)s,%(baz)s', set(['bar', 'baz']))
 
         parser = SchemaConfigParser(MySchema())
@@ -438,7 +444,7 @@
         """Test get_interpolation_keys for a list."""
         class MySchema(Schema):
             foo = ListOption(item=StringOption())
-        config = StringIO("[__main__]\nfoo=%(bar)s\n    %(baz)s")
+        config = BytesIO(b"[__main__]\nfoo=%(bar)s\n    %(baz)s")
         expected = ('%(bar)s\n%(baz)s', set(['bar', 'baz']))
 
         parser = SchemaConfigParser(MySchema())
@@ -450,8 +456,8 @@
         """Test get_interpolation_keys for a list of tuples."""
         class MySchema(Schema):
             foo = ListOption(item=TupleOption(2))
-        config = StringIO(
-            "[__main__]\nfoo=%(bar)s,%(bar)s\n    %(baz)s,%(baz)s")
+        config = BytesIO(
+            b"[__main__]\nfoo=%(bar)s,%(bar)s\n    %(baz)s,%(baz)s")
         expected = ('%(bar)s,%(bar)s\n%(baz)s,%(baz)s',
                     set(['bar', 'baz']))
 
@@ -464,14 +470,14 @@
         """Test get_interpolation_keys for a dict."""
         class MySchema(Schema):
             foo = DictOption(spec={'a': IntOption()})
-        config = StringIO(textwrap.dedent("""
+        config = BytesIO(textwrap.dedent("""
             [__noschema__]
             bar=4
             [__main__]
             foo=mydict
             [mydict]
             a=%(bar)s
-            """))
+            """).encode(CONFIG_FILE_ENCODING))
         expected = ('mydict', set([]))
 
         parser = SchemaConfigParser(MySchema())
@@ -483,8 +489,8 @@
         """Test interpolate_value for a duplicate key."""
         class MySchema(Schema):
             foo = TupleOption(2)
-        config = StringIO(
-            "[__noschema__]\nbar=4\n[__main__]\nfoo=%(bar)s,%(bar)s")
+        config = BytesIO(
+            b"[__noschema__]\nbar=4\n[__main__]\nfoo=%(bar)s,%(bar)s")
         expected_value = '4,4'
 
         parser = SchemaConfigParser(MySchema())
@@ -496,7 +502,7 @@
         """Test interpolate_value with an invalid key."""
         class MySchema(Schema):
             foo = TupleOption(2)
-        config = StringIO("[other]\nbar=4\n[__main__]\nfoo=%(bar)s,%(bar)s")
+        config = BytesIO(b"[other]\nbar=4\n[__main__]\nfoo=%(bar)s,%(bar)s")
         expected_value = None
 
         parser = SchemaConfigParser(MySchema())
@@ -508,7 +514,7 @@
         """Test interpolate_value with no keys."""
         class MySchema(Schema):
             foo = TupleOption(2)
-        config = StringIO("[__main__]\nfoo=%(bar)s,%(bar)s")
+        config = BytesIO(b"[__main__]\nfoo=%(bar)s,%(bar)s")
 
         mock_get_interpolation_keys = Mock(return_value=('%(bar)s', None))
 
@@ -524,7 +530,7 @@
         """Test get using a raw value."""
         class MySchema(Schema):
             foo = StringOption(raw=True)
-        config = StringIO('[__main__]\nfoo=blah%(asd)##$@@dddf2kjhkjs')
+        config = BytesIO(b'[__main__]\nfoo=blah%(asd)##$@@dddf2kjhkjs')
         expected_value = 'blah%(asd)##$@@dddf2kjhkjs'
 
         parser = SchemaConfigParser(MySchema())
@@ -536,14 +542,14 @@
         """Test interpolation while parsing a dict."""
         class MySchema(Schema):
             foo = DictOption(spec={'a': IntOption()})
-        config = StringIO(textwrap.dedent("""
+        config = BytesIO(textwrap.dedent("""
             [__noschema__]
             bar=4
             [__main__]
             foo=mydict
             [mydict]
             a=%(bar)s
-            """))
+            """).encode(CONFIG_FILE_ENCODING))
         expected = {'__main__': {'foo': {'a': 4}}}
 
         parser = SchemaConfigParser(MySchema())
@@ -558,7 +564,7 @@
             foo = StringOption()
         self.schema = MySchema()
         self.parser = SchemaConfigParser(self.schema)
-        self.config = StringIO("[__main__]\nfoo = bar")
+        self.config = BytesIO(b"[__main__]\nfoo = bar")
 
     def test_init_no_args(self):
         self.assertRaises(TypeError, SchemaConfigParser)
@@ -583,13 +589,13 @@
         self.assertRaises(NoSectionError, self.parser.items, '__main__')
 
     def test_items_raw(self):
-        config = StringIO('[__main__]\nfoo=%(baz)s')
+        config = BytesIO(b'[__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')
+        config = BytesIO(b'[__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')]))
@@ -603,14 +609,14 @@
                 bar = StringOption()
 
         parser = SchemaConfigParser(MySchema())
-        config = StringIO('[__main__]\nfoo=%(bar)s\n[baz]\nbar=42')
+        config = BytesIO(b'[__main__]\nfoo=%(bar)s\n[baz]\nbar=42')
         parser.readfp(config)
         # test interpolate
         items = parser.items('baz')
-        self.assertEqual(items, {'bar': '42'}.items())
+        self.assertEqual(items, list(iteritems({'bar': '42'})))
 
     def test_items_interpolate_error(self):
-        config = StringIO('[__main__]\nfoo=%(bar)s')
+        config = BytesIO(b'[__main__]\nfoo=%(bar)s')
         self.parser.readfp(config)
         self.assertRaises(InterpolationMissingOptionError, self.parser.items,
                           '__main__')
@@ -637,7 +643,7 @@
             class baz(Section):
                 bar = IntOption()
 
-        config = StringIO("[foo]\nbar=3\n[baz]\nbar=4")
+        config = BytesIO(b"[foo]\nbar=3\n[baz]\nbar=4")
         expected_values = {'foo': {'bar': 3}, 'baz': {'bar': 4}}
 
         schema = MySchema()
@@ -655,7 +661,7 @@
             class bar(Section):
                 baz = IntOption()
 
-        config = StringIO("[foo]\nbar=3\n[bar]\nbaz=4")
+        config = BytesIO(b"[foo]\nbar=3\n[bar]\nbaz=4")
         expected_values = {'foo': {'bar': 3}, 'bar': {'baz': 4}}
 
         schema = MySchema()
@@ -671,7 +677,7 @@
                 bar = StringOption()
 
         expected_value = 'baz'
-        config = StringIO("[foo]\nbar = baz")
+        config = BytesIO(b"[foo]\nbar = baz")
         parser = SchemaConfigParser(MyOtherSchema())
         parser.readfp(config)
         value = parser.get('foo', 'bar')
@@ -691,14 +697,14 @@
                 bla = StringOption(default='hello')
 
         schema = MySchema()
-        config = StringIO("[bar]\nbaz=123")
+        config = BytesIO(b"[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")
+        config = BytesIO(b"[bar]\nbla=123")
         expected = {
             '__main__': {'foo': True},
             'bar': {'baz': 0, 'bla': '123'}}
@@ -712,13 +718,13 @@
             foo = IntOption(fatal=True)
             bar = IntOption()
         schema = MySchema()
-        config = StringIO("[__main__]\nfoo=123")
+        config = BytesIO(b"[__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")
+        config = BytesIO(b"[__main__]\nbar=123")
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
         self.assertRaises(NoOptionError, parser.values)
@@ -728,7 +734,7 @@
         class MySchema(Schema):
             foo = DictOption(spec={'bar': IntOption()})
 
-        config = StringIO("[__main__]\nfoo=mydict\n[mydict]\nbar=1")
+        config = BytesIO(b"[__main__]\nfoo=mydict\n[mydict]\nbar=1")
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
         parser.parse_all()
@@ -754,10 +760,10 @@
         class MySchema(Schema):
             foo = DictOption()
 
-        config = StringIO(textwrap.dedent("""
+        config = BytesIO(textwrap.dedent("""
             [__main__]
             foo = dict1
-            """))
+            """).encode(CONFIG_FILE_ENCODING))
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
         parser.parse_all()
@@ -770,8 +776,8 @@
             foo = ListOption(
                 item=DictOption(spec={'bar': IntOption()}))
 
-        config = StringIO('[__main__]\nfoo=d1\n    d2\n    d3\n'
-                          '[d1]\nbar=1\n[d2]\nbar=2\n[d3]\nbar=3')
+        config = BytesIO(b'[__main__]\nfoo=d1\n    d2\n    d3\n'
+                         b'[d1]\nbar=1\n[d2]\nbar=2\n[d3]\nbar=3')
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
         parser.parse_all()
@@ -781,14 +787,14 @@
         self.assertEqual(expected_sections, extra_sections)
 
     def test_get_default(self):
-        config = StringIO("[__main__]\n")
+        config = BytesIO(b"[__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")
+        config = BytesIO(b"[__noschema__]\nbar=1\n[__main__]\n")
         expected = '1'
         self.parser.readfp(config)
         default = self.parser._get_default('__noschema__', 'bar')
@@ -799,7 +805,7 @@
         class MySchema(Schema):
             class foo(Section):
                 bar = IntOption()
-        config = StringIO("[__main__]\n")
+        config = BytesIO(b"[__main__]\n")
         expected = 0
 
         parser = SchemaConfigParser(MySchema())
@@ -822,8 +828,8 @@
                 'bar': IntOption(),
                 'baz': IntOption(),
             }, strict=True)
-        config1 = StringIO('[__main__]\nfoo=mydict\n[mydict]\nbar=1\nbaz=1')
-        config2 = StringIO('[mydict]\nbaz=2')
+        config1 = BytesIO(b'[__main__]\nfoo=mydict\n[mydict]\nbar=1\nbaz=1')
+        config2 = BytesIO(b'[mydict]\nbaz=2')
         expected_values = {'__main__': {'foo': {'bar': 1, 'baz': 2}}}
 
         parser = SchemaConfigParser(MySchema())
@@ -840,7 +846,7 @@
                     'baz': IntOption(),
                 }, strict=True))
 
-        config1 = StringIO('[__main__]\nfoo=mydict\n[mydict]\nbar=1\nbaz=1')
+        config1 = BytesIO(b'[__main__]\nfoo=mydict\n[mydict]\nbar=1\nbaz=1')
         expected_values = {'__main__': {'foo': [{'bar': 1, 'baz': 1}]}}
 
         parser = SchemaConfigParser(MySchema())
@@ -848,19 +854,19 @@
         self.assertEqual(parser.values(), expected_values)
 
         # override used dictionaries
-        config2 = StringIO('[__main__]\nfoo=otherdict\n[otherdict]\nbar=2')
+        config2 = BytesIO(b'[__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')
+        config3 = BytesIO(b'[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')
+        config4 = BytesIO(b'[__main__]\nfoo=mydict\n    otherdict')
         expected_values = {'__main__': {'foo': [{'bar': 1, 'baz': 1},
                                                {'bar': 2, 'baz': 3}]}}
         parser.readfp(config4)
@@ -896,25 +902,29 @@
         fp, filename = tempfile.mkstemp()
 
         try:
-            f = open(filename, 'w')
-            f.write(u'[__main__]\nfoo=€'.encode(CONFIG_FILE_ENCODING))
+            if PY2:
+                f = open(filename, 'w')
+                f.write('[__main__]\nfoo=€'.encode(CONFIG_FILE_ENCODING))
+            else:
+                f = open(filename, 'w', encoding=CONFIG_FILE_ENCODING)
+                f.write('[__main__]\nfoo=€')
             f.close()
 
             self.parser.read(filename)
             self.assertEqual(self.parser.values(),
-                {'__main__': {'foo': u'€'}})
+                {'__main__': {'foo': '€'}})
         finally:
             # destroy config file
             os.remove(filename)
 
     def test_readfp_with_utf8_encoded_text(self):
-        config = StringIO(u'[__main__]\nfoo=€'.encode(CONFIG_FILE_ENCODING))
+        config = BytesIO('[__main__]\nfoo=€'.encode(CONFIG_FILE_ENCODING))
         self.parser.readfp(config)
-        self.assertEqual(self.parser.values(), {'__main__': {'foo': u'€'}})
+        self.assertEqual(self.parser.values(), {'__main__': {'foo': '€'}})
 
     def test_set(self):
         with tempfile.NamedTemporaryFile() as f:
-            f.write('[__main__]\nfoo=1')
+            f.write(b'[__main__]\nfoo=1')
             f.flush()
 
             self.parser.read(f.name)
@@ -953,9 +963,9 @@
                 pass
 
         parser = SchemaConfigParser(MySchema())
-        expected = u"[{0}]\nbaz = 2\n\n[__main__]\nfoo = bar".format(
+        expected = "[{0}]\nbaz = 2\n\n[__main__]\nfoo = bar".format(
             DEFAULTSECT)
-        config = StringIO(expected)
+        config = BytesIO(expected.encode(CONFIG_FILE_ENCODING))
         parser.readfp(config)
 
         # create config file
@@ -974,7 +984,7 @@
             foo = IntOption()
 
         parser = SchemaConfigParser(MySchema())
-        expected = u"[__main__]\nfoo = 0"
+        expected = "[__main__]\nfoo = 0"
 
         # create config file
         fp, filename = tempfile.mkstemp()
@@ -987,15 +997,15 @@
             os.unlink(filename)
 
     def test_save_config(self):
-        expected = u'[__main__]\nfoo = 42'
+        expected = '[__main__]\nfoo = 42'
         self._check_save_file(expected)
 
     def test_save_config_non_ascii(self):
-        expected = u'[__main__]\nfoo = fóobâr'
+        expected = '[__main__]\nfoo = fóobâr'
         self._check_save_file(expected)
 
     def _check_save_file(self, expected, read_config=True):
-        config = StringIO(expected.encode(CONFIG_FILE_ENCODING))
+        config = BytesIO(expected.encode(CONFIG_FILE_ENCODING))
         if read_config:
             self.parser.readfp(config)
 
@@ -1003,19 +1013,21 @@
         fp, filename = tempfile.mkstemp()
         try:
             self.parser.save(open(filename, 'w'))
-            result = open(filename, 'r').read().strip()
-            self.assertEqual(result.decode(CONFIG_FILE_ENCODING), expected)
+            result = codecs.open(filename, 'r',
+                                 encoding=CONFIG_FILE_ENCODING).read().strip()
+            self.assertEqual(result, expected)
 
             self.parser.save(filename)
-            result = open(filename, 'r').read().strip()
-            self.assertEqual(result.decode(CONFIG_FILE_ENCODING), expected)
+            result = codecs.open(filename, 'r',
+                                 encoding=CONFIG_FILE_ENCODING).read().strip()
+            self.assertEqual(result, expected)
         finally:
             # remove the file
             os.unlink(filename)
 
     def test_save_config_prefill_parser(self):
         """Test parser save config when no config files read."""
-        expected = u'[__main__]\nfoo ='
+        expected = '[__main__]\nfoo ='
         self._check_save_file(expected, read_config=False)
 
     def test_save_no_config_same_files(self):
@@ -1131,7 +1143,7 @@
             foo = StringOption()
         self.schema = MySchema()
         self.parser = SchemaConfigParser(self.schema)
-        self.config = StringIO("[__main__]\nfoo = bar")
+        self.config = BytesIO(b"[__main__]\nfoo = bar")
 
     def test_basic_is_valid(self):
         """Test basic validation without error reporting."""
@@ -1139,7 +1151,7 @@
             foo = IntOption()
 
         schema = MySchema()
-        config = StringIO("[__main__]\nfoo = 5")
+        config = BytesIO(b"[__main__]\nfoo = 5")
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
 
@@ -1150,7 +1162,7 @@
         class MySchema(Schema):
             foo = IntOption()
 
-        config = StringIO("[__main__]\nfoo=5")
+        config = BytesIO(b"[__main__]\nfoo=5")
         expected = (True, [])
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
@@ -1163,7 +1175,7 @@
             foo = IntOption()
 
         schema = MySchema()
-        config = StringIO("[__main__]\nfoo = 5\nbar = 6")
+        config = BytesIO(b"[__main__]\nfoo = 5\nbar = 6")
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
 
@@ -1174,7 +1186,7 @@
         class MySchema(Schema):
             foo = IntOption()
 
-        config = StringIO("[__main__]\nfoo=5\nbar=6")
+        config = BytesIO(b"[__main__]\nfoo=5\nbar=6")
         errors = ["Configuration includes invalid options for "
                   "section '__main__': bar"]
         expected = (False, errors)
@@ -1193,7 +1205,7 @@
             assert False
 
         schema = MySchema()
-        config = StringIO("[__main__]\nfoo = 5")
+        config = BytesIO(b"[__main__]\nfoo = 5")
         parser = SchemaConfigParser(schema)
         parser.parse_all = mock_parse_all
         parser.readfp(config)
@@ -1201,22 +1213,22 @@
         self.assertFalse(parser.is_valid())
 
     def test_parse_invalid_section(self):
-        config = StringIO("[bar]\nbaz=foo")
+        config = BytesIO(b"[bar]\nbaz=foo")
         self.parser.readfp(config)
 
         self.assertFalse(self.parser.is_valid())
 
     def test_parse_invalid_section_with_report(self):
-        config = StringIO("[bar]\nbaz=foo")
+        config = BytesIO(b"[bar]\nbaz=foo")
         self.parser.readfp(config)
 
         valid, errors = self.parser.is_valid(report=True)
         self.assertFalse(valid)
         self.assertEqual(errors[0],
-            u'Sections in configuration are missing from schema: bar')
+            'Sections in configuration are missing from schema: bar')
 
     def test_different_sections(self):
-        config = StringIO("[__main__]\nfoo=1\n[bar]\nbaz=2")
+        config = BytesIO(b"[__main__]\nfoo=1\n[bar]\nbaz=2")
         self.parser.readfp(config)
 
         self.assertFalse(self.parser.is_valid())
@@ -1227,7 +1239,7 @@
             foo = IntOption()
             bar = IntOption(fatal=True)
 
-        config = StringIO("[__main__]\nfoo=1")
+        config = BytesIO(b"[__main__]\nfoo=1")
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
 
@@ -1239,7 +1251,7 @@
             foo = IntOption()
             bar = IntOption(fatal=True)
 
-        config = StringIO("[__main__]\nbar=2")
+        config = BytesIO(b"[__main__]\nbar=2")
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
 
@@ -1250,7 +1262,7 @@
         class MySchema(Schema):
             foo = DictOption(spec={'bar': IntOption()})
 
-        config = StringIO("[__main__]\nfoo=mydict\n[mydict]\nbar=1")
+        config = BytesIO(b"[__main__]\nfoo=mydict\n[mydict]\nbar=1")
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
         parser.parse_all()
@@ -1262,7 +1274,7 @@
         class MySchema(Schema):
             foo = DictOption(item=DictOption())
 
-        config = StringIO("""
+        config = BytesIO(b"""
 [__main__]
 foo=dict1
 [dict1]
@@ -1283,7 +1295,7 @@
         class MySchema(Schema):
             foo = DictOption(spec={'bar': DictOption()}, strict=True)
 
-        config = StringIO("""
+        config = BytesIO(b"""
 [__main__]
 foo=dict1
 [dict1]
@@ -1305,7 +1317,7 @@
             foo = ListOption(
                 item=DictOption(item=DictOption()))
 
-        config = StringIO("""
+        config = BytesIO(b"""
 [__main__]
 foo = dict1
       dict2
@@ -1334,7 +1346,7 @@
             foo = DictOption(
                 item=ListOption(item=DictOption()))
 
-        config = StringIO("""
+        config = BytesIO(b"""
 [__main__]
 foo = dict1
 [dict1]
@@ -1360,7 +1372,7 @@
                 item=DictOption(
                     item=ListOption(item=DictOption())))
 
-        config = StringIO("""
+        config = BytesIO(b"""
 [__main__]
 foo = dict1
       dict2
@@ -1394,10 +1406,10 @@
         class MySchema(Schema):
             foo = DictOption()
 
-        config = StringIO(textwrap.dedent("""
+        config = BytesIO(textwrap.dedent("""
             [__main__]
             foo = dict1
-            """))
+            """).encode(CONFIG_FILE_ENCODING))
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
         parser.parse_all()
@@ -1412,8 +1424,8 @@
             foo = ListOption(
                 item=DictOption(spec={'bar': IntOption()}))
 
-        config = StringIO('[__main__]\nfoo=d1\n    d2\n    d3\n'
-                          '[d1]\nbar=1\n[d2]\nbar=2\n[d3]\nbar=3')
+        config = BytesIO(b'[__main__]\nfoo=d1\n    d2\n    d3\n'
+                         b'[d1]\nbar=1\n[d2]\nbar=2\n[d3]\nbar=3')
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
         parser.parse_all()
@@ -1421,8 +1433,8 @@
         self.assertTrue(parser.is_valid())
 
     def test_noschema_section(self):
-        config = StringIO(
-            "[__main__]\nfoo=%(bar)s\n[__noschema__]\nbar=hello")
+        config = BytesIO(
+            b"[__main__]\nfoo=%(bar)s\n[__noschema__]\nbar=hello")
         parser = SchemaConfigParser(self.schema)
         parser.readfp(config)
         parser.parse_all()
@@ -1437,10 +1449,10 @@
             class bar(Section):
                 baz = BoolOption()
 
-        config = StringIO(textwrap.dedent("""
+        config = BytesIO(textwrap.dedent("""
             [foo]
             bar = 3
-            """))
+            """).encode(CONFIG_FILE_ENCODING))
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
         parser.parse_all()

=== modified file 'configglue/tests/test_schema.py'
--- configglue/tests/test_schema.py	2011-09-30 19:30:46 +0000
+++ configglue/tests/test_schema.py	2013-05-27 01:13:29 +0000
@@ -14,15 +14,17 @@
 # For bug reports, support, and new releases: http://launchpad.net/configglue
 #
 ###############################################################################
+from __future__ import unicode_literals
 
 import textwrap
 import unittest
-from ConfigParser import (
+from configparser import (
     NoOptionError,
     NoSectionError,
 )
-from StringIO import StringIO
+from io import BytesIO
 
+from configglue._compat import text_type
 from configglue.parser import (
     SchemaConfigParser,
     SchemaValidationError,
@@ -389,19 +391,19 @@
         class MySchema(Schema):
             foo = self.cls()
 
-        config = StringIO("[__main__]\nfoo = 42")
+        config = BytesIO(b"[__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 =")
+        config = BytesIO(b"[__main__]\nfoo =")
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
         self.assertRaises(ValueError, parser.values)
 
-        config = StringIO("[__main__]\nfoo = bla")
+        config = BytesIO(b"[__main__]\nfoo = bla")
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
         self.assertRaises(ValueError, parser.values)
@@ -435,24 +437,24 @@
         class MySchema(Schema):
             foo = self.cls()
 
-        config = StringIO("[__main__]\nfoo = Yes")
+        config = BytesIO(b"[__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")
+        config = BytesIO(b"[__main__]\nfoo = tRuE")
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
         self.assertEqual(parser.values(), expected_values)
 
-        config = StringIO("[__main__]\nfoo =")
+        config = BytesIO(b"[__main__]\nfoo =")
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
         self.assertRaises(ValueError, parser.values)
 
-        config = StringIO("[__main__]\nfoo = bla")
+        config = BytesIO(b"[__main__]\nfoo = bla")
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
         self.assertRaises(ValueError, parser.values)
@@ -486,7 +488,7 @@
         class MySchema(Schema):
             foo = self.cls(item=IntOption())
 
-        config = StringIO("[__main__]\nfoo = 42\n 43\n 44")
+        config = BytesIO(b"[__main__]\nfoo = 42\n 43\n 44")
         expected_values = {'__main__': {'foo': [42, 43, 44]}}
         schema = MySchema()
         parser = SchemaConfigParser(schema)
@@ -497,7 +499,7 @@
         class MySchema(Schema):
             foo = self.cls(item=IntOption(), parse_json=False)
 
-        config = StringIO("[__main__]\nfoo = 42\n 43\n 44")
+        config = BytesIO(b"[__main__]\nfoo = 42\n 43\n 44")
         expected_values = {'__main__': {'foo': [42, 43, 44]}}
         schema = MySchema()
         parser = SchemaConfigParser(schema)
@@ -508,7 +510,7 @@
         class MySchema(Schema):
             foo = self.cls(item=IntOption(), parse_json=False)
 
-        config = StringIO("[__main__]\nfoo = [42, 43, 44]")
+        config = BytesIO(b"[__main__]\nfoo = [42, 43, 44]")
         schema = MySchema()
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
@@ -518,7 +520,7 @@
         class MySchema(Schema):
             foo = self.cls(item=IntOption())
 
-        config = StringIO("[__main__]\nfoo = [42, 43, 44]")
+        config = BytesIO(b"[__main__]\nfoo = [42, 43, 44]")
         expected_values = {'__main__': {'foo': [42, 43, 44]}}
         schema = MySchema()
         parser = SchemaConfigParser(schema)
@@ -529,7 +531,7 @@
         class MySchema(Schema):
             foo = self.cls(item=IntOption())
 
-        config = StringIO('[__main__]\nfoo = 1, 2, 3')
+        config = BytesIO(b'[__main__]\nfoo = 1, 2, 3')
         schema = MySchema()
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
@@ -539,7 +541,7 @@
         class MySchema(Schema):
             foo = self.cls(item=IntOption())
 
-        config = StringIO('[__main__]\nfoo = {"foo": "bar"}')
+        config = BytesIO(b'[__main__]\nfoo = {"foo": "bar"}')
         schema = MySchema()
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
@@ -551,7 +553,7 @@
             foo = self.cls(item=BoolOption())
 
         schema = MySchema()
-        config = StringIO("[__main__]\nfoo = tRuE\n No\n 0\n 1")
+        config = BytesIO(b"[__main__]\nfoo = tRuE\n No\n 0\n 1")
         expected_values = {'__main__': {'foo': [True, False, False, True]}}
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
@@ -563,7 +565,7 @@
             foo = self.cls(item=BoolOption())
 
         schema = MySchema()
-        config = StringIO("[__main__]\nfoo =")
+        config = BytesIO(b"[__main__]\nfoo =")
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
         expected_values = {'__main__': {'foo': []}}
@@ -575,12 +577,12 @@
             foo = self.cls(item=BoolOption())
 
         schema = MySchema()
-        config = StringIO("[__main__]\nfoo = bla")
+        config = BytesIO(b"[__main__]\nfoo = bla")
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
         self.assertRaises(ValueError, parser.values)
 
-        config = StringIO("[__main__]\nfoo = True\n bla")
+        config = BytesIO(b"[__main__]\nfoo = True\n bla")
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
         self.assertRaises(ValueError, parser.values)
@@ -596,7 +598,7 @@
             foo = self.cls(item=StringOption(), remove_duplicates=True)
 
         schema = MySchema()
-        config = StringIO("[__main__]\nfoo = bla\n blah\n bla")
+        config = BytesIO(b"[__main__]\nfoo = bla\n blah\n bla")
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
         self.assertEquals({'__main__': {'foo': ['bla', 'blah']}},
@@ -608,7 +610,7 @@
             foo = self.cls(item=DictOption(), remove_duplicates=True)
 
         schema = MyOtherSchema()
-        config = StringIO("[__main__]\nfoo = bla\n bla\n[bla]\nbar = baz")
+        config = BytesIO(b"[__main__]\nfoo = bla\n bla\n[bla]\nbar = baz")
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
         self.assertEquals({'__main__': {'foo': [{'bar': 'baz'}]}},
@@ -661,7 +663,8 @@
     def test_to_string_when_no_json(self):
         option = ListOption(parse_json=False)
         result = option.to_string(['1', '2', '3'])
-        self.assertEqual(result, "['1', '2', '3']")
+        expected = list(map(text_type, [1, 2, 3]))
+        self.assertEqual(result, text_type(expected))
 
 
 class TestTupleOption(unittest.TestCase):
@@ -683,7 +686,7 @@
         class MySchema(Schema):
             foo = self.cls()
 
-        config = StringIO('[__main__]\nfoo=1,2,3,4')
+        config = BytesIO(b'[__main__]\nfoo=1,2,3,4')
         expected_values = {'__main__': {'foo': ('1', '2', '3', '4')}}
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
@@ -694,19 +697,19 @@
         class MySchema(Schema):
             foo = self.cls(length=4)
 
-        config = StringIO('[__main__]\nfoo = 1, 2, 3, 4')
+        config = BytesIO(b'[__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')
+        config = BytesIO(b'[__main__]\nfoo = 1, 2, 3')
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
         self.assertRaises(ValueError, parser.values)
 
-        config = StringIO('[__main__]\nfoo = ')
+        config = BytesIO(b'[__main__]\nfoo = ')
         parser = SchemaConfigParser(schema)
         parser.readfp(config)
         self.assertRaises(ValueError, parser.values)
@@ -763,7 +766,7 @@
         class MySchema(Schema):
             foo = self.cls(item=self.cls())
 
-        config = StringIO("""
+        config = BytesIO(b"""
 [__main__]
 foo=dict1
 [dict1]
@@ -788,7 +791,7 @@
                 'bla': BoolOption(),
             })
 
-        config = StringIO("""[__main__]
+        config = BytesIO(b"""[__main__]
 foo = mydict
 [mydict]
 bar=baz
@@ -813,7 +816,7 @@
                 'bla': BoolOption(),
             }, parse_json=False)
 
-        config = StringIO("""[__main__]
+        config = BytesIO(b"""[__main__]
 foo = mydict
 [mydict]
 bar=baz
@@ -838,13 +841,13 @@
                 'bla': BoolOption(),
             })
 
-        config = StringIO(textwrap.dedent("""
+        config = BytesIO(textwrap.dedent("""
             [__main__]
             foo = {
                 "bar": "baz",
                 "baz": "42",
                 "bla": "Yes"}
-            """))
+            """).encode('utf-8'))
         expected_values = {
             '__main__': {
                 'foo': {'bar': 'baz', 'baz': 42, 'bla': True}}}
@@ -863,10 +866,10 @@
                 'bla': BoolOption(),
             })
 
-        config = StringIO(textwrap.dedent("""
+        config = BytesIO(textwrap.dedent("""
             [__main__]
             foo = {'bar': 23}
-            """))
+            """).encode('utf-8'))
 
         schema = MySchema()
         parser = SchemaConfigParser(schema)
@@ -882,10 +885,10 @@
                 'bla': BoolOption(),
             })
 
-        config = StringIO(textwrap.dedent("""
+        config = BytesIO(textwrap.dedent("""
             [__main__]
             foo = [1, 2, 3]
-            """))
+            """).encode('utf-8'))
 
         schema = MySchema()
         parser = SchemaConfigParser(schema)
@@ -901,13 +904,13 @@
                 'bla': BoolOption(),
             }, parse_json=False)
 
-        config = StringIO(textwrap.dedent("""
+        config = BytesIO(textwrap.dedent("""
             [__main__]
             foo = {
                 "bar": "baz",
                 "baz": "42",
                 "bla": "Yes"}
-            """))
+            """).encode('utf-8'))
 
         schema = MySchema()
         parser = SchemaConfigParser(schema)
@@ -923,7 +926,7 @@
                 'bla': BoolOption(),
             })
 
-        config = StringIO("""[__main__]
+        config = BytesIO(b"""[__main__]
 foo = mydict
 [mydict]
 baz=42
@@ -942,10 +945,10 @@
             class logging(Section):
                 formatters = self.cls(raw=True, item=self.cls())
 
-        config = StringIO(textwrap.dedent("""
+        config = BytesIO(textwrap.dedent("""
             [logging]
             formatters = {"sample": {"format": "%(name)s"}}
-            """))
+            """).encode('utf-8'))
         expected = {'sample': {'format': '%(name)s'}}
 
         schema = MySchema()
@@ -960,7 +963,7 @@
             class logging(Section):
                 formatters = self.cls(raw=True, item=self.cls())
 
-        config = StringIO(textwrap.dedent("""
+        config = BytesIO(textwrap.dedent("""
             [logging]
             formatters = logging_formatters
 
@@ -969,7 +972,7 @@
 
             [sample_formatter]
             format = %%(name)s
-            """))
+            """).encode('utf-8'))
         expected = {'sample': {'format': '%(name)s'}}
 
         schema = MySchema()
@@ -983,7 +986,7 @@
         class MySchema(Schema):
             foo = self.cls(spec={'bar': IntOption()})
 
-        config = StringIO("[__main__]\nfoo=mydict\n[mydict]\nbaz=2")
+        config = BytesIO(b"[__main__]\nfoo=mydict\n[mydict]\nbaz=2")
         expected_values = {'__main__': {'foo': {'bar': 0, 'baz': '2'}}}
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
@@ -996,7 +999,7 @@
                 'bar': IntOption(),
                 'baz': IntOption(fatal=True)})
 
-        config = StringIO("[__main__]\nfoo=mydict\n[mydict]\nbar=2")
+        config = BytesIO(b"[__main__]\nfoo=mydict\n[mydict]\nbar=2")
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
         self.assertRaises(ValueError, parser.parse_all)
@@ -1010,7 +1013,7 @@
         class MySchema(Schema):
             foo = self.cls(spec={'bar': IntOption()})
 
-        config = StringIO("[__main__]\nfoo=mydict\n[mydict]")
+        config = BytesIO(b"[__main__]\nfoo=mydict\n[mydict]")
         expected_values = {'__main__': {'foo': {'bar': 0}}}
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
@@ -1020,7 +1023,7 @@
         class MySchema(Schema):
             foo = self.cls()
 
-        config = StringIO("[__main__]\nfoo=mydict\n[mydict]\nbar=2")
+        config = BytesIO(b"[__main__]\nfoo=mydict\n[mydict]\nbar=2")
         expected_values = {'__main__': {'foo': {'bar': '2'}}}
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
@@ -1032,7 +1035,7 @@
             foo = self.cls(
                       item=self.cls(
                           item=IntOption()))
-        config = StringIO("""
+        config = BytesIO(b"""
 [__main__]
 foo = mydict
 [mydict]
@@ -1051,7 +1054,7 @@
             spec = {'bar': IntOption()}
             foo = self.cls(spec=spec, strict=True)
 
-        config = StringIO("[__main__]\nfoo=mydict\n[mydict]\nbar=2")
+        config = BytesIO(b"[__main__]\nfoo=mydict\n[mydict]\nbar=2")
         expected_values = {'__main__': {'foo': {'bar': 2}}}
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
@@ -1064,7 +1067,7 @@
                     'baz': IntOption()}
             foo = self.cls(spec=spec, strict=True)
 
-        config = StringIO("[__main__]\nfoo=mydict\n[mydict]\nbar=2")
+        config = BytesIO(b"[__main__]\nfoo=mydict\n[mydict]\nbar=2")
         expected_values = {'__main__': {'foo': {'bar': 2, 'baz': 0}}}
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
@@ -1076,7 +1079,7 @@
             spec = {'bar': IntOption()}
             foo = self.cls(spec=spec, strict=True)
 
-        config = StringIO("[__main__]\nfoo=mydict\n[mydict]\nbar=2\nbaz=3")
+        config = BytesIO(b"[__main__]\nfoo=mydict\n[mydict]\nbar=2\nbaz=3")
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
         self.assertRaises(ValueError, parser.parse_all)
@@ -1116,7 +1119,7 @@
     def test_to_string_when_no_json(self):
         option = DictOption(parse_json=False)
         result = option.to_string({'foo': '1'})
-        self.assertEqual(result, str({'foo': '1'}))
+        self.assertEqual(result, text_type({'foo': '1'}))
 
 
 class TestListOfDictOption(unittest.TestCase):
@@ -1130,7 +1133,7 @@
                     'bla': BoolOption(),
                 }))
 
-        config = StringIO("""[__main__]
+        config = BytesIO(b"""[__main__]
 foo = mylist0
       mylist1
 [mylist0]
@@ -1167,7 +1170,7 @@
         class MySchema(Schema):
             foo = DictOption(spec=spec)
 
-        config = StringIO("""[__main__]
+        config = BytesIO(b"""[__main__]
 foo = outerdict
 [outerdict]
 options = innerdict
@@ -1194,19 +1197,19 @@
         self.parser = SchemaConfigParser(schema)
 
     def test_parse_list_of_tuples(self):
-        config = StringIO('[__main__]\nfoo = a, b, c\n      d, e, f')
+        config = BytesIO(b'[__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')
+        config = BytesIO(b'[__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=()')
+        config = BytesIO(b'[__main__]\nfoo=()')
         expected_values = {'__main__': {'foo': [()]}}
         self.parser.readfp(config)
         self.assertEqual(self.parser.values(), expected_values)
@@ -1331,6 +1334,6 @@
         try:
             merge(SchemaA, SchemaB)
             self.fail('SchemaValidationError not raised.')
-        except SchemaValidationError, e:
-            self.assertEqual(str(e),
+        except SchemaValidationError as e:
+            self.assertEqual(text_type(e),
                 "Conflicting option '__main__.foo' while merging schemas.")

=== modified file 'configglue/tests/test_schemaconfig.py'
--- configglue/tests/test_schemaconfig.py	2011-07-29 22:16:30 +0000
+++ configglue/tests/test_schemaconfig.py	2013-05-27 01:13:29 +0000
@@ -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 @@
     patch,
 )
 
+from configglue._compat import PY2
 from configglue.glue import (
     configglue,
     schemaconfigglue,
@@ -146,7 +148,7 @@
 
     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 @@
 
     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 @@
 
     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 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 @@
     @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 @@
     @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 @@
         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 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 @@
 
     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 @@
         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()
+        output = mock_stdout.getvalue()
         self.assertTrue(output.startswith('Usage:'))
 
     def test_parser_set_with_encoding(self):
@@ -410,7 +408,7 @@
         class MySchema(Schema):
             foo = IntOption(fatal=True)
 
-        config = StringIO("[__main__]\nfoo=1")
+        config = BytesIO(b"[__main__]\nfoo=1")
         parser = SchemaConfigParser(MySchema())
         parser.readfp(config)
 

=== modified file 'setup.py'
--- setup.py	2011-07-22 15:44:24 +0000
+++ setup.py	2013-05-27 01:13:29 +0000
@@ -21,6 +21,12 @@
 )
 
 import configglue
+from configglue._compat import PY2
+
+
+install_requires = ['pyxdg']
+if PY2:
+    install_requires.extend(['configparser'])
 
 
 setup(name='configglue',
@@ -36,12 +42,18 @@
         'License :: OSI Approved :: BSD License',
         'Topic :: Software Development :: Libraries :: Python Modules',
         'Programming Language :: Python',
+        'Programming Language :: Python :: 2',
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.2',
+        'Programming Language :: Python :: 3.3',
         ],
       author='John R. Lenton, Ricardo Kirkner',
       author_email='john.lenton@xxxxxxxxxxxxx, ricardo.kirkner@xxxxxxxxxxxxx',
       url='https://launchpad.net/configglue',
       license='BSD License',
-      install_requires=['pyxdg'],
+      install_requires=install_requires,
       dependency_links=['http://www.freedesktop.org/wiki/Software/pyxdg'],
       packages=find_packages(),
       include_package_data=True,

=== modified file 'tox.ini'
--- tox.ini	2011-06-30 02:12:02 +0000
+++ tox.ini	2013-05-27 01:13:29 +0000
@@ -1,12 +1,23 @@
 [tox]
-envlist = py26,py27,docs
+envlist = py26,py27,py32,py33,docs
 
 [testenv]
 deps =
     mock
+    pyxdg
 commands =
     python setup.py test
 
+[testenv:py26]
+deps =
+    {[testenv]deps}
+    configparser
+
+[testenv:py27]
+deps =
+    {[testenv]deps}
+    configparser
+
 [testenv:docs]
 changedir = doc
 deps =


Follow ups