diff --git a/benedict/serializers/ini.py b/benedict/serializers/ini.py index ca6ec6c4..8a33e473 100644 --- a/benedict/serializers/ini.py +++ b/benedict/serializers/ini.py @@ -37,6 +37,8 @@ def _get_parser(options: dict[str, Any]) -> RawConfigParser: def _get_section_option_value( parser: RawConfigParser, section: str, option: str ) -> str | int | float | bool | None: + if parser.get(section, option) is None: + return None value = None funcs: list[Callable[[str, str], Any]] = [ parser.getint, diff --git a/tests/dicts/io/test_io_dict_ini.py b/tests/dicts/io/test_io_dict_ini.py index d146f709..78c470e4 100644 --- a/tests/dicts/io/test_io_dict_ini.py +++ b/tests/dicts/io/test_io_dict_ini.py @@ -8,6 +8,31 @@ class io_dict_ini_test_case(io_dict_test_case): This class describes an IODict / ini test case. """ + def test_from_ini_with_valueless_options(self) -> None: + s = "[app]\nflag\nempty =\ncount = 2\nratio = 1.5\nenabled = yes\n" + expected = { + "app": { + "flag": None, + "empty": "", + "count": 2, + "ratio": 1.5, + "enabled": True, + }, + } + self.assertEqual(IODict.from_ini(s, allow_no_value=True), expected) + self.assertEqual(IODict(s, format="ini", allow_no_value=True), expected) + + def test_from_ini_with_valueless_defaults(self) -> None: + s = "[DEFAULT]\nflag\n[app]\nname = demo\n" + expected = {"flag": None, "app": {"flag": None, "name": "demo"}} + self.assertEqual(IODict.from_ini(s, allow_no_value=True), expected) + + def test_from_ini_rejects_valueless_options_by_default(self) -> None: + s = "[app]\nflag\n" + for options in ({}, {"allow_no_value": False}): + with self.subTest(options=options), self.assertRaises(ValueError): + IODict.from_ini(s, **options) + def test_from_ini_with_valid_data(self) -> None: s = """ [DEFAULT]