From a950110a80bef7aa46555565e4d72480baee8db6 Mon Sep 17 00:00:00 2001 From: Bibo Hao Date: Tue, 23 Jun 2026 21:22:28 +0800 Subject: [PATCH 1/2] Refactor _fixup_self_references method for clarity --- pyhocon/config_parser.py | 67 ++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/pyhocon/config_parser.py b/pyhocon/config_parser.py index cc1c1de..55564ec 100644 --- a/pyhocon/config_parser.py +++ b/pyhocon/config_parser.py @@ -467,40 +467,41 @@ def _resolve_variable(cls, config, substitution): @classmethod def _fixup_self_references(cls, config, accept_unresolved=False): if isinstance(config, ConfigTree) and config.root: - for key in config: # Traverse history of element - history = config.history[key] - previous_item = history[0] - for current_item in history[1:]: - for substitution in cls._find_substitutions(current_item): - prop_path = ConfigTree.parse_key(substitution.variable) - if len(prop_path) > 1 and config.get(substitution.variable, None) is not None: - continue # If value is present in latest version, don't do anything - if prop_path[0] == key: - if isinstance(previous_item, ConfigValues) and not accept_unresolved: - # We hit a dead end, we cannot evaluate - raise ConfigSubstitutionException( - "Property {variable} cannot be substituted. Check for cycles.".format( - variable=substitution.variable - ) + for key in list(config.keys()): # Use list to avoid mutation error during iteration + history = config.history.get(key) + if not history: + continue + previous_item = history[0] + for current_item in history[1:]: + for substitution in cls._find_substitutions(current_item): + prop_path = ConfigTree.parse_key(substitution.variable) + if len(prop_path) > 1 and config.get(substitution.variable, None) is not None: + continue + if prop_path[0] == key: + if isinstance(previous_item, ConfigValues) and not accept_unresolved: + raise ConfigSubstitutionException( + "Property {variable} cannot be substituted. Check for cycles.".format( + variable=substitution.variable ) - else: - value = previous_item if len(prop_path) == 1 else previous_item.get( - ".".join(prop_path[1:])) - _, _, current_item = cls._do_substitute(substitution, value) - previous_item = current_item - - if len(history) == 1: - for substitution in cls._find_substitutions(previous_item): - prop_path = ConfigTree.parse_key(substitution.variable) - if len(prop_path) > 1 and config.get(substitution.variable, None) is not None: - continue # If value is present in latest version, don't do anything - if prop_path[0] == key: - value = os.environ.get(key) - if value is not None: - cls._do_substitute(substitution, value) - continue - if substitution.optional: # special case, when self optional referencing without existing - cls._do_substitute(substitution, None) + ) + else: + value = previous_item if len(prop_path) == 1 else previous_item.get( + ".".join(prop_path[1:])) + _, _, current_item = cls._do_substitute(substitution, value) + previous_item = current_item + + if len(history) == 1: + for substitution in cls._find_substitutions(previous_item): + prop_path = ConfigTree.parse_key(substitution.variable) + if len(prop_path) > 1 and config.get(substitution.variable, None) is not None: + continue + if prop_path[0] == key: + value = os.environ.get(key) + if value is not None: + cls._do_substitute(substitution, value) + continue + if substitution.optional: + cls._do_substitute(substitution, None) # traverse config to find all the substitutions @classmethod From 857a1f9afe448206dee852d9096de06d88bd9339 Mon Sep 17 00:00:00 2001 From: Bibo Hao Date: Tue, 23 Jun 2026 21:26:35 +0800 Subject: [PATCH 2/2] Update config_parser.py --- pyhocon/config_parser.py | 66 ++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/pyhocon/config_parser.py b/pyhocon/config_parser.py index 55564ec..39b19a3 100644 --- a/pyhocon/config_parser.py +++ b/pyhocon/config_parser.py @@ -467,41 +467,41 @@ def _resolve_variable(cls, config, substitution): @classmethod def _fixup_self_references(cls, config, accept_unresolved=False): if isinstance(config, ConfigTree) and config.root: - for key in list(config.keys()): # Use list to avoid mutation error during iteration - history = config.history.get(key) - if not history: - continue - previous_item = history[0] - for current_item in history[1:]: - for substitution in cls._find_substitutions(current_item): - prop_path = ConfigTree.parse_key(substitution.variable) - if len(prop_path) > 1 and config.get(substitution.variable, None) is not None: - continue - if prop_path[0] == key: - if isinstance(previous_item, ConfigValues) and not accept_unresolved: - raise ConfigSubstitutionException( - "Property {variable} cannot be substituted. Check for cycles.".format( - variable=substitution.variable + for key in list(config.keys()): # Use list to avoid mutation error during iteration + history = config.history.get(key) + if not history: + continue + previous_item = history[0] + for current_item in history[1:]: + for substitution in cls._find_substitutions(current_item): + prop_path = ConfigTree.parse_key(substitution.variable) + if len(prop_path) > 1 and config.get(substitution.variable, None) is not None: + continue + if prop_path[0] == key: + if isinstance(previous_item, ConfigValues) and not accept_unresolved: + raise ConfigSubstitutionException( + "Property {variable} cannot be substituted. Check for cycles.".format( + variable=substitution.variable + ) ) - ) - else: - value = previous_item if len(prop_path) == 1 else previous_item.get( - ".".join(prop_path[1:])) - _, _, current_item = cls._do_substitute(substitution, value) - previous_item = current_item - - if len(history) == 1: - for substitution in cls._find_substitutions(previous_item): - prop_path = ConfigTree.parse_key(substitution.variable) - if len(prop_path) > 1 and config.get(substitution.variable, None) is not None: - continue - if prop_path[0] == key: - value = os.environ.get(key) - if value is not None: - cls._do_substitute(substitution, value) + else: + value = previous_item if len(prop_path) == 1 else previous_item.get( + ".".join(prop_path[1:])) + _, _, current_item = cls._do_substitute(substitution, value) + previous_item = current_item + + if len(history) == 1: + for substitution in cls._find_substitutions(previous_item): + prop_path = ConfigTree.parse_key(substitution.variable) + if len(prop_path) > 1 and config.get(substitution.variable, None) is not None: continue - if substitution.optional: - cls._do_substitute(substitution, None) + if prop_path[0] == key: + value = os.environ.get(key) + if value is not None: + cls._do_substitute(substitution, value) + continue + if substitution.optional: + cls._do_substitute(substitution, None) # traverse config to find all the substitutions @classmethod