"""Utilities for currency normalization within pricing and financial workflows.""" from __future__ import annotations import re from dataclasses import dataclass VALID_CURRENCY_PATTERN = re.compile(r"^[A-Z]{3}$") @dataclass(frozen=True) class CurrencyValidationError(ValueError): """Raised when a currency code fails validation.""" code: str def __str__(self) -> str: # pragma: no cover - dataclass repr not required in tests return f"Invalid currency code: {self.code!r}" def normalise_currency(code: str | None) -> str | None: """Normalise currency codes to uppercase ISO-4217 values.""" if code is None: return None candidate = code.strip().upper() if not VALID_CURRENCY_PATTERN.match(candidate): raise CurrencyValidationError(candidate) return candidate def require_currency(code: str | None, default: str | None = None) -> str: """Return normalised currency code, falling back to default when missing.""" normalised = normalise_currency(code) if normalised is not None: return normalised if default is None: raise CurrencyValidationError("") fallback = normalise_currency(default) if fallback is None: raise CurrencyValidationError("") return fallback