diff --git a/doc/source/user_guide/codelist.rst b/doc/source/user_guide/codelist.rst index 07631bda..46fdb0f7 100644 --- a/doc/source/user_guide/codelist.rst +++ b/doc/source/user_guide/codelist.rst @@ -50,6 +50,29 @@ to be used as tag. The files defining the tags must be named like ``tag_*.yaml`` - Some Key: description: a short description of the key -When importing a tagged codelist, any occurrence of ``{Tag}`` in the name of a code will -be replaced by every element in the Tag dictionary. The ``{Tag}`` will also be replaced -in any of the code attributes. +When importing a tagged codelist, any code containing ``{Tag}`` in its name will +be replaced by the name of the corresponding tag. In addition any code attributes +containing ``{Tag}`` will also be replaced. If the corresponding tag contains the attribute, this will be used for substitution, otherwise the tag name is used. + +To illustrate, consider the following variable: + +.. code:: yaml + + - Variable|{Tag}: + description: The description contains {Tag} + weight: {Tag} + + +which will be translated to: + +.. code:: yaml + + - Variable|Some Key: + description: The description contains a short description of the key + weight: Some Key + +Since the tag contains a *description* attribute with value *a short description of the +key* this is substituted in the *description* of the variable. + +In contrast, the variable contains the attribute *weight* which features ``{Tag}``, +which is not present in the tag. In this case the tag name *Some Key* is used. diff --git a/nomenclature/code.py b/nomenclature/code.py index 96f4ab75..110c0f58 100644 --- a/nomenclature/code.py +++ b/nomenclature/code.py @@ -68,6 +68,13 @@ def contains_tags(self) -> bool: def tags(self): return re.findall("(?<={).*?(?=})", self.name) + @property + def flattened_dict(self): + return { + **{k: v for k, v in self.dict().items() if k != "extra_attributes"}, + **self.extra_attributes, + } + def replace_tag(self, tag: str, target: "Code") -> "Code": """Return a new instance with tag applied @@ -84,27 +91,24 @@ def replace_tag(self, tag: str, target: "Code") -> "Code": New Code instance with occurrences of "{tag}" replaced by target """ - mapping = { - key: value - for key, value in self.dict().items() - if key != "extra_attributes" - } - # replace name and description - mapping["name"] = mapping["name"].replace("{" + tag + "}", target.name) - mapping["description"] = mapping["description"].replace( - "{" + tag + "}", target.description - ) - - # replace any other attribute - extra_attributes = self.extra_attributes.copy() - for attr, value in target.extra_attributes.items(): - if isinstance(extra_attributes.get(attr), str): - extra_attributes[attr] = extra_attributes[attr].replace( - "{" + tag + "}", value - ) - elif isinstance(mapping.get(attr), str): - mapping[attr] = mapping[attr].replace("{" + tag + "}", value) - return self.__class__(**mapping, extra_attributes=extra_attributes) + mapping = {} + for attr, value in self.flattened_dict.items(): + # if the attribute is a string and contains "{tag}" replace + if isinstance(value, str) and "{" + tag + "}" in value: + # if the the target has the corresponding attribute replace + if attr in target.flattened_dict: + mapping[attr] = value.replace( + "{" + tag + "}", getattr(target, attr) + ) + # otherwise insert the name + else: + mapping[attr] = value.replace("{" + tag + "}", target.name) + # otherwise append as is + else: + mapping[attr] = value + name = mapping["name"] + del mapping["name"] + return self.__class__.from_dict({name: mapping}) def __getattr__(self, k): try: diff --git a/nomenclature/codelist.py b/nomenclature/codelist.py index 69470a2e..449b6a2f 100644 --- a/nomenclature/codelist.py +++ b/nomenclature/codelist.py @@ -362,13 +362,12 @@ def codelist_repr(self) -> Dict: nice_dict = {} for name, code in self.mapping.items(): - code_dict = code.dict() - del code_dict["name"] - for attr, value in code.dict().items(): - if value is None and attr != "unit": - del code_dict[attr] - code_dict.update(code_dict["extra_attributes"]) - del code_dict["extra_attributes"] + code_dict = { + k: v + for k, v in code.flattened_dict.items() + if (v is not None and k != "name") or k == "unit" + } + nice_dict[name] = code_dict return nice_dict diff --git a/tests/data/tagged_codelist/tag_sector.yaml b/tests/data/tagged_codelist/tag_sector.yaml index dd825cb3..e2afdc74 100644 --- a/tests/data/tagged_codelist/tag_sector.yaml +++ b/tests/data/tagged_codelist/tag_sector.yaml @@ -1,9 +1,6 @@ - Sector: - Energy: definition: energy - value_bool: true - value_number: 2.3 weight: Energy - Industry: definition: industrial - weight: Energy diff --git a/tests/test_codelist.py b/tests/test_codelist.py index 0d41b838..11764c3d 100644 --- a/tests/test_codelist.py +++ b/tests/test_codelist.py @@ -59,10 +59,25 @@ def test_tagged_codelist(): "variable", TEST_DATA_DIR / "tagged_codelist" ) - v = "Final Energy|Industry|Renewables" - d = "Final energy consumption of renewables in the industrial sector" - assert v in code - assert code[v].description == d + exp = { + "Final Energy|Industry|Renewables": { + "description": ( + "Final energy consumption of renewables in the industrial sector" + ), + "weight": "Final Energy|Industry", + }, + "Final Energy|Energy|Renewables": { + "description": ( + "Final energy consumption of renewables in the energy sector" + ), + "weight": "Final Energy|Energy", + }, + } + + for code_name, attrs in exp.items(): + assert code_name in code + for attr_name, value in attrs.items(): + assert getattr(code[code_name], attr_name) == value def test_region_codelist():