1"""Import-time hygiene checks shared by the vocabulary data modules.
2
3Every constant here is looked up in normalized form, so a stray capital
4or a surrounding space makes an entry unreachable by a direct membership
5test -- ``'actor ' in TITLES`` is False -- even though the parser's own
6ingest (:func:`nameparser._lexicon._normalize`) normalizes it away and
7papers over the typo. Checking at import turns a silently-inert entry
8into an immediate failure.
9
10Deliberately a weaker fold than ``_normalize``, which also
11NFC-composes and strips edge full stops: entries like ``'esq.'`` are
12legitimate data here, and this module cannot import ``_lexicon``
13anyway (``_lexicon`` imports these constants). The relationship checks
14between constants stay in the modules that own them -- those encode
15facts about the data, not hygiene.
16
17Interior whitespace is checked but not forbidden. A PHRASE entry is
18legitimate in the two fields ``_lexicon._PHRASE_FIELDS`` names, and
19``MAIDEN_MARKERS``'s ``'z domu'`` is the first one shipped; what this
20asserts of one is that it is stored as the single-spaced, lowercase,
21edge-clean form, so ``'z domu'`` and ``'Z domu '`` fail here rather
22than becoming entries nothing can match. The remaining half of a
23phrase's storage rule -- that each WORD is separately stripped of its
24periods, so ``'z. domu'`` is stored ``'z domu'`` -- is
25``_lexicon._title_key``'s and is deliberately NOT checked here. The
26reason is altitude, not layering: importing ``_lexicon`` from here
27would in fact work (its config imports are all inside
28``_default_lexicon()``, nothing at module scope), but a data module
29asserting things with the parser's own fold makes the constant's
30hygiene depend on the parser, and the question worth asking is not
31"is this entry pre-folded" but "does ``Lexicon`` store it unchanged".
32That is one assertion over ``_PHRASE_FIELDS`` in
33``tests/v2/test_ledger_guards.py``, the suite that already imports the
34parser's fold for exactly this purpose, and it is where an entry
35written ``'z. domu'`` is caught.
36"""
37from __future__ import annotations
38
39from collections.abc import Iterable
40
41
42def assert_normalized(name: str, entries: Iterable[str]) -> None:
43 """Assert every entry is stored lowercase, edge-clean, and -- where
44 it is a phrase -- single-spaced."""
45 # " ".join(w.split()) rather than w.strip(): it subsumes the strip
46 # and additionally collapses interior runs, which is what a phrase
47 # entry made worth checking. For a single word the two are
48 # identical, so no existing constant's verdict changes.
49 offenders = sorted(w for w in entries if w != " ".join(w.lower().split()))
50 assert not offenders, (
51 f"{name} entries must be stored lowercase, without edge "
52 f"whitespace, and single-spaced if a phrase; "
53 f"offending: {offenders}"
54 )