Skip to content

Commit

Permalink
Allow nomenclatureconfig to have region country only (#320)
Browse files Browse the repository at this point in the history
* Remove None as default

* Use NomenclatureConfig as default

* Use NomenclatureConfig default object

* Clean up imports

* Remove suppress and use NomenclatureConfig default

* Add test for nomenclature definition region only
  • Loading branch information
phackstock authored Feb 9, 2024
1 parent 86d38f9 commit f4627d9
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 72 deletions.
87 changes: 38 additions & 49 deletions nomenclature/codelist.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
from contextlib import suppress
import logging
from pathlib import Path
from typing import ClassVar, Dict, List

import numpy as np
import pandas as pd
import yaml
from pyam.utils import write_sheet
from pydantic import field_validator, BaseModel, ValidationInfo
from pyam.utils import is_list_like, write_sheet
from pydantic import BaseModel, ValidationInfo, field_validator
from pydantic_core import PydanticCustomError

import nomenclature
from nomenclature.code import Code, MetaCode, RegionCode, VariableCode
from nomenclature.config import NomenclatureConfig
from nomenclature.error import custom_pydantic_errors, ErrorCollector
from pyam.utils import is_list_like
from nomenclature.config import CodeListConfig, NomenclatureConfig
from nomenclature.error import ErrorCollector, custom_pydantic_errors

here = Path(__file__).parent.absolute()

Expand Down Expand Up @@ -198,21 +196,14 @@ def from_directory(
"""
code_list = cls._parse_codelist_dir(path, file_glob_pattern)

with suppress(AttributeError):
dimension = path.name
codelistconfig = getattr(config.definitions, dimension)
for repo in codelistconfig.repositories:
repo_path = (
config.repositories[repo].local_path / "definitions" / dimension
)
code_list = (
cls._parse_codelist_dir(
repo_path,
file_glob_pattern,
)
+ code_list
)
config = config or NomenclatureConfig()
for repo in getattr(
config.definitions, name.lower(), CodeListConfig()
).repositories:
repo_path = config.repositories[repo].local_path / "definitions" / name
code_list = (
cls._parse_codelist_dir(repo_path, file_glob_pattern) + code_list
)
errors = ErrorCollector()
mapping: Dict[str, Code] = {}
for code in code_list:
Expand Down Expand Up @@ -604,35 +595,33 @@ def from_directory(
code_list: List[RegionCode] = []

# initializing from general configuration
with suppress(AttributeError):
# adding all countries
if config.definitions.region.country is True:
for c in nomenclature.countries:
try:
code_list.append(
RegionCode(
name=c.name, iso3_codes=c.alpha_3, hierarchy="Country"
)
# adding all countries
config = config or NomenclatureConfig()
if config.definitions.region.country is True:
for c in nomenclature.countries:
try:
code_list.append(
RegionCode(
name=c.name, iso3_codes=c.alpha_3, hierarchy="Country"
)
# special handling for countries that do not have an alpha_3 code
except AttributeError:
code_list.append(RegionCode(name=c.name, hierarchy="Country"))

# importing from an external repository
for repo in config.definitions.region.repositories:
repo_path = (
config.repositories[repo].local_path / "definitions" / "region"
)

code_list = cls._parse_region_code_dir(
code_list,
repo_path,
file_glob_pattern,
repository=config.definitions.region.repositories,
)
code_list = cls._parse_and_replace_tags(
code_list, repo_path, file_glob_pattern
)
)
# special handling for countries that do not have an alpha_3 code
except AttributeError:
code_list.append(RegionCode(name=c.name, hierarchy="Country"))

# importing from an external repository
for repo in config.definitions.region.repositories:
repo_path = config.repositories[repo].local_path / "definitions" / "region"

code_list = cls._parse_region_code_dir(
code_list,
repo_path,
file_glob_pattern,
repository=config.definitions.region.repositories,
)
code_list = cls._parse_and_replace_tags(
code_list, repo_path, file_glob_pattern
)

# parse from current repository
code_list = cls._parse_region_code_dir(code_list, path, file_glob_pattern)
Expand Down
23 changes: 11 additions & 12 deletions nomenclature/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ def convert_to_set(v: str | list[str] | set[str]) -> set[str]:


class CodeListConfig(BaseModel):
dimension: str
repositories: Annotated[set[str] | None, BeforeValidator(convert_to_set)] = Field(
None, alias="repository"
dimension: str | None = None
repositories: Annotated[set[str], BeforeValidator(convert_to_set)] = Field(
default_factory=set, alias="repository"
)
model_config = ConfigDict(populate_by_name=True)

Expand Down Expand Up @@ -94,8 +94,8 @@ class DataStructureConfig(BaseModel):
"""

region: Optional[RegionCodeListConfig] = None
variable: Optional[CodeListConfig] = None
region: Optional[RegionCodeListConfig] = Field(default_factory=RegionCodeListConfig)
variable: Optional[CodeListConfig] = Field(default_factory=CodeListConfig)

@field_validator("region", "variable", mode="before")
@classmethod
Expand All @@ -107,30 +107,29 @@ def repos(self) -> dict[str, str]:
return {
dimension: getattr(self, dimension).repositories
for dimension in ("region", "variable")
if getattr(self, dimension) and getattr(self, dimension).repositories
if getattr(self, dimension).repositories
}


class RegionMappingConfig(BaseModel):
repositories: Annotated[set[str], BeforeValidator(convert_to_set)] = Field(
..., alias="repository"
default_factory=set, alias="repository"
)
model_config = ConfigDict(populate_by_name=True)


class NomenclatureConfig(BaseModel):
repositories: dict[str, Repository] = {}
definitions: Optional[DataStructureConfig] = None
mappings: Optional[RegionMappingConfig] = None
repositories: dict[str, Repository] = Field(default_factory=dict)
definitions: DataStructureConfig = Field(default_factory=DataStructureConfig)
mappings: RegionMappingConfig = Field(default_factory=RegionMappingConfig)

@model_validator(mode="after")
@classmethod
def check_definitions_repository(
cls, v: "NomenclatureConfig"
) -> "NomenclatureConfig":
definitions_repos = v.definitions.repos if v.definitions else {}
mapping_repos = {"mappings": v.mappings.repositories} if v.mappings else {}
repos = {**definitions_repos, **mapping_repos}
repos = {**v.definitions.repos, **mapping_repos}
for use, repositories in repos.items():
if repositories - v.repositories.keys():
raise ValueError((f"Unknown repository {repositories} in '{use}'."))
Expand Down
6 changes: 4 additions & 2 deletions nomenclature/definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@ def __init__(self, path, dimensions=None):
if (file := path.parent / "nomenclature.yaml").exists():
self.config = NomenclatureConfig.from_file(file=file)
else:
self.config = None
self.config = NomenclatureConfig()

if not path.is_dir() and (self.config is None or not self.config.repositories):
if not path.is_dir() and not (
self.config.repositories or self.config.definitions.region.country
):
raise NotADirectoryError(f"Definitions directory not found: {path}")

self.dimensions = dimensions or ["region", "variable"]
Expand Down
17 changes: 8 additions & 9 deletions nomenclature/processor/region.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,15 +483,14 @@ def from_directory(cls, path: DirectoryPath, dsd: DataStructureDefinition):

mapping_files = [f for f in path.glob("**/*") if f.suffix in {".yaml", ".yml"}]

if dsd.config and dsd.config.mappings:
for repository in dsd.config.mappings.repositories:
mapping_files.extend(
f
for f in (
dsd.config.repositories[repository].local_path / "mappings"
).glob("**/*")
if f.suffix in {".yaml", ".yml"}
)
for repository in dsd.config.mappings.repositories:
mapping_files.extend(
f
for f in (
dsd.config.repositories[repository].local_path / "mappings"
).glob("**/*")
if f.suffix in {".yaml", ".yml"}
)

for file in mapping_files:
try:
Expand Down
3 changes: 3 additions & 0 deletions tests/data/general-config-only-country/nomenclature.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
definitions:
region:
country: true
8 changes: 8 additions & 0 deletions tests/test_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ def test_definition_from_general_config(workflow_folder):
clean_up_external_repos(obs.config.repositories)


def test_definition_general_config_country_only():
obs = DataStructureDefinition(
TEST_DATA_DIR / "general-config-only-country" / "definitions",
dimensions=["region"],
)
assert all(region in obs.region for region in ("Austria", "Bolivia", "Kosovo"))


def test_to_excel(simple_definition, tmpdir):
"""Check writing a DataStructureDefinition to file"""
file = tmpdir / "testing_export.xlsx"
Expand Down

0 comments on commit f4627d9

Please sign in to comment.