Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/nameparser/_locale.py: 53%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""The Locale type: a named delta over (Lexicon, Policy).
3A Locale dissolves at parser construction (parser_for, a later plan):
4lexicon fragments union onto the base, the PolicyPatch folds via
5apply_patch. Packs are pure data; they have no privileged capabilities.
7Layering: imports _lexicon and _policy only (enforced by
8tests/v2/test_layering.py).
9"""
10from __future__ import annotations
12import dataclasses
13import re
14from dataclasses import dataclass
16from nameparser._lexicon import Lexicon
17from nameparser._policy import UNSET, PolicyPatch
18from nameparser._types import _guarded_getstate, _guarded_setstate
21@dataclass(frozen=True, slots=True)
22class Locale:
23 """A named, shareable bundle of vocabulary and behavior for a
24 naming tradition: a lexicon fragment plus a policy patch, applied
25 together by :func:`nameparser.parser_for`. The packs shipped with
26 nameparser live in :mod:`nameparser.locales`; building your own
27 needs no registration -- construct one and pass it to
28 ``parser_for``. See the :doc:`locale packs guide </locales>` for
29 using, creating, and contributing packs."""
31 #: Identifier, lowercase ``[a-z0-9_]+`` (e.g. "ru", "tr_az").
32 code: str
33 #: Vocabulary ADDED to the base parser's lexicon (unioned; a pack
34 #: never removes base vocabulary).
35 lexicon: Lexicon
36 #: Behavior changes folded onto the base policy (set-valued fields
37 #: union; scalars override, later pack wins).
38 policy: PolicyPatch = PolicyPatch()
40 # in the class body so @dataclass(slots=True) keeps them
41 __getstate__ = _guarded_getstate
42 __setstate__ = _guarded_setstate
44 def __post_init__(self) -> None:
45 if not isinstance(self.code, str):
46 raise TypeError(
47 f"Locale.code must be a str, got {self.code!r}"
48 )
49 if not self.code.strip():
50 raise ValueError(
51 f"Locale.code must be a non-empty string, got {self.code!r}"
52 )
53 if self.code != self.code.lower():
54 raise ValueError(
55 f"Locale.code must be lowercase, got {self.code!r}"
56 )
57 # Codes are registry keys (parser_for, third-party packs): every
58 # accepted character is supported forever, so pin the charset
59 # while relaxing later is still compatible. One separator only --
60 # allowing '-' too would make tr-az and tr_az distinct keys.
61 if not re.fullmatch(r"[a-z0-9_]+", self.code):
62 raise ValueError(
63 f"Locale.code must match [a-z0-9_]+, got {self.code!r}"
64 )
65 if not isinstance(self.lexicon, Lexicon):
66 raise TypeError(
67 f"Locale.lexicon must be a Lexicon, got {self.lexicon!r}"
68 )
69 if not isinstance(self.policy, PolicyPatch):
70 raise TypeError(
71 f"Locale.policy must be a PolicyPatch, got {self.policy!r}"
72 )
74 def __repr__(self) -> str:
75 # Bounded: shows the code and which Policy fields the patch sets,
76 # never the Lexicon contents or the patched values themselves
77 # (design rule, see nameparser._types module docstring).
78 patched = [f.name for f in dataclasses.fields(self.policy)
79 if getattr(self.policy, f.name) is not UNSET]
80 suffix = f": {', '.join(patched)}" if patched else ""
81 return f"Locale({self.code!r}{suffix})"