from __future__ import annotations import re from dataclasses import dataclass # UK postcode format, matched against the canonical (no-space, upper) value. # A pure structural check — it does not confirm the postcode actually exists # (that would need a network lookup, which a value object must not do). _UK_POSTCODE_RE = re.compile(r"[A-Z]{1,2}\d[A-Z\d]?\d[A-Z]{2}") @dataclass(frozen=True) class Postcode: value: str def __post_init__(self) -> None: # Frozen dataclass: bypass the descriptor with object.__setattr__. object.__setattr__(self, "value", "".join(self.value.split()).upper()) def __str__(self) -> str: return self.value def is_valid(self) -> bool: """Whether the canonical value is a well-formed UK postcode (format only — no existence check, so it is pure and offline).""" return _UK_POSTCODE_RE.fullmatch(self.value) is not None