nameparser 2.3.0__tar.gz → 2.4.1__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.3.0/nameparser.egg-info → nameparser-2.4.1}/PKG-INFO +28 -13
- {nameparser-2.3.0 → nameparser-2.4.1}/README.rst +26 -12
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/__init__.py +2 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/_config_shim.py +226 -85
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/_facade.py +225 -56
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/_lexicon.py +168 -16
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/_parser.py +6 -2
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/_pipeline/_assemble.py +47 -5
- nameparser-2.4.1/nameparser/_pipeline/_assign.py +1465 -0
- nameparser-2.4.1/nameparser/_pipeline/_classify.py +351 -0
- nameparser-2.4.1/nameparser/_pipeline/_comma.py +437 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/_pipeline/_extract.py +15 -10
- nameparser-2.4.1/nameparser/_pipeline/_group.py +1535 -0
- nameparser-2.4.1/nameparser/_pipeline/_pieces.py +1957 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/_pipeline/_post_rules.py +87 -203
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/_pipeline/_script_segment.py +150 -64
- nameparser-2.4.1/nameparser/_pipeline/_segment.py +210 -0
- nameparser-2.4.1/nameparser/_pipeline/_state.py +359 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/_pipeline/_tokenize.py +16 -11
- nameparser-2.4.1/nameparser/_pipeline/_vocab.py +1498 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/_policy.py +170 -22
- nameparser-2.4.1/nameparser/_render.py +694 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/_types.py +239 -23
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/_version.py +4 -4
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/config/_invariants.py +2 -3
- nameparser-2.4.1/nameparser/config/capitalization.py +76 -0
- nameparser-2.4.1/nameparser/config/conjunctions.py +92 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/config/particles.py +29 -1
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/config/regexes.py +10 -1
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/config/suffixes.py +50 -34
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/config/titles.py +75 -1
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/locales/ja.py +3 -1
- {nameparser-2.3.0 → nameparser-2.4.1/nameparser.egg-info}/PKG-INFO +28 -13
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser.egg-info/SOURCES.txt +7 -1
- {nameparser-2.3.0 → nameparser-2.4.1}/pyproject.toml +14 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_capitalization.py +242 -64
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_constants.py +3 -3
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_initials.py +37 -5
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_output_format.py +1 -1
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_suffixes.py +10 -5
- nameparser-2.4.1/tests/v2/_differential_fixtures.py +55 -0
- nameparser-2.4.1/tests/v2/cases.py +11996 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/conftest.py +14 -0
- nameparser-2.4.1/tests/v2/pipeline/test_assemble.py +252 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/pipeline/test_assign.py +354 -21
- nameparser-2.4.1/tests/v2/pipeline/test_classify.py +815 -0
- nameparser-2.4.1/tests/v2/pipeline/test_comma.py +396 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/pipeline/test_group.py +956 -87
- nameparser-2.4.1/tests/v2/pipeline/test_pieces.py +1298 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/pipeline/test_post_rules.py +152 -29
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/pipeline/test_script_segment.py +127 -18
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/pipeline/test_segment.py +35 -44
- nameparser-2.4.1/tests/v2/pipeline/test_state.py +365 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/pipeline/test_tokenize.py +4 -2
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/pipeline/test_vocab.py +380 -3
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/rules_doc.py +22 -1
- nameparser-2.4.1/tests/v2/test_benchmark.py +1139 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_cases.py +119 -9
- nameparser-2.4.1/tests/v2/test_cldr_agreement.py +208 -0
- nameparser-2.4.1/tests/v2/test_cldr_corpus.py +118 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_config_aliases.py +1 -2
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_config_shim.py +429 -2
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_contracts.py +3 -0
- nameparser-2.4.1/tests/v2/test_corpora.py +190 -0
- nameparser-2.4.1/tests/v2/test_differential.py +454 -0
- nameparser-2.4.1/tests/v2/test_doc_citations.py +393 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_facade.py +465 -5
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_facade_cases.py +100 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_layering.py +18 -6
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_lexicon.py +237 -10
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_locales.py +3 -0
- nameparser-2.4.1/tests/v2/test_name_usage_tool.py +303 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_order_correspondence.py +1 -1
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_parser.py +421 -78
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_policy.py +159 -3
- nameparser-2.4.1/tests/v2/test_properties.py +3334 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_regex_sync.py +42 -7
- nameparser-2.4.1/tests/v2/test_render.py +1695 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_rules_doc.py +11 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_rules_doc_grammar.py +2 -6
- nameparser-2.4.1/tests/v2/test_snapshot.py +48 -0
- nameparser-2.3.0/nameparser/_pipeline/_assign.py +0 -718
- nameparser-2.3.0/nameparser/_pipeline/_classify.py +0 -230
- nameparser-2.3.0/nameparser/_pipeline/_group.py +0 -978
- nameparser-2.3.0/nameparser/_pipeline/_pieces.py +0 -503
- nameparser-2.3.0/nameparser/_pipeline/_segment.py +0 -90
- nameparser-2.3.0/nameparser/_pipeline/_state.py +0 -156
- nameparser-2.3.0/nameparser/_pipeline/_vocab.py +0 -544
- nameparser-2.3.0/nameparser/_render.py +0 -316
- nameparser-2.3.0/nameparser/config/capitalization.py +0 -16
- nameparser-2.3.0/nameparser/config/conjunctions.py +0 -37
- nameparser-2.3.0/tests/v2/_differential_fixtures.py +0 -120
- nameparser-2.3.0/tests/v2/cases.py +0 -5529
- nameparser-2.3.0/tests/v2/pipeline/test_assemble.py +0 -133
- nameparser-2.3.0/tests/v2/pipeline/test_classify.py +0 -98
- nameparser-2.3.0/tests/v2/pipeline/test_pieces.py +0 -252
- nameparser-2.3.0/tests/v2/pipeline/test_state.py +0 -132
- nameparser-2.3.0/tests/v2/test_benchmark.py +0 -344
- nameparser-2.3.0/tests/v2/test_differential.py +0 -4792
- nameparser-2.3.0/tests/v2/test_doc_citations.py +0 -176
- nameparser-2.3.0/tests/v2/test_ledger_guards.py +0 -5077
- nameparser-2.3.0/tests/v2/test_properties.py +0 -557
- nameparser-2.3.0/tests/v2/test_render.py +0 -660
- {nameparser-2.3.0 → nameparser-2.4.1}/AUTHORS +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/LICENSE +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/MANIFEST.in +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/__main__.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/_locale.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/_pipeline/__init__.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/config/__init__.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/config/_deprecated.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/config/bound_first_names.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/config/bound_given_names.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/config/maiden_markers.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/config/prefixes.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/config/surnames.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/locales/__init__.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/locales/ru.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/locales/tr_az.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/locales/zh.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/parser.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/py.typed +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser/util.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser.egg-info/dependency_links.txt +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser.egg-info/requires.txt +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/nameparser.egg-info/top_level.txt +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/setup.cfg +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/__init__.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/base.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/conftest.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_bound_given_names.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_brute_force.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_comma_variants.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_conjunctions.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_east_slavic_patronymic_order.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_first_name.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_middle_name_as_last.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_nicknames.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_particles.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_python_api.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_titles.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_turkic_patronymic_order.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/test_variations.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/__init__.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/pipeline/__init__.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/pipeline/test_extract.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_cli.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_doc_spellings.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_locale.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/tests/v2/test_reprs.py +0 -0
- {nameparser-2.3.0 → nameparser-2.4.1}/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.4.1
|
|
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
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
17
18
|
Classifier: Development Status :: 5 - Production/Stable
|
|
18
19
|
Classifier: Natural Language :: English
|
|
19
20
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
@@ -35,19 +36,25 @@ nameparser parses human names into seven fields — title, given, middle,
|
|
|
35
36
|
family, suffix, nickname, maiden. Results are immutable, configuration is
|
|
36
37
|
composable, and locale packs are opt-in.
|
|
37
38
|
|
|
38
|
-
📣 **nameparser 2.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
📣 **nameparser 2.4 is out.** It uses letter case to read post-nominal
|
|
40
|
+
credentials much more reliably — ``Jack MA`` is Jack with an MA and
|
|
41
|
+
``Jack Ma`` is the surname Ma, and ``Jane Smith, PhD MA`` keeps both
|
|
42
|
+
credentials together — recognizes more naming conventions (Catalan,
|
|
43
|
+
Irish, Finnish, Malay and others), and writes credentials the usual way
|
|
44
|
+
when it repairs case. The `release log
|
|
45
|
+
<https://nameparser.readthedocs.io/en/latest/release_log.html>`__ has the
|
|
46
|
+
details.
|
|
47
|
+
|
|
48
|
+
Chinese, Japanese and Korean names written in their own scripts are read
|
|
49
|
+
family-first, unspaced Korean names are split against the census surname
|
|
50
|
+
list, and CJK honorifics are recognized. See `East Asian names
|
|
49
51
|
<https://nameparser.readthedocs.io/en/latest/usage.html#east-asian-names>`__.
|
|
50
52
|
|
|
53
|
+
Existing ``HumanName`` code from 1.x keeps working through 2.x, and most
|
|
54
|
+
of it needs no changes; see `Coming from 1.x`_ below. Please `open an
|
|
55
|
+
issue <https://github.com/derek73/python-nameparser/issues>`__ for anything
|
|
56
|
+
that parses wrong.
|
|
57
|
+
|
|
51
58
|
Installation
|
|
52
59
|
------------
|
|
53
60
|
|
|
@@ -80,6 +87,13 @@ Quick Start Example
|
|
|
80
87
|
>>> name.render("{family}, {given}")
|
|
81
88
|
'de la Vega, Juan'
|
|
82
89
|
|
|
90
|
+
>>> parse("Jack MA").suffix # capitals mark a credential
|
|
91
|
+
'MA'
|
|
92
|
+
>>> parse("Jack Ma").family # and a surname
|
|
93
|
+
'Ma'
|
|
94
|
+
>>> parse("Jane Smith, PhD MA").suffix
|
|
95
|
+
'PhD MA'
|
|
96
|
+
|
|
83
97
|
>>> parse("김민준").family # Korean: unspaced, split on the census list
|
|
84
98
|
'김'
|
|
85
99
|
>>> parse("高橋 みなみ").family # Japanese: kanji with kana, family first
|
|
@@ -119,8 +133,9 @@ deprecations 1.3 and 1.4 announced, so if your test suite runs clean on
|
|
|
119
133
|
Two things that check will not catch: four removals 1.4 never warned
|
|
120
134
|
about (three raise on contact, the fourth only warns), and one that
|
|
121
135
|
changes results silently — ``name == "John Smith"`` is now ``False``.
|
|
136
|
+
Later 2.x releases add a few breaks of their own.
|
|
122
137
|
`Migrating from HumanName <https://nameparser.readthedocs.io/en/latest/migrate.html>`__
|
|
123
|
-
covers
|
|
138
|
+
covers all of them, and translates a v1 customization into the new API whenever
|
|
124
139
|
that's convenient for you.
|
|
125
140
|
|
|
126
141
|
See the `release log <https://nameparser.readthedocs.io/en/latest/release_log.html>`__
|
|
@@ -7,19 +7,25 @@ nameparser parses human names into seven fields — title, given, middle,
|
|
|
7
7
|
family, suffix, nickname, maiden. Results are immutable, configuration is
|
|
8
8
|
composable, and locale packs are opt-in.
|
|
9
9
|
|
|
10
|
-
📣 **nameparser 2.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
📣 **nameparser 2.4 is out.** It uses letter case to read post-nominal
|
|
11
|
+
credentials much more reliably — ``Jack MA`` is Jack with an MA and
|
|
12
|
+
``Jack Ma`` is the surname Ma, and ``Jane Smith, PhD MA`` keeps both
|
|
13
|
+
credentials together — recognizes more naming conventions (Catalan,
|
|
14
|
+
Irish, Finnish, Malay and others), and writes credentials the usual way
|
|
15
|
+
when it repairs case. The `release log
|
|
16
|
+
<https://nameparser.readthedocs.io/en/latest/release_log.html>`__ has the
|
|
17
|
+
details.
|
|
18
|
+
|
|
19
|
+
Chinese, Japanese and Korean names written in their own scripts are read
|
|
20
|
+
family-first, unspaced Korean names are split against the census surname
|
|
21
|
+
list, and CJK honorifics are recognized. See `East Asian names
|
|
21
22
|
<https://nameparser.readthedocs.io/en/latest/usage.html#east-asian-names>`__.
|
|
22
23
|
|
|
24
|
+
Existing ``HumanName`` code from 1.x keeps working through 2.x, and most
|
|
25
|
+
of it needs no changes; see `Coming from 1.x`_ below. Please `open an
|
|
26
|
+
issue <https://github.com/derek73/python-nameparser/issues>`__ for anything
|
|
27
|
+
that parses wrong.
|
|
28
|
+
|
|
23
29
|
Installation
|
|
24
30
|
------------
|
|
25
31
|
|
|
@@ -52,6 +58,13 @@ Quick Start Example
|
|
|
52
58
|
>>> name.render("{family}, {given}")
|
|
53
59
|
'de la Vega, Juan'
|
|
54
60
|
|
|
61
|
+
>>> parse("Jack MA").suffix # capitals mark a credential
|
|
62
|
+
'MA'
|
|
63
|
+
>>> parse("Jack Ma").family # and a surname
|
|
64
|
+
'Ma'
|
|
65
|
+
>>> parse("Jane Smith, PhD MA").suffix
|
|
66
|
+
'PhD MA'
|
|
67
|
+
|
|
55
68
|
>>> parse("김민준").family # Korean: unspaced, split on the census list
|
|
56
69
|
'김'
|
|
57
70
|
>>> parse("高橋 みなみ").family # Japanese: kanji with kana, family first
|
|
@@ -91,8 +104,9 @@ deprecations 1.3 and 1.4 announced, so if your test suite runs clean on
|
|
|
91
104
|
Two things that check will not catch: four removals 1.4 never warned
|
|
92
105
|
about (three raise on contact, the fourth only warns), and one that
|
|
93
106
|
changes results silently — ``name == "John Smith"`` is now ``False``.
|
|
107
|
+
Later 2.x releases add a few breaks of their own.
|
|
94
108
|
`Migrating from HumanName <https://nameparser.readthedocs.io/en/latest/migrate.html>`__
|
|
95
|
-
covers
|
|
109
|
+
covers all of them, and translates a v1 customization into the new API whenever
|
|
96
110
|
that's convenient for you.
|
|
97
111
|
|
|
98
112
|
See the `release log <https://nameparser.readthedocs.io/en/latest/release_log.html>`__
|
|
@@ -16,6 +16,7 @@ from nameparser._policy import (
|
|
|
16
16
|
FAMILY_FIRST_GIVEN_LAST,
|
|
17
17
|
GIVEN_FIRST,
|
|
18
18
|
UNSET,
|
|
19
|
+
CapsSuffixes,
|
|
19
20
|
PatronymicRule,
|
|
20
21
|
Policy,
|
|
21
22
|
PolicyPatch,
|
|
@@ -40,6 +41,7 @@ __all__ = [
|
|
|
40
41
|
"Span", "Role", "Token", "Ambiguity", "AmbiguityKind", "ParsedName",
|
|
41
42
|
"STABLE_TAGS", "Segmentation", "Segmenter",
|
|
42
43
|
"Lexicon", "Policy", "PolicyPatch", "PatronymicRule", "Script", "UNSET",
|
|
44
|
+
"CapsSuffixes",
|
|
43
45
|
"GIVEN_FIRST", "FAMILY_FIRST", "FAMILY_FIRST_GIVEN_LAST",
|
|
44
46
|
"DEFAULT_NICKNAME_DELIMITERS", "DEFAULT_SCRIPT_ORDERS", "Locale",
|
|
45
47
|
"Parser", "parse", "parser_for",
|
|
@@ -28,7 +28,9 @@ from collections.abc import (
|
|
|
28
28
|
)
|
|
29
29
|
from typing import NamedTuple, Self
|
|
30
30
|
|
|
31
|
-
from nameparser._lexicon import
|
|
31
|
+
from nameparser._lexicon import (
|
|
32
|
+
Lexicon, _MaskValueError, _normalize, _title_key, _warn_dead_entry,
|
|
33
|
+
)
|
|
32
34
|
from nameparser._parser import Parser
|
|
33
35
|
from nameparser._policy import PatronymicRule, Policy
|
|
34
36
|
from nameparser.util import lc
|
|
@@ -433,8 +435,9 @@ class TupleManager(dict[str, object]):
|
|
|
433
435
|
|
|
434
436
|
#: The named delimiter buckets, translated to the ``Policy``
|
|
435
437
|
#: (open, close) pairs they stand for. The first three are
|
|
436
|
-
#: v1's; the rest are the #273 typographic conventions
|
|
437
|
-
#:
|
|
438
|
+
#: v1's; the rest are the #273 typographic conventions plus #597's
|
|
439
|
+
#: halfwidth corner brackets, named so the v1 keyed idioms
|
|
440
|
+
#: (pop/move/del) work on them like the originals.
|
|
438
441
|
#: Keep in sync with DEFAULT_NICKNAME_DELIMITERS in _policy.py (pinned
|
|
439
442
|
#: by the default-Constants equality test).
|
|
440
443
|
_SENTINEL_PAIRS = {
|
|
@@ -448,6 +451,7 @@ _SENTINEL_PAIRS = {
|
|
|
448
451
|
"reversed_guillemets": ("»", "«"),
|
|
449
452
|
"corner_brackets": ("「", "」"),
|
|
450
453
|
"white_corner_brackets": ("『", "』"),
|
|
454
|
+
"halfwidth_corner_brackets": ("「", "」"),
|
|
451
455
|
"fullwidth_parenthesis": ("(", ")"),
|
|
452
456
|
}
|
|
453
457
|
|
|
@@ -474,7 +478,7 @@ class RegexTupleManager(TupleManager): # pickle-compat: do NOT delete
|
|
|
474
478
|
class _DelimiterManager(TupleManager):
|
|
475
479
|
"""v1 ``nickname_delimiters``/``maiden_delimiters`` bucket. In 2.0
|
|
476
480
|
only the named sentinels in ``_DELIMITER_SENTINELS`` exist (the v1
|
|
477
|
-
trio plus the #273
|
|
481
|
+
trio plus the typographic pairs, #273 and #597) -- assigning any
|
|
478
482
|
other key raises so a caller reaches for a custom-delimiter Policy
|
|
479
483
|
kwarg instead of a dict entry that silently does nothing. ``pop()``/
|
|
480
484
|
``__setitem__``/``__delitem__`` stay open (inherited) for the
|
|
@@ -619,6 +623,57 @@ _MANAGER_FIELDS = _SET_FIELDS + (
|
|
|
619
623
|
"capitalization_exceptions", "nickname_delimiters", "maiden_delimiters",
|
|
620
624
|
)
|
|
621
625
|
|
|
626
|
+
|
|
627
|
+
def _v1_matchable(entry: str) -> bool:
|
|
628
|
+
"""Should the shim translate this config entry at all? v1 compared
|
|
629
|
+
``lc()`` of a parsed piece, and a piece comes from a whitespace
|
|
630
|
+
split, re-joined only with single spaces, so it is never empty and
|
|
631
|
+
never holds edge whitespace, a whitespace run or a non-space
|
|
632
|
+
whitespace character. An entry failing the whitespace test matched
|
|
633
|
+
nothing in 1.x; translating it either starts matching (Lexicon strips the
|
|
634
|
+
whitespace) or trips a Lexicon invariant the set algebra in
|
|
635
|
+
``_build_snapshot`` could not see (#541). ``lc`` first, for a
|
|
636
|
+
``capitalization_exceptions`` key, which TupleManager stores as
|
|
637
|
+
written; set entries are already ``lc()``-folded. The one exception,
|
|
638
|
+
and the one departure from 1.4.0: an entry Lexicon folds to empty,
|
|
639
|
+
which since 2.3.0 (FULL_STOPS, #322) includes a lone CJK full stop.
|
|
640
|
+
1.4.0 through 2.2.0 could match a token made of that full stop, but
|
|
641
|
+
since 2.3.0 Lexicon's fold empties both the entry and that token's
|
|
642
|
+
lookup key, so translating it only raises (#582)."""
|
|
643
|
+
folded = lc(entry)
|
|
644
|
+
return (bool(folded) and folded == " ".join(folded.split())
|
|
645
|
+
and bool(_normalize(folded)))
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
# Two entries of nameparser 1.4.0's own shipped TITLES carry a trailing
|
|
649
|
+
# space, so 1.4.0 never matched them ("Actor John Smith" -> first
|
|
650
|
+
# "Actor"). A restored 1.4 pickle carries them and the user never wrote
|
|
651
|
+
# them: they are dropped like any unmatchable entry but not named in the
|
|
652
|
+
# warning. The 2.x data module spells them without the space. Not the
|
|
653
|
+
# same roster as _LEGACY_DEAD_ENTRIES, on purpose: that one is subtracted
|
|
654
|
+
# in __setstate__, only when the whole pre-2.0 set is present, so a user's
|
|
655
|
+
# own re-added entry survives; this one is skipped at snapshot time from
|
|
656
|
+
# any source, and nothing else about it depends on how the entry got here.
|
|
657
|
+
_V14_SHIPPED_UNMATCHABLE = frozenset({
|
|
658
|
+
("titles", "actor "), ("titles", "television "),
|
|
659
|
+
})
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def _warn_unmatchable(dropped: list[tuple[str, str]]) -> None:
|
|
663
|
+
listed = ", ".join(f"{field}: {entry!r}" for field, entry in dropped)
|
|
664
|
+
remedy = "; ".join(
|
|
665
|
+
f"del constants.capitalization_exceptions[{entry!r}]"
|
|
666
|
+
if field == "capitalization_exceptions"
|
|
667
|
+
else f"constants.{field}.remove({entry!r})"
|
|
668
|
+
for field, entry in dropped)
|
|
669
|
+
_warn_dead_entry(
|
|
670
|
+
f"ignoring Constants entries that match no name word (each is "
|
|
671
|
+
f"empty, only full stops and spaces, or holds whitespace no name word "
|
|
672
|
+
f"carries): {listed}. "
|
|
673
|
+
f"Add the stripped word if one was meant, and remove these: "
|
|
674
|
+
f"{remedy}")
|
|
675
|
+
|
|
676
|
+
|
|
622
677
|
#: v1's Constants.__repr__ field order (#221) -- kept as its own tuple
|
|
623
678
|
#: rather than reusing _SET_FIELDS, whose order differs (v1 lists
|
|
624
679
|
#: suffix_acronyms_ambiguous last, not fourth).
|
|
@@ -989,13 +1044,35 @@ class Constants:
|
|
|
989
1044
|
singleton -- only direct attribute mutation is on the 3.0
|
|
990
1045
|
removal path.
|
|
991
1046
|
"""
|
|
1047
|
+
from nameparser.config.conjunctions import CONJUNCTIONS_AMBIGUOUS
|
|
992
1048
|
from nameparser.config.maiden_markers import MAIDEN_MARKERS
|
|
993
1049
|
from nameparser.config.suffixes import GLUED_HONORIFICS
|
|
994
1050
|
from nameparser.config.surnames import KOREAN_SURNAMES
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1051
|
+
# Every set field and every capitalization_exceptions key passes
|
|
1052
|
+
# _v1_matchable BEFORE any set algebra below: an entry v1 could
|
|
1053
|
+
# never match, or one Lexicon folds to empty (#582), is dropped
|
|
1054
|
+
# and named in one warning (#541). The survivors are passed RAW,
|
|
1055
|
+
# as Lexicon folds each field on its own; only the two cross-set
|
|
1056
|
+
# computations below compare folded spellings.
|
|
1057
|
+
dropped: list[tuple[str, str]] = []
|
|
1058
|
+
|
|
1059
|
+
def matchable(field: str) -> frozenset[str]:
|
|
1060
|
+
kept = []
|
|
1061
|
+
for entry in sorted(getattr(self, field)):
|
|
1062
|
+
if _v1_matchable(entry):
|
|
1063
|
+
kept.append(entry)
|
|
1064
|
+
elif (field, entry) not in _V14_SHIPPED_UNMATCHABLE:
|
|
1065
|
+
dropped.append((field, entry))
|
|
1066
|
+
return frozenset(kept)
|
|
1067
|
+
|
|
1068
|
+
acronyms = matchable("suffix_acronyms")
|
|
1069
|
+
particles = matchable("prefixes")
|
|
1070
|
+
conjunctions = matchable("conjunctions")
|
|
1071
|
+
bound = matchable("bound_first_names")
|
|
1072
|
+
titles = matchable("titles")
|
|
1073
|
+
first_name_titles = matchable("first_name_titles")
|
|
1074
|
+
non_given = matchable("non_first_name_prefixes")
|
|
1075
|
+
ambiguous_acronyms = matchable("suffix_acronyms_ambiguous") & acronyms
|
|
999
1076
|
# Drop any ambiguous acronym from the word set rather than the
|
|
1000
1077
|
# other way round. Lexicon forbids the overlap because the word
|
|
1001
1078
|
# branch bypasses the period gate, and adding an ambiguous
|
|
@@ -1006,85 +1083,149 @@ class Constants:
|
|
|
1006
1083
|
# dropping it from the AMBIGUOUS set instead ungated the word
|
|
1007
1084
|
# and lost the family name -- a silent misparse worse than the
|
|
1008
1085
|
# raise it avoided.
|
|
1009
|
-
|
|
1086
|
+
# Folded here, and only here (#582): Lexicon folds each field on
|
|
1087
|
+
# its own but checks this disjointness AFTER its fold, so the
|
|
1088
|
+
# shim must compare folded spellings exactly where Lexicon
|
|
1089
|
+
# re-checks. Folding anything else would change readings on
|
|
1090
|
+
# configs that never raised.
|
|
1091
|
+
ambiguous_folded = {_normalize(a) for a in ambiguous_acronyms}
|
|
1092
|
+
suffix_words = frozenset(
|
|
1093
|
+
w for w in matchable("suffix_not_acronyms")
|
|
1094
|
+
if _normalize(w) not in ambiguous_folded)
|
|
1095
|
+
# Likewise folded only here (#582): Lexicon checks bound_given_names
|
|
1096
|
+
# & particles against particles_ambiguous after its own fold.
|
|
1097
|
+
bound_folded = {_normalize(b) for b in bound}
|
|
1098
|
+
bound_particles = frozenset(
|
|
1099
|
+
p for p in particles if _normalize(p) in bound_folded)
|
|
1100
|
+
capitalization_exceptions: list[tuple[str, object]] = []
|
|
1101
|
+
for key, value in sorted(self.capitalization_exceptions.items()):
|
|
1102
|
+
if _v1_matchable(key):
|
|
1103
|
+
capitalization_exceptions.append((key, value))
|
|
1104
|
+
else:
|
|
1105
|
+
dropped.append(("capitalization_exceptions", key))
|
|
1106
|
+
if dropped:
|
|
1107
|
+
_warn_unmatchable(dropped)
|
|
1010
1108
|
# keep in sync with _lexicon._default_lexicon() (pinned by
|
|
1011
1109
|
# tests/v2/test_config_shim.py::test_snapshot_field_translation)
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1110
|
+
# A capitalization_exceptions value that does not spell its key
|
|
1111
|
+
# is the one DECIDED exception to this method's never-raise
|
|
1112
|
+
# rule (#459, decisions.md#R4 and #3-0-reevaluations).
|
|
1113
|
+
# _normpairs raises _MaskValueError for exactly that shape, and
|
|
1114
|
+
# the except clause re-spells its offered fix for a v1 caller.
|
|
1115
|
+
# Caught by TYPE, so every other Lexicon error passes through
|
|
1116
|
+
# unchanged. Only the three SUBSET checks (particles_ambiguous
|
|
1117
|
+
# inside particles, suffix_acronyms_ambiguous inside
|
|
1118
|
+
# suffix_acronyms, honorific_tails inside suffix_words) are
|
|
1119
|
+
# satisfied BY CONSTRUCTION: each is built below as an
|
|
1120
|
+
# intersection or union with its OWN base -- particles_ambiguous
|
|
1121
|
+
# from `particles` itself, honorific_tails from `suffix_words`
|
|
1122
|
+
# itself -- so the image of a subset relation under any
|
|
1123
|
+
# element-wise normalization is still a subset, whatever the
|
|
1124
|
+
# raw spelling. The CONTRADICTION check (bound_given_names &
|
|
1125
|
+
# particles inside particles_ambiguous) and the GATE-BYPASS check
|
|
1126
|
+
# (suffix_acronyms_ambiguous disjoint from suffix_words) do NOT
|
|
1127
|
+
# have that property -- each compares two sets built by SEPARATE
|
|
1128
|
+
# set arithmetic on the shim's own strings. They hold here
|
|
1129
|
+
# because the entries are filtered by _v1_matchable and the two
|
|
1130
|
+
# cross-set computations (suffix_words, bound_particles) compare
|
|
1131
|
+
# _normalize'd spellings, which are the ones Lexicon compares;
|
|
1132
|
+
# every other field is passed raw, as Lexicon folds it itself
|
|
1133
|
+
# (#541, #582).
|
|
1134
|
+
# What else reaches it is a non-str value's TypeError -- a raise
|
|
1135
|
+
# v1 also had, later, at capitalize().
|
|
1136
|
+
try:
|
|
1137
|
+
lexicon = Lexicon(
|
|
1138
|
+
titles=titles,
|
|
1139
|
+
# TRANSLATE, do not filter. The two versions build the same
|
|
1140
|
+
# lookup key differently: v1 joins the raw title run and
|
|
1141
|
+
# then applies lc(), which strips only the whole string's
|
|
1142
|
+
# edge periods, so an interior word keeps its own ("lt.
|
|
1143
|
+
# col"). v2 normalizes each token and then joins ("lt col").
|
|
1144
|
+
# Re-folding per word converts a v1 entry into the v2
|
|
1145
|
+
# spelling; filtering instead dropped every multi-word
|
|
1146
|
+
# honorific containing an abbreviation or a conjunction and
|
|
1147
|
+
# silently swapped given and family.
|
|
1148
|
+
# Entries that match no name word were dropped above
|
|
1149
|
+
# (_v1_matchable); since that includes every entry
|
|
1150
|
+
# Lexicon folds to empty, no key built here is empty.
|
|
1151
|
+
given_name_titles=frozenset(
|
|
1152
|
+
_title_key(e.split()) for e in first_name_titles),
|
|
1153
|
+
suffix_acronyms=acronyms,
|
|
1154
|
+
suffix_words=suffix_words,
|
|
1155
|
+
# Intersect with acronyms: Lexicon enforces ambiguous <=
|
|
1156
|
+
# acronyms; v1 behaves the same when an acronym is deleted
|
|
1157
|
+
# but its ambiguous entry lingers (the entry stops
|
|
1158
|
+
# mattering).
|
|
1159
|
+
suffix_acronyms_ambiguous=ambiguous_acronyms,
|
|
1160
|
+
particles=particles,
|
|
1161
|
+
# complement translation: v1 marks the never-given subset;
|
|
1162
|
+
# v2 marks the may-be-given subset. The trailing union keeps
|
|
1163
|
+
# a config v1 accepted: particles.py asserts its own data has
|
|
1164
|
+
# no word in both NON_GIVEN_NAME_PARTICLES and
|
|
1165
|
+
# BOUND_GIVEN_NAMES, but nothing stops a caller adding one at
|
|
1166
|
+
# runtime, and v1 then lets the bound rule win (leading "dos
|
|
1167
|
+
# Santos Silva" parses first="dos Santos"). Treating such a
|
|
1168
|
+
# word as may-be-given reproduces that rather than raising.
|
|
1169
|
+
#
|
|
1170
|
+
# KNOWN DEVIATION, pinned by
|
|
1171
|
+
# test_bound_never_given_prefix_deviates_on_two_pieces: v1's
|
|
1172
|
+
# join has a reserve_last guard, so with only two pieces it
|
|
1173
|
+
# does NOT fire and the word stays never-given ("dos Santos"
|
|
1174
|
+
# -> last="dos Santos"). Promotion here is unconditional, so
|
|
1175
|
+
# that case reads given="dos", family="Santos". v1's rule is
|
|
1176
|
+
# piece-count dependent and a static vocabulary set cannot
|
|
1177
|
+
# express it; the alternative is raising on a config v1
|
|
1178
|
+
# accepted, which is worse. Only reachable via a runtime
|
|
1179
|
+
# config the shipped data forbids.
|
|
1180
|
+
particles_ambiguous=(
|
|
1181
|
+
particles - non_given)
|
|
1182
|
+
| bound_particles,
|
|
1183
|
+
conjunctions=conjunctions,
|
|
1184
|
+
# no v1 manager of its own: the ambiguous-connective
|
|
1185
|
+
# subset is 2.4 behavior (#383/#479), so it rides in the
|
|
1186
|
+
# snapshot only. Lexicon does NOT check this pair -- unlike
|
|
1187
|
+
# honorific_tails against suffix_words below -- so the
|
|
1188
|
+
# intersection is a provable no-op, kept only for
|
|
1189
|
+
# `_snapshot() == Lexicon.default()` legibility; the v1
|
|
1190
|
+
# knob (deleting the conjunction) turns the marking off
|
|
1191
|
+
# through the fork's own base-vocabulary test rather than
|
|
1192
|
+
# through this intersection.
|
|
1193
|
+
conjunctions_ambiguous=CONJUNCTIONS_AMBIGUOUS & conjunctions,
|
|
1194
|
+
bound_given_names=bound,
|
|
1195
|
+
# v1 Constants has no manager for these (#274 is 2.0
|
|
1196
|
+
# behavior); the data module is the only source
|
|
1197
|
+
maiden_markers=MAIDEN_MARKERS,
|
|
1198
|
+
# likewise no v1 manager: the unspaced-name segmentation
|
|
1199
|
+
# vocabulary is 2.0 behavior (#271), so it rides in the
|
|
1200
|
+
# snapshot only -- v1's Constants surface stays frozen.
|
|
1201
|
+
surnames=KOREAN_SURNAMES,
|
|
1202
|
+
# likewise no v1 manager: the glued-honorific tail set is
|
|
1203
|
+
# 2.1 behavior (#308), so it rides in the snapshot only.
|
|
1204
|
+
# Intersect with the word set: Lexicon enforces tails <=
|
|
1205
|
+
# suffix_words, and v1 semantics are that deleting a suffix
|
|
1206
|
+
# word turns the behavior off -- a lingering tail simply
|
|
1207
|
+
# stops mattering, the same rule ambiguous_acronyms gets
|
|
1208
|
+
# against suffix_acronyms above.
|
|
1209
|
+
honorific_tails=GLUED_HONORIFICS & suffix_words,
|
|
1210
|
+
# TupleManager is dict[str, object] (v1 parity: values were
|
|
1211
|
+
# never statically str-typed); every real entry is a str,
|
|
1212
|
+
# same assumption _DelimiterManager's sentinel lookup makes.
|
|
1213
|
+
# NOT translated: see the comment above the try: block.
|
|
1214
|
+
capitalization_exceptions=tuple(capitalization_exceptions), # type: ignore[arg-type]
|
|
1215
|
+
)
|
|
1216
|
+
except _MaskValueError as e:
|
|
1217
|
+
# A v1-ONLY message built from the fields, not `{e}` --
|
|
1218
|
+
# the 2.0 exception's own message offers the 2.0
|
|
1219
|
+
# `capitalization_exceptions=((...),)` constructor spelling,
|
|
1220
|
+
# which no v1 Constants caller can paste.
|
|
1221
|
+
raise ValueError(
|
|
1222
|
+
f"capitalization_exceptions value {e.value!r} for key "
|
|
1223
|
+
f"{e.key!r} does not spell the key's letters and "
|
|
1224
|
+
f"digits: a value is a case mask, the key's own "
|
|
1225
|
+
f"letters and digits recased -- write "
|
|
1226
|
+
f"constants.capitalization_exceptions[{e.key!r}] = "
|
|
1227
|
+
f"{e.offered!r}"
|
|
1228
|
+
) from e
|
|
1088
1229
|
rules = frozenset({PatronymicRule.EAST_SLAVIC, PatronymicRule.TURKIC}) \
|
|
1089
1230
|
if self.patronymic_name_order else frozenset()
|
|
1090
1231
|
policy = Policy(
|