From 7613986c7dd64f79199434f36247924faafaf891 Mon Sep 17 00:00:00 2001 From: Daniel Huppmann Date: Thu, 13 Jul 2023 11:49:14 +0200 Subject: [PATCH] Add a general-config feature to add all countries to RegionCodeList (#262) --- docs/api.rst | 1 + docs/api/datastructureconfig.rst | 7 ++ docs/api/datastructuredefinition.rst | 2 +- docs/user_guide/directory-structure.rst | 4 ++ nomenclature/code.py | 2 + nomenclature/codelist.py | 66 ++++++++++++++++-- nomenclature/config.py | 39 +++++++++++ nomenclature/definition.py | 17 +++-- templates/model-registration-template.xlsx | Bin 25870 -> 25871 bytes .../general-config-definitions/config.yaml | 2 + .../region/regions.yaml | 2 + tests/test_definition.py | 16 +++++ 12 files changed, 148 insertions(+), 10 deletions(-) create mode 100644 docs/api/datastructureconfig.rst create mode 100644 nomenclature/config.py create mode 100644 tests/data/general-config-definitions/config.yaml create mode 100644 tests/data/general-config-definitions/region/regions.yaml diff --git a/docs/api.rst b/docs/api.rst index 532a5648..b9fff186 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -10,6 +10,7 @@ API documentation api/nomenclature api/datastructuredefinition + api/datastructureconfig api/codelist api/regionprocessor api/testing diff --git a/docs/api/datastructureconfig.rst b/docs/api/datastructureconfig.rst new file mode 100644 index 00000000..3a7d8073 --- /dev/null +++ b/docs/api/datastructureconfig.rst @@ -0,0 +1,7 @@ +.. currentmodule:: nomenclature.config + +**DataStructureConfig** +======================= + +.. autoclass:: DataStructureConfig + :members: from_file diff --git a/docs/api/datastructuredefinition.rst b/docs/api/datastructuredefinition.rst index 9b46e23e..1b5b971c 100644 --- a/docs/api/datastructuredefinition.rst +++ b/docs/api/datastructuredefinition.rst @@ -4,4 +4,4 @@ =========================== .. autoclass:: DataStructureDefinition - :members: + :members: validate, check_aggregate, to_excel diff --git a/docs/user_guide/directory-structure.rst b/docs/user_guide/directory-structure.rst index 5598197b..a3e1fcf1 100644 --- a/docs/user_guide/directory-structure.rst +++ b/docs/user_guide/directory-structure.rst @@ -11,6 +11,7 @@ This is the directory structure for validation and region processing: . ├── definitions + │ ├── config.yaml │ ├── region │ │ ├── regions.yaml │ │ └── ... @@ -32,5 +33,8 @@ The :class:`DataStructureDefinition` reads the codelists from the *definitions* object is initialized, all files in a dimension folder are combined into a single :class:`CodeList` object for that dimension. +* General configurations of the :class:`DataStructureDefinition` can be specified + via the *config.yaml* file. + The :class:`RegionProcessor` reads model-specific region-mappings from the *mappings* folder. If the project has no model specific mappings, this folder can also be omitted. diff --git a/nomenclature/code.py b/nomenclature/code.py index 7c786526..ecc618f1 100644 --- a/nomenclature/code.py +++ b/nomenclature/code.py @@ -217,6 +217,8 @@ class RegionCode(Code): Name of the RegionCode hierarchy : str Hierarchy of the RegionCode + iso3_codes : str or list of str + ISO3 codes of countries in that region """ diff --git a/nomenclature/codelist.py b/nomenclature/codelist.py index 22f8651d..f80db951 100644 --- a/nomenclature/codelist.py +++ b/nomenclature/codelist.py @@ -8,7 +8,10 @@ from pyam.utils import write_sheet from pydantic import BaseModel, validator +from pycountry import countries + from nomenclature.code import Code, MetaCode, RegionCode, VariableCode +from nomenclature.config import DataStructureConfig from nomenclature.error.codelist import DuplicateCodeError from nomenclature.error.variable import ( MissingWeightError, @@ -16,6 +19,32 @@ VariableRenameTargetError, ) + +# The RegionCodeList uses pycountry to (optionally) add all countries and ISO3 codes +# For readability and in line with conventions of the IAMC community, +# several "standard" country names are shortened +# Please keep this list in sync with `templates/model-registration-template.xlsx` +PYCOUNTRY_NAME_OVERRIDE = { + "Bolivia, Plurinational State of": "Bolivia", + "Holy See (Vatican City State)": "Vatican", + "Micronesia, Federated States of": "Micronesia", + "Congo, The Democratic Republic of the": "Democratic Republic of the Congo", + "Iran, Islamic Republic of": "Iran", + "Korea, Republic of": "South Korea", + "Korea, Democratic People's Republic of": "North Korea", + "Lao People's Democratic Republic": "Laos", + "Syrian Arab Republic": "Syria", + "Moldova, Republic of": "Moldova", + "Tanzania, United Republic of": "Tanzania", + "Venezuela, Bolivarian Republic of": "Venezuela", + "Palestine, State of": "Palestine", + "Taiwan, Province of China": "Taiwan", +} +PYCOUNTRY_NAME_ADD = [ + "Kosovo", +] + + here = Path(__file__).parent.absolute() @@ -164,7 +193,13 @@ def _parse_and_replace_tags( return codes_without_tags + codes_with_tags @classmethod - def from_directory(cls, name: str, path: Path, file_glob_pattern: str = "**/*"): + def from_directory( + cls, + name: str, + path: Path, + config: DataStructureConfig = None, + file_glob_pattern: str = "**/*", + ): """Initialize a CodeList from a directory with codelist files Parameters @@ -173,12 +208,14 @@ def from_directory(cls, name: str, path: Path, file_glob_pattern: str = "**/*"): Name of the CodeList path : :class:`pathlib.Path` or path-like Directory with the codelist files + config: :class:`DataStructureConfig`, optional + Attributes for configuring the CodeList file_glob_pattern : str, optional Pattern to downselect codelist files by name Returns ------- - instance of cls (CodeList if not inherited) + instance of cls (:class:`CodeList` if not inherited) """ code_list: List[Code] = [] @@ -511,7 +548,13 @@ class RegionCodeList(CodeList): validation_schema: ClassVar[str] = "region" @classmethod - def from_directory(cls, name: str, path: Path, file_glob_pattern: str = "**/*"): + def from_directory( + cls, + name: str, + path: Path, + config: DataStructureConfig = None, + file_glob_pattern: str = "**/*", + ): """Initialize a RegionCodeList from a directory with codelist files Parameters @@ -520,6 +563,8 @@ def from_directory(cls, name: str, path: Path, file_glob_pattern: str = "**/*"): Name of the CodeList path : :class:`pathlib.Path` or path-like Directory with the codelist files + config : :class:`DataStructureConfig`, optional + Attributes for configuring the CodeList file_glob_pattern : str, optional Pattern to downselect codelist files by name, default: "**/*" (i.e. all files in all sub-folders) @@ -529,9 +574,22 @@ def from_directory(cls, name: str, path: Path, file_glob_pattern: str = "**/*"): RegionCodeList """ - mapping: Dict[str, RegionCode] = {} + code_list: List[RegionCode] = [] + if config is not None and config.region is not None: + if config.region.country is True: + for i in countries: + code_list.append( + RegionCode( + name=PYCOUNTRY_NAME_OVERRIDE.get(i.name, i.name), + iso3_codes=i.alpha_3, + hierarchy="Country", + ) + ) + for c in PYCOUNTRY_NAME_ADD: + code_list.append(RegionCode(name=c, hierarchy="Country")) + for yaml_file in ( f for f in path.glob(file_glob_pattern) diff --git a/nomenclature/config.py b/nomenclature/config.py new file mode 100644 index 00000000..69bca87d --- /dev/null +++ b/nomenclature/config.py @@ -0,0 +1,39 @@ +from pathlib import Path +from typing import Dict, Optional +from pydantic import BaseModel + +import yaml + + +class RegionCodeListConfig(BaseModel): + country: Optional[bool] + + +class DataStructureConfig(BaseModel): + """A class for configuration of a DataStructureDefinition + + Attributes + ---------- + region : RegionCodeListConfig + Attributes for configuring the RegionCodeList + + """ + + region: Optional[RegionCodeListConfig] + + @classmethod + def from_file(cls, path: Path, file: str): + """Read a DataStructureConfig from a file + + Parameters + ---------- + path : :class:`pathlib.Path` or path-like + `definitions` directory + file : str + File name + + """ + with open(path / file, "r", encoding="utf-8") as stream: + config = yaml.safe_load(stream) + + return cls(region=RegionCodeListConfig(**config["region"])) diff --git a/nomenclature/definition.py b/nomenclature/definition.py index c26a830c..9fe3d551 100644 --- a/nomenclature/definition.py +++ b/nomenclature/definition.py @@ -12,6 +12,7 @@ VariableCodeList, MetaCodeList, ) +from nomenclature.config import DataStructureConfig from nomenclature.validation import validate logger = logging.getLogger(__name__) @@ -37,19 +38,25 @@ def __init__(self, path, dimensions=None): from a sub-folder of `path` of that name. """ - if dimensions is None: - dimensions = ["region", "variable"] - if not isinstance(path, Path): path = Path(path) if not path.is_dir(): raise NotADirectoryError(f"Definitions directory not found: {path}") - self.dimensions = dimensions + if (path / "config.yaml").exists(): + self.config = DataStructureConfig.from_file( + path=path, + file="config.yaml", + ) + else: + self.config = DataStructureConfig() + + self.dimensions = dimensions or ["region", "variable"] for dim in self.dimensions: + codelist_cls = SPECIAL_CODELIST.get(dim, CodeList) self.__setattr__( - dim, SPECIAL_CODELIST.get(dim, CodeList).from_directory(dim, path / dim) + dim, codelist_cls.from_directory(dim, path / dim, self.config) ) empty = [d for d in self.dimensions if not self.__getattribute__(d)] diff --git a/templates/model-registration-template.xlsx b/templates/model-registration-template.xlsx index 582c8a1b2c8f7a73342ba09634408ac71094bf2b..77fa63e19399c4703315f503d059cd948305a1dd 100644 GIT binary patch delta 15073 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 E0Isv+#sB~S delta 15043 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 EA2D@_zyJUM diff --git a/tests/data/general-config-definitions/config.yaml b/tests/data/general-config-definitions/config.yaml new file mode 100644 index 00000000..8c18eb3e --- /dev/null +++ b/tests/data/general-config-definitions/config.yaml @@ -0,0 +1,2 @@ +region: + country: true diff --git a/tests/data/general-config-definitions/region/regions.yaml b/tests/data/general-config-definitions/region/regions.yaml new file mode 100644 index 00000000..19895e7c --- /dev/null +++ b/tests/data/general-config-definitions/region/regions.yaml @@ -0,0 +1,2 @@ +- common: + - World diff --git a/tests/test_definition.py b/tests/test_definition.py index 41868edd..77ca948b 100644 --- a/tests/test_definition.py +++ b/tests/test_definition.py @@ -40,6 +40,22 @@ def test_empty_codelist_raises(): DataStructureDefinition(TEST_DATA_DIR / "simple_codelist") +def test_definition_from_general_config(): + obs = DataStructureDefinition( + TEST_DATA_DIR / "general-config-definitions", + dimensions=["region"], + ) + + # explicitly defined in `general-config-definitions/region/regions.yaml` + assert "World" in obs.region + # added via general-config definitions + assert "Austria" in obs.region + # added via general-config definitions renamed from pycountry name + assert "Bolivia" in obs.region + # added via general-config definitions in addition to pycountry.countries + assert "Kosovo" in obs.region + + def test_to_excel(simple_definition, tmpdir): """Check writing a DataStructureDefinition to file""" file = tmpdir / "testing_export.xlsx"