summaryrefslogtreecommitdiff
path: root/configobj.py
diff options
context:
space:
mode:
Diffstat (limited to 'configobj.py')
-rw-r--r--configobj.py118
1 files changed, 64 insertions, 54 deletions
diff --git a/configobj.py b/configobj.py
index 3691978..3439954 100644
--- a/configobj.py
+++ b/configobj.py
@@ -19,6 +19,7 @@ import sys
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
+import six
# imported lazily to avoid startup performance hit if it isn't used
compiler = None
@@ -80,16 +81,6 @@ MISSING = object()
__version__ = '5.0.1'
-try:
- any
-except NameError:
- def any(iterable):
- for entry in iterable:
- if entry:
- return True
- return False
-
-
__all__ = (
'__version__',
'DEFAULT_INDENT_TYPE',
@@ -134,10 +125,6 @@ OPTION_DEFAULTS = {
# this could be replaced if six is used for compatibility, or there are no
# more assertions about items being a string
-if sys.version_info < (3,):
- string_type = basestring
-else:
- string_type = str
def getObj(s):
@@ -569,11 +556,11 @@ class Section(dict):
"""Fetch the item and do string interpolation."""
val = dict.__getitem__(self, key)
if self.main.interpolation:
- if isinstance(val, string_type):
+ if isinstance(val, six.string_types):
return self._interpolate(key, val)
if isinstance(val, list):
def _check(entry):
- if isinstance(entry, string_type):
+ if isinstance(entry, six.string_types):
return self._interpolate(key, entry)
return entry
new = [_check(entry) for entry in val]
@@ -596,7 +583,7 @@ class Section(dict):
``unrepr`` must be set when setting a value to a dictionary, without
creating a new sub-section.
"""
- if not isinstance(key, string_type):
+ if not isinstance(key, six.string_types):
raise ValueError('The key "%s" is not a string.' % key)
# add the comment
@@ -630,11 +617,11 @@ class Section(dict):
if key not in self:
self.scalars.append(key)
if not self.main.stringify:
- if isinstance(value, string_type):
+ if isinstance(value, six.string_types):
pass
elif isinstance(value, (list, tuple)):
for entry in value:
- if not isinstance(entry, string_type):
+ if not isinstance(entry, six.string_types):
raise TypeError('Value is not a string "%s".' % entry)
else:
raise TypeError('Value is not a string "%s".' % value)
@@ -975,7 +962,7 @@ class Section(dict):
return False
else:
try:
- if not isinstance(val, string_type):
+ if not isinstance(val, six.string_types):
# TODO: Why do we raise a KeyError here?
raise KeyError()
else:
@@ -1246,12 +1233,11 @@ class ConfigObj(Section):
def _load(self, infile, configspec):
- if isinstance(infile, string_type):
+ if isinstance(infile, six.string_types):
self.filename = infile
if os.path.isfile(infile):
- h = open(infile, 'rb')
- infile = h.read() or []
- h.close()
+ with open(infile, 'rb') as h:
+ content = h.read() or []
elif self.file_error:
# raise an error if the file doesn't exist
raise IOError('Config file not found: "%s".' % self.filename)
@@ -1260,13 +1246,12 @@ class ConfigObj(Section):
if self.create_empty:
# this is a good test that the filename specified
# isn't impossible - like on a non-existent device
- h = open(infile, 'w')
- h.write('')
- h.close()
- infile = []
+ with open(infile, 'w') as h:
+ h.write('')
+ content = []
elif isinstance(infile, (list, tuple)):
- infile = list(infile)
+ content = list(infile)
elif isinstance(infile, dict):
# initialise self
@@ -1294,20 +1279,20 @@ class ConfigObj(Section):
elif getattr(infile, 'read', MISSING) is not MISSING:
# This supports file like objects
- infile = infile.read() or []
+ content = infile.read() or []
# needs splitting into lines - but needs doing *after* decoding
# in case it's not an 8 bit encoding
else:
raise TypeError('infile must be a filename, file like object, or list of lines.')
- if infile:
+ if content:
# don't do it for the empty ConfigObj
- infile = self._handle_bom(infile)
+ content = self._handle_bom(content)
# infile is now *always* a list
#
# Set the newlines attribute (first line ending it finds)
# and strip trailing '\n' or '\r' from lines
- for line in infile:
+ for line in content:
if (not line) or (line[-1] not in ('\r', '\n')):
continue
for end in ('\r\n', '\n', '\r'):
@@ -1316,10 +1301,10 @@ class ConfigObj(Section):
break
break
- assert all(isinstance(line, string_type) for line in infile), repr(infile)
- infile = [line.rstrip('\r\n') for line in infile]
+ assert all(isinstance(line, six.string_types) for line in content), repr(content)
+ content = [line.rstrip('\r\n') for line in content]
- self._parse(infile)
+ self._parse(content)
# if we had any errors, now is the time to raise them
if self._errors:
info = "at line %s." % self._errors[0].line_number
@@ -1408,6 +1393,7 @@ class ConfigObj(Section):
``infile`` must always be returned as a list of lines, but may be
passed in as a single string.
"""
+
if ((self.encoding is not None) and
(self.encoding.lower() not in BOM_LIST)):
# No need to check for a BOM
@@ -1419,6 +1405,13 @@ class ConfigObj(Section):
line = infile[0]
else:
line = infile
+
+ if isinstance(line, six.text_type):
+ # it's already decoded and there's no need to do anything
+ # else, just use the _decode utility method to handle
+ # listifying appropriately
+ return self._decode(infile, self.encoding)
+
if self.encoding is not None:
# encoding explicitly supplied
# And it could have an associated BOM
@@ -1458,7 +1451,8 @@ class ConfigObj(Section):
# No encoding specified - so we need to check for UTF8/UTF16
for BOM, (encoding, final_encoding) in list(BOMS.items()):
- if not isinstance(line, bytes) or not line.startswith(BOM):
+ if not isinstance(line, six.binary_type) or not line.startswith(BOM):
+ # didn't specify a BOM, or it's not a bytestring
continue
else:
# BOM discovered
@@ -1473,15 +1467,22 @@ class ConfigObj(Section):
else:
infile = newline
# UTF-8
- if isinstance(infile, str):
+ if isinstance(infile, six.text_type):
return infile.splitlines(True)
+ elif isinstance(infile, six.binary_type):
+ return infile.decode('utf-8').splitlines(True)
else:
return self._decode(infile, 'utf-8')
# UTF16 - have to decode
return self._decode(infile, encoding)
+
+ if six.PY2 and isinstance(line, str):
+ # don't actually do any decoding, since we're on python 2 and
+ # returning a bytestring is fine
+ return self._decode(infile, None)
# No BOM discovered and no encoding specified, default to UTF-8
- if isinstance(infile, bytes):
+ if isinstance(infile, six.binary_type):
return infile.decode('utf-8').splitlines(True)
else:
return self._decode(infile, 'utf-8')
@@ -1489,7 +1490,7 @@ class ConfigObj(Section):
def _a_to_u(self, aString):
"""Decode ASCII strings to unicode if a self.encoding is specified."""
- if isinstance(aString, bytes) and self.encoding:
+ if isinstance(aString, six.binary_type) and self.encoding:
return aString.decode(self.encoding)
else:
return aString
@@ -1501,23 +1502,28 @@ class ConfigObj(Section):
if is a string, it also needs converting to a list.
"""
- if isinstance(infile, string_type):
+ if isinstance(infile, six.string_types):
return infile.splitlines(True)
- if isinstance(infile, bytes):
+ if isinstance(infile, six.binary_type):
# NOTE: Could raise a ``UnicodeDecodeError``
- return infile.decode(encoding).splitlines(True)
- for i, line in enumerate(infile):
- if not isinstance(line, string_type):
- # NOTE: The isinstance test here handles mixed lists of unicode/string
- # NOTE: But the decode will break on any non-string values
- # NOTE: Or could raise a ``UnicodeDecodeError``
- infile[i] = line.decode(encoding)
+ if encoding:
+ return infile.decode(encoding).splitlines(True)
+ else:
+ return infile.splitlines(True)
+
+ if encoding:
+ for i, line in enumerate(infile):
+ if isinstance(line, six.binary_type):
+ # NOTE: The isinstance test here handles mixed lists of unicode/string
+ # NOTE: But the decode will break on any non-string values
+ # NOTE: Or could raise a ``UnicodeDecodeError``
+ infile[i] = line.decode(encoding)
return infile
def _decode_element(self, line):
"""Decode element to unicode if necessary."""
- if isinstance(line, bytes) and self.default_encoding:
+ if isinstance(line, six.binary_type) and self.default_encoding:
return line.decode(self.default_encoding)
else:
return line
@@ -1529,7 +1535,9 @@ class ConfigObj(Section):
Used by ``stringify`` within validate, to turn non-string values
into strings.
"""
- if not isinstance(value, string_type):
+ if not isinstance(value, six.string_types):
+ # intentially 'str' because it's just whatever the "normal"
+ # string type is for the python version we're dealing with
return str(value)
else:
return value
@@ -1621,7 +1629,7 @@ class ConfigObj(Section):
mat = self._keyword.match(line)
if mat is None:
self._handle_error(
- 'Invalid line ({!r}) (matched as neither section nor keyword) at line "%s".'.format(line),
+ 'Invalid line ({0!r}) (matched as neither section nor keyword) at line "%s".'.format(line),
ParseError, infile, cur_index)
else:
# is a keyword value
@@ -1780,8 +1788,10 @@ class ConfigObj(Section):
return self._quote(value[0], multiline=False) + ','
return ', '.join([self._quote(val, multiline=False)
for val in value])
- if not isinstance(value, string_type):
+ if not isinstance(value, six.string_types):
if self.stringify:
+ # intentially 'str' because it's just whatever the "normal"
+ # string type is for the python version we're dealing with
value = str(value)
else:
raise TypeError('Value "%s" is not a string.' % value)
@@ -2341,7 +2351,7 @@ class ConfigObj(Section):
This method raises a ``ReloadError`` if the ConfigObj doesn't have
a filename attribute pointing to a file.
"""
- if not isinstance(self.filename, string_type):
+ if not isinstance(self.filename, six.string_types):
raise ReloadError()
filename = self.filename