"""Published IVA country-vocabulary indexes.
Private implementation for the canonical establishment resolvers.
"""
from __future__ import annotations
from collections.abc import Mapping
from typing import TYPE_CHECKING, TypeGuard
from ...core.text_fold import fold_printed_phrase
from .errors import IvaCatalogueError
if TYPE_CHECKING:
from ..calculations.registry.authority import PinnedAuthorityOperation
def _is_object_mapping(value: object) -> TypeGuard[Mapping[object, object]]:
"""Narrow one runtime component to an object-keyed mapping before validation."""
return isinstance(value, Mapping)
[docs]
def normalise_printed_country_name(printed: str) -> str:
"""Return the form a printed country name is matched under.
Three normalisations and no more. Case is folded because a document sets its
address block in whatever typography it likes. Runs of whitespace collapse to
one because a name broken across an address line arrives with the break in
it. Combining accents are folded away because invoicing systems routinely
print ASCII-only, so ``"Mexico"`` and ``"México"`` are the same printed name.
Punctuation is deliberately NOT stripped: ``"EE.UU."`` is carried in the
vocabulary with its stops, and squeezing punctuation generally would start
matching strings that are not names.
"""
return fold_printed_phrase(printed)
[docs]
def country_codes_by_printed_name(
*,
operation: PinnedAuthorityOperation,
) -> dict[str, str]:
"""Return every vocabulary name, normalised, mapped to its alpha-2 code.
Read from the published runtime authority rather than written here, so this
resolver shares the validated catalogue identity used by other consumers.
Raises:
IvaCatalogueError: When the bundled vocabulary cannot be read, names a
malformed code, or maps one normalised name to two different
countries. The last is the check that makes accent folding safe:
folding is only sound while no two distinct countries fold together,
and this refuses the table rather than resolving the collision to
whichever record happened to be read last.
"""
from ..calculations.registry.runtime_catalogues import CountryVocabularyRecord
loaded = operation.runtime_catalogue("countries")
if not _is_object_mapping(loaded):
raise IvaCatalogueError("indexed authority country component has an invalid shape")
records = tuple(value for value in loaded.values() if isinstance(value, CountryVocabularyRecord))
if len(records) != len(loaded):
raise IvaCatalogueError("indexed authority country component has an invalid shape")
resolved: dict[str, str] = {}
for record in records:
for name in record.names:
_claim_printed_country_name(resolved, name, code=record.code, target="published authority")
return resolved
def _claim_printed_country_name(
resolved: dict[str, str],
name: object,
*,
code: str,
target: str,
) -> None:
"""Bind one printed name to its country, refusing a blank name or a cross-country collision.
Raises:
IvaCatalogueError: When the name normalises to nothing, or when two
DIFFERENT countries claim one normalised name.
"""
from .errors import IvaCatalogueError
normalised = normalise_printed_country_name(str(name))
if not normalised:
raise IvaCatalogueError(f"{target}: country {code} carries a blank printed name")
claimed = resolved.get(normalised)
if claimed is not None and claimed != code:
raise IvaCatalogueError(
f"{target}: the printed name {name!r} normalises to {normalised!r}, which both "
f"{claimed} and {code} claim; a name that cannot name one country cannot establish one",
)
resolved[normalised] = code
[docs]
def country_codes_by_alpha3(
*,
operation: PinnedAuthorityOperation,
) -> dict[str, str]:
"""Return every vocabulary record's alpha-3 code mapped to its alpha-2 code.
Read from the same published country projection as the printed names, and
deliberately so: the alpha-3 form is a second way of
STATING the country that record already names, so recording it anywhere else
would put two authorities on one country.
Raises:
IvaCatalogueError: When the bundled vocabulary cannot be read or a record
breaks the one-code-one-country invariant.
"""
from ..calculations.registry.runtime_catalogues import CountryVocabularyRecord
loaded = operation.runtime_catalogue("countries")
if not _is_object_mapping(loaded):
raise IvaCatalogueError("indexed authority country component has an invalid shape")
records = tuple(value for value in loaded.values() if isinstance(value, CountryVocabularyRecord))
if len(records) != len(loaded):
raise IvaCatalogueError("indexed authority country component has an invalid shape")
resolved: dict[str, str] = {}
alpha3_by_code: dict[str, str] = {}
for record in records:
_claim_country_alpha3(
resolved,
alpha3_by_code,
target="published authority",
code=record.code,
alpha3=record.alpha3,
)
return resolved
def _claim_country_alpha3(
resolved: dict[str, str],
alpha3_by_code: dict[str, str],
*,
target: str,
code: str,
alpha3: str,
) -> None:
"""Bind one country identity, refusing either direction of contradiction."""
from .errors import IvaCatalogueError
claimed = resolved.get(alpha3)
if claimed is not None and claimed != code:
raise IvaCatalogueError(
f"{target}: the alpha-3 code {alpha3!r} is claimed by both {claimed} and {code}; "
f"a code that cannot name one country cannot establish one",
)
stated = alpha3_by_code.get(code)
if stated is not None and stated != alpha3:
raise IvaCatalogueError(
f"{target}: country {code} states two different alpha-3 codes, {stated!r} and {alpha3!r}",
)
resolved[alpha3] = code
alpha3_by_code[code] = alpha3