diff --git a/configargparse.py b/configargparse.py index 3c74014..790c743 100644 --- a/configargparse.py +++ b/configargparse.py @@ -289,6 +289,7 @@ def get_syntax_description(self): See https://docs.python.org/3/library/configparser.html for details. Note: INI file sections names are still treated as comments. + DEFAULT-only files, including serialize() output, are also supported. """ return msg @@ -310,7 +311,7 @@ def parse(self, stream): # convert to dict and remove INI section names result = OrderedDict() - for section in config.sections(): + for section in config.sections() or [configparser.DEFAULTSECT]: for k, v in config[section].items(): multiLine2SingleLine = v.replace("\n", " ").replace("\r", " ") # handle special case for lists diff --git a/tests/test_configargparse.py b/tests/test_configargparse.py index e7b3f1c..29337c7 100644 --- a/tests/test_configargparse.py +++ b/tests/test_configargparse.py @@ -4327,6 +4327,64 @@ def make_parser(): self.assertEqual(contents, expected_contents) +class TestConfigparserDefaultSection(TestCase): + def test_default_only_values(self): + parser = configargparse.ConfigparserConfigFileParser() + contents = ( + "[DEFAULT]\nName = alice\nlabel = %(name)s\nempty =\n" + "items = ['a', 'b']\nmessage = first\n second\n" + ) + self.assertEqual( + parser.parse(StringIO(contents)), + OrderedDict( + [ + ("name", "alice"), + ("label", "alice"), + ("empty", ""), + ("items", ["a", "b"]), + ("message", "first second"), + ] + ), + ) + + def test_serialization_round_trip(self): + parser = configargparse.ConfigparserConfigFileParser() + items = OrderedDict([("name", "alice"), ("items", ["a", "b"])]) + self.assertEqual(parser.parse(StringIO(parser.serialize(items))), items) + + def test_saved_config_round_trip(self): + parser = self.initParser( + config_file_parser_class=configargparse.ConfigparserConfigFileParser + ) + parser.add_argument("--name") + parser.add_argument("--count", type=int) + expected = parser.parse_args(["--name", "alice", "--count", "3"]) + with tempfile.TemporaryDirectory() as directory: + path = os.path.join(directory, "saved.ini") + parser.write_config_file(expected, [path]) + with open(path) as stream: + contents = stream.read() + actual = parser.parse_args([], config_file_contents=contents) + self.assertEqual(actual, expected) + + def test_named_sections_keep_default_overrides(self): + parser = configargparse.ConfigparserConfigFileParser() + contents = ( + "[DEFAULT]\nname = default\nshared = value\n" + "[first]\nname = alice\n[first_extra]\nname = bob\n" + ) + self.assertEqual( + parser.parse(StringIO(contents)), + OrderedDict([("name", "bob"), ("shared", "value")]), + ) + + def test_empty_file(self): + parser = configargparse.ConfigparserConfigFileParser() + for contents in ("", "[DEFAULT]\n"): + with self.subTest(contents=contents): + self.assertEqual(parser.parse(StringIO(contents)), OrderedDict()) + + ################################################################################ # since configargparse should work as a drop-in replacement for argparse # in all situations, run argparse unittests on configargparse by modifying