diff options
author | Fred Drake <fdrake@acm.org> | 2010-08-09 12:52:45 (GMT) |
---|---|---|
committer | Fred Drake <fdrake@acm.org> | 2010-08-09 12:52:45 (GMT) |
commit | a492362f9a2a44e411147fd7b2886466bb0bb17f (patch) | |
tree | 0e150dd20d8c8add5b3282bbac9efe16b8696e21 /Lib | |
parent | f14c2632806ec19b0d58c2c1f721c6a31b535209 (diff) | |
download | cpython-a492362f9a2a44e411147fd7b2886466bb0bb17f.zip cpython-a492362f9a2a44e411147fd7b2886466bb0bb17f.tar.gz cpython-a492362f9a2a44e411147fd7b2886466bb0bb17f.tar.bz2 |
issue #9452:
Add read_file, read_string, and read_dict to the configparser API;
new source attribute to exceptions.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/configparser.py | 253 | ||||
-rw-r--r-- | Lib/test/test_cfgparser.py | 243 |
2 files changed, 376 insertions, 120 deletions
diff --git a/Lib/configparser.py b/Lib/configparser.py index 6e38f26..eb29b02 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -26,10 +26,10 @@ ConfigParser -- responsible for parsing a list of __init__(defaults=None, dict_type=_default_dict, delimiters=('=', ':'), comment_prefixes=('#', ';'), - empty_lines_in_values=True, allow_no_value=False): + strict=False, empty_lines_in_values=True, allow_no_value=False): Create the parser. When `defaults' is given, it is initialized into the dictionary or intrinsic defaults. The keys must be strings, the values - must be appropriate for %()s string interpolation. Note that `__name__' + must be appropriate for %()s string interpolation. Note that `__name__' is always an intrinsic default; its value is the section's name. When `dict_type' is given, it will be used to create the dictionary @@ -42,6 +42,10 @@ ConfigParser -- responsible for parsing a list of When `comment_prefixes' is given, it will be used as the set of substrings that prefix comments in a line. + When `strict` is True, the parser won't allow for any section or option + duplicates while reading from a single source (file, string or + dictionary). Default is False. + When `empty_lines_in_values' is False (default: True), each empty line marks the end of an option. Otherwise, internal empty lines of a multiline option are kept as part of the value. @@ -66,10 +70,19 @@ ConfigParser -- responsible for parsing a list of name. A single filename is also allowed. Non-existing files are ignored. Return list of successfully read files. - readfp(fp, filename=None) + read_file(f, filename=None) Read and parse one configuration file, given as a file object. - The filename defaults to fp.name; it is only used in error - messages (if fp has no `name' attribute, the string `<???>' is used). + The filename defaults to f.name; it is only used in error + messages (if f has no `name' attribute, the string `<???>' is used). + + read_string(string) + Read configuration from a given string. + + read_dict(dictionary) + Read configuration from a dictionary. Keys are section names, + values are dictionaries with keys and values that should be present + in the section. If the used dictionary type preserves order, sections + and their keys will be added in order. get(section, option, raw=False, vars=None) Return a string value for the named option. All % interpolations are @@ -114,11 +127,13 @@ except ImportError: # fallback for setup.py which hasn't yet built _collections _default_dict = dict +import io import re import sys +import warnings -__all__ = ["NoSectionError", "DuplicateSectionError", "NoOptionError", - "InterpolationError", "InterpolationDepthError", +__all__ = ["NoSectionError", "DuplicateOptionError", "DuplicateSectionError", + "NoOptionError", "InterpolationError", "InterpolationDepthError", "InterpolationSyntaxError", "ParsingError", "MissingSectionHeaderError", "ConfigParser", "SafeConfigParser", "RawConfigParser", @@ -147,8 +162,8 @@ class Error(Exception): self.__message = value # BaseException.message has been deprecated since Python 2.6. To prevent - # DeprecationWarning from popping up over this pre-existing attribute, use a - # new property that takes lookup precedence. + # DeprecationWarning from popping up over this pre-existing attribute, use + # a new property that takes lookup precedence. message = property(_get_message, _set_message) def __init__(self, msg=''): @@ -171,12 +186,56 @@ class NoSectionError(Error): class DuplicateSectionError(Error): - """Raised when a section is multiply-created.""" - - def __init__(self, section): - Error.__init__(self, "Section %r already exists" % section) + """Raised when a section is repeated in an input source. + + Possible repetitions that raise this exception are: multiple creation + using the API or in strict parsers when a section is found more than once + in a single input file, string or dictionary. + """ + + def __init__(self, section, source=None, lineno=None): + msg = [repr(section), " already exists"] + if source is not None: + message = ["While reading from ", source] + if lineno is not None: + message.append(" [line {0:2d}]".format(lineno)) + message.append(": section ") + message.extend(msg) + msg = message + else: + msg.insert(0, "Section ") + Error.__init__(self, "".join(msg)) self.section = section - self.args = (section, ) + self.source = source + self.lineno = lineno + self.args = (section, source, lineno) + + +class DuplicateOptionError(Error): + """Raised by strict parsers when an option is repeated in an input source. + + Current implementation raises this exception only when an option is found + more than once in a single file, string or dictionary. + """ + + def __init__(self, section, option, source=None, lineno=None): + msg = [repr(option), " in section ", repr(section), + " already exists"] + if source is not None: + message = ["While reading from ", source] + if lineno is not None: + message.append(" [line {0:2d}]".format(lineno)) + message.append(": option ") + message.extend(msg) + msg = message + else: + msg.insert(0, "Option ") + Error.__init__(self, "".join(msg)) + self.section = section + self.option = option + self.source = source + self.lineno = lineno + self.args = (section, option, source, lineno) class NoOptionError(Error): @@ -216,8 +275,12 @@ class InterpolationMissingOptionError(InterpolationError): class InterpolationSyntaxError(InterpolationError): - """Raised when the source text into which substitutions are made - does not conform to the required syntax.""" + """Raised when the source text contains invalid syntax. + + Current implementation raises this exception only for SafeConfigParser + instances when the source text into which substitutions are made + does not conform to the required syntax. + """ class InterpolationDepthError(InterpolationError): @@ -236,11 +299,40 @@ class InterpolationDepthError(InterpolationError): class ParsingError(Error): """Raised when a configuration file does not follow legal syntax.""" - def __init__(self, filename): - Error.__init__(self, 'File contains parsing errors: %s' % filename) - self.filename = filename + def __init__(self, source=None, filename=None): + # Exactly one of `source'/`filename' arguments has to be given. + # `filename' kept for compatibility. + if filename and source: + raise ValueError("Cannot specify both `filename' and `source'. " + "Use `source'.") + elif not filename and not source: + raise ValueError("Required argument `source' not given.") + elif filename: + source = filename + Error.__init__(self, 'Source contains parsing errors: %s' % source) + self.source = source self.errors = [] - self.args = (filename, ) + self.args = (source, ) + + @property + def filename(self): + """Deprecated, use `source'.""" + warnings.warn( + "This 'filename' attribute will be removed in future versions. " + "Use 'source' instead.", + PendingDeprecationWarning, stacklevel=2 + ) + return self.source + + @filename.setter + def filename(self, value): + """Deprecated, user `source'.""" + warnings.warn( + "The 'filename' attribute will be removed in future versions. " + "Use 'source' instead.", + PendingDeprecationWarning, stacklevel=2 + ) + self.source = value def append(self, lineno, line): self.errors.append((lineno, line)) @@ -255,7 +347,7 @@ class MissingSectionHeaderError(ParsingError): self, 'File contains no section headers.\nfile: %s, line: %d\n%r' % (filename, lineno, line)) - self.filename = filename + self.source = filename self.lineno = lineno self.line = line self.args = (filename, lineno, line) @@ -302,8 +394,9 @@ class RawConfigParser: _COMPATIBLE = object() def __init__(self, defaults=None, dict_type=_default_dict, - delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, - empty_lines_in_values=True, allow_no_value=False): + allow_no_value=False, *, delimiters=('=', ':'), + comment_prefixes=_COMPATIBLE, strict=False, + empty_lines_in_values=True): self._dict = dict_type self._sections = self._dict() self._defaults = self._dict() @@ -314,12 +407,12 @@ class RawConfigParser: if delimiters == ('=', ':'): self._optcre = self.OPTCRE_NV if allow_no_value else self.OPTCRE else: - delim = "|".join(re.escape(d) for d in delimiters) + d = "|".join(re.escape(d) for d in delimiters) if allow_no_value: - self._optcre = re.compile(self._OPT_NV_TMPL.format(delim=delim), + self._optcre = re.compile(self._OPT_NV_TMPL.format(delim=d), re.VERBOSE) else: - self._optcre = re.compile(self._OPT_TMPL.format(delim=delim), + self._optcre = re.compile(self._OPT_TMPL.format(delim=d), re.VERBOSE) if comment_prefixes is self._COMPATIBLE: self._startonly_comment_prefixes = ('#',) @@ -327,6 +420,7 @@ class RawConfigParser: else: self._startonly_comment_prefixes = () self._comment_prefixes = tuple(comment_prefixes or ()) + self._strict = strict self._empty_lines_in_values = empty_lines_in_values def defaults(self): @@ -394,20 +488,59 @@ class RawConfigParser: read_ok.append(filename) return read_ok - def readfp(self, fp, filename=None): + def read_file(self, f, source=None): """Like read() but the argument must be a file-like object. - The `fp' argument must have a `readline' method. Optional - second argument is the `filename', which if not given, is - taken from fp.name. If fp has no `name' attribute, `<???>' is - used. + The `f' argument must have a `readline' method. Optional second + argument is the `source' specifying the name of the file being read. If + not given, it is taken from f.name. If `f' has no `name' attribute, + `<???>' is used. """ - if filename is None: + if source is None: try: - filename = fp.name + srouce = f.name except AttributeError: - filename = '<???>' - self._read(fp, filename) + source = '<???>' + self._read(f, source) + + def read_string(self, string, source='<string>'): + """Read configuration from a given string.""" + sfile = io.StringIO(string) + self.read_file(sfile, source) + + def read_dict(self, dictionary, source='<dict>'): + """Read configuration from a dictionary. + + Keys are section names, values are dictionaries with keys and values + that should be present in the section. If the used dictionary type + preserves order, sections and their keys will be added in order. + + Optional second argument is the `source' specifying the name of the + dictionary being read. + """ + elements_added = set() + for section, keys in dictionary.items(): + try: + self.add_section(section) + except DuplicateSectionError: + if self._strict and section in elements_added: + raise + elements_added.add(section) + for key, value in keys.items(): + key = self.optionxform(key) + if self._strict and (section, key) in elements_added: + raise DuplicateOptionError(section, key, source) + elements_added.add((section, key)) + self.set(section, key, value) + + def readfp(self, fp, filename=None): + """Deprecated, use read_file instead.""" + warnings.warn( + "This method will be removed in future versions. " + "Use 'parser.read_file()' instead.", + PendingDeprecationWarning, stacklevel=2 + ) + self.read_file(fp, source=filename) def get(self, section, option): opt = self.optionxform(option) @@ -461,7 +594,6 @@ class RawConfigParser: def has_option(self, section, option): """Check for the existence of a given option in a given section.""" - if not section or section == DEFAULTSECT: option = self.optionxform(option) return option in self._defaults @@ -474,7 +606,6 @@ class RawConfigParser: def set(self, section, option, value=None): """Set an option.""" - if not section or section == DEFAULTSECT: sectdict = self._defaults else: @@ -538,21 +669,23 @@ class RawConfigParser: def _read(self, fp, fpname): """Parse a sectioned configuration file. - Each section in a configuration file contains a header, indicated by a - name in square brackets (`[]'), plus key/value options, indicated by + Each section in a configuration file contains a header, indicated by + a name in square brackets (`[]'), plus key/value options, indicated by `name' and `value' delimited with a specific substring (`=' or `:' by default). - Values can span multiple lines, as long as they are indented deeper than - the first line of the value. Depending on the parser's mode, blank lines - may be treated as parts of multiline values or ignored. + Values can span multiple lines, as long as they are indented deeper + than the first line of the value. Depending on the parser's mode, blank + lines may be treated as parts of multiline values or ignored. Configuration files may include comments, prefixed by specific - characters (`#' and `;' by default). Comments may appear on their own in - an otherwise empty line or may be entered in lines holding values or + characters (`#' and `;' by default). Comments may appear on their own + in an otherwise empty line or may be entered in lines holding values or section names. """ + elements_added = set() cursect = None # None, or a dictionary + sectname = None optname = None lineno = 0 indent_level = 0 @@ -598,13 +731,18 @@ class RawConfigParser: if mo: sectname = mo.group('header') if sectname in self._sections: + if self._strict and sectname in elements_added: + raise DuplicateSectionError(sectname, fpname, + lineno) cursect = self._sections[sectname] + elements_added.add(sectname) elif sectname == DEFAULTSECT: cursect = self._defaults else: cursect = self._dict() cursect['__name__'] = sectname self._sections[sectname] = cursect + elements_added.add(sectname) # So sections can't start with a continuation line optname = None # no section header in the file? @@ -618,6 +756,11 @@ class RawConfigParser: if not optname: e = self._handle_error(e, fpname, lineno, line) optname = self.optionxform(optname.rstrip()) + if (self._strict and + (sectname, optname) in elements_added): + raise DuplicateOptionError(sectname, optname, + fpname, lineno) + elements_added.add((sectname, optname)) # This check is fine because the OPTCRE cannot # match if it would set optval to None if optval is not None: @@ -692,8 +835,7 @@ class ConfigParser(RawConfigParser): return self._interpolate(section, option, value, d) def items(self, section, raw=False, vars=None): - """Return a list of tuples with (name, value) for each option - in the section. + """Return a list of (name, value) tuples for each option in a section. All % interpolations are expanded in the return values, based on the defaults passed into the constructor, unless the optional argument @@ -799,7 +941,8 @@ class SafeConfigParser(ConfigParser): else: raise InterpolationSyntaxError( option, section, - "'%%' must be followed by '%%' or '(', found: %r" % (rest,)) + "'%%' must be followed by '%%' or '(', " + "found: %r" % (rest,)) def set(self, section, option, value=None): """Set an option. Extend ConfigParser.set: check for string values.""" @@ -811,13 +954,11 @@ class SafeConfigParser(ConfigParser): if self._optcre is self.OPTCRE or value: if not isinstance(value, str): raise TypeError("option values must be strings") - # check for bad percent signs: - # first, replace all "good" interpolations - tmp_value = value.replace('%%', '') - tmp_value = self._interpvar_re.sub('', tmp_value) - # then, check if there's a lone percent sign left - percent_index = tmp_value.find('%') - if percent_index != -1: - raise ValueError("invalid interpolation syntax in %r at " - "position %d" % (value, percent_index)) + # check for bad percent signs + if value: + tmp_value = value.replace('%%', '') # escaped percent signs + tmp_value = self._interpvar_re.sub('', tmp_value) # valid syntax + if '%' in tmp_value: + raise ValueError("invalid interpolation syntax in %r at " + "position %d" % (value, tmp_value.find('%'))) ConfigParser.set(self, section, option, value) diff --git a/Lib/test/test_cfgparser.py b/Lib/test/test_cfgparser.py index e54ccfe..f43d1d7 100644 --- a/Lib/test/test_cfgparser.py +++ b/Lib/test/test_cfgparser.py @@ -30,62 +30,28 @@ class CfgParserTestCaseClass(unittest.TestCase): comment_prefixes = (';', '#') empty_lines_in_values = True dict_type = configparser._default_dict + strict = False def newconfig(self, defaults=None): arguments = dict( + defaults=defaults, allow_no_value=self.allow_no_value, delimiters=self.delimiters, comment_prefixes=self.comment_prefixes, empty_lines_in_values=self.empty_lines_in_values, dict_type=self.dict_type, + strict=self.strict, ) - if defaults is None: - self.cf = self.config_class(**arguments) - else: - self.cf = self.config_class(defaults, - **arguments) - return self.cf + return self.config_class(**arguments) def fromstring(self, string, defaults=None): cf = self.newconfig(defaults) - sio = io.StringIO(string) - cf.readfp(sio) + cf.read_string(string) return cf class BasicTestCase(CfgParserTestCaseClass): - def test_basic(self): - config_string = """\ -[Foo Bar] -foo{0[0]}bar -[Spacey Bar] -foo {0[0]} bar -[Spacey Bar From The Beginning] - foo {0[0]} bar - baz {0[0]} qwe -[Commented Bar] -foo{0[1]} bar {1[1]} comment -baz{0[0]}qwe {1[0]}another one -[Long Line] -foo{0[1]} this line is much, much longer than my editor - likes it. -[Section\\with$weird%characters[\t] -[Internationalized Stuff] -foo[bg]{0[1]} Bulgarian -foo{0[0]}Default -foo[en]{0[0]}English -foo[de]{0[0]}Deutsch -[Spaces] -key with spaces {0[1]} value -another with spaces {0[0]} splat! -""".format(self.delimiters, self.comment_prefixes) - if self.allow_no_value: - config_string += ( - "[NoValue]\n" - "option-without-value\n" - ) - - cf = self.fromstring(config_string) + def basic_test(self, cf): L = cf.sections() L.sort() E = ['Commented Bar', @@ -137,6 +103,125 @@ another with spaces {0[0]} splat! eq(cf.get('Long Line', 'foo'), 'this line is much, much longer than my editor\nlikes it.') + def test_basic(self): + config_string = """\ +[Foo Bar] +foo{0[0]}bar +[Spacey Bar] +foo {0[0]} bar +[Spacey Bar From The Beginning] + foo {0[0]} bar + baz {0[0]} qwe +[Commented Bar] +foo{0[1]} bar {1[1]} comment +baz{0[0]}qwe {1[0]}another one +[Long Line] +foo{0[1]} this line is much, much longer than my editor + likes it. +[Section\\with$weird%characters[\t] +[Internationalized Stuff] +foo[bg]{0[1]} Bulgarian +foo{0[0]}Default +foo[en]{0[0]}English +foo[de]{0[0]}Deutsch +[Spaces] +key with spaces {0[1]} value +another with spaces {0[0]} splat! +""".format(self.delimiters, self.comment_prefixes) + if self.allow_no_value: + config_string += ( + "[NoValue]\n" + "option-without-value\n" + ) + cf = self.fromstring(config_string) + self.basic_test(cf) + if self.strict: + with self.assertRaises(configparser.DuplicateOptionError): + cf.read_string(textwrap.dedent("""\ + [Duplicate Options Here] + option {0[0]} with a value + option {0[1]} with another value + """.format(self.delimiters))) + with self.assertRaises(configparser.DuplicateSectionError): + cf.read_string(textwrap.dedent("""\ + [And Now For Something] + completely different {0[0]} True + [And Now For Something] + the larch {0[1]} 1 + """.format(self.delimiters))) + else: + cf.read_string(textwrap.dedent("""\ + [Duplicate Options Here] + option {0[0]} with a value + option {0[1]} with another value + """.format(self.delimiters))) + + cf.read_string(textwrap.dedent("""\ + [And Now For Something] + completely different {0[0]} True + [And Now For Something] + the larch {0[1]} 1 + """.format(self.delimiters))) + + def test_basic_from_dict(self): + config = { + "Foo Bar": { + "foo": "bar", + }, + "Spacey Bar": { + "foo": "bar", + }, + "Spacey Bar From The Beginning": { + "foo": "bar", + "baz": "qwe", + }, + "Commented Bar": { + "foo": "bar", + "baz": "qwe", + }, + "Long Line": { + "foo": "this line is much, much longer than my editor\nlikes " + "it.", + }, + "Section\\with$weird%characters[\t": { + }, + "Internationalized Stuff": { + "foo[bg]": "Bulgarian", + "foo": "Default", + "foo[en]": "English", + "foo[de]": "Deutsch", + }, + "Spaces": { + "key with spaces": "value", + "another with spaces": "splat!", + } + } + if self.allow_no_value: + config.update({ + "NoValue": { + "option-without-value": None, + } + }) + cf = self.newconfig() + cf.read_dict(config) + self.basic_test(cf) + if self.strict: + with self.assertRaises(configparser.DuplicateOptionError): + cf.read_dict({ + "Duplicate Options Here": { + 'option': 'with a value', + 'OPTION': 'with another value', + }, + }) + else: + cf.read_dict({ + "Duplicate Options Here": { + 'option': 'with a value', + 'OPTION': 'with another value', + }, + }) + + def test_case_sensitivity(self): cf = self.newconfig() cf.add_section("A") @@ -185,25 +270,25 @@ another with spaces {0[0]} splat! "could not locate option, expecting case-insensitive defaults") def test_parse_errors(self): - self.newconfig() - self.parse_error(configparser.ParsingError, + cf = self.newconfig() + self.parse_error(cf, configparser.ParsingError, "[Foo]\n" "{}val-without-opt-name\n".format(self.delimiters[0])) - self.parse_error(configparser.ParsingError, + self.parse_error(cf, configparser.ParsingError, "[Foo]\n" "{}val-without-opt-name\n".format(self.delimiters[1])) - e = self.parse_error(configparser.MissingSectionHeaderError, + e = self.parse_error(cf, configparser.MissingSectionHeaderError, "No Section!\n") self.assertEqual(e.args, ('<???>', 1, "No Section!\n")) if not self.allow_no_value: - e = self.parse_error(configparser.ParsingError, + e = self.parse_error(cf, configparser.ParsingError, "[Foo]\n wrong-indent\n") self.assertEqual(e.args, ('<???>',)) - def parse_error(self, exc, src): + def parse_error(self, cf, exc, src): sio = io.StringIO(src) with self.assertRaises(exc) as cm: - self.cf.readfp(sio) + cf.read_file(sio) return cm.exception def test_query_errors(self): @@ -217,15 +302,15 @@ another with spaces {0[0]} splat! cf.options("Foo") with self.assertRaises(configparser.NoSectionError): cf.set("foo", "bar", "value") - e = self.get_error(configparser.NoSectionError, "foo", "bar") + e = self.get_error(cf, configparser.NoSectionError, "foo", "bar") self.assertEqual(e.args, ("foo",)) cf.add_section("foo") - e = self.get_error(configparser.NoOptionError, "foo", "bar") + e = self.get_error(cf, configparser.NoOptionError, "foo", "bar") self.assertEqual(e.args, ("bar", "foo")) - def get_error(self, exc, section, option): + def get_error(self, cf, exc, section, option): try: - self.cf.get(section, option) + cf.get(section, option) except exc as e: return e else: @@ -262,7 +347,31 @@ another with spaces {0[0]} splat! cf.add_section("Foo") with self.assertRaises(configparser.DuplicateSectionError) as cm: cf.add_section("Foo") - self.assertEqual(cm.exception.args, ("Foo",)) + e = cm.exception + self.assertEqual(str(e), "Section 'Foo' already exists") + self.assertEqual(e.args, ("Foo", None, None)) + + if self.strict: + with self.assertRaises(configparser.DuplicateSectionError) as cm: + cf.read_string(textwrap.dedent("""\ + [Foo] + will this be added{equals}True + [Bar] + what about this{equals}True + [Foo] + oops{equals}this won't + """.format(equals=self.delimiters[0])), source='<foo-bar>') + e = cm.exception + self.assertEqual(str(e), "While reading from <foo-bar> [line 5]: " + "section 'Foo' already exists") + self.assertEqual(e.args, ("Foo", '<foo-bar>', 5)) + + with self.assertRaises(configparser.DuplicateOptionError) as cm: + cf.read_dict({'Bar': {'opt': 'val', 'OPT': 'is really `opt`'}}) + e = cm.exception + self.assertEqual(str(e), "While reading from <dict>: option 'opt' " + "in section 'Bar' already exists") + self.assertEqual(e.args, ("Bar", "opt", "<dict>", None)) def test_write(self): config_string = ( @@ -392,6 +501,11 @@ another with spaces {0[0]} splat! self.assertEqual(L, expected) +class StrictTestCase(BasicTestCase): + config_class = configparser.RawConfigParser + strict = True + + class ConfigParserTestCase(BasicTestCase): config_class = configparser.ConfigParser @@ -409,7 +523,7 @@ class ConfigParserTestCase(BasicTestCase): "something with lots of interpolation (9 steps)") eq(cf.get("Foo", "bar10"), "something with lots of interpolation (10 steps)") - e = self.get_error(configparser.InterpolationDepthError, "Foo", "bar11") + e = self.get_error(cf, configparser.InterpolationDepthError, "Foo", "bar11") self.assertEqual(e.args, ("bar11", "Foo", rawval[self.config_class])) def test_interpolation_missing_value(self): @@ -417,8 +531,8 @@ class ConfigParserTestCase(BasicTestCase): configparser.ConfigParser: '%(reference)s', configparser.SafeConfigParser: '', } - self.get_interpolation_config() - e = self.get_error(configparser.InterpolationMissingOptionError, + cf = self.get_interpolation_config() + e = self.get_error(cf, configparser.InterpolationMissingOptionError, "Interpolation Error", "name") self.assertEqual(e.reference, "reference") self.assertEqual(e.section, "Interpolation Error") @@ -482,7 +596,7 @@ class MultilineValuesTestCase(BasicTestCase): # during performance updates in Python 3.2 cf_from_file = self.newconfig() with open(support.TESTFN) as f: - cf_from_file.readfp(f) + cf_from_file.read_file(f) self.assertEqual(cf_from_file.get('section8', 'lovely_spam4'), self.wonderful_spam.replace('\t\n', '\n')) @@ -645,15 +759,15 @@ class SortedTestCase(RawConfigParserTestCase): dict_type = SortedDict def test_sorted(self): - self.fromstring("[b]\n" - "o4=1\n" - "o3=2\n" - "o2=3\n" - "o1=4\n" - "[a]\n" - "k=v\n") + cf = self.fromstring("[b]\n" + "o4=1\n" + "o3=2\n" + "o2=3\n" + "o1=4\n" + "[a]\n" + "k=v\n") output = io.StringIO() - self.cf.write(output) + cf.write(output) self.assertEquals(output.getvalue(), "[a]\n" "k = v\n\n" @@ -697,6 +811,7 @@ def test_main(): SafeConfigParserTestCaseNoValue, SafeConfigParserTestCaseTrickyFile, SortedTestCase, + StrictTestCase, CompatibleTestCase, ) |