diff --git a/confection/_parser.py b/confection/_parser.py index f05f131..7052b15 100644 --- a/confection/_parser.py +++ b/confection/_parser.py @@ -48,7 +48,7 @@ def parse_config( result: dict[str, Any] = {} section_parts = [section.split(".") for section in config_parser.sections()] # Phase 1: Build the skeleton of nested dicts from section names. - for parts in section_parts: + for parts in sorted(section_parts, key=len): node = result for part in parts[:-1]: node = node.setdefault(part, {}) if part == "*" else node[part] diff --git a/tests/test_config_basic.py b/tests/test_config_basic.py index 2e684ce..9d79f7e 100644 --- a/tests/test_config_basic.py +++ b/tests/test_config_basic.py @@ -7,6 +7,7 @@ """ from configparser import InterpolationDepthError +from itertools import permutations import pytest from hypothesis import HealthCheck, given, settings @@ -21,6 +22,21 @@ ) +@pytest.mark.parametrize("order", list(permutations(range(4)))) +def test_sections_can_precede_their_parents(order): + sections = [ + "[training]\nseed = 42", + "[training.optimizer]\nrate = 0.01", + "[model]\nwidth = 64", + "[model.encoder]\ndepth = 2", + ] + text = "\n\n".join(sections[i] for i in order) + assert Config().from_str(text) == { + "training": {"seed": 42, "optimizer": {"rate": 0.01}}, + "model": {"width": 64, "encoder": {"depth": 2}}, + } + + @given(config_dicts) def test_roundtrip(data): """Config.from_str(config.to_str()) should reproduce the original dict."""