Skip to content

Commit

Permalink
Add a countries attribute to the region codelist (#374)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielhuppmann authored Sep 3, 2024
1 parent 9055d38 commit 08748f5
Show file tree
Hide file tree
Showing 11 changed files with 53 additions and 13 deletions.
4 changes: 4 additions & 0 deletions docs/api/countries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ and alpha-3 or alpha-2 codes, as shown in this example.
from nomenclature import countries
# list of country names
countries.names
# mappings between ISO3 (alpha_3), alpha_2 and country names
name = countries.get(alpha_3="...").name
alpha_3 = countries.get(name="...").alpha_3
alpha_2 = countries.get(name="...").alpha_2
Expand Down
21 changes: 18 additions & 3 deletions nomenclature/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import re
from keyword import iskeyword
from pathlib import Path
from typing import Any, Dict, List, Set, Union
from typing import Any, Dict, List, Set, Union, Optional
from pydantic import (
field_validator,
field_serializer,
Expand Down Expand Up @@ -233,13 +233,28 @@ class RegionCode(Code):
Name of the RegionCode
hierarchy : str
Hierarchy of the RegionCode
iso3_codes : str or list of str
countries : list of str, optional
List of countries in that region
iso3_codes : str or list of str, optional
ISO3 codes of countries in that region
"""

hierarchy: str = None
iso3_codes: Union[List[str], str] = None
countries: Optional[List[str]] = None
iso3_codes: Optional[Union[List[str], str]] = None

@field_validator("countries")
def check_countries(cls, v: List[str], info: ValidationInfo) -> List[str]:
"""Verifies that each country name is defined in `nomenclature.countries`."""
if invalid_country_names := set(v) - set(countries.names):
raise ValueError(
f"Region '{info.data['name']}' uses non-standard country name(s): "
+ ", ".join(invalid_country_names)
+ "\nPlease use `nomenclature.countries` for consistency. (https://"
+ "nomenclature-iamc.readthedocs.io/en/stable/api/countries.html)"
)
return v

@field_validator("iso3_codes")
def check_iso3_codes(cls, v: List[str], info: ValidationInfo) -> List[str]:
Expand Down
4 changes: 4 additions & 0 deletions nomenclature/countries.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ def get(self, **kwargs):

return country

@property
def names(self):
return [country.name for country in self.objects]


# Initialize `countries` for direct access via API and in codelist module
countries = Countries()
1 change: 0 additions & 1 deletion tests/data/general-config-only/nomenclature.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@ repositories:
definitions:
region:
repository: common-definitions
country: true
variable:
repository: common-definitions
1 change: 0 additions & 1 deletion tests/data/general-config/nomenclature.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@ repositories:
definitions:
region:
repository: common-definitions
country: true
variable:
repository: common-definitions
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
- common:
- World
- countries:
- Some region:
# "Czech Republic" this was renamed to "Czechia" in ISO 3166-1 in 2023
countries: [ Austria, Germany, Czech Republic ]

File renamed without changes.
File renamed without changes.
18 changes: 16 additions & 2 deletions tests/test_codelist.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ def test_tags_in_list_attributes():

def test_region_codelist():
"""Check replacing top-level hierarchy of yaml file as attribute for regions"""
code = RegionCodeList.from_directory("region", TEST_DATA_DIR / "region_codelist")
code = RegionCodeList.from_directory(
"region", TEST_DATA_DIR / "region_codelist" / "simple"
)

assert "World" in code
assert code["World"].hierarchy == "common"
Expand All @@ -157,9 +159,21 @@ def test_region_codelist():
assert code["Some Country"].iso2 == "XY"


def test_region_codelist_nonexisting_country_name():
"""Check that countries are validated against `nomenclature.countries`"""
with pytest.raises(ValueError, match="Region 'Some region' .*: Czech Republic"):
RegionCodeList.from_directory(
"region",
TEST_DATA_DIR / "region_codelist" / "countries_attribute_non-existing_name",
)


def test_norway_as_str():
"""guard against casting of 'NO' to boolean `False` by PyYAML or pydantic"""
region = RegionCodeList.from_directory("region", TEST_DATA_DIR / "norway_as_bool")
region = RegionCodeList.from_directory(
"region",
TEST_DATA_DIR / "region_codelist" / "norway_as_bool",
)
assert region["Norway"].eu_member is False
assert region["Norway"].iso2 == "NO"

Expand Down
4 changes: 4 additions & 0 deletions tests/test_countries.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ def test_countries_override(nc_name, iso_name, alpha_3):
assert countries.get(name=iso_name).alpha_3 == "BOL"
assert countries.get(alpha_3=alpha_3).name == nc_name

assert "Bolivia" in countries.names


def test_countries_add():
"""Check that countries added to ISO 3166 can be found"""
Expand All @@ -23,6 +25,8 @@ def test_countries_add():
with pytest.raises(AttributeError):
countries.get(name="Kosovo").alpha_2

assert "Kosovo" in countries.names


@pytest.mark.parametrize("alpha_2_eu, alpha_2", [("EL", "GR"), ("UK", "GB")])
def test_alternative_alpha2(alpha_2_eu, alpha_2):
Expand Down
6 changes: 0 additions & 6 deletions tests/test_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,6 @@ def test_definition_from_general_config(workflow_folder):
assert "Region A" in obs.region
# imported from https://github.com/IAMconsortium/common-definitions repo
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

# imported from https://github.com/IAMconsortium/common-definitions repo
assert "Primary Energy" in obs.variable
Expand Down

0 comments on commit 08748f5

Please sign in to comment.