From 10f6b882443782b2698cf9652c618e177ce68947 Mon Sep 17 00:00:00 2001 From: Derek Gulbranson Date: Thu, 5 Nov 2026 02:55:58 -0800 Subject: [PATCH 1/2] Simplify the config side's 3.0 changes: DEVIATES aliases, parser_for comments A quality-only pass over the 3.0 cycle's changes to _parser, _policy and the locale packs; no behavior, message, repr or pickle changes. - cs, sk, es, es_ar: DEVIATES is deviates_by_two_family_names itself, not a wrapper whose docstring restated (and in cs and sk had let go stale) the shared helper's - parser_for: the per-pack tag check rewraps through _rewrapped, as the patch loop does (same text); the "one choice" comment moves to the loop it describes; rules/D2's second quote moves from above the warning re-emission to the apply_patch wrap it covers - _no_strict_language: `if locales:` for a loop that ran at most once - PolicyPatch: the comment naming every scalar field by name states the rule instead, so a new field cannot make it stale Co-Authored-By: Claude Opus 5.5 --- nameparser/_parser.py | 30 +++++++++++++++--------------- nameparser/_policy.py | 7 ++----- nameparser/locales/cs.py | 7 ++----- nameparser/locales/es.py | 9 ++------- nameparser/locales/es_ar.py | 9 ++------- nameparser/locales/sk.py | 7 ++----- 6 files changed, 25 insertions(+), 44 deletions(-) diff --git a/nameparser/_parser.py b/nameparser/_parser.py index 895f3a21..edd2dcfd 100644 --- a/nameparser/_parser.py +++ b/nameparser/_parser.py @@ -458,10 +458,6 @@ def parser_for(*locales: Locale, base: Parser | None = None, # segmenter it was asked to drop. if segmenter is UNSET: segmenter = base.segmenter if base is not None else None - # one choice for all the packs and the base's, so applying them is - # order-free; each pack's tags are checked on their own first, so a - # tag no file holds is reported with the pack that named it - # (rules/D2) # strict's three states, as segmenter's: omitted keeps base's # strictness; an explicit False on a strict base is refused, a # strict choice not being undone by a later call, and an explicit @@ -475,14 +471,18 @@ def parser_for(*locales: Locale, base: Parser | None = None, "choice keeps it: leave strict out or pass strict=True, or " "choose every language in one call from the default lexicon, " "base's packs included, parser_for(*packs, strict=False)") + # one choice for all the packs and the base's, so applying them is + # order-free; each pack's tags are checked on their own first, so a + # tag no file holds is reported with the pack that named it + # (rules/D2) languages = lexicon.languages for loc in locales: if loc.languages: try: _reading_index_selected(loc.languages, strict) except ValueError as exc: - raise ValueError( - f"while applying locale {loc.code!r}: {exc}") from exc + raise _rewrapped( + exc, f"while applying locale {loc.code!r}") from exc languages |= loc.languages if strict and not languages: raise ValueError(_no_strict_language(locales)) @@ -508,6 +508,9 @@ def parser_for(*locales: Locale, base: Parser | None = None, scalars = [f.name for f in dataclasses.fields(PolicyPatch) if f.metadata.get("compose") != "union"] for loc in locales: + # rules/D2: "applying a locale pack wraps any such error with + # the locale's code, so a stacked configuration names which layer + # broke" (history: decisions/D2) try: policy = apply_patch(policy, loc.policy) except (TypeError, ValueError) as exc: @@ -528,9 +531,6 @@ def parser_for(*locales: Locale, base: Parser | None = None, # scripts with no covering surnames and no segmenter warns at # construction, naming the dead scripts and each way out" # (history: decisions/D1) - # rules/D2: "applying a locale pack wraps any such error with - # the locale's code, so a stacked configuration names which layer - # broke" (history: decisions/D2) # Construction warnings (the segmenterless-activation check in # Parser.__post_init__) re-emit from THIS frame: its stacklevel is # sized for direct Parser(...) construction, and through this @@ -548,17 +548,17 @@ def parser_for(*locales: Locale, base: Parser | None = None, def _no_strict_language(locales: tuple[Locale, ...]) -> str: """The refusal of a strict call that chooses no language, which would keep only mul.toml's and native-script vocabulary, dropping - every language's own words in silence. The fix it offers is a pack choosing the first pack's - code where that is a language a strict choice accepts, else - ``en``.""" + every language's own words in silence. The fix it offers is a pack + choosing the first pack's code where that is a language a strict + choice accepts, else ``en``.""" tag = "en" - for loc in locales[:1]: + if locales: try: - _reading_index_selected(frozenset({loc.code}), True) + _reading_index_selected(frozenset({locales[0].code}), True) except ValueError: pass else: - tag = loc.code + tag = locales[0].code codes = ", ".join(repr(loc.code) for loc in locales) named = ("no pack was given" if not locales else f"the pack {codes} chooses none" if len(locales) == 1 diff --git a/nameparser/_policy.py b/nameparser/_policy.py index 914ede53..f876ccaf 100644 --- a/nameparser/_policy.py +++ b/nameparser/_policy.py @@ -1008,11 +1008,8 @@ def __post_init__(self) -> None: f"{f.name} must be an iterable, got {value!r}{hint}" ) from None object.__setattr__(self, f.name, frozenset(value)) - # middle_as_family, lenient_comma_suffixes, - # unlisted_dotted_suffixes, unlisted_caps_suffixes, strip_emoji, - # strip_bidi, two_family_names and two_given_names are scalar - # (compose="override") - # fields and DELIBERATELY get no type check here, unlike + # the bool scalars, every field but the union ones, name_order + # and script_orders, DELIBERATELY get no type check here, unlike # name_order and the union fields above: a # PolicyPatch(strip_emoji="off") is constructible, and only raises # once apply_patch runs Policy.__post_init__'s bool check. This is diff --git a/nameparser/locales/cs.py b/nameparser/locales/cs.py index 2cb93852..051866ce 100644 --- a/nameparser/locales/cs.py +++ b/nameparser/locales/cs.py @@ -47,8 +47,5 @@ ) -def DEVIATES(name: str) -> bool: - """True when this pack's patch may parse `name` differently from the - default parser: rules/O6's declaration, shared with the es pack - (_locale.deviates_by_two_family_names).""" - return deviates_by_two_family_names(name) +# rules/O6's declaration, which every pack turning O6 on shares +DEVIATES = deviates_by_two_family_names diff --git a/nameparser/locales/es.py b/nameparser/locales/es.py index f848ecea..9834b235 100644 --- a/nameparser/locales/es.py +++ b/nameparser/locales/es.py @@ -39,10 +39,5 @@ ) -def DEVIATES(name: str) -> bool: - """True when this pack's patch or fragment may parse `name` - differently from the default parser: rules/O6's declaration, which - the cs and sk packs share (_locale.deviates_by_two_family_names); - the given-name list acts only where O6 does, so it declares - nothing more.""" - return deviates_by_two_family_names(name) +# rules/O6's declaration, which every pack turning O6 on shares +DEVIATES = deviates_by_two_family_names diff --git a/nameparser/locales/es_ar.py b/nameparser/locales/es_ar.py index 56da228e..e4b1f578 100644 --- a/nameparser/locales/es_ar.py +++ b/nameparser/locales/es_ar.py @@ -33,10 +33,5 @@ ) -def DEVIATES(name: str) -> bool: - """True when this pack's patch or fragment may parse `name` - differently from the default parser: rules/O6's declaration, which - every pack turning O6 on shares (_locale.deviates_by_two_family_names); - the given-name list acts only where O6 does, so it declares nothing - more.""" - return deviates_by_two_family_names(name) +# rules/O6's declaration, which every pack turning O6 on shares +DEVIATES = deviates_by_two_family_names diff --git a/nameparser/locales/sk.py b/nameparser/locales/sk.py index 21e84412..d4fbcdfe 100644 --- a/nameparser/locales/sk.py +++ b/nameparser/locales/sk.py @@ -46,8 +46,5 @@ ) -def DEVIATES(name: str) -> bool: - """True when this pack's patch may parse `name` differently from the - default parser: rules/O6's declaration, shared with the es pack - (_locale.deviates_by_two_family_names).""" - return deviates_by_two_family_names(name) +# rules/O6's declaration, which every pack turning O6 on shares +DEVIATES = deviates_by_two_family_names From da22a5fd47c8c2056bcd70330d5eb280b8bb2aa1 Mon Sep 17 00:00:00 2001 From: Derek Gulbranson Date: Thu, 5 Nov 2026 02:59:00 -0800 Subject: [PATCH 2/2] Correct the PolicyPatch scalar-fields comment: unlisted_caps_suffixes is not a bool Co-Authored-By: Claude Opus 5.5 --- nameparser/_policy.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nameparser/_policy.py b/nameparser/_policy.py index f876ccaf..a4f61fcb 100644 --- a/nameparser/_policy.py +++ b/nameparser/_policy.py @@ -1008,11 +1008,11 @@ def __post_init__(self) -> None: f"{f.name} must be an iterable, got {value!r}{hint}" ) from None object.__setattr__(self, f.name, frozenset(value)) - # the bool scalars, every field but the union ones, name_order - # and script_orders, DELIBERATELY get no type check here, unlike - # name_order and the union fields above: a + # Every other field (the scalars: the bools and + # unlisted_caps_suffixes) DELIBERATELY gets no type check here, + # unlike name_order and the union fields above: a # PolicyPatch(strip_emoji="off") is constructible, and only raises - # once apply_patch runs Policy.__post_init__'s bool check. This is + # once apply_patch runs Policy.__post_init__'s checks. This is # the one place the module's eager-validation ethos doesn't apply # -- see the class docstring ("Values are validated when the patch # is applied... not at patch construction").