nameparser 2.1.0__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nameparser-2.1.0/nameparser.egg-info → nameparser-2.2.0}/PKG-INFO +3 -1
- {nameparser-2.1.0 → nameparser-2.2.0}/README.rst +2 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/__main__.py +1 -1
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/_config_shim.py +41 -41
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/_facade.py +59 -20
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/_lexicon.py +101 -47
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/_parser.py +29 -21
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/_pipeline/_assemble.py +4 -0
- nameparser-2.2.0/nameparser/_pipeline/_assign.py +429 -0
- nameparser-2.2.0/nameparser/_pipeline/_classify.py +228 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/_pipeline/_extract.py +81 -27
- nameparser-2.2.0/nameparser/_pipeline/_group.py +989 -0
- nameparser-2.2.0/nameparser/_pipeline/_pieces.py +313 -0
- nameparser-2.2.0/nameparser/_pipeline/_post_rules.py +629 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/_pipeline/_script_segment.py +26 -115
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/_pipeline/_segment.py +22 -25
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/_pipeline/_state.py +46 -8
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/_pipeline/_tokenize.py +16 -27
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/_pipeline/_vocab.py +154 -5
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/_policy.py +15 -3
- nameparser-2.2.0/nameparser/_render.py +316 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/_types.py +138 -24
- nameparser-2.2.0/nameparser/_version.py +19 -0
- nameparser-2.2.0/nameparser/config/__init__.py +41 -0
- nameparser-2.2.0/nameparser/config/_deprecated.py +141 -0
- nameparser-2.2.0/nameparser/config/_invariants.py +53 -0
- nameparser-2.2.0/nameparser/config/bound_first_names.py +23 -0
- nameparser-2.2.0/nameparser/config/bound_given_names.py +60 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/config/conjunctions.py +2 -2
- nameparser-2.2.0/nameparser/config/maiden_markers.py +111 -0
- nameparser-2.2.0/nameparser/config/particles.py +275 -0
- nameparser-2.2.0/nameparser/config/prefixes.py +38 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/config/suffixes.py +133 -41
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/config/surnames.py +3 -4
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/config/titles.py +58 -19
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/locales/__init__.py +4 -3
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/locales/ja.py +6 -3
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/locales/ru.py +5 -3
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/locales/tr_az.py +3 -2
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/locales/zh.py +2 -1
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/parser.py +2 -1
- {nameparser-2.1.0 → nameparser-2.2.0/nameparser.egg-info}/PKG-INFO +3 -1
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser.egg-info/SOURCES.txt +15 -2
- nameparser-2.1.0/tests/test_bound_first_names.py → nameparser-2.2.0/tests/test_bound_given_names.py +58 -1
- nameparser-2.2.0/tests/test_capitalization.py +364 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/test_conjunctions.py +1 -1
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/test_first_name.py +24 -4
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/test_initials.py +27 -6
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/test_nicknames.py +0 -1
- nameparser-2.1.0/tests/test_prefixes.py → nameparser-2.2.0/tests/test_particles.py +29 -25
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/test_python_api.py +1 -1
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/test_titles.py +3 -2
- nameparser-2.2.0/tests/v2/_differential_fixtures.py +109 -0
- nameparser-2.2.0/tests/v2/cases.py +3601 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/pipeline/test_assign.py +190 -5
- nameparser-2.2.0/tests/v2/pipeline/test_group.py +1130 -0
- nameparser-2.2.0/tests/v2/pipeline/test_pieces.py +103 -0
- nameparser-2.2.0/tests/v2/pipeline/test_post_rules.py +816 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/pipeline/test_script_segment.py +1 -1
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/pipeline/test_state.py +8 -2
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/pipeline/test_vocab.py +83 -2
- nameparser-2.2.0/tests/v2/rules_doc.py +200 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/test_benchmark.py +154 -14
- nameparser-2.2.0/tests/v2/test_cases.py +72 -0
- nameparser-2.2.0/tests/v2/test_config_aliases.py +404 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/test_config_shim.py +9 -6
- nameparser-2.2.0/tests/v2/test_contracts.py +241 -0
- nameparser-2.2.0/tests/v2/test_differential.py +1361 -0
- nameparser-2.2.0/tests/v2/test_doc_citations.py +176 -0
- nameparser-2.2.0/tests/v2/test_doc_spellings.py +30 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/test_facade.py +45 -2
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/test_facade_cases.py +14 -1
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/test_layering.py +63 -5
- nameparser-2.2.0/tests/v2/test_ledger_guards.py +2452 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/test_lexicon.py +36 -4
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/test_locales.py +23 -10
- nameparser-2.2.0/tests/v2/test_parser.py +1499 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/test_properties.py +19 -6
- nameparser-2.2.0/tests/v2/test_regex_sync.py +211 -0
- nameparser-2.2.0/tests/v2/test_render.py +641 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/test_reprs.py +15 -1
- nameparser-2.2.0/tests/v2/test_rules_doc.py +145 -0
- nameparser-2.2.0/tests/v2/test_rules_doc_grammar.py +95 -0
- nameparser-2.1.0/nameparser/_pipeline/_assign.py +0 -354
- nameparser-2.1.0/nameparser/_pipeline/_classify.py +0 -97
- nameparser-2.1.0/nameparser/_pipeline/_group.py +0 -408
- nameparser-2.1.0/nameparser/_pipeline/_post_rules.py +0 -130
- nameparser-2.1.0/nameparser/_render.py +0 -177
- nameparser-2.1.0/nameparser/_version.py +0 -9
- nameparser-2.1.0/nameparser/config/__init__.py +0 -20
- nameparser-2.1.0/nameparser/config/_invariants.py +0 -27
- nameparser-2.1.0/nameparser/config/bound_first_names.py +0 -28
- nameparser-2.1.0/nameparser/config/maiden_markers.py +0 -70
- nameparser-2.1.0/nameparser/config/prefixes.py +0 -130
- nameparser-2.1.0/tests/test_capitalization.py +0 -144
- nameparser-2.1.0/tests/v2/cases.py +0 -1807
- nameparser-2.1.0/tests/v2/pipeline/test_group.py +0 -252
- nameparser-2.1.0/tests/v2/pipeline/test_post_rules.py +0 -122
- nameparser-2.1.0/tests/v2/test_cases.py +0 -26
- nameparser-2.1.0/tests/v2/test_contracts.py +0 -104
- nameparser-2.1.0/tests/v2/test_differential.py +0 -696
- nameparser-2.1.0/tests/v2/test_parser.py +0 -813
- nameparser-2.1.0/tests/v2/test_regex_sync.py +0 -366
- nameparser-2.1.0/tests/v2/test_render.py +0 -290
- {nameparser-2.1.0 → nameparser-2.2.0}/AUTHORS +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/LICENSE +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/MANIFEST.in +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/__init__.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/_locale.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/_pipeline/__init__.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/config/capitalization.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/config/regexes.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/py.typed +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser/util.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser.egg-info/dependency_links.txt +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser.egg-info/requires.txt +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/nameparser.egg-info/top_level.txt +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/pyproject.toml +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/setup.cfg +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/__init__.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/base.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/conftest.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/test_brute_force.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/test_comma_variants.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/test_constants.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/test_east_slavic_patronymic_order.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/test_middle_name_as_last.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/test_output_format.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/test_suffixes.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/test_turkic_patronymic_order.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/test_variations.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/__init__.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/conftest.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/pipeline/__init__.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/pipeline/test_assemble.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/pipeline/test_classify.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/pipeline/test_extract.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/pipeline/test_segment.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/pipeline/test_tokenize.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/test_cli.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/test_locale.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/test_policy.py +0 -0
- {nameparser-2.1.0 → nameparser-2.2.0}/tests/v2/test_types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nameparser
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: A simple Python module for parsing human names into their individual components.
|
|
5
5
|
Author-email: Derek Gulbranson <derek73@gmail.com>
|
|
6
6
|
License: LGPL
|
|
@@ -75,6 +75,8 @@ Quick Start Example
|
|
|
75
75
|
]>
|
|
76
76
|
>>> name.family_base, name.family_particles
|
|
77
77
|
('Vega', 'de la')
|
|
78
|
+
>>> name.given_names, name.surnames
|
|
79
|
+
('Juan Q. Xavier', 'Q. Xavier de la Vega')
|
|
78
80
|
>>> name.render("{family}, {given}")
|
|
79
81
|
'de la Vega, Juan'
|
|
80
82
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
"""v1 ``Constants`` compatibility shim over Lexicon/Policy
|
|
2
|
-
|
|
1
|
+
"""v1 ``Constants`` compatibility shim over Lexicon/Policy
|
|
2
|
+
(mechanisms.md#CONFIG-SHIM-SNAPSHOT). ``nameparser.config``
|
|
3
|
+
re-exports these names from the swap
|
|
3
4
|
commit onward; the whole module is deleted in 3.0 with the facade.
|
|
4
5
|
|
|
5
6
|
Layering: facade layer -- may import anything public; here that's
|
|
@@ -99,7 +100,7 @@ def _normalize_iterable_of_strings(
|
|
|
99
100
|
class SetManager:
|
|
100
101
|
"""v1 ``SetManager`` surface over a plain set of ``lc()``-normalized
|
|
101
102
|
strings. Mutations call ``_on_change`` (the owning Constants'
|
|
102
|
-
generation bump, wired by
|
|
103
|
+
generation bump, wired by the facade). ``__call__`` and the
|
|
103
104
|
missing-member-tolerant ``remove()`` are gone per the #243 schedule
|
|
104
105
|
(warned 1.3.0, removed 2.0): ``remove()`` of a missing member raises
|
|
105
106
|
``KeyError``, matching ``set.remove``.
|
|
@@ -316,7 +317,7 @@ class TupleManager(dict[str, object]):
|
|
|
316
317
|
``AttributeError`` naming the key (#256, warned 1.4, enforced 2.0 --
|
|
317
318
|
the v1 ``DeprecationWarning`` is gone, this shim only speaks 2.0).
|
|
318
319
|
Mutations call ``_on_change`` (the owning Constants' generation
|
|
319
|
-
bump, wired by
|
|
320
|
+
bump, wired by the facade).
|
|
320
321
|
"""
|
|
321
322
|
|
|
322
323
|
_on_change: Callable[[], None] | None
|
|
@@ -431,7 +432,7 @@ class TupleManager(dict[str, object]):
|
|
|
431
432
|
|
|
432
433
|
|
|
433
434
|
#: The named delimiter buckets, translated to the ``Policy``
|
|
434
|
-
#: (open, close) pairs they stand for
|
|
435
|
+
#: (open, close) pairs they stand for. The first three are
|
|
435
436
|
#: v1's; the rest are the #273 typographic conventions, named so the
|
|
436
437
|
#: v1 keyed idioms (pop/move/del) work on them like the originals.
|
|
437
438
|
#: Keep in sync with DEFAULT_NICKNAME_DELIMITERS in _policy.py (pinned
|
|
@@ -472,8 +473,8 @@ class RegexTupleManager(TupleManager): # pickle-compat: do NOT delete
|
|
|
472
473
|
|
|
473
474
|
class _DelimiterManager(TupleManager):
|
|
474
475
|
"""v1 ``nickname_delimiters``/``maiden_delimiters`` bucket. In 2.0
|
|
475
|
-
only the named sentinels in ``_DELIMITER_SENTINELS`` exist (
|
|
476
|
-
|
|
476
|
+
only the named sentinels in ``_DELIMITER_SENTINELS`` exist (the v1
|
|
477
|
+
trio plus the #273 typographic pairs) -- assigning any
|
|
477
478
|
other key raises so a caller reaches for a custom-delimiter Policy
|
|
478
479
|
kwarg instead of a dict entry that silently does nothing. ``pop()``/
|
|
479
480
|
``__setitem__``/``__delitem__`` stay open (inherited) for the
|
|
@@ -517,7 +518,7 @@ class _RegexesProxy:
|
|
|
517
518
|
``CONSTANTS.regexes.word`` stays informational -- but 2.0 configures
|
|
518
519
|
parsing behavior through named ``Policy`` flags, not by mutating a
|
|
519
520
|
regex, so any attribute *or* item assignment raises ``TypeError``
|
|
520
|
-
(
|
|
521
|
+
(the shim's uniform read-only rule).
|
|
521
522
|
"""
|
|
522
523
|
|
|
523
524
|
@staticmethod
|
|
@@ -664,36 +665,38 @@ _SHARED_MUTATION_MESSAGE = (
|
|
|
664
665
|
)
|
|
665
666
|
|
|
666
667
|
|
|
667
|
-
def _default_vocab() -> dict[str,
|
|
668
|
+
def _default_vocab() -> dict[str, frozenset[str]]:
|
|
668
669
|
# v1 data modules stay the single vocabulary source through 2.x
|
|
669
670
|
# (same rule as Lexicon.default()).
|
|
670
|
-
from nameparser.config.
|
|
671
|
+
from nameparser.config.bound_given_names import BOUND_GIVEN_NAMES
|
|
671
672
|
from nameparser.config.conjunctions import CONJUNCTIONS
|
|
672
|
-
from nameparser.config.
|
|
673
|
-
|
|
673
|
+
from nameparser.config.particles import (
|
|
674
|
+
NON_GIVEN_NAME_PARTICLES, PARTICLES,
|
|
674
675
|
)
|
|
675
676
|
from nameparser.config.suffixes import (
|
|
676
|
-
SUFFIX_ACRONYMS, SUFFIX_ACRONYMS_AMBIGUOUS,
|
|
677
|
+
SUFFIX_ACRONYMS, SUFFIX_ACRONYMS_AMBIGUOUS, SUFFIX_WORDS,
|
|
677
678
|
)
|
|
678
|
-
from nameparser.config.titles import
|
|
679
|
+
from nameparser.config.titles import GIVEN_NAME_TITLES, TITLES
|
|
679
680
|
return {
|
|
680
|
-
"prefixes":
|
|
681
|
+
"prefixes": PARTICLES,
|
|
681
682
|
"suffix_acronyms": SUFFIX_ACRONYMS,
|
|
682
|
-
"suffix_not_acronyms":
|
|
683
|
+
"suffix_not_acronyms": SUFFIX_WORDS,
|
|
683
684
|
"suffix_acronyms_ambiguous": SUFFIX_ACRONYMS_AMBIGUOUS,
|
|
684
685
|
"titles": TITLES,
|
|
685
|
-
"first_name_titles":
|
|
686
|
+
"first_name_titles": GIVEN_NAME_TITLES,
|
|
686
687
|
"conjunctions": CONJUNCTIONS,
|
|
687
|
-
"bound_first_names":
|
|
688
|
-
"non_first_name_prefixes":
|
|
688
|
+
"bound_first_names": BOUND_GIVEN_NAMES,
|
|
689
|
+
"non_first_name_prefixes": NON_GIVEN_NAME_PARTICLES,
|
|
689
690
|
}
|
|
690
691
|
|
|
691
692
|
|
|
692
693
|
class _RenderDefaults(NamedTuple):
|
|
693
694
|
"""v1 scalar rendering knobs that have no home on ``Policy``
|
|
694
|
-
(
|
|
695
|
+
(mechanisms.md#CONFIG-SHIM-SNAPSHOT): ``__str__``/initials
|
|
696
|
+
formatting and capitalization stay
|
|
695
697
|
per-Constants defaults, layered onto a shared ``Parser`` by the
|
|
696
|
-
facade (
|
|
698
|
+
facade (nameparser/_facade.py) rather than folded into the cache
|
|
699
|
+
key."""
|
|
697
700
|
|
|
698
701
|
string_format: str | None
|
|
699
702
|
initials_format: str
|
|
@@ -707,7 +710,8 @@ class _RenderDefaults(NamedTuple):
|
|
|
707
710
|
@functools.lru_cache(maxsize=64)
|
|
708
711
|
def _cached_parser(lexicon: Lexicon, policy: Policy) -> Parser:
|
|
709
712
|
# keyed on hashable value objects: shared across every facade whose
|
|
710
|
-
# Constants resolve to the same snapshot
|
|
713
|
+
# Constants resolve to the same snapshot
|
|
714
|
+
# (mechanisms.md#CONFIG-SHIM-SNAPSHOT)
|
|
711
715
|
return Parser(lexicon=lexicon, policy=policy)
|
|
712
716
|
|
|
713
717
|
|
|
@@ -716,8 +720,8 @@ class Constants:
|
|
|
716
720
|
a frozen ``(Lexicon, Policy, _RenderDefaults)`` snapshot via
|
|
717
721
|
``_snapshot()``. ``_generation`` increments on every mutation;
|
|
718
722
|
facades compare it against a cached value to decide whether their
|
|
719
|
-
snapshot is stale (dirty-tracking
|
|
720
|
-
|
|
723
|
+
snapshot is stale (dirty-tracking -- the facade side lives in
|
|
724
|
+
nameparser/_facade.py).
|
|
721
725
|
|
|
722
726
|
The module-level ``CONSTANTS`` singleton (below) has ``_shared``
|
|
723
727
|
flipped to ``True``: any mutation reached through it emits
|
|
@@ -980,7 +984,7 @@ class Constants:
|
|
|
980
984
|
|
|
981
985
|
def _build_snapshot(self) -> tuple[Lexicon, Policy, _RenderDefaults]:
|
|
982
986
|
"""Resolve this v1-shaped, mutable Constants into the frozen
|
|
983
|
-
2.0 value objects it corresponds to
|
|
987
|
+
2.0 value objects it corresponds to. A pure read: no
|
|
984
988
|
generation bump, no deprecation warning even on the shared
|
|
985
989
|
singleton -- only direct attribute mutation is on the 3.0
|
|
986
990
|
removal path.
|
|
@@ -1038,9 +1042,9 @@ class Constants:
|
|
|
1038
1042
|
particles=particles,
|
|
1039
1043
|
# complement translation: v1 marks the never-given subset;
|
|
1040
1044
|
# v2 marks the may-be-given subset. The trailing union keeps
|
|
1041
|
-
# a config v1 accepted:
|
|
1042
|
-
# no word in both
|
|
1043
|
-
#
|
|
1045
|
+
# a config v1 accepted: particles.py asserts its own data has
|
|
1046
|
+
# no word in both NON_GIVEN_NAME_PARTICLES and
|
|
1047
|
+
# BOUND_GIVEN_NAMES, but nothing stops a caller adding one at
|
|
1044
1048
|
# runtime, and v1 then lets the bound rule win (leading "dos
|
|
1045
1049
|
# Santos Silva" parses first="dos Santos"). Treating such a
|
|
1046
1050
|
# word as may-be-given reproduces that rather than raising.
|
|
@@ -1062,23 +1066,19 @@ class Constants:
|
|
|
1062
1066
|
bound_given_names=bound,
|
|
1063
1067
|
# v1 Constants has no manager for these (#274 is 2.0
|
|
1064
1068
|
# behavior); the data module is the only source
|
|
1065
|
-
maiden_markers=
|
|
1069
|
+
maiden_markers=MAIDEN_MARKERS,
|
|
1066
1070
|
# likewise no v1 manager: the unspaced-name segmentation
|
|
1067
1071
|
# vocabulary is 2.0 behavior (#271), so it rides in the
|
|
1068
1072
|
# snapshot only -- v1's Constants surface stays frozen.
|
|
1069
|
-
# Unwrapped where maiden_markers above is wrapped: this
|
|
1070
|
-
# module is born frozen (#293), so no wrap
|
|
1071
1073
|
surnames=KOREAN_SURNAMES,
|
|
1072
1074
|
# likewise no v1 manager: the glued-honorific tail set is
|
|
1073
1075
|
# 2.1 behavior (#308), so it rides in the snapshot only.
|
|
1074
|
-
#
|
|
1075
|
-
#
|
|
1076
|
-
#
|
|
1077
|
-
#
|
|
1078
|
-
#
|
|
1079
|
-
|
|
1080
|
-
# gets against suffix_acronyms above.
|
|
1081
|
-
honorific_tails=frozenset(GLUED_HONORIFICS) & suffix_words,
|
|
1076
|
+
# Intersect with the word set: Lexicon enforces tails <=
|
|
1077
|
+
# suffix_words, and v1 semantics are that deleting a suffix
|
|
1078
|
+
# word turns the behavior off -- a lingering tail simply
|
|
1079
|
+
# stops mattering, the same rule ambiguous_acronyms gets
|
|
1080
|
+
# against suffix_acronyms above.
|
|
1081
|
+
honorific_tails=GLUED_HONORIFICS & suffix_words,
|
|
1082
1082
|
# TupleManager is dict[str, object] (v1 parity: values were
|
|
1083
1083
|
# never statically str-typed); every real entry is a str,
|
|
1084
1084
|
# same assumption _DelimiterManager's sentinel lookup makes
|
|
@@ -1099,8 +1099,8 @@ class Constants:
|
|
|
1099
1099
|
_SENTINEL_PAIRS[k] for k in self.maiden_delimiters
|
|
1100
1100
|
if k not in self.nickname_delimiters),
|
|
1101
1101
|
# suffix_delimiter is a _RenderDefaults-only field here; the
|
|
1102
|
-
# facade layers it onto extra_suffix_delimiters per
|
|
1103
|
-
#
|
|
1102
|
+
# facade layers it onto extra_suffix_delimiters per
|
|
1103
|
+
# instance -- _snapshot() itself stays pure translation
|
|
1104
1104
|
)
|
|
1105
1105
|
defaults = _RenderDefaults(
|
|
1106
1106
|
self.string_format, self.initials_format, self.initials_delimiter,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
"""The 2.0 ``HumanName`` facade (
|
|
1
|
+
"""The 2.0 ``HumanName`` facade (mechanisms.md#FACADE-CONTRACT): a
|
|
2
|
+
mutable wrapper
|
|
2
3
|
over a frozen ParsedName, delegating parsing to the core Parser resolved
|
|
3
4
|
from the bound Constants shim. Keeps every v1 spelling. Deleted in 3.0.
|
|
4
5
|
|
|
@@ -31,7 +32,8 @@ import nameparser._render as _render
|
|
|
31
32
|
from nameparser._config_shim import CONSTANTS, Constants, _cached_parser
|
|
32
33
|
from nameparser._lexicon import _normalize
|
|
33
34
|
from nameparser._parser import Parser
|
|
34
|
-
from nameparser._types import FOLDED_TAG,
|
|
35
|
+
from nameparser._types import (FOLDED_TAG, UNCLASSIFIED_TAG, ParsedName,
|
|
36
|
+
Role, Token)
|
|
35
37
|
|
|
36
38
|
_V2_FIELD = {"first": "given", "last": "family"} # v1 name -> v2 name
|
|
37
39
|
_V1_SPELLING = {v2: v1 for v1, v2 in _V2_FIELD.items()}
|
|
@@ -41,7 +43,8 @@ _MEMBERS = tuple(_V1_SPELLING.get(r.value, r.value) for r in Role)
|
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
|
|
44
|
-
#: v1 parsing hooks the facade never calls
|
|
46
|
+
#: v1 parsing hooks the facade never calls
|
|
47
|
+
#: (mechanisms.md#FACADE-CONTRACT / #280).
|
|
45
48
|
_V1_HOOKS = (
|
|
46
49
|
"pre_process", "post_process", "parse_full_name", "parse_pieces",
|
|
47
50
|
"parse_nicknames", "join_on_conjunctions", "squash_emoji",
|
|
@@ -152,7 +155,8 @@ class HumanName:
|
|
|
152
155
|
DeprecationWarning, stacklevel=3)
|
|
153
156
|
|
|
154
157
|
# -- render defaults -----------------------------------------------------
|
|
155
|
-
# One-line validating setters (
|
|
158
|
+
# One-line validating setters (mechanisms.md#FACADE-CONTRACT):
|
|
159
|
+
# assigning a non-str (or, for
|
|
156
160
|
# the two fields that allow it, non-str-non-None) raises TypeError at
|
|
157
161
|
# assignment time instead of failing later inside .format().
|
|
158
162
|
|
|
@@ -218,7 +222,8 @@ class HumanName:
|
|
|
218
222
|
# -- config / parsing ---------------------------------------------------
|
|
219
223
|
|
|
220
224
|
def _resolve(self) -> Parser:
|
|
221
|
-
"""Dirty-tracked parser resolution
|
|
225
|
+
"""Dirty-tracked parser resolution
|
|
226
|
+
(mechanisms.md#CONFIG-SHIM-SNAPSHOT): rebuild the
|
|
222
227
|
snapshot only when the bound Constants' generation moved."""
|
|
223
228
|
gen = self._C._generation
|
|
224
229
|
if self._snapshot_gen != gen:
|
|
@@ -472,10 +477,14 @@ class HumanName:
|
|
|
472
477
|
return _normalize(text) in self._lexicon.conjunctions
|
|
473
478
|
|
|
474
479
|
def _split_last(self) -> tuple[list[str], list[str]]:
|
|
475
|
-
#
|
|
476
|
-
#
|
|
477
|
-
#
|
|
478
|
-
#
|
|
480
|
+
# rules.md#R2: "a name part whose every word is particle
|
|
481
|
+
# vocabulary is a part where none of them is doing a
|
|
482
|
+
# particle's work" -- the all-particle guard
|
|
483
|
+
# below is this rule, and predates its statement: v1 assumed a
|
|
484
|
+
# family name does not consist entirely of particles, e.g. the
|
|
485
|
+
# surname "Do" which also appears in PARTICLES. v1
|
|
486
|
+
# parser.py _split_last otherwise verbatim, vocabulary lookup
|
|
487
|
+
# at ACCESS time so assigned last names split too.
|
|
479
488
|
words = " ".join(self.last_list).split()
|
|
480
489
|
i = 0
|
|
481
490
|
while i < len(words) and self._is_particle(words[i]):
|
|
@@ -517,20 +526,40 @@ class HumanName:
|
|
|
517
526
|
if len(initials) > 0:
|
|
518
527
|
return self.initials_separator.join(initials)
|
|
519
528
|
# Return '' (never empty_attribute_default, which may be None)
|
|
520
|
-
# when a part has no initialable words
|
|
521
|
-
#
|
|
522
|
-
#
|
|
529
|
+
# when a part has no initialable words. group_initials below
|
|
530
|
+
# decides what that means: one such element among others is
|
|
531
|
+
# dropped; a group that yields nothing AND is wholly particles
|
|
532
|
+
# initials its words; and a group that yields nothing for any
|
|
533
|
+
# other reason -- a conjunction, or particles mixed with one --
|
|
534
|
+
# is still dropped ("Vega, Santa de y" drops its middle).
|
|
523
535
|
return ""
|
|
524
536
|
|
|
525
537
|
def _initials_lists(self) -> tuple[list[str], list[str], list[str]]:
|
|
526
538
|
"""Initials for the first, middle and last name groups. Parts
|
|
527
|
-
that yield no initials
|
|
528
|
-
|
|
539
|
+
that yield no initials are dropped rather than kept as empty
|
|
540
|
+
strings -- except a part that is wholly PARTICLES, whose words
|
|
541
|
+
initial as ordinary name words since #404, so the prefix-only
|
|
542
|
+
middle name "de la" is no longer an example of the dropping.
|
|
529
543
|
"""
|
|
530
544
|
def group_initials(names: list[str],
|
|
531
545
|
firstname: bool = False) -> list[str]:
|
|
532
|
-
|
|
533
|
-
|
|
546
|
+
got = [i for i in (self._process_initial(n, firstname)
|
|
547
|
+
for n in names if n) if i]
|
|
548
|
+
words = [w for n in names if n for w in n.split()]
|
|
549
|
+
if got or not words or not all(self._is_particle(w)
|
|
550
|
+
for w in words):
|
|
551
|
+
return got
|
|
552
|
+
# rules.md#R3: "except the particles of a part whose every
|
|
553
|
+
# word is one, which are not acting as particles there"
|
|
554
|
+
# -- nothing survived
|
|
555
|
+
# the filter, so the whole group is particles. The
|
|
556
|
+
# facade's twin of the core's
|
|
557
|
+
# UNJOINED_TAG. NOT pinned against it: both case runners
|
|
558
|
+
# compare the seven role fields only, and Case carries no
|
|
559
|
+
# initials column, so the one covering test is
|
|
560
|
+
# tests/test_initials.py::test_initials_middle_name_all_prefixes. _split_last already applies the same guard to
|
|
561
|
+
# the base, which is why last_base was never empty here.
|
|
562
|
+
return [w[0] for w in words]
|
|
534
563
|
return (group_initials(self.first_list, True),
|
|
535
564
|
group_initials(self.middle_list),
|
|
536
565
|
group_initials(self.last_list))
|
|
@@ -685,7 +714,8 @@ class HumanName:
|
|
|
685
714
|
self._suffix_delimiter = state.get("suffix_delimiter",
|
|
686
715
|
defaults.suffix_delimiter)
|
|
687
716
|
self._full_name = state.get("_full_name", "")
|
|
688
|
-
# Components come back exactly as pickled
|
|
717
|
+
# Components come back exactly as pickled
|
|
718
|
+
# (mechanisms.md#FACADE-CONTRACT): synthetic
|
|
689
719
|
# tokens, never a re-parse. Build them per *_list ENTRY rather
|
|
690
720
|
# than from one joined string -- an entry may hold several words
|
|
691
721
|
# ("Ph. D.", "Q.C. M.P."), and re-splitting the joined string on
|
|
@@ -717,8 +747,17 @@ class HumanName:
|
|
|
717
747
|
f"nameparser"
|
|
718
748
|
)
|
|
719
749
|
for position, word in enumerate(entry.split()):
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
750
|
+
# UNCLASSIFIED_TAG for the same reason replace()
|
|
751
|
+
# stamps it: a pickle carries the *_list STRINGS
|
|
752
|
+
# and no tags, so nothing here was read by a parse
|
|
753
|
+
# and case repair must ask the vocabulary rather
|
|
754
|
+
# than read an absent conjunction tag. Without it a
|
|
755
|
+
# restored "juan ortega y gasset" repairs to
|
|
756
|
+
# "Ortega Y Gasset", which is neither v1's answer
|
|
757
|
+
# nor the same name's unpickled one.
|
|
758
|
+
tags = {UNCLASSIFIED_TAG}
|
|
759
|
+
if position:
|
|
760
|
+
tags.add("joined")
|
|
761
|
+
tokens.append(Token(word, None, role, frozenset(tags)))
|
|
723
762
|
self._parsed = ParsedName(
|
|
724
763
|
original=str(state.get("original", "")), tokens=tuple(tokens))
|
|
@@ -55,7 +55,7 @@ _VOCAB_FIELDS = (
|
|
|
55
55
|
#: nothing needs the acronym half: the shipped tails are CJK
|
|
56
56
|
#: honorifics, which are words.
|
|
57
57
|
#: The same relation is asserted a second time in config/suffixes.py,
|
|
58
|
-
#: over the raw GLUED_HONORIFICS/
|
|
58
|
+
#: over the raw GLUED_HONORIFICS/SUFFIX_WORDS constants at
|
|
59
59
|
#: import. The two are not redundant in the way they look: that one
|
|
60
60
|
#: is an `assert`, stripped under `python -O`, while the check here
|
|
61
61
|
#: raises unconditionally -- so under -O this is what still holds the
|
|
@@ -75,6 +75,25 @@ _SUBSET_FIELDS = (
|
|
|
75
75
|
)
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
#: The fields whose entries may be PHRASES: stored space-joined and
|
|
79
|
+
#: folded per word (_title_key), and exempt from the dead-entry warning
|
|
80
|
+
#: below. Every other field is matched one word at a time, where a
|
|
81
|
+
#: multi-word entry can never match.
|
|
82
|
+
#:
|
|
83
|
+
#: The two are not the same mechanism, and the difference is why the
|
|
84
|
+
#: exemption is a list rather than a rule. A given_name_titles run is
|
|
85
|
+
#: identified per word FIRST -- 'lt' and 'col' are each title
|
|
86
|
+
#: vocabulary -- and only then joined and looked up. A maiden marker
|
|
87
|
+
#: phrase has no such per-word foothold: 'z' and 'domu' are not markers
|
|
88
|
+
#: individually, and adding them separately (which this warning used to
|
|
89
|
+
#: advise) reads 'Maria Kowalska z domu Nowak' as maiden 'domu Nowak'
|
|
90
|
+
#: and strips 'Anna z Nowak' of its family name. So markers are matched
|
|
91
|
+
#: by a genuine multi-token lookahead instead --
|
|
92
|
+
#: _pipeline._vocab.maiden_marker_run, longest first -- and only the
|
|
93
|
+
#: STORAGE rule is shared with titles.
|
|
94
|
+
_PHRASE_FIELDS = ("given_name_titles", "maiden_markers")
|
|
95
|
+
|
|
96
|
+
|
|
78
97
|
def _normalize(word: str) -> str:
|
|
79
98
|
"""Lowercase, strip whitespace and EDGE periods -- v1's lc()
|
|
80
99
|
semantics. Interior periods survive on purpose: 'J.R.' must not
|
|
@@ -110,8 +129,9 @@ def _title_key(words: Iterable[str]) -> str:
|
|
|
110
129
|
A multi-word title is matched as one key ('lt col'), so the fold has
|
|
111
130
|
to run per word and rejoin -- _normalize on the whole phrase would
|
|
112
131
|
leave interior periods. Defined once because it is built at match
|
|
113
|
-
time (post_rules
|
|
114
|
-
|
|
132
|
+
time (post_rules for H1, group for the P5 licence -- which must
|
|
133
|
+
agree, see #369) and at translation time (the v1 facade's
|
|
134
|
+
first_name_titles), and a divergence between them fails silently:
|
|
115
135
|
the entry simply stops matching.
|
|
116
136
|
|
|
117
137
|
Words that fold away are DROPPED, not joined as empty. Keeping the
|
|
@@ -191,13 +211,14 @@ def _normset(
|
|
|
191
211
|
raise TypeError(
|
|
192
212
|
f"Lexicon.{field_name} entries must be strings, got {w!r}"
|
|
193
213
|
)
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
# never match anything
|
|
198
|
-
# Every other field holds single words, where the two
|
|
199
|
-
# agree.
|
|
200
|
-
|
|
214
|
+
# A phrase field's entries are matched as a multi-word run, so
|
|
215
|
+
# they fold per word: stored as the same key the match site
|
|
216
|
+
# builds, or 'lt. col' (and 'z. domu') would be kept verbatim
|
|
217
|
+
# and never match anything -- a silent no-op on the config
|
|
218
|
+
# surface. Every other field holds single words, where the two
|
|
219
|
+
# folds agree. See _PHRASE_FIELDS for how the two differ once
|
|
220
|
+
# stored.
|
|
221
|
+
n = _title_key(w.split()) if field_name in _PHRASE_FIELDS \
|
|
201
222
|
else _normalize(w)
|
|
202
223
|
# "." or "" is a data bug (stray split artifact, empty CSV
|
|
203
224
|
# cell); dropping it silently would also let a data-module typo
|
|
@@ -207,7 +228,7 @@ def _normset(
|
|
|
207
228
|
f"Lexicon.{field_name} entry {w!r} normalizes to empty "
|
|
208
229
|
f"(lowercase + strip periods/whitespace leaves nothing)"
|
|
209
230
|
)
|
|
210
|
-
# Every field
|
|
231
|
+
# Every field outside _PHRASE_FIELDS is matched one word at a
|
|
211
232
|
# time, so a multi-word entry can never match -- the library
|
|
212
233
|
# itself shipped eight such dead entries for years (repaired
|
|
213
234
|
# 2026-07-26). Warn, never raise: an inert entry produces
|
|
@@ -217,8 +238,10 @@ def _normset(
|
|
|
217
238
|
# new instance's __post_init__; remove() stores nothing, so
|
|
218
239
|
# warning there would name entries the caller is trying to get
|
|
219
240
|
# RID of, with "split it" advice that makes no sense for a
|
|
220
|
-
# no-op.
|
|
221
|
-
|
|
241
|
+
# no-op. Both invariants are indifferent to WHICH fields are
|
|
242
|
+
# exempt: the exemption only decides whether a warning exists to
|
|
243
|
+
# be emitted once or suppressed.
|
|
244
|
+
if (warn and field_name not in _PHRASE_FIELDS
|
|
222
245
|
# interior whitespace test; split() covers all Unicode
|
|
223
246
|
# whitespace
|
|
224
247
|
and n != "".join(n.split())):
|
|
@@ -303,8 +326,12 @@ class Lexicon:
|
|
|
303
326
|
Entries are normalized at construction -- lowercased, edge periods
|
|
304
327
|
stripped -- so matching is case-insensitive. Vocabulary entries are
|
|
305
328
|
single words -- a multi-word entry warns at construction and can
|
|
306
|
-
never match
|
|
307
|
-
|
|
329
|
+
never match. Two fields are exempt, and they differ in HOW they
|
|
330
|
+
match: ``given_name_titles`` is looked up as the space-joined run
|
|
331
|
+
of words the parse has ALREADY read as titles, while
|
|
332
|
+
``maiden_markers`` is matched by lookahead, longest first, over
|
|
333
|
+
words that need not be markers on their own (``"z domu"``).
|
|
334
|
+
Field docs below show examples, not full
|
|
308
335
|
contents; inspect any field's shipped vocabulary directly, e.g.
|
|
309
336
|
``Lexicon.default().conjunctions``."""
|
|
310
337
|
|
|
@@ -313,7 +340,7 @@ class Lexicon:
|
|
|
313
340
|
titles: frozenset[str] = frozenset()
|
|
314
341
|
#: Titles whose single following name reads as a GIVEN name
|
|
315
342
|
#: ("sheikh", "sister", ...) rather than a family name. Full
|
|
316
|
-
#: default list: :data:`~nameparser.config.titles.
|
|
343
|
+
#: default list: :data:`~nameparser.config.titles.GIVEN_NAME_TITLES`.
|
|
317
344
|
given_name_titles: frozenset[str] = frozenset()
|
|
318
345
|
#: Post-nominal acronym suffixes, matched with or without periods
|
|
319
346
|
#: ("phd" matches "PhD" and "Ph.D."). Full default list:
|
|
@@ -321,7 +348,7 @@ class Lexicon:
|
|
|
321
348
|
suffix_acronyms: frozenset[str] = frozenset()
|
|
322
349
|
#: Post-nominal word suffixes ("jr", "esquire", "iii", ...). Full
|
|
323
350
|
#: default list:
|
|
324
|
-
#: :data:`~nameparser.config.suffixes.
|
|
351
|
+
#: :data:`~nameparser.config.suffixes.SUFFIX_WORDS`.
|
|
325
352
|
suffix_words: frozenset[str] = frozenset()
|
|
326
353
|
#: Subset of suffix_acronyms counted as suffixes only when written
|
|
327
354
|
#: WITH periods -- their bare forms are common surnames ("ma",
|
|
@@ -330,14 +357,32 @@ class Lexicon:
|
|
|
330
357
|
suffix_acronyms_ambiguous: frozenset[str] = frozenset()
|
|
331
358
|
#: Family-name particles that chain onto the following piece
|
|
332
359
|
#: ("van", "de", "bin", ...). Full default list:
|
|
333
|
-
#: :data:`~nameparser.config.
|
|
360
|
+
#: :data:`~nameparser.config.particles.PARTICLES`.
|
|
334
361
|
particles: frozenset[str] = frozenset()
|
|
335
|
-
#: Subset of particles that can also BE a given name
|
|
336
|
-
#:
|
|
337
|
-
#:
|
|
338
|
-
#:
|
|
362
|
+
#: Subset of particles that can also BE a given name ("Van
|
|
363
|
+
#: Johnson", but also "Van Buren"). Membership decides nothing
|
|
364
|
+
#: about chaining: the prefix chain skips the name's first piece
|
|
365
|
+
#: and never consults this set, so it leaves a leading particle a
|
|
366
|
+
#: piece of its own whether listed or not -- "de Mesnil" groups
|
|
367
|
+
#: into two pieces exactly as "van Gogh" does, and since #367 the
|
|
368
|
+
#: NAME in "Dr. de Mesnil" and "Dr. Van Johnson" groups into those
|
|
369
|
+
#: same two pieces behind the title piece, a title not being
|
|
370
|
+
#: part of the name it precedes. What membership
|
|
371
|
+
#: decides is what becomes of that piece afterwards. Under ANY
|
|
372
|
+
#: ``name_order`` a member records a particle-or-given ambiguity
|
|
373
|
+
#: and a non-member records none, and a non-member is additionally
|
|
374
|
+
#: folded back into the family name once roles exist WHERE IT OPENS
|
|
375
|
+
#: THE NAME, so the whole name is the surname ("de Mesnil" -- a bare "de", with nothing to
|
|
376
|
+
#: fold into, is left alone). That fold is order-independent too
|
|
377
|
+
#: (#359) -- not because the word could not be a given name, which
|
|
378
|
+
#: it can where position forces it (``parse("de")`` reports given
|
|
379
|
+
#: "de"), but because a particle OPENING the name has the rest of
|
|
380
|
+
#: the name to join forward to, and that evidence is positional
|
|
381
|
+
#: rather than vocabulary, so no declared order contradicts it. Which field a MEMBER's piece
|
|
382
|
+
#: lands in is ``name_order``'s question, not this set's.
|
|
383
|
+
#: No constant of its own -- the default derives
|
|
339
384
|
#: as particles minus
|
|
340
|
-
#: :data:`~nameparser.config.
|
|
385
|
+
#: :data:`~nameparser.config.particles.NON_GIVEN_NAME_PARTICLES`
|
|
341
386
|
#: (which marks the opposite, never-given subset).
|
|
342
387
|
particles_ambiguous: frozenset[str] = frozenset()
|
|
343
388
|
#: Words or characters that join surrounding pieces into one
|
|
@@ -347,10 +392,15 @@ class Lexicon:
|
|
|
347
392
|
#: Given-name prefixes that bind to the following word to form one
|
|
348
393
|
#: given name ("abdul" -> "Abdul Salam"); never standalone names.
|
|
349
394
|
#: Full default list:
|
|
350
|
-
#: :data:`~nameparser.config.
|
|
395
|
+
#: :data:`~nameparser.config.bound_given_names.BOUND_GIVEN_NAMES`.
|
|
351
396
|
bound_given_names: frozenset[str] = frozenset()
|
|
352
397
|
#: Marker words introducing a birth surname, routed to the maiden
|
|
353
|
-
#: field ("née", "geb.", "
|
|
398
|
+
#: field ("née", "geb.", "rozená", ...). An entry may be a PHRASE
|
|
399
|
+
#: ("z domu"): entries are matched by lookahead, longest first, so
|
|
400
|
+
#: a phrase wins where it matches and a word entry that starts one
|
|
401
|
+
#: still matches on its own everywhere else. A phrase matches only
|
|
402
|
+
#: where its words stand together -- a bracketed clause or a comma
|
|
403
|
+
#: between them ends the run. Full default list:
|
|
354
404
|
#: :data:`~nameparser.config.maiden_markers.MAIDEN_MARKERS`.
|
|
355
405
|
maiden_markers: frozenset[str] = frozenset()
|
|
356
406
|
#: Family names for the unspaced-name segmentation stage (#271),
|
|
@@ -419,8 +469,8 @@ class Lexicon:
|
|
|
419
469
|
# the expensive one -- three working configurations broken
|
|
420
470
|
# across two attempts. Do not add a third.
|
|
421
471
|
#
|
|
422
|
-
# The v2 form of
|
|
423
|
-
# from-
|
|
472
|
+
# The v2 form of particles.py's NON_GIVEN_NAME_PARTICLES-disjoint-
|
|
473
|
+
# from-BOUND_GIVEN_NAMES assertion. That module guards its own
|
|
424
474
|
# data at import; this guards vocabulary a caller supplies.
|
|
425
475
|
contradictory = (
|
|
426
476
|
self.bound_given_names & self.particles) - self.particles_ambiguous
|
|
@@ -614,39 +664,43 @@ class Lexicon:
|
|
|
614
664
|
@functools.cache
|
|
615
665
|
def _default_lexicon() -> Lexicon:
|
|
616
666
|
# v1 data modules are the single source of vocabulary through 2.x.
|
|
617
|
-
from nameparser.config.
|
|
667
|
+
from nameparser.config.bound_given_names import BOUND_GIVEN_NAMES
|
|
618
668
|
from nameparser.config.capitalization import CAPITALIZATION_EXCEPTIONS
|
|
619
669
|
from nameparser.config.conjunctions import CONJUNCTIONS
|
|
620
670
|
from nameparser.config.maiden_markers import MAIDEN_MARKERS
|
|
621
|
-
from nameparser.config.
|
|
671
|
+
from nameparser.config.particles import NON_GIVEN_NAME_PARTICLES, PARTICLES
|
|
622
672
|
from nameparser.config.suffixes import (
|
|
623
673
|
GLUED_HONORIFICS, SUFFIX_ACRONYMS, SUFFIX_ACRONYMS_AMBIGUOUS,
|
|
624
|
-
|
|
674
|
+
SUFFIX_WORDS,
|
|
625
675
|
)
|
|
626
676
|
from nameparser.config.surnames import KOREAN_SURNAMES
|
|
627
|
-
from nameparser.config.titles import
|
|
628
|
-
|
|
629
|
-
#
|
|
630
|
-
#
|
|
677
|
+
from nameparser.config.titles import GIVEN_NAME_TITLES, TITLES
|
|
678
|
+
|
|
679
|
+
# every vocabulary constant is a frozenset since #293, so each one
|
|
680
|
+
# feeds its strictly-typed frozenset[str] field as it stands -- and
|
|
681
|
+
# this cache reading them ONCE is the reason they are frozen. A
|
|
682
|
+
# mutated module set always reached a freshly built Constants, and
|
|
683
|
+
# reached this Lexicon only when the edit landed before the first
|
|
684
|
+
# call; after it, the cache was already built and the same edit was
|
|
685
|
+
# invisible here. Which of the two a program got was not something
|
|
686
|
+
# the code doing the mutating could see.
|
|
631
687
|
# keep in sync with _config_shim.Constants._snapshot() (pinned by the
|
|
632
688
|
# default-Constants equality test in tests/v2/test_config_shim.py)
|
|
633
689
|
return Lexicon(
|
|
634
|
-
titles=
|
|
635
|
-
given_name_titles=
|
|
636
|
-
suffix_acronyms=
|
|
637
|
-
suffix_words=
|
|
638
|
-
suffix_acronyms_ambiguous=
|
|
639
|
-
particles=
|
|
690
|
+
titles=TITLES,
|
|
691
|
+
given_name_titles=GIVEN_NAME_TITLES,
|
|
692
|
+
suffix_acronyms=SUFFIX_ACRONYMS,
|
|
693
|
+
suffix_words=SUFFIX_WORDS,
|
|
694
|
+
suffix_acronyms_ambiguous=SUFFIX_ACRONYMS_AMBIGUOUS,
|
|
695
|
+
particles=PARTICLES,
|
|
640
696
|
# FLIPPED from v1: v1 marks the never-given subset; v2 marks the
|
|
641
697
|
# may-be-given subset (migration: complement translation).
|
|
642
|
-
particles_ambiguous=
|
|
643
|
-
conjunctions=
|
|
644
|
-
bound_given_names=
|
|
645
|
-
maiden_markers=
|
|
646
|
-
# surnames.py is born frozen (#293) -- no call-site wrap needed,
|
|
647
|
-
# unlike the v1 modules above (their wraps drop when #293 lands)
|
|
698
|
+
particles_ambiguous=PARTICLES - NON_GIVEN_NAME_PARTICLES,
|
|
699
|
+
conjunctions=CONJUNCTIONS,
|
|
700
|
+
bound_given_names=BOUND_GIVEN_NAMES,
|
|
701
|
+
maiden_markers=MAIDEN_MARKERS,
|
|
648
702
|
surnames=KOREAN_SURNAMES,
|
|
649
|
-
honorific_tails=
|
|
703
|
+
honorific_tails=GLUED_HONORIFICS,
|
|
650
704
|
# pass canonical pair-tuples so this strictly-typed call site never
|
|
651
705
|
# feeds a Mapping to the tuple-annotated field; __post_init__
|
|
652
706
|
# still tolerates a Mapping at runtime for interactive use
|