summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--configobj.py118
-rw-r--r--setup.py2
-rw-r--r--six.py632
-rw-r--r--test_configobj.py386
-rw-r--r--tests/test_configobj.py480
5 files changed, 1176 insertions, 442 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
diff --git a/setup.py b/setup.py
index 5e74c30..3e466e2 100644
--- a/setup.py
+++ b/setup.py
@@ -16,7 +16,7 @@ from configobj import __version__ as VERSION
NAME = 'configobj'
-MODULES = 'configobj', 'validate'
+MODULES = 'configobj', 'validate', 'six'
DESCRIPTION = 'Config file reading, writing and validation.'
diff --git a/six.py b/six.py
new file mode 100644
index 0000000..ab2c7e8
--- /dev/null
+++ b/six.py
@@ -0,0 +1,632 @@
+"""Utilities for writing code that runs on Python 2 and 3"""
+
+# Copyright (c) 2010-2014 Benjamin Peterson
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import operator
+import sys
+import types
+
+__author__ = "Benjamin Peterson <benjamin@python.org>"
+__version__ = "1.5.2"
+
+
+# Useful for very coarse version differentiation.
+PY2 = sys.version_info[0] == 2
+PY3 = sys.version_info[0] == 3
+
+if PY3:
+ string_types = str,
+ integer_types = int,
+ class_types = type,
+ text_type = str
+ binary_type = bytes
+
+ MAXSIZE = sys.maxsize
+else:
+ string_types = basestring,
+ integer_types = (int, long)
+ class_types = (type, types.ClassType)
+ text_type = unicode
+ binary_type = str
+
+ if sys.platform.startswith("java"):
+ # Jython always uses 32 bits.
+ MAXSIZE = int((1 << 31) - 1)
+ else:
+ # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
+ class X(object):
+ def __len__(self):
+ return 1 << 31
+ try:
+ len(X())
+ except OverflowError:
+ # 32-bit
+ MAXSIZE = int((1 << 31) - 1)
+ else:
+ # 64-bit
+ MAXSIZE = int((1 << 63) - 1)
+ del X
+
+
+def _add_doc(func, doc):
+ """Add documentation to a function."""
+ func.__doc__ = doc
+
+
+def _import_module(name):
+ """Import module, returning the module after the last dot."""
+ __import__(name)
+ return sys.modules[name]
+
+
+class _LazyDescr(object):
+
+ def __init__(self, name):
+ self.name = name
+
+ def __get__(self, obj, tp):
+ result = self._resolve()
+ setattr(obj, self.name, result) # Invokes __set__.
+ # This is a bit ugly, but it avoids running this again.
+ delattr(obj.__class__, self.name)
+ return result
+
+
+class MovedModule(_LazyDescr):
+
+ def __init__(self, name, old, new=None):
+ super(MovedModule, self).__init__(name)
+ if PY3:
+ if new is None:
+ new = name
+ self.mod = new
+ else:
+ self.mod = old
+
+ def _resolve(self):
+ return _import_module(self.mod)
+
+ def __getattr__(self, attr):
+ # Hack around the Django autoreloader. The reloader tries to get
+ # __file__ or __name__ of every module in sys.modules. This doesn't work
+ # well if this MovedModule is for an module that is unavailable on this
+ # machine (like winreg on Unix systems). Thus, we pretend __file__ and
+ # __name__ don't exist if the module hasn't been loaded yet. See issues
+ # #51 and #53.
+ if attr in ("__file__", "__name__") and self.mod not in sys.modules:
+ raise AttributeError
+ _module = self._resolve()
+ value = getattr(_module, attr)
+ setattr(self, attr, value)
+ return value
+
+
+class _LazyModule(types.ModuleType):
+
+ def __init__(self, name):
+ super(_LazyModule, self).__init__(name)
+ self.__doc__ = self.__class__.__doc__
+
+ def __dir__(self):
+ attrs = ["__doc__", "__name__"]
+ attrs += [attr.name for attr in self._moved_attributes]
+ return attrs
+
+ # Subclasses should override this
+ _moved_attributes = []
+
+
+class MovedAttribute(_LazyDescr):
+
+ def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
+ super(MovedAttribute, self).__init__(name)
+ if PY3:
+ if new_mod is None:
+ new_mod = name
+ self.mod = new_mod
+ if new_attr is None:
+ if old_attr is None:
+ new_attr = name
+ else:
+ new_attr = old_attr
+ self.attr = new_attr
+ else:
+ self.mod = old_mod
+ if old_attr is None:
+ old_attr = name
+ self.attr = old_attr
+
+ def _resolve(self):
+ module = _import_module(self.mod)
+ return getattr(module, self.attr)
+
+
+
+class _MovedItems(_LazyModule):
+ """Lazy loading of moved objects"""
+
+
+_moved_attributes = [
+ MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
+ MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
+ MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
+ MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
+ MovedAttribute("map", "itertools", "builtins", "imap", "map"),
+ MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
+ MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
+ MovedAttribute("reduce", "__builtin__", "functools"),
+ MovedAttribute("StringIO", "StringIO", "io"),
+ MovedAttribute("UserString", "UserString", "collections"),
+ MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
+ MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
+ MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
+
+ MovedModule("builtins", "__builtin__"),
+ MovedModule("configparser", "ConfigParser"),
+ MovedModule("copyreg", "copy_reg"),
+ MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
+ MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
+ MovedModule("http_cookies", "Cookie", "http.cookies"),
+ MovedModule("html_entities", "htmlentitydefs", "html.entities"),
+ MovedModule("html_parser", "HTMLParser", "html.parser"),
+ MovedModule("http_client", "httplib", "http.client"),
+ MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
+ MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
+ MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
+ MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
+ MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
+ MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
+ MovedModule("cPickle", "cPickle", "pickle"),
+ MovedModule("queue", "Queue"),
+ MovedModule("reprlib", "repr"),
+ MovedModule("socketserver", "SocketServer"),
+ MovedModule("_thread", "thread", "_thread"),
+ MovedModule("tkinter", "Tkinter"),
+ MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
+ MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
+ MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
+ MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
+ MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
+ MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
+ MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
+ MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
+ MovedModule("tkinter_colorchooser", "tkColorChooser",
+ "tkinter.colorchooser"),
+ MovedModule("tkinter_commondialog", "tkCommonDialog",
+ "tkinter.commondialog"),
+ MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
+ MovedModule("tkinter_font", "tkFont", "tkinter.font"),
+ MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
+ MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
+ "tkinter.simpledialog"),
+ MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
+ MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
+ MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
+ MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
+ MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
+ MovedModule("winreg", "_winreg"),
+]
+for attr in _moved_attributes:
+ setattr(_MovedItems, attr.name, attr)
+ if isinstance(attr, MovedModule):
+ sys.modules[__name__ + ".moves." + attr.name] = attr
+del attr
+
+_MovedItems._moved_attributes = _moved_attributes
+
+moves = sys.modules[__name__ + ".moves"] = _MovedItems(__name__ + ".moves")
+
+
+class Module_six_moves_urllib_parse(_LazyModule):
+ """Lazy loading of moved objects in six.moves.urllib_parse"""
+
+
+_urllib_parse_moved_attributes = [
+ MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
+ MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
+ MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
+ MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
+ MovedAttribute("urljoin", "urlparse", "urllib.parse"),
+ MovedAttribute("urlparse", "urlparse", "urllib.parse"),
+ MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
+ MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
+ MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
+ MovedAttribute("quote", "urllib", "urllib.parse"),
+ MovedAttribute("quote_plus", "urllib", "urllib.parse"),
+ MovedAttribute("unquote", "urllib", "urllib.parse"),
+ MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
+ MovedAttribute("urlencode", "urllib", "urllib.parse"),
+]
+for attr in _urllib_parse_moved_attributes:
+ setattr(Module_six_moves_urllib_parse, attr.name, attr)
+del attr
+
+Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
+
+sys.modules[__name__ + ".moves.urllib_parse"] = sys.modules[__name__ + ".moves.urllib.parse"] = Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse")
+
+
+class Module_six_moves_urllib_error(_LazyModule):
+ """Lazy loading of moved objects in six.moves.urllib_error"""
+
+
+_urllib_error_moved_attributes = [
+ MovedAttribute("URLError", "urllib2", "urllib.error"),
+ MovedAttribute("HTTPError", "urllib2", "urllib.error"),
+ MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
+]
+for attr in _urllib_error_moved_attributes:
+ setattr(Module_six_moves_urllib_error, attr.name, attr)
+del attr
+
+Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
+
+sys.modules[__name__ + ".moves.urllib_error"] = sys.modules[__name__ + ".moves.urllib.error"] = Module_six_moves_urllib_error(__name__ + ".moves.urllib.error")
+
+
+class Module_six_moves_urllib_request(_LazyModule):
+ """Lazy loading of moved objects in six.moves.urllib_request"""
+
+
+_urllib_request_moved_attributes = [
+ MovedAttribute("urlopen", "urllib2", "urllib.request"),
+ MovedAttribute("install_opener", "urllib2", "urllib.request"),
+ MovedAttribute("build_opener", "urllib2", "urllib.request"),
+ MovedAttribute("pathname2url", "urllib", "urllib.request"),
+ MovedAttribute("url2pathname", "urllib", "urllib.request"),
+ MovedAttribute("getproxies", "urllib", "urllib.request"),
+ MovedAttribute("Request", "urllib2", "urllib.request"),
+ MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
+ MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
+ MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
+ MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
+ MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
+ MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
+ MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
+ MovedAttribute("FileHandler", "urllib2", "urllib.request"),
+ MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
+ MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
+ MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
+ MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
+ MovedAttribute("urlretrieve", "urllib", "urllib.request"),
+ MovedAttribute("urlcleanup", "urllib", "urllib.request"),
+ MovedAttribute("URLopener", "urllib", "urllib.request"),
+ MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
+ MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
+]
+for attr in _urllib_request_moved_attributes:
+ setattr(Module_six_moves_urllib_request, attr.name, attr)
+del attr
+
+Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
+
+sys.modules[__name__ + ".moves.urllib_request"] = sys.modules[__name__ + ".moves.urllib.request"] = Module_six_moves_urllib_request(__name__ + ".moves.urllib.request")
+
+
+class Module_six_moves_urllib_response(_LazyModule):
+ """Lazy loading of moved objects in six.moves.urllib_response"""
+
+
+_urllib_response_moved_attributes = [
+ MovedAttribute("addbase", "urllib", "urllib.response"),
+ MovedAttribute("addclosehook", "urllib", "urllib.response"),
+ MovedAttribute("addinfo", "urllib", "urllib.response"),
+ MovedAttribute("addinfourl", "urllib", "urllib.response"),
+]
+for attr in _urllib_response_moved_attributes:
+ setattr(Module_six_moves_urllib_response, attr.name, attr)
+del attr
+
+Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
+
+sys.modules[__name__ + ".moves.urllib_response"] = sys.modules[__name__ + ".moves.urllib.response"] = Module_six_moves_urllib_response(__name__ + ".moves.urllib.response")
+
+
+class Module_six_moves_urllib_robotparser(_LazyModule):
+ """Lazy loading of moved objects in six.moves.urllib_robotparser"""
+
+
+_urllib_robotparser_moved_attributes = [
+ MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
+]
+for attr in _urllib_robotparser_moved_attributes:
+ setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
+del attr
+
+Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
+
+sys.modules[__name__ + ".moves.urllib_robotparser"] = sys.modules[__name__ + ".moves.urllib.robotparser"] = Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser")
+
+
+class Module_six_moves_urllib(types.ModuleType):
+ """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
+ parse = sys.modules[__name__ + ".moves.urllib_parse"]
+ error = sys.modules[__name__ + ".moves.urllib_error"]
+ request = sys.modules[__name__ + ".moves.urllib_request"]
+ response = sys.modules[__name__ + ".moves.urllib_response"]
+ robotparser = sys.modules[__name__ + ".moves.urllib_robotparser"]
+
+ def __dir__(self):
+ return ['parse', 'error', 'request', 'response', 'robotparser']
+
+
+sys.modules[__name__ + ".moves.urllib"] = Module_six_moves_urllib(__name__ + ".moves.urllib")
+
+
+def add_move(move):
+ """Add an item to six.moves."""
+ setattr(_MovedItems, move.name, move)
+
+
+def remove_move(name):
+ """Remove item from six.moves."""
+ try:
+ delattr(_MovedItems, name)
+ except AttributeError:
+ try:
+ del moves.__dict__[name]
+ except KeyError:
+ raise AttributeError("no such move, %r" % (name,))
+
+
+if PY3:
+ _meth_func = "__func__"
+ _meth_self = "__self__"
+
+ _func_closure = "__closure__"
+ _func_code = "__code__"
+ _func_defaults = "__defaults__"
+ _func_globals = "__globals__"
+
+ _iterkeys = "keys"
+ _itervalues = "values"
+ _iteritems = "items"
+ _iterlists = "lists"
+else:
+ _meth_func = "im_func"
+ _meth_self = "im_self"
+
+ _func_closure = "func_closure"
+ _func_code = "func_code"
+ _func_defaults = "func_defaults"
+ _func_globals = "func_globals"
+
+ _iterkeys = "iterkeys"
+ _itervalues = "itervalues"
+ _iteritems = "iteritems"
+ _iterlists = "iterlists"
+
+
+try:
+ advance_iterator = next
+except NameError:
+ def advance_iterator(it):
+ return it.next()
+next = advance_iterator
+
+
+try:
+ callable = callable
+except NameError:
+ def callable(obj):
+ return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
+
+
+if PY3:
+ def get_unbound_function(unbound):
+ return unbound
+
+ create_bound_method = types.MethodType
+
+ Iterator = object
+else:
+ def get_unbound_function(unbound):
+ return unbound.im_func
+
+ def create_bound_method(func, obj):
+ return types.MethodType(func, obj, obj.__class__)
+
+ class Iterator(object):
+
+ def next(self):
+ return type(self).__next__(self)
+
+ callable = callable
+_add_doc(get_unbound_function,
+ """Get the function out of a possibly unbound function""")
+
+
+get_method_function = operator.attrgetter(_meth_func)
+get_method_self = operator.attrgetter(_meth_self)
+get_function_closure = operator.attrgetter(_func_closure)
+get_function_code = operator.attrgetter(_func_code)
+get_function_defaults = operator.attrgetter(_func_defaults)
+get_function_globals = operator.attrgetter(_func_globals)
+
+
+def iterkeys(d, **kw):
+ """Return an iterator over the keys of a dictionary."""
+ return iter(getattr(d, _iterkeys)(**kw))
+
+def itervalues(d, **kw):
+ """Return an iterator over the values of a dictionary."""
+ return iter(getattr(d, _itervalues)(**kw))
+
+def iteritems(d, **kw):
+ """Return an iterator over the (key, value) pairs of a dictionary."""
+ return iter(getattr(d, _iteritems)(**kw))
+
+def iterlists(d, **kw):
+ """Return an iterator over the (key, [values]) pairs of a dictionary."""
+ return iter(getattr(d, _iterlists)(**kw))
+
+
+if PY3:
+ def b(s):
+ return s.encode("latin-1")
+ def u(s):
+ return s
+ unichr = chr
+ if sys.version_info[1] <= 1:
+ def int2byte(i):
+ return bytes((i,))
+ else:
+ # This is about 2x faster than the implementation above on 3.2+
+ int2byte = operator.methodcaller("to_bytes", 1, "big")
+ byte2int = operator.itemgetter(0)
+ indexbytes = operator.getitem
+ iterbytes = iter
+ import io
+ StringIO = io.StringIO
+ BytesIO = io.BytesIO
+else:
+ def b(s):
+ return s
+ # Workaround for standalone backslash
+ def u(s):
+ return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
+ unichr = unichr
+ int2byte = chr
+ def byte2int(bs):
+ return ord(bs[0])
+ def indexbytes(buf, i):
+ return ord(buf[i])
+ def iterbytes(buf):
+ return (ord(byte) for byte in buf)
+ import StringIO
+ StringIO = BytesIO = StringIO.StringIO
+_add_doc(b, """Byte literal""")
+_add_doc(u, """Text literal""")
+
+
+if PY3:
+ exec_ = getattr(moves.builtins, "exec")
+
+
+ def reraise(tp, value, tb=None):
+ if value.__traceback__ is not tb:
+ raise value.with_traceback(tb)
+ raise value
+
+else:
+ def exec_(_code_, _globs_=None, _locs_=None):
+ """Execute code in a namespace."""
+ if _globs_ is None:
+ frame = sys._getframe(1)
+ _globs_ = frame.f_globals
+ if _locs_ is None:
+ _locs_ = frame.f_locals
+ del frame
+ elif _locs_ is None:
+ _locs_ = _globs_
+ exec("""exec _code_ in _globs_, _locs_""")
+
+
+ exec_("""def reraise(tp, value, tb=None):
+ raise tp, value, tb
+""")
+
+
+print_ = getattr(moves.builtins, "print", None)
+if print_ is None:
+ def print_(*args, **kwargs):
+ """The new-style print function for Python 2.4 and 2.5."""
+ fp = kwargs.pop("file", sys.stdout)
+ if fp is None:
+ return
+ def write(data):
+ if not isinstance(data, basestring):
+ data = str(data)
+ # If the file has an encoding, encode unicode with it.
+ if (isinstance(fp, file) and
+ isinstance(data, unicode) and
+ fp.encoding is not None):
+ errors = getattr(fp, "errors", None)
+ if errors is None:
+ errors = "strict"
+ data = data.encode(fp.encoding, errors)
+ fp.write(data)
+ want_unicode = False
+ sep = kwargs.pop("sep", None)
+ if sep is not None:
+ if isinstance(sep, unicode):
+ want_unicode = True
+ elif not isinstance(sep, str):
+ raise TypeError("sep must be None or a string")
+ end = kwargs.pop("end", None)
+ if end is not None:
+ if isinstance(end, unicode):
+ want_unicode = True
+ elif not isinstance(end, str):
+ raise TypeError("end must be None or a string")
+ if kwargs:
+ raise TypeError("invalid keyword arguments to print()")
+ if not want_unicode:
+ for arg in args:
+ if isinstance(arg, unicode):
+ want_unicode = True
+ break
+ if want_unicode:
+ newline = unicode("\n")
+ space = unicode(" ")
+ else:
+ newline = "\n"
+ space = " "
+ if sep is None:
+ sep = space
+ if end is None:
+ end = newline
+ for i, arg in enumerate(args):
+ if i:
+ write(sep)
+ write(arg)
+ write(end)
+
+_add_doc(reraise, """Reraise an exception.""")
+
+
+def with_metaclass(meta, *bases):
+ """Create a base class with a metaclass."""
+ return meta("NewBase", bases, {})
+
+def add_metaclass(metaclass):
+ """Class decorator for creating a class with a metaclass."""
+ def wrapper(cls):
+ orig_vars = cls.__dict__.copy()
+ orig_vars.pop('__dict__', None)
+ orig_vars.pop('__weakref__', None)
+ slots = orig_vars.get('__slots__')
+ if slots is not None:
+ if isinstance(slots, str):
+ slots = [slots]
+ for slots_var in slots:
+ orig_vars.pop(slots_var)
+ return metaclass(cls.__name__, cls.__bases__, orig_vars)
+ return wrapper \ No newline at end of file
diff --git a/test_configobj.py b/test_configobj.py
index 47d1959..b8e68a2 100644
--- a/test_configobj.py
+++ b/test_configobj.py
@@ -295,392 +295,6 @@ def _test_reload():
"""
-def _doctest():
- """
- Dummy function to hold some of the doctests.
-
- >>> a.depth
- 0
- >>> a == {
- ... 'key2': 'val',
- ... 'key1': 'val',
- ... 'lev1c': {
- ... 'lev2c': {
- ... 'lev3c': {
- ... 'key1': 'val',
- ... },
- ... },
- ... },
- ... 'lev1b': {
- ... 'key2': 'val',
- ... 'key1': 'val',
- ... 'lev2ba': {
- ... 'key1': 'val',
- ... },
- ... 'lev2bb': {
- ... 'key1': 'val',
- ... },
- ... },
- ... 'lev1a': {
- ... 'key2': 'val',
- ... 'key1': 'val',
- ... },
- ... }
- 1
- >>> b.depth
- 0
- >>> b == {
- ... 'key3': 'val3',
- ... 'key2': 'val2',
- ... 'key1': 'val1',
- ... 'section 1': {
- ... 'keys11': 'val1',
- ... 'keys13': 'val3',
- ... 'keys12': 'val2',
- ... },
- ... 'section 2': {
- ... 'section 2 sub 1': {
- ... 'fish': '3',
- ... },
- ... 'keys21': 'val1',
- ... 'keys22': 'val2',
- ... 'keys23': 'val3',
- ... },
- ... }
- 1
- >>> t = '''
- ... 'a' = b # !"$%^&*(),::;'@~#= 33
- ... "b" = b #= 6, 33
- ... ''' .split('\\n')
- >>> t2 = ConfigObj(t)
- >>> assert t2 == {'a': 'b', 'b': 'b'}
- >>> t2.inline_comments['b'] = ''
- >>> del t2['a']
- >>> assert t2.write() == ['','b = b', '']
-
- # Test ``list_values=False`` stuff
- >>> c = '''
- ... key1 = no quotes
- ... key2 = 'single quotes'
- ... key3 = "double quotes"
- ... key4 = "list", 'with', several, "quotes"
- ... '''
- >>> cfg = ConfigObj(c.splitlines(), list_values=False)
- >>> cfg == {'key1': 'no quotes', 'key2': "'single quotes'",
- ... 'key3': '"double quotes"',
- ... 'key4': '"list", \\'with\\', several, "quotes"'
- ... }
- 1
- >>> cfg = ConfigObj(list_values=False)
- >>> cfg['key1'] = 'Multiline\\nValue'
- >>> cfg['key2'] = '''"Value" with 'quotes' !'''
- >>> cfg.write()
- ["key1 = '''Multiline\\nValue'''", 'key2 = "Value" with \\'quotes\\' !']
- >>> cfg.list_values = True
- >>> cfg.write() == ["key1 = '''Multiline\\nValue'''",
- ... 'key2 = \\'\\'\\'"Value" with \\'quotes\\' !\\'\\'\\'']
- 1
-
- Test flatten_errors:
-
- >>> config = '''
- ... test1=40
- ... test2=hello
- ... test3=3
- ... test4=5.0
- ... [section]
- ... test1=40
- ... test2=hello
- ... test3=3
- ... test4=5.0
- ... [[sub section]]
- ... test1=40
- ... test2=hello
- ... test3=3
- ... test4=5.0
- ... '''.split('\\n')
- >>> configspec = '''
- ... test1= integer(30,50)
- ... test2= string
- ... test3=integer
- ... test4=float(6.0)
- ... [section]
- ... test1=integer(30,50)
- ... test2=string
- ... test3=integer
- ... test4=float(6.0)
- ... [[sub section]]
- ... test1=integer(30,50)
- ... test2=string
- ... test3=integer
- ... test4=float(6.0)
- ... '''.split('\\n')
- >>> val = Validator()
- >>> c1 = ConfigObj(config, configspec=configspec)
- >>> res = c1.validate(val)
- >>> flatten_errors(c1, res) == [([], 'test4', False), (['section'], 'test4', False), (['section', 'sub section'], 'test4', False)]
- True
- >>> res = c1.validate(val, preserve_errors=True)
- >>> check = flatten_errors(c1, res)
- >>> check[0][:2]
- ([], 'test4')
- >>> check[1][:2]
- (['section'], 'test4')
- >>> check[2][:2]
- (['section', 'sub section'], 'test4')
- >>> for entry in check:
- ... isinstance(entry[2], VdtValueTooSmallError)
- ... print(str(entry[2]))
- True
- the value "5.0" is too small.
- True
- the value "5.0" is too small.
- True
- the value "5.0" is too small.
-
- Test unicode handling, BOM, write witha file like object and line endings :
- >>> u_base = '''
- ... # initial comment
- ... # inital comment 2
- ...
- ... test1 = some value
- ... # comment
- ... test2 = another value # inline comment
- ... # section comment
- ... [section] # inline comment
- ... test = test # another inline comment
- ... test2 = test2
- ...
- ... # final comment
- ... # final comment2
- ... '''
- >>> u = u_base.encode('utf_8').splitlines(True)
- >>> u[0] = BOM_UTF8 + u[0]
- >>> uc = ConfigObj(u)
- >>> uc.encoding = None
- >>> uc.BOM == True
- 1
- >>> uc == {'test1': 'some value', 'test2': 'another value',
- ... 'section': {'test': 'test', 'test2': 'test2'}}
- 1
- >>> uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
- >>> uc.BOM
- 1
- >>> isinstance(uc['test1'], str)
- 1
- >>> uc.encoding
- 'utf_8'
- >>> uc.newlines
- '\\n'
- >>> uc['latin1'] = "This costs lot's of "
- >>> a_list = uc.write()
- >>> len(a_list)
- 15
- >>> isinstance(a_list[0], bytes)
- 1
- >>> a_list[0].startswith(BOM_UTF8)
- 1
- >>> u = u_base.replace('\\n', '\\r\\n').encode('utf_8').splitlines(True)
- >>> uc = ConfigObj(u)
- >>> uc.newlines
- '\\r\\n'
- >>> uc.newlines = '\\r'
- >>> file_like = StringIO()
- >>> uc.write(file_like)
- >>> _ = file_like.seek(0)
- >>> uc2 = ConfigObj(file_like)
- >>> uc2 == uc
- 1
- >>> uc2.filename == None
- 1
- >>> uc2.newlines == '\\r'
- 1
-
- Test validate in copy mode
- >>> a = '''
- ... # Initial Comment
- ...
- ... key1 = string(default=Hello)
- ...
- ... # section comment
- ... [section] # inline comment
- ... # key1 comment
- ... key1 = integer(default=6)
- ... # key2 comment
- ... key2 = boolean(default=True)
- ...
- ... # subsection comment
- ... [[sub-section]] # inline comment
- ... # another key1 comment
- ... key1 = float(default=3.0)'''.splitlines()
- >>> b = ConfigObj(configspec=a)
- >>> b.validate(val, copy=True)
- 1
- >>> b.write() == ['',
- ... '# Initial Comment',
- ... '',
- ... 'key1 = Hello',
- ... '',
- ... '# section comment',
- ... '[section] # inline comment',
- ... ' # key1 comment',
- ... ' key1 = 6',
- ... ' # key2 comment',
- ... ' key2 = True',
- ... ' ',
- ... ' # subsection comment',
- ... ' [[sub-section]] # inline comment',
- ... ' # another key1 comment',
- ... ' key1 = 3.0']
- 1
-
- Test Writing Empty Values
- >>> a = '''
- ... key1 =
- ... key2 =# a comment'''
- >>> b = ConfigObj(a.splitlines())
- >>> b.write()
- ['', 'key1 = ""', 'key2 = "" # a comment']
- >>> b.write_empty_values = True
- >>> b.write()
- ['', 'key1 = ', 'key2 = # a comment']
-
- Test unrepr when reading
- >>> a = '''
- ... key1 = (1, 2, 3) # comment
- ... key2 = True
- ... key3 = 'a string'
- ... key4 = [1, 2, 3, 'a mixed list']
- ... '''.splitlines()
- >>> b = ConfigObj(a, unrepr=True)
- >>> b == {'key1': (1, 2, 3),
- ... 'key2': True,
- ... 'key3': 'a string',
- ... 'key4': [1, 2, 3, 'a mixed list']}
- 1
-
- Test unrepr when writing
- >>> c = ConfigObj(b.write(), unrepr=True)
- >>> c == b
- 1
-
- Test unrepr with multiline values
- >>> a = '''k = \"""{
- ... 'k1': 3,
- ... 'k2': 6.0}\"""
- ... '''.splitlines()
- >>> c = ConfigObj(a, unrepr=True)
- >>> c == {'k': {'k1': 3, 'k2': 6.0}}
- 1
-
- Test unrepr with a dictionary
- >>> a = 'k = {"a": 1}'.splitlines()
- >>> c = ConfigObj(a, unrepr=True)
- >>> isinstance(c['k'], dict)
- True
-
- >>> a = ConfigObj()
- >>> a['a'] = 'fish'
- >>> a.as_bool('a')
- Traceback (most recent call last):
- ValueError: Value "fish" is neither True nor False
- >>> a['b'] = 'True'
- >>> a.as_bool('b')
- 1
- >>> a['b'] = 'off'
- >>> a.as_bool('b')
- 0
-
- >>> a = ConfigObj()
- >>> a['a'] = 'fish'
- >>> try:
- ... a.as_int('a')
- ... except ValueError as e:
- ... err_mess = str(e)
- >>> err_mess.startswith('invalid literal for int()')
- 1
- >>> a['b'] = '1'
- >>> a.as_int('b')
- 1
- >>> a['b'] = '3.2'
- >>> try:
- ... a.as_int('b')
- ... except ValueError as e:
- ... err_mess = str(e)
- >>> err_mess.startswith('invalid literal for int()')
- 1
-
- >>> a = ConfigObj()
- >>> a['a'] = 'fish'
- >>> a.as_float('a')
- Traceback (most recent call last):
- ValueError: invalid literal for float(): fish
- >>> a['b'] = '1'
- >>> a.as_float('b')
- 1.0
- >>> a['b'] = '3.2'
- >>> a.as_float('b')
- 3.2...
-
- Test # with unrepr
- >>> a = '''
- ... key1 = (1, 2, 3) # comment
- ... key2 = True
- ... key3 = 'a string'
- ... key4 = [1, 2, 3, 'a mixed list#']
- ... '''.splitlines()
- >>> b = ConfigObj(a, unrepr=True)
- >>> b == {'key1': (1, 2, 3),
- ... 'key2': True,
- ... 'key3': 'a string',
- ... 'key4': [1, 2, 3, 'a mixed list#']}
- 1
- """
-
- # Comments are no longer parsed from values in configspecs
- # so the following test fails and is disabled
- untested = """
- Test validate in copy mode
- >>> a = '''
- ... # Initial Comment
- ...
- ... key1 = string(default=Hello) # comment 1
- ...
- ... # section comment
- ... [section] # inline comment
- ... # key1 comment
- ... key1 = integer(default=6) # an integer value
- ... # key2 comment
- ... key2 = boolean(default=True) # a boolean
- ...
- ... # subsection comment
- ... [[sub-section]] # inline comment
- ... # another key1 comment
- ... key1 = float(default=3.0) # a float'''.splitlines()
- >>> b = ConfigObj(configspec=a)
- >>> b.validate(val, copy=True)
- 1
- >>> b.write()
- >>> b.write() == ['',
- ... '# Initial Comment',
- ... '',
- ... 'key1 = Hello # comment 1',
- ... '',
- ... '# section comment',
- ... '[section] # inline comment',
- ... ' # key1 comment',
- ... ' key1 = 6 # an integer value',
- ... ' # key2 comment',
- ... ' key2 = True # a boolean',
- ... ' ',
- ... ' # subsection comment',
- ... ' [[sub-section]] # inline comment',
- ... ' # another key1 comment',
- ... ' key1 = 3.0 # a float']
- 1
- """
-
-
def _test_configobj():
"""
Testing ConfigObj
diff --git a/tests/test_configobj.py b/tests/test_configobj.py
index 7c6773d..319949d 100644
--- a/tests/test_configobj.py
+++ b/tests/test_configobj.py
@@ -1,7 +1,11 @@
+from codecs import BOM_UTF8
import os
-from configobj import ConfigObj
+import sys
+from configobj import ConfigObj, flatten_errors
+from validate import Validator, VdtValueTooSmallError
import pytest
+import six
try:
#TODO(robdennis): determine if this is really 2.6 and newer
@@ -100,3 +104,477 @@ def test_interoplation_repr():
# This raises a MissingInterpolationOption exception in 4.7.1 and earlier
repr(c)
+
+#issue #18
+def test_unicode_conversion_when_encoding_is_set():
+ cfg = """
+test = some string
+ """.splitlines()
+
+ c = ConfigObj(cfg, encoding='utf8')
+
+ if six.PY2:
+ assert not isinstance(c['test'], str)
+ assert isinstance(c['test'], unicode)
+ else:
+ assert isinstance(c['test'], str)
+
+
+#issue #18
+def test_no_unicode_conversion_when_encoding_is_omitted():
+ cfg = """
+test = some string
+ """.splitlines()
+
+ c = ConfigObj(cfg)
+ if six.PY2:
+ assert isinstance(c['test'], str)
+ assert not isinstance(c['test'], unicode)
+ else:
+ assert isinstance(c['test'], str)
+
+
+@pytest.fixture
+def testconfig1():
+ """
+ copied from the main doctest
+ """
+ return """\
+ key1= val # comment 1
+ key2= val # comment 2
+ # comment 3
+ [lev1a] # comment 4
+ key1= val # comment 5
+ key2= val # comment 6
+ # comment 7
+ [lev1b] # comment 8
+ key1= val # comment 9
+ key2= val # comment 10
+ # comment 11
+ [[lev2ba]] # comment 12
+ key1= val # comment 13
+ # comment 14
+ [[lev2bb]] # comment 15
+ key1= val # comment 16
+ # comment 17
+ [lev1c] # comment 18
+ # comment 19
+ [[lev2c]] # comment 20
+ # comment 21
+ [[[lev3c]]] # comment 22
+ key1 = val # comment 23"""
+
+
+@pytest.fixture
+def testconfig2():
+ return """\
+ key1 = 'val1'
+ key2 = "val2"
+ key3 = val3
+ ["section 1"] # comment
+ keys11 = val1
+ keys12 = val2
+ keys13 = val3
+ [section 2]
+ keys21 = val1
+ keys22 = val2
+ keys23 = val3
+
+ [['section 2 sub 1']]
+ fish = 3
+ """
+
+@pytest.fixture
+def a(testconfig1):
+ """
+ also copied from main doc tests
+ """
+ return ConfigObj(testconfig1.split('\n'), raise_errors=True)
+
+
+@pytest.fixture
+def b(testconfig2):
+ """
+ also copied from main doc tests
+ """
+ return ConfigObj(testconfig2.split('\n'), raise_errors=True)
+
+
+def test_configobj_dict_representation(a, b):
+
+ assert a.depth == 0
+ assert a == {
+ 'key2': 'val',
+ 'key1': 'val',
+ 'lev1c': {
+ 'lev2c': {
+ 'lev3c': {
+ 'key1': 'val',
+ },
+ },
+ },
+ 'lev1b': {
+ 'key2': 'val',
+ 'key1': 'val',
+ 'lev2ba': {
+ 'key1': 'val',
+ },
+ 'lev2bb': {
+ 'key1': 'val',
+ },
+ },
+ 'lev1a': {
+ 'key2': 'val',
+ 'key1': 'val',
+ },
+ }
+
+ assert b.depth == 0
+ assert b == {
+ 'key3': 'val3',
+ 'key2': 'val2',
+ 'key1': 'val1',
+ 'section 1': {
+ 'keys11': 'val1',
+ 'keys13': 'val3',
+ 'keys12': 'val2',
+ },
+ 'section 2': {
+ 'section 2 sub 1': {
+ 'fish': '3',
+ },
+ 'keys21': 'val1',
+ 'keys22': 'val2',
+ 'keys23': 'val3',
+ },
+ }
+
+ t = '''
+ 'a' = b # !"$%^&*(),::;'@~#= 33
+ "b" = b #= 6, 33
+''' .split('\n')
+ t2 = ConfigObj(t)
+ assert t2 == {'a': 'b', 'b': 'b'}
+ t2.inline_comments['b'] = ''
+ del t2['a']
+ assert t2.write() == ['','b = b', '']
+
+
+def test_behavior_when_list_values_is_false():
+ c = '''
+ key1 = no quotes
+ key2 = 'single quotes'
+ key3 = "double quotes"
+ key4 = "list", 'with', several, "quotes"
+ '''
+ cfg = ConfigObj(c.splitlines(), list_values=False)
+ assert cfg == {
+ 'key1': 'no quotes',
+ 'key2': "'single quotes'",
+ 'key3': '"double quotes"',
+ 'key4': '"list", \'with\', several, "quotes"'
+ }
+
+ cfg2 = ConfigObj(list_values=False)
+ cfg2['key1'] = 'Multiline\nValue'
+ cfg2['key2'] = '''"Value" with 'quotes' !'''
+ assert cfg2.write() == [
+ "key1 = '''Multiline\nValue'''",
+ 'key2 = "Value" with \'quotes\' !'
+ ]
+
+ cfg2.list_values = True
+ assert cfg2.write() == [
+ "key1 = '''Multiline\nValue'''",
+ 'key2 = \'\'\'"Value" with \'quotes\' !\'\'\''
+ ]
+
+
+def test_flatten_errors():
+ config = '''
+ test1=40
+ test2=hello
+ test3=3
+ test4=5.0
+ [section]
+ test1=40
+ test2=hello
+ test3=3
+ test4=5.0
+ [[sub section]]
+ test1=40
+ test2=hello
+ test3=3
+ test4=5.0
+ '''.split('\n')
+ configspec = '''
+ test1= integer(30,50)
+ test2= string
+ test3=integer
+ test4=float(6.0)
+ [section]
+ test1=integer(30,50)
+ test2=string
+ test3=integer
+ test4=float(6.0)
+ [[sub section]]
+ test1=integer(30,50)
+ test2=string
+ test3=integer
+ test4=float(6.0)
+ '''.split('\n')
+ val = Validator()
+ c1 = ConfigObj(config, configspec=configspec)
+ res = c1.validate(val)
+ assert flatten_errors(c1, res) == [([], 'test4', False), (['section'], 'test4', False), (['section', 'sub section'], 'test4', False)]
+ res = c1.validate(val, preserve_errors=True)
+ check = flatten_errors(c1, res)
+ assert check[0][:2] == ([], 'test4')
+ assert check[1][:2] == (['section'], 'test4')
+ assert check[2][:2] == (['section', 'sub section'], 'test4')
+ for entry in check:
+ assert isinstance(entry[2], VdtValueTooSmallError)
+ assert str(entry[2]) == 'the value "5.0" is too small.'
+
+
+def test_unicode_handling():
+ u_base = '''
+ # initial comment
+ # inital comment 2
+ test1 = some value
+ # comment
+ test2 = another value # inline comment
+ # section comment
+ [section] # inline comment
+ test = test # another inline comment
+ test2 = test2
+ # final comment
+ # final comment2
+ '''
+
+ u = u_base.encode('utf_8').splitlines(True)
+ u[0] = BOM_UTF8 + u[0]
+ uc = ConfigObj(u)
+ uc.encoding = None
+ assert uc.BOM
+ assert uc == {'test1': 'some value', 'test2': 'another value',
+ 'section': {'test': 'test', 'test2': 'test2'}}
+ uc = ConfigObj(u, encoding='utf_8', default_encoding='latin-1')
+ assert uc.BOM
+ assert isinstance(uc['test1'], six.text_type)
+ assert uc.encoding == 'utf_8'
+ assert uc.newlines == '\n'
+ assert len(uc.write()) == 13
+ uc['latin1'] = "This costs lot's of "
+ a_list = uc.write()
+ assert 'latin1' in str(a_list)
+ assert len(a_list) == 14
+ assert isinstance(a_list[0], six.binary_type)
+ assert a_list[0].startswith(BOM_UTF8)
+
+ u = u_base.replace('\n', '\r\n').encode('utf-8').splitlines(True)
+ uc = ConfigObj(u)
+ assert uc.newlines == '\r\n'
+ uc.newlines = '\r'
+ file_like = six.StringIO()
+ uc.write(file_like)
+ file_like.seek(0)
+ uc2 = ConfigObj(file_like)
+ assert uc2 == uc
+ assert uc2.filename == None
+ assert uc2.newlines == '\r'
+
+
+def _doctest():
+ """
+ Dummy function to hold some of the doctests.
+
+ Test flatten_errors:
+
+ Test unicode handling, BOM, write witha file like object and line endings :
+
+
+
+ Test validate in copy mode
+ a = '''
+ # Initial Comment
+ ...
+ key1 = string(default=Hello)
+ ...
+ # section comment
+ [section] # inline comment
+ # key1 comment
+ key1 = integer(default=6)
+ # key2 comment
+ key2 = boolean(default=True)
+ ...
+ # subsection comment
+ [[sub-section]] # inline comment
+ # another key1 comment
+ key1 = float(default=3.0)'''.splitlines()
+ b = ConfigObj(configspec=a)
+ b.validate(val, copy=True)
+ 1
+ b.write() == ['',
+ '# Initial Comment',
+ '',
+ 'key1 = Hello',
+ '',
+ '# section comment',
+ '[section] # inline comment',
+ ' # key1 comment',
+ ' key1 = 6',
+ ' # key2 comment',
+ ' key2 = True',
+ ' ',
+ ' # subsection comment',
+ ' [[sub-section]] # inline comment',
+ ' # another key1 comment',
+ ' key1 = 3.0']
+ 1
+
+ Test Writing Empty Values
+ a = '''
+ key1 =
+ key2 =# a comment'''
+ b = ConfigObj(a.splitlines())
+ b.write()
+ ['', 'key1 = ""', 'key2 = "" # a comment']
+ b.write_empty_values = True
+ b.write()
+ ['', 'key1 = ', 'key2 = # a comment']
+
+ Test unrepr when reading
+ a = '''
+ key1 = (1, 2, 3) # comment
+ key2 = True
+ key3 = 'a string'
+ key4 = [1, 2, 3, 'a mixed list']
+ '''.splitlines()
+ b = ConfigObj(a, unrepr=True)
+ b == {'key1': (1, 2, 3),
+ 'key2': True,
+ 'key3': 'a string',
+ 'key4': [1, 2, 3, 'a mixed list']}
+ 1
+
+ Test unrepr when writing
+ c = ConfigObj(b.write(), unrepr=True)
+ c == b
+ 1
+
+ Test unrepr with multiline values
+ a = '''k = \"""{
+ 'k1': 3,
+ 'k2': 6.0}\"""
+ '''.splitlines()
+ c = ConfigObj(a, unrepr=True)
+ c == {'k': {'k1': 3, 'k2': 6.0}}
+ 1
+
+ Test unrepr with a dictionary
+ a = 'k = {"a": 1}'.splitlines()
+ c = ConfigObj(a, unrepr=True)
+ isinstance(c['k'], dict)
+ True
+
+ a = ConfigObj()
+ a['a'] = 'fish'
+ a.as_bool('a')
+ Traceback (most recent call last):
+ ValueError: Value "fish" is neither True nor False
+ a['b'] = 'True'
+ a.as_bool('b')
+ 1
+ a['b'] = 'off'
+ a.as_bool('b')
+ 0
+
+ a = ConfigObj()
+ a['a'] = 'fish'
+ try:
+ a.as_int('a')
+ except ValueError as e:
+ err_mess = str(e)
+ err_mess.startswith('invalid literal for int()')
+ 1
+ a['b'] = '1'
+ a.as_int('b')
+ 1
+ a['b'] = '3.2'
+ try:
+ a.as_int('b')
+ except ValueError as e:
+ err_mess = str(e)
+ err_mess.startswith('invalid literal for int()')
+ 1
+
+ a = ConfigObj()
+ a['a'] = 'fish'
+ a.as_float('a')
+ Traceback (most recent call last):
+ ValueError: invalid literal for float(): fish
+ a['b'] = '1'
+ a.as_float('b')
+ 1.0
+ a['b'] = '3.2'
+ a.as_float('b')
+ 3.2...
+
+ Test # with unrepr
+ a = '''
+ key1 = (1, 2, 3) # comment
+ key2 = True
+ key3 = 'a string'
+ key4 = [1, 2, 3, 'a mixed list#']
+ '''.splitlines()
+ b = ConfigObj(a, unrepr=True)
+ b == {'key1': (1, 2, 3),
+ 'key2': True,
+ 'key3': 'a string',
+ 'key4': [1, 2, 3, 'a mixed list#']}
+ 1
+ """
+
+ # Comments are no longer parsed from values in configspecs
+ # so the following test fails and is disabled
+ untested = """
+ Test validate in copy mode
+ a = '''
+ # Initial Comment
+ ...
+ key1 = string(default=Hello) # comment 1
+ ...
+ # section comment
+ [section] # inline comment
+ # key1 comment
+ key1 = integer(default=6) # an integer value
+ # key2 comment
+ key2 = boolean(default=True) # a boolean
+ ...
+ # subsection comment
+ [[sub-section]] # inline comment
+ # another key1 comment
+ key1 = float(default=3.0) # a float'''.splitlines()
+ b = ConfigObj(configspec=a)
+ b.validate(val, copy=True)
+ 1
+ b.write()
+ b.write() == ['',
+ '# Initial Comment',
+ '',
+ 'key1 = Hello # comment 1',
+ '',
+ '# section comment',
+ '[section] # inline comment',
+ ' # key1 comment',
+ ' key1 = 6 # an integer value',
+ ' # key2 comment',
+ ' key2 = True # a boolean',
+ ' ',
+ ' # subsection comment',
+ ' [[sub-section]] # inline comment',
+ ' # another key1 comment',
+ ' key1 = 3.0 # a float']
+ 1
+ """