diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f7da28a..3469fc47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ +# v2.9.1 + +- New nested version syntax `~nested:version` + # v2.9.0 - Resolve @-directives relative to `_machinable` module rather than top project level diff --git a/src/machinable/config/interface.py b/src/machinable/config/interface.py index aff604f3..5200b439 100644 --- a/src/machinable/config/interface.py +++ b/src/machinable/config/interface.py @@ -13,7 +13,7 @@ from .parser import parse_mixins -def collect_updates(version): +def _collect_updates(version): collection = [] for update in version: arguments = update["arguments"] @@ -42,7 +42,7 @@ def __init__(self, parsed_config, version=None, default_class=None): self.default_class = default_class self.schema_validation = get_settings()["schema_validation"] - def _get_version(self, name, config, current=None): + def _get_version(self, name, config): # from yaml file if name.endswith(".yaml") or name.endswith(".json"): with open(name) as f: @@ -63,24 +63,22 @@ def _get_version(self, name, config, current=None): return yaml.load(name, Loader=yaml.FullLoader) # from local version - if name in config: - version = copy.deepcopy(config[name]) - if not current: - return version - else: - # nested lookup - try: - return update_dict(copy.deepcopy(current[name]), version) - except KeyError: - return version - - if current: - # nested lookup - try: - return copy.deepcopy(current[name]) - except KeyError: - # ignore non-existing nested lookups - return {} + version = {} + path = name[1:].split(":") + level = config + try: + for key in path: + level = level["~" + key] + # extract config on this level + u = { + k: copy.deepcopy(v) + for k, v in level.items() + if not k.startswith("~") + } + version = update_dict(version, u) + return version + except KeyError: + pass raise KeyError( f"Version '{name}' could not be found.\n" @@ -252,8 +250,7 @@ def get_component(self, name, version=None, flags=None): versions.append({"arguments": {"components": copy.deepcopy(version)}}) version = {} - for updates in collect_updates(versions): - # select components/node subsection + for updates in _collect_updates(versions): update = updates.get("components", None) if update is None: continue @@ -267,7 +264,7 @@ def get_component(self, name, version=None, flags=None): # load arguments from machinable.yaml if isinstance(k, str): config["versions"].append(k) - k = self._get_version(k, config["args"], version) + k = self._get_version(k, config["args"]) elif isinstance(k, dict): # evaluate computed properties for key in k.keys(): @@ -287,7 +284,7 @@ def get_component(self, name, version=None, flags=None): # remove unused versions config["args"] = { - k: v if not k.startswith("~") else "_" + k: v if not k.startswith("~") else ":" for k, v in config["args"].items() if not k.startswith("~") or k in config["versions"] } diff --git a/tests/config/config_interface_test.py b/tests/config/config_interface_test.py index 10cae05f..8e174fd6 100644 --- a/tests/config/config_interface_test.py +++ b/tests/config/config_interface_test.py @@ -69,23 +69,24 @@ def test_config_versioning(): assert e["beta"]["test"] # nested - t = Experiment().components(("thenode", ("~three", "~nested"))) + t = Experiment().components(("thenode", "~three:nested")) e, m = to_config(test_project, t) - assert e["works"] - assert e["nested"] + assert e["unaffected"] == "value" + assert e["nested"] is None assert e["alpha"] == 4 assert e["beta"] == "nested" + assert "should_not_be" not in e t = Experiment().components(("thenode", ("~two", "~nested"))) e, m = to_config(test_project, t) assert e["alpha"] == 2 - assert e["nested"] + assert e["nested"] is True - t = Experiment().components(("thenode", ("~three", "~nested", "~nestednested"))) + t = Experiment().components(("thenode", ("~three:nested:nestednested"))) e, m = to_config(test_project, t) - assert e["works"] + assert e["unaffected"] == "value" + assert e["works"] is False assert e["alpha"] == 5 - assert e["q"] == -1 assert e["beta"] == "overwritten" assert e["added"] == "value" diff --git a/tests/test_project/machinable.yaml b/tests/test_project/machinable.yaml index 42bf0f75..f7204a49 100644 --- a/tests/test_project/machinable.yaml +++ b/tests/test_project/machinable.yaml @@ -28,6 +28,7 @@ components: works: nested: added: + unaffected: true q: key: mixing: @@ -42,6 +43,7 @@ components: alpha: 2 ~three: alpha: 3 + unaffected: value ~nested: works: False alpha: 4 @@ -50,8 +52,8 @@ components: beta: overwritten alpha: 5 added: value - ~nestednested: - q: -1 + ~second_nested: + should_not_be: selected ~nested: nested: True works: True