Source code for nomenclature.processor.country
import logging
import re
from pathlib import Path
from pydantic import validate_call
from pyam.utils import escape_regexp
from nomenclature.countries import countries
from nomenclature.definition import DataStructureDefinition
from nomenclature.processor import RegionAggregationMapping, RegionProcessor
from nomenclature.processor.region import CommonRegion, NativeRegion
logger = logging.getLogger(__name__)
here = Path(__file__).parent.absolute()
class CountryProcessor(RegionProcessor):
"""A RegionProcessor configured for country-level aggregation.
This is a convenience class that generates region aggregation mappings
for hierarchies from the region codelist. Defaults to R5/R9/R10 regions.
"""
[docs]
@classmethod
@validate_call(config={"arbitrary_types_allowed": True})
def from_codelist(
cls,
dsd: DataStructureDefinition,
models: list[str],
hierarchies: set[str] = {"R5", "R9", "R10"},
skip_patterns: list[str] | None = None,
):
"""Create a processor for country-to-regional aggregation.
This factory method generates region aggregation mappings on-the-fly by
extracting regions with constituent countries matching the specified
hierarchies from the region codelist.
Parameters
----------
dsd : DataStructureDefinition
Project data structure definition containing region and variable codelists.
models : list[str]
Models for which to apply the aggregation.
hierarchies : list[str]
List of hierarchy values to match (e.g., ["R5", "R9", "R10"]).
Regions with these exact hierarchy values will be used for aggregation.
skip_patterns : list[str], optional
Optional regex patterns to skip certain regions from aggregation.
Returns
-------
CountryProcessor
A CountryProcessor instance with generated mappings.
Raises
------
If regions with the specified hierarchies lack constituent country information.
"""
models = models or dsd.config.processor.country
if not models:
raise ValueError("No models configured for country processor")
skip_patterns = ["Other (R*)"] + (skip_patterns or [])
available_hierarchies = hierarchies & set(dsd.region.hierarchy)
# Extract regional aggregates from codelist for given hierarchies
regional_aggregates = {}
for hierarchy in available_hierarchies:
for region_code in dsd.region.filter(hierarchy=hierarchy).values():
if skip_patterns and any(
re.match(escape_regexp(pattern), region_code.name)
for pattern in skip_patterns
):
continue
if not region_code.countries:
raise ValueError(
f"List of constituent countries for region '{region_code.name}' "
"not found in codelist."
)
if region_code.countries:
regional_aggregates[region_code.name] = region_code.countries
if not regional_aggregates:
logger.warning(
f"No regional aggregates for hierarchies {hierarchies} found in "
"region codelist."
)
# Get all countries from the codelist
country_names = set(countries.names)
all_countries_in_codelist = dsd.region.filter(name=country_names).values()
# Create native regions for all countries (to keep them in output)
native_regions = [
NativeRegion(name=country.name) for country in all_countries_in_codelist
]
# Create common regions for aggregation
common_regions = [
CommonRegion(name=region_name, constituent_regions=constituent_countries)
for region_name, constituent_countries in regional_aggregates.items()
]
# Create a mapping for all models
mapping = RegionAggregationMapping(
model=models,
file=Path(__file__),
native_regions=native_regions,
common_regions=common_regions,
)
return cls(
mappings={model: mapping for model in models},
region_codelist=dsd.region,
variable_codelist=dsd.variable,
)