nameparser 2.5.0__tar.gz → 2.7.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.5.0/nameparser.egg-info → nameparser-2.7.0}/PKG-INFO +11 -9
- {nameparser-2.5.0 → nameparser-2.7.0}/README.rst +9 -6
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/__init__.py +5 -1
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_config_shim.py +101 -6
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_facade.py +5 -4
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_lexicon.py +144 -22
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_parser.py +2 -2
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_pipeline/_assemble.py +38 -5
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_pipeline/_assign.py +516 -106
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_pipeline/_classify.py +219 -38
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_pipeline/_comma.py +228 -73
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_pipeline/_extract.py +17 -13
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_pipeline/_group.py +198 -241
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_pipeline/_pieces.py +462 -134
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_pipeline/_post_rules.py +228 -138
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_pipeline/_script_segment.py +22 -87
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_pipeline/_segment.py +17 -6
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_pipeline/_state.py +99 -10
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_pipeline/_tokenize.py +25 -13
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_pipeline/_vocab.py +719 -89
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_policy.py +54 -15
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_render.py +154 -36
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_types.py +191 -32
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_version.py +4 -4
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/config/__init__.py +2 -0
- nameparser-2.7.0/nameparser/config/_compiled.py +2573 -0
- nameparser-2.7.0/nameparser/config/_invariants.py +39 -0
- nameparser-2.7.0/nameparser/config/_titles_2_6_0.py +826 -0
- nameparser-2.7.0/nameparser/config/bound_given_names.py +27 -0
- nameparser-2.7.0/nameparser/config/capitalization.py +46 -0
- nameparser-2.7.0/nameparser/config/conjunctions.py +26 -0
- nameparser-2.7.0/nameparser/config/given_names.py +18 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/config/maiden_markers.py +6 -23
- nameparser-2.7.0/nameparser/config/particles.py +102 -0
- nameparser-2.7.0/nameparser/config/suffixes.py +165 -0
- nameparser-2.7.0/nameparser/config/titles.py +99 -0
- nameparser-2.7.0/nameparser/vocab/ak.toml +5 -0
- nameparser-2.7.0/nameparser/vocab/am.toml +9 -0
- nameparser-2.7.0/nameparser/vocab/ar.toml +539 -0
- nameparser-2.7.0/nameparser/vocab/as.toml +19 -0
- nameparser-2.7.0/nameparser/vocab/be.toml +23 -0
- nameparser-2.7.0/nameparser/vocab/bg.toml +61 -0
- nameparser-2.7.0/nameparser/vocab/bin.toml +9 -0
- nameparser-2.7.0/nameparser/vocab/bn.toml +181 -0
- nameparser-2.7.0/nameparser/vocab/bo.toml +13 -0
- nameparser-2.7.0/nameparser/vocab/bs.toml +29 -0
- nameparser-2.7.0/nameparser/vocab/ca.toml +8 -0
- nameparser-2.7.0/nameparser/vocab/cs.toml +49 -0
- nameparser-2.7.0/nameparser/vocab/da.toml +41 -0
- nameparser-2.7.0/nameparser/vocab/de.toml +212 -0
- nameparser-2.7.0/nameparser/vocab/el.toml +33 -0
- nameparser-2.7.0/nameparser/vocab/en.toml +5032 -0
- nameparser-2.7.0/nameparser/vocab/es.toml +103 -0
- nameparser-2.7.0/nameparser/vocab/et.toml +18 -0
- nameparser-2.7.0/nameparser/vocab/fa.toml +37 -0
- nameparser-2.7.0/nameparser/vocab/ff.toml +5 -0
- nameparser-2.7.0/nameparser/vocab/fi.toml +26 -0
- nameparser-2.7.0/nameparser/vocab/fil.toml +28 -0
- nameparser-2.7.0/nameparser/vocab/fr.toml +183 -0
- nameparser-2.7.0/nameparser/vocab/ga.toml +63 -0
- nameparser-2.7.0/nameparser/vocab/gd.toml +9 -0
- nameparser-2.7.0/nameparser/vocab/gu.toml +13 -0
- nameparser-2.7.0/nameparser/vocab/ha.toml +17 -0
- nameparser-2.7.0/nameparser/vocab/he.toml +134 -0
- nameparser-2.7.0/nameparser/vocab/hi.toml +210 -0
- nameparser-2.7.0/nameparser/vocab/hr.toml +41 -0
- nameparser-2.7.0/nameparser/vocab/hu.toml +17 -0
- nameparser-2.7.0/nameparser/vocab/hy.toml +23 -0
- nameparser-2.7.0/nameparser/vocab/id.toml +5 -0
- nameparser-2.7.0/nameparser/vocab/is.toml +25 -0
- nameparser-2.7.0/nameparser/vocab/it.toml +195 -0
- nameparser-2.7.0/nameparser/vocab/ja.toml +144 -0
- nameparser-2.7.0/nameparser/vocab/ka.toml +21 -0
- nameparser-2.7.0/nameparser/vocab/kk.toml +7 -0
- nameparser-2.7.0/nameparser/vocab/km.toml +11 -0
- nameparser-2.7.0/nameparser/vocab/kn.toml +5 -0
- nameparser-2.7.0/nameparser/vocab/ko.toml +88 -0
- nameparser-2.7.0/nameparser/vocab/kok.toml +7 -0
- nameparser-2.7.0/nameparser/vocab/lo.toml +5 -0
- nameparser-2.7.0/nameparser/vocab/lt.toml +35 -0
- nameparser-2.7.0/nameparser/vocab/lv.toml +17 -0
- nameparser-2.7.0/nameparser/vocab/mi.toml +5 -0
- nameparser-2.7.0/nameparser/vocab/mk.toml +15 -0
- nameparser-2.7.0/nameparser/vocab/ml.toml +9 -0
- nameparser-2.7.0/nameparser/vocab/mn.toml +11 -0
- nameparser-2.7.0/nameparser/vocab/mr.toml +91 -0
- nameparser-2.7.0/nameparser/vocab/ms.toml +52 -0
- nameparser-2.7.0/nameparser/vocab/mul.toml +58 -0
- nameparser-2.7.0/nameparser/vocab/my.toml +5 -0
- nameparser-2.7.0/nameparser/vocab/nb.toml +45 -0
- nameparser-2.7.0/nameparser/vocab/ne.toml +11 -0
- nameparser-2.7.0/nameparser/vocab/nl.toml +150 -0
- nameparser-2.7.0/nameparser/vocab/nn.toml +5 -0
- nameparser-2.7.0/nameparser/vocab/or.toml +9 -0
- nameparser-2.7.0/nameparser/vocab/pa.toml +30 -0
- nameparser-2.7.0/nameparser/vocab/pl.toml +25 -0
- nameparser-2.7.0/nameparser/vocab/pt-BR.toml +49 -0
- nameparser-2.7.0/nameparser/vocab/pt.toml +101 -0
- nameparser-2.7.0/nameparser/vocab/ro.toml +17 -0
- nameparser-2.7.0/nameparser/vocab/ru.toml +88 -0
- nameparser-2.7.0/nameparser/vocab/sd.toml +21 -0
- nameparser-2.7.0/nameparser/vocab/shn.toml +5 -0
- nameparser-2.7.0/nameparser/vocab/si.toml +5 -0
- nameparser-2.7.0/nameparser/vocab/sk.toml +29 -0
- nameparser-2.7.0/nameparser/vocab/sl.toml +29 -0
- nameparser-2.7.0/nameparser/vocab/sr.toml +51 -0
- nameparser-2.7.0/nameparser/vocab/sv.toml +38 -0
- nameparser-2.7.0/nameparser/vocab/sw.toml +6 -0
- nameparser-2.7.0/nameparser/vocab/ta.toml +17 -0
- nameparser-2.7.0/nameparser/vocab/te.toml +9 -0
- nameparser-2.7.0/nameparser/vocab/th.toml +9 -0
- nameparser-2.7.0/nameparser/vocab/ti.toml +9 -0
- nameparser-2.7.0/nameparser/vocab/tr.toml +28 -0
- nameparser-2.7.0/nameparser/vocab/uk.toml +54 -0
- nameparser-2.7.0/nameparser/vocab/und.toml +21 -0
- nameparser-2.7.0/nameparser/vocab/ur.toml +33 -0
- nameparser-2.7.0/nameparser/vocab/vi.toml +9 -0
- nameparser-2.7.0/nameparser/vocab/yi.toml +5 -0
- nameparser-2.7.0/nameparser/vocab/yo.toml +15 -0
- nameparser-2.7.0/nameparser/vocab/zh.toml +153 -0
- nameparser-2.7.0/nameparser/vocab/zu.toml +6 -0
- {nameparser-2.5.0 → nameparser-2.7.0/nameparser.egg-info}/PKG-INFO +11 -9
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser.egg-info/SOURCES.txt +98 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/pyproject.toml +16 -4
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/conftest.py +28 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_capitalization.py +3 -3
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_conjunctions.py +2 -2
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_first_name.py +11 -2
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_initials.py +1 -1
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_particles.py +3 -2
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_suffixes.py +49 -34
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_titles.py +4 -4
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/_differential_fixtures.py +62 -1
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/cases.py +3072 -511
- nameparser-2.7.0/tests/v2/design_docs.py +498 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/pipeline/test_assemble.py +3 -2
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/pipeline/test_assign.py +51 -17
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/pipeline/test_classify.py +20 -9
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/pipeline/test_comma.py +16 -12
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/pipeline/test_group.py +38 -29
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/pipeline/test_pieces.py +67 -26
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/pipeline/test_post_rules.py +57 -7
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/pipeline/test_script_segment.py +17 -12
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/pipeline/test_state.py +7 -6
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/pipeline/test_tokenize.py +43 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/pipeline/test_vocab.py +343 -17
- nameparser-2.7.0/tests/v2/rules_budget.py +154 -0
- nameparser-2.7.0/tests/v2/rules_doc.py +366 -0
- nameparser-2.7.0/tests/v2/rules_skeleton.py +482 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_benchmark.py +77 -8
- nameparser-2.7.0/tests/v2/test_cases.py +814 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_config_shim.py +210 -1
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_contracts.py +18 -12
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_corpora.py +8 -8
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_corpus_build.py +12 -0
- nameparser-2.7.0/tests/v2/test_design_docs.py +556 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_differential.py +38 -6
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_doc_citations.py +272 -52
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_facade.py +26 -12
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_facade_cases.py +48 -1
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_lexicon.py +91 -14
- nameparser-2.7.0/tests/v2/test_local_agreement.py +470 -0
- nameparser-2.7.0/tests/v2/test_local_corpora.py +4498 -0
- nameparser-2.7.0/tests/v2/test_local_jrc.py +548 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_locales.py +12 -9
- nameparser-2.7.0/tests/v2/test_measure.py +178 -0
- nameparser-2.7.0/tests/v2/test_name_usage_tool.py +2297 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_parser.py +162 -38
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_policy.py +5 -2
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_properties.py +434 -33
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_regex_sync.py +27 -3
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_render.py +197 -11
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_reprs.py +6 -1
- nameparser-2.7.0/tests/v2/test_rules_budget.py +182 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_rules_doc.py +111 -1
- nameparser-2.7.0/tests/v2/test_rules_doc_grammar.py +236 -0
- nameparser-2.7.0/tests/v2/test_rules_skeleton.py +489 -0
- nameparser-2.7.0/tests/v2/test_snapshot.py +224 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_types.py +140 -5
- nameparser-2.7.0/tests/v2/test_vocab.py +1138 -0
- nameparser-2.7.0/tests/v2/test_word_report.py +309 -0
- nameparser-2.5.0/nameparser/config/_invariants.py +0 -53
- nameparser-2.5.0/nameparser/config/bound_given_names.py +0 -60
- nameparser-2.5.0/nameparser/config/capitalization.py +0 -76
- nameparser-2.5.0/nameparser/config/conjunctions.py +0 -94
- nameparser-2.5.0/nameparser/config/particles.py +0 -333
- nameparser-2.5.0/nameparser/config/suffixes.py +0 -1134
- nameparser-2.5.0/nameparser/config/titles.py +0 -1033
- nameparser-2.5.0/tests/v2/design_docs.py +0 -107
- nameparser-2.5.0/tests/v2/rules_doc.py +0 -267
- nameparser-2.5.0/tests/v2/test_cases.py +0 -362
- nameparser-2.5.0/tests/v2/test_design_docs.py +0 -150
- nameparser-2.5.0/tests/v2/test_name_usage_tool.py +0 -1156
- nameparser-2.5.0/tests/v2/test_rules_doc_grammar.py +0 -168
- nameparser-2.5.0/tests/v2/test_snapshot.py +0 -48
- {nameparser-2.5.0 → nameparser-2.7.0}/AUTHORS +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/LICENSE +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/MANIFEST.in +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/__main__.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_locale.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/_pipeline/__init__.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/config/_deprecated.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/config/bound_first_names.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/config/prefixes.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/config/regexes.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/config/surnames.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/locales/__init__.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/locales/ja.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/locales/ru.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/locales/tr_az.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/locales/zh.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/parser.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/py.typed +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser/util.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser.egg-info/dependency_links.txt +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser.egg-info/requires.txt +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/nameparser.egg-info/top_level.txt +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/setup.cfg +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/__init__.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/base.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_bound_given_names.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_brute_force.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_comma_variants.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_constants.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_east_slavic_patronymic_order.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_middle_name_as_last.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_nicknames.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_output_format.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_python_api.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_turkic_patronymic_order.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/test_variations.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/__init__.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/conftest.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/pipeline/__init__.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/pipeline/test_extract.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/pipeline/test_segment.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_cldr_agreement.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_cldr_corpus.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_cli.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_config_aliases.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_doc_spellings.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_gnd_corpus.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_layering.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_locale.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_order_correspondence.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_wikidata_corpus.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_wikidata_native_corpus.py +0 -0
- {nameparser-2.5.0 → nameparser-2.7.0}/tests/v2/test_zh_surnames_tool.py +0 -0
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nameparser
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7.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
|
-
License: LGPL
|
|
6
|
+
License-Expression: LGPL-2.1-or-later
|
|
7
7
|
Keywords: names,parser
|
|
8
8
|
Classifier: Intended Audience :: Developers
|
|
9
|
-
Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
|
|
10
9
|
Classifier: Operating System :: OS Independent
|
|
11
10
|
Classifier: Programming Language :: Python
|
|
12
11
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -36,12 +35,15 @@ nameparser parses human names into seven fields — title, given, middle,
|
|
|
36
35
|
family, suffix, nickname, maiden. Results are immutable, configuration is
|
|
37
36
|
composable, and locale packs are opt-in.
|
|
38
37
|
|
|
39
|
-
📣 **nameparser 2.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
📣 **nameparser 2.7 is out.** It is the first release working towards
|
|
39
|
+
the new language and vocabulary model described in the `vocabulary
|
|
40
|
+
model RFC <https://github.com/derek73/python-nameparser/blob/master/docs/design/vocabulary-model-rfc.md>`__.
|
|
41
|
+
It introduces more nuanced handling of titles and particles
|
|
42
|
+
(``Chief Justice John Roberts`` reads the title ``Chief Justice``,
|
|
43
|
+
while ``Justice Smith`` keeps the given name ``Justice``), adjusts the
|
|
44
|
+
default vocabulary to better align with example name corpora, and
|
|
45
|
+
surfaces alternative name readings in the published API. The `release log
|
|
46
|
+
<https://nameparser.readthedocs.io/en/latest/release_log.html>`__ has
|
|
45
47
|
the details.
|
|
46
48
|
|
|
47
49
|
Chinese, Japanese and Korean names written in their own scripts are read
|
|
@@ -7,12 +7,15 @@ 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
|
-
|
|
10
|
+
📣 **nameparser 2.7 is out.** It is the first release working towards
|
|
11
|
+
the new language and vocabulary model described in the `vocabulary
|
|
12
|
+
model RFC <https://github.com/derek73/python-nameparser/blob/master/docs/design/vocabulary-model-rfc.md>`__.
|
|
13
|
+
It introduces more nuanced handling of titles and particles
|
|
14
|
+
(``Chief Justice John Roberts`` reads the title ``Chief Justice``,
|
|
15
|
+
while ``Justice Smith`` keeps the given name ``Justice``), adjusts the
|
|
16
|
+
default vocabulary to better align with example name corpora, and
|
|
17
|
+
surfaces alternative name readings in the published API. The `release log
|
|
18
|
+
<https://nameparser.readthedocs.io/en/latest/release_log.html>`__ has
|
|
16
19
|
the details.
|
|
17
20
|
|
|
18
21
|
Chinese, Japanese and Korean names written in their own scripts are read
|
|
@@ -27,6 +27,9 @@ from nameparser._types import (
|
|
|
27
27
|
Ambiguity,
|
|
28
28
|
AmbiguityKind,
|
|
29
29
|
ParsedName,
|
|
30
|
+
Reading,
|
|
31
|
+
ReadingKind,
|
|
32
|
+
ReadingSource,
|
|
30
33
|
Role,
|
|
31
34
|
Segmentation,
|
|
32
35
|
Segmenter,
|
|
@@ -38,7 +41,8 @@ __all__ = [
|
|
|
38
41
|
# v1 (compatibility layer)
|
|
39
42
|
"HumanName",
|
|
40
43
|
# v2 core
|
|
41
|
-
"Span", "Role", "Token", "Ambiguity", "AmbiguityKind", "
|
|
44
|
+
"Span", "Role", "Token", "Ambiguity", "AmbiguityKind", "Reading",
|
|
45
|
+
"ReadingKind", "ReadingSource", "ParsedName",
|
|
42
46
|
"STABLE_TAGS", "Segmentation", "Segmenter",
|
|
43
47
|
"Lexicon", "Policy", "PolicyPatch", "PatronymicRule", "Script", "UNSET",
|
|
44
48
|
"CapsSuffixes",
|
|
@@ -731,13 +731,19 @@ def _default_vocab() -> dict[str, frozenset[str]]:
|
|
|
731
731
|
from nameparser.config.suffixes import (
|
|
732
732
|
SUFFIX_ACRONYMS, SUFFIX_ACRONYMS_AMBIGUOUS, SUFFIX_WORDS,
|
|
733
733
|
)
|
|
734
|
-
from nameparser.config.
|
|
734
|
+
from nameparser.config._titles_2_6_0 import TITLES_2_6_0
|
|
735
|
+
from nameparser.config.titles import (
|
|
736
|
+
GIVEN_NAME_TITLES, TITLES, TITLES_AMBIGUOUS)
|
|
735
737
|
return {
|
|
736
738
|
"prefixes": PARTICLES,
|
|
737
739
|
"suffix_acronyms": SUFFIX_ACRONYMS,
|
|
738
740
|
"suffix_not_acronyms": SUFFIX_WORDS,
|
|
739
741
|
"suffix_acronyms_ambiguous": SUFFIX_ACRONYMS_AMBIGUOUS,
|
|
740
|
-
|
|
742
|
+
# 2.6.0's titles, less any word that is no title now: one a
|
|
743
|
+
# later release made a title only inside a title phrase
|
|
744
|
+
# (rules/H9) stays a v1 title, so remove() and `in` answer as
|
|
745
|
+
# they did; _build_snapshot reads it by the phrase
|
|
746
|
+
"titles": TITLES | (TITLES_2_6_0 & TITLES_AMBIGUOUS),
|
|
741
747
|
"first_name_titles": GIVEN_NAME_TITLES,
|
|
742
748
|
"conjunctions": CONJUNCTIONS,
|
|
743
749
|
"bound_first_names": BOUND_GIVEN_NAMES,
|
|
@@ -1045,9 +1051,15 @@ class Constants:
|
|
|
1045
1051
|
removal path.
|
|
1046
1052
|
"""
|
|
1047
1053
|
from nameparser.config.conjunctions import CONJUNCTIONS_AMBIGUOUS
|
|
1054
|
+
from nameparser.config.given_names import GIVEN_NAME_ABBREVIATIONS
|
|
1048
1055
|
from nameparser.config.maiden_markers import MAIDEN_MARKERS
|
|
1049
|
-
from nameparser.config.suffixes import
|
|
1056
|
+
from nameparser.config.suffixes import (
|
|
1057
|
+
AGNOMENS, GLUED_HONORIFICS, SUFFIX_WORDS_AMBIGUOUS)
|
|
1050
1058
|
from nameparser.config.surnames import KOREAN_SURNAMES
|
|
1059
|
+
from nameparser.config._titles_2_6_0 import TITLES_2_6_0
|
|
1060
|
+
from nameparser.config.titles import (
|
|
1061
|
+
AFTER_GIVEN_TITLES, TITLE_MODIFIERS, TITLE_PHRASES, TITLE_STYLES,
|
|
1062
|
+
TITLES_AMBIGUOUS)
|
|
1051
1063
|
# Every set field and every capitalization_exceptions key passes
|
|
1052
1064
|
# _v1_matchable BEFORE any set algebra below: an entry v1 could
|
|
1053
1065
|
# never match, or one Lexicon folds to empty (#582), is dropped
|
|
@@ -1070,6 +1082,27 @@ class Constants:
|
|
|
1070
1082
|
conjunctions = matchable("conjunctions")
|
|
1071
1083
|
bound = matchable("bound_first_names")
|
|
1072
1084
|
titles = matchable("titles")
|
|
1085
|
+
# A title 2.6.0 listed that a later release made a title only
|
|
1086
|
+
# inside a title phrase (rules/H9) is in v1's default `titles`,
|
|
1087
|
+
# where 2.6.0 had it, so remove(), discard() and `in` answer as
|
|
1088
|
+
# they did (decisions/H9, 2026-10-26). Kept, it is read as the
|
|
1089
|
+
# default reads it, by the phrase; removed, it is no title at
|
|
1090
|
+
# all, as removing it made it in 2.6.0. The one 2.6.0 reading
|
|
1091
|
+
# with no v1 spelling left is the word as a title everywhere:
|
|
1092
|
+
# v1 can say "a title" or "not a title", and "a title" now means
|
|
1093
|
+
# the phrase reading. A word 2.6.0 never listed (`dean`) is not
|
|
1094
|
+
# in `moved`, so adding it is still a title everywhere, the 1.4
|
|
1095
|
+
# recipe the phrase test honours below. Compared folded, as the
|
|
1096
|
+
# two cross-set computations below are (#582), so a spelling
|
|
1097
|
+
# Lexicon folds onto a moved word (`king。`) is read as it is.
|
|
1098
|
+
moved = TITLES_2_6_0 & TITLES_AMBIGUOUS
|
|
1099
|
+
kept_moved = frozenset(w for w in titles if _normalize(w) in moved)
|
|
1100
|
+
phrase_only = ((TITLES_AMBIGUOUS - moved) | kept_moved
|
|
1101
|
+
if titles else frozenset())
|
|
1102
|
+
# ...and the three intersections with the phrase vocabulary read
|
|
1103
|
+
# the same folded spellings, or a folded moved word kept its
|
|
1104
|
+
# phrase reading but lost its modifier one (`wing。`)
|
|
1105
|
+
any_title = frozenset(_normalize(w) for w in titles | phrase_only)
|
|
1073
1106
|
first_name_titles = matchable("first_name_titles")
|
|
1074
1107
|
non_given = matchable("non_first_name_prefixes")
|
|
1075
1108
|
ambiguous_acronyms = matchable("suffix_acronyms_ambiguous") & acronyms
|
|
@@ -1135,7 +1168,7 @@ class Constants:
|
|
|
1135
1168
|
# v1 also had, later, at capitalize().
|
|
1136
1169
|
try:
|
|
1137
1170
|
lexicon = Lexicon(
|
|
1138
|
-
titles=titles,
|
|
1171
|
+
titles=titles - kept_moved,
|
|
1139
1172
|
# TRANSLATE, do not filter. The two versions build the same
|
|
1140
1173
|
# lookup key differently: v1 joins the raw title run and
|
|
1141
1174
|
# then applies lc(), which strips only the whole string's
|
|
@@ -1160,8 +1193,8 @@ class Constants:
|
|
|
1160
1193
|
particles=particles,
|
|
1161
1194
|
# complement translation: v1 marks the never-given subset;
|
|
1162
1195
|
# v2 marks the may-be-given subset. The trailing union keeps
|
|
1163
|
-
# a config v1 accepted:
|
|
1164
|
-
#
|
|
1196
|
+
# a config v1 accepted: the vocabulary build refuses a
|
|
1197
|
+
# shipped word in both NON_GIVEN_NAME_PARTICLES and
|
|
1165
1198
|
# BOUND_GIVEN_NAMES, but nothing stops a caller adding one at
|
|
1166
1199
|
# runtime, and v1 then lets the bound rule win (leading "dos
|
|
1167
1200
|
# Santos Silva" parses first="dos Santos"). Treating such a
|
|
@@ -1192,6 +1225,15 @@ class Constants:
|
|
|
1192
1225
|
# through this intersection.
|
|
1193
1226
|
conjunctions_ambiguous=CONJUNCTIONS_AMBIGUOUS & conjunctions,
|
|
1194
1227
|
bound_given_names=bound,
|
|
1228
|
+
# likewise no v1 manager: an abbreviated given name
|
|
1229
|
+
# reading over rules/H2's shape is 2.7 behavior (#789),
|
|
1230
|
+
# so the set rides in the snapshot only, passed whole.
|
|
1231
|
+
# No v1 knob reaches it, and none needs to: a word a v1
|
|
1232
|
+
# caller ADDS to `titles` is a title wherever it stands,
|
|
1233
|
+
# since the leading peel asks the title vocabulary before
|
|
1234
|
+
# it weighs the shape, as it did in 1.4
|
|
1235
|
+
# (`c.titles.add("chas")`)
|
|
1236
|
+
given_name_abbreviations=GIVEN_NAME_ABBREVIATIONS,
|
|
1195
1237
|
# v1 Constants has no manager for these (#274 is 2.0
|
|
1196
1238
|
# behavior); the data module is the only source
|
|
1197
1239
|
maiden_markers=MAIDEN_MARKERS,
|
|
@@ -1207,6 +1249,59 @@ class Constants:
|
|
|
1207
1249
|
# stops mattering, the same rule ambiguous_acronyms gets
|
|
1208
1250
|
# against suffix_acronyms above.
|
|
1209
1251
|
honorific_tails=GLUED_HONORIFICS & suffix_words,
|
|
1252
|
+
# likewise no v1 manager: the salutations ending a given
|
|
1253
|
+
# part are 2.6 behavior (#835), so they ride in the
|
|
1254
|
+
# snapshot only, and the one v1 knob that reaches them is
|
|
1255
|
+
# deleting the title. Lexicon does not check this pair
|
|
1256
|
+
# (classify reads a member only where it is a title too),
|
|
1257
|
+
# so the intersection is kept for `_snapshot() ==
|
|
1258
|
+
# Lexicon.default()` legibility, as conjunctions_ambiguous'.
|
|
1259
|
+
after_given_titles=AFTER_GIVEN_TITLES & titles,
|
|
1260
|
+
# likewise no v1 manager: a suffix word that is also a
|
|
1261
|
+
# name is 2.7 behavior (#752), so it rides in the
|
|
1262
|
+
# snapshot only. Intersect with the word set, as the
|
|
1263
|
+
# tails are: Lexicon enforces the subset, and deleting
|
|
1264
|
+
# the suffix word is the one v1 knob, which turns the
|
|
1265
|
+
# reading off. agnomens is consulted only inside it, so
|
|
1266
|
+
# its intersection is kept for legibility alone.
|
|
1267
|
+
# A v1 caller who added such a word to suffix_acronyms
|
|
1268
|
+
# had it read as a suffix through the acronym branch,
|
|
1269
|
+
# so the word leaves the gate rather than raising at
|
|
1270
|
+
# Lexicon's word-gate check (folded, as the two
|
|
1271
|
+
# cross-set computations above are).
|
|
1272
|
+
suffix_words_ambiguous=frozenset(
|
|
1273
|
+
w for w in SUFFIX_WORDS_AMBIGUOUS & suffix_words
|
|
1274
|
+
if _normalize(w) not in {
|
|
1275
|
+
_normalize(a) for a in acronyms - ambiguous_acronyms}),
|
|
1276
|
+
agnomens=AGNOMENS & SUFFIX_WORDS_AMBIGUOUS & suffix_words,
|
|
1277
|
+
# likewise no v1 manager: the title phrase (rules/H9) is
|
|
1278
|
+
# 2.7 behavior (#752), so its sets ride in the snapshot
|
|
1279
|
+
# only. A title that may be a given name is not in
|
|
1280
|
+
# TITLES, so v1's `titles` holds one only where 2.6.0
|
|
1281
|
+
# listed it (`moved`, above, read by the phrase), and one
|
|
1282
|
+
# 2.6.0 never listed that a v1 caller ADDS to `titles` is
|
|
1283
|
+
# a title wherever it stands, as it was in 1.4
|
|
1284
|
+
# (`c.titles.add("dean")`, the documented recipe): the
|
|
1285
|
+
# phrase test reads a word of titles_ambiguous only where
|
|
1286
|
+
# it is not in titles. The
|
|
1287
|
+
# knob that reaches the other three is deleting a title,
|
|
1288
|
+
# which turns its modifier, style or phrase reading off
|
|
1289
|
+
# with it; Lexicon checks none of them, so the
|
|
1290
|
+
# intersections are kept for legibility, as
|
|
1291
|
+
# after_given_titles' is.
|
|
1292
|
+
# ...save where the caller emptied `titles`, v1's way to
|
|
1293
|
+
# turn title detection off (customize.rst). A caller who
|
|
1294
|
+
# REPLACES the list keeps the phrase reading of the words
|
|
1295
|
+
# 2.6.0 never listed: v1 has no knob for them, and what a
|
|
1296
|
+
# replaced list means for words it never held is not
|
|
1297
|
+
# something it says. A moved word reads by whether the
|
|
1298
|
+
# replaced list holds it, as any v1 title does
|
|
1299
|
+
titles_ambiguous=phrase_only,
|
|
1300
|
+
title_modifiers=TITLE_MODIFIERS & any_title,
|
|
1301
|
+
title_styles=TITLE_STYLES & any_title,
|
|
1302
|
+
title_phrases=frozenset(
|
|
1303
|
+
p for p in TITLE_PHRASES
|
|
1304
|
+
if all(w in any_title for w in p.split(" "))),
|
|
1210
1305
|
# TupleManager is dict[str, object] (v1 parity: values were
|
|
1211
1306
|
# never statically str-typed); every real entry is a str,
|
|
1212
1307
|
# same assumption _DelimiterManager's sentinel lookup makes.
|
|
@@ -700,10 +700,11 @@ class HumanName:
|
|
|
700
700
|
if got or not words or not all(self._is_particle(w)
|
|
701
701
|
for w in words):
|
|
702
702
|
return got
|
|
703
|
-
# rules/R3: "
|
|
704
|
-
#
|
|
705
|
-
# -- nothing survived
|
|
706
|
-
# the filter, so the whole group is particles
|
|
703
|
+
# rules/R3: "A part whose every word is a particle initials
|
|
704
|
+
# those particles: they are not acting as particles there"
|
|
705
|
+
# -- the Except bullet this quote opens; nothing survived
|
|
706
|
+
# the filter, so the whole group is particles
|
|
707
|
+
# (decisions/R3). The
|
|
707
708
|
# facade's twin of the core's
|
|
708
709
|
# UNJOINED_TAG. NOT pinned against it by the case runners,
|
|
709
710
|
# which compare the seven role fields only (Case carries
|
|
@@ -25,7 +25,10 @@ _VOCAB_FIELDS = (
|
|
|
25
25
|
"titles", "given_name_titles", "suffix_acronyms", "suffix_words",
|
|
26
26
|
"suffix_acronyms_ambiguous", "particles", "particles_ambiguous",
|
|
27
27
|
"conjunctions", "conjunctions_ambiguous", "bound_given_names",
|
|
28
|
-
"
|
|
28
|
+
"given_name_abbreviations",
|
|
29
|
+
"maiden_markers", "surnames", "honorific_tails", "after_given_titles",
|
|
30
|
+
"suffix_words_ambiguous", "agnomens", "titles_ambiguous",
|
|
31
|
+
"title_modifiers", "title_styles", "title_phrases",
|
|
29
32
|
)
|
|
30
33
|
|
|
31
34
|
#: (marker, base, why) triples. Each marker QUALIFIES how entries of
|
|
@@ -55,21 +58,27 @@ _VOCAB_FIELDS = (
|
|
|
55
58
|
#: as a tail would peel a period-gated word off a real name), and
|
|
56
59
|
#: nothing needs the acronym half: the shipped tails are CJK
|
|
57
60
|
#: honorifics, which are words.
|
|
58
|
-
#: The
|
|
59
|
-
#:
|
|
60
|
-
#:
|
|
61
|
-
#: is
|
|
62
|
-
#: raises unconditionally -- so under -O this is what still holds the
|
|
63
|
-
#: SHIPPED vocabulary to the invariant, as it is the only thing that
|
|
64
|
-
#: ever held a caller's own.
|
|
61
|
+
#: The shipped vocabulary holds the relation by construction: its
|
|
62
|
+
#: build puts a word in GLUED_HONORIFICS only through an `attached`
|
|
63
|
+
#: property on a word reading (tools/vocab/build.py). The check here
|
|
64
|
+
#: is the only thing that holds a caller's own.
|
|
65
65
|
#:
|
|
66
|
-
#:
|
|
66
|
+
#: Three fields are deliberately NOT here and have no check of their own.
|
|
67
67
|
#: given_name_titles -- see the note in __post_init__ for why every
|
|
68
68
|
#: attempt at one rejected working configurations. conjunctions_ambiguous
|
|
69
69
|
#: -- an orphan is never consulted because both the classify fork and
|
|
70
70
|
#: its ambiguity emitter require the base entry as well, so an orphan
|
|
71
71
|
#: decides nothing, and AGENTS.md's invariants rule guards harm, not
|
|
72
|
-
#: no-ops.
|
|
72
|
+
#: no-ops. after_given_titles -- the same: classify tags a word from it
|
|
73
|
+
#: only where the word is in titles too, so an orphan is never read.
|
|
74
|
+
#: agnomens -- the same: classify reads it only for a word already in
|
|
75
|
+
#: suffix_words_ambiguous, so an orphan decides nothing.
|
|
76
|
+
#: title_modifiers, title_styles and title_phrases -- the same: the
|
|
77
|
+
#: title phrase test (rules/H9) reads them only for words that are
|
|
78
|
+
#: titles, so an orphan decides nothing. titles_ambiguous is no subset
|
|
79
|
+
#: at all: its words are titles only inside a phrase, so a caller lists
|
|
80
|
+
#: one there without listing it in titles, and one in both is a title
|
|
81
|
+
#: everywhere.
|
|
73
82
|
_SUBSET_FIELDS = (
|
|
74
83
|
("particles_ambiguous", "particles",
|
|
75
84
|
"an orphan emits a spurious particle-or-given ambiguity"),
|
|
@@ -77,6 +86,8 @@ _SUBSET_FIELDS = (
|
|
|
77
86
|
"an orphan silently becomes a period-gated suffix"),
|
|
78
87
|
("honorific_tails", "suffix_words",
|
|
79
88
|
"an orphan splits the name and leaves the tail inside it"),
|
|
89
|
+
("suffix_words_ambiguous", "suffix_words",
|
|
90
|
+
"an orphan silently becomes a suffix gated by the count"),
|
|
80
91
|
)
|
|
81
92
|
|
|
82
93
|
|
|
@@ -100,7 +111,7 @@ _SUBSET_FIELDS = (
|
|
|
100
111
|
#: by a genuine multi-token lookahead instead --
|
|
101
112
|
#: _pipeline._vocab.maiden_marker_run, longest first -- and only the
|
|
102
113
|
#: STORAGE rule is shared with titles.
|
|
103
|
-
_PHRASE_FIELDS = ("given_name_titles", "maiden_markers")
|
|
114
|
+
_PHRASE_FIELDS = ("given_name_titles", "maiden_markers", "title_phrases")
|
|
104
115
|
|
|
105
116
|
|
|
106
117
|
#: Every character the parser reads as a full stop at a WORD'S EDGE
|
|
@@ -569,12 +580,14 @@ class Lexicon:
|
|
|
569
580
|
Entries are normalized at construction -- lowercased, NFC-composed,
|
|
570
581
|
edge full stops stripped -- so matching is case-insensitive.
|
|
571
582
|
Vocabulary entries are single words -- a multi-word entry warns at
|
|
572
|
-
construction and can never match.
|
|
583
|
+
construction and can never match. Three fields are exempt, and they
|
|
573
584
|
differ in HOW they match: ``given_name_titles`` is looked up
|
|
574
585
|
against the run of words the parse has ALREADY read as titles --
|
|
575
|
-
the whole run space-joined, or that run's last word --
|
|
586
|
+
the whole run space-joined, or that run's last word --
|
|
576
587
|
``maiden_markers`` is matched by lookahead, longest first, over
|
|
577
|
-
words that need not be markers on their own (``"z domu"``)
|
|
588
|
+
words that need not be markers on their own (``"z domu"``), and
|
|
589
|
+
``title_phrases`` holds two-word phrases, stored space-joined and
|
|
590
|
+
matched against two adjacent title words (``"chief justice"``).
|
|
578
591
|
Field docs below show examples, not full
|
|
579
592
|
contents; inspect any field's shipped vocabulary directly, e.g.
|
|
580
593
|
``Lexicon.default().conjunctions``."""
|
|
@@ -583,7 +596,7 @@ class Lexicon:
|
|
|
583
596
|
#: list: :data:`~nameparser.config.titles.TITLES`.
|
|
584
597
|
titles: frozenset[str] = frozenset()
|
|
585
598
|
#: Titles whose single name word, before or after them, reads as a
|
|
586
|
-
#: GIVEN name ("
|
|
599
|
+
#: GIVEN name ("sir", "sister", ...) rather than a family name. Full
|
|
587
600
|
#: default list: :data:`~nameparser.config.titles.GIVEN_NAME_TITLES`.
|
|
588
601
|
given_name_titles: frozenset[str] = frozenset()
|
|
589
602
|
#: Post-nominal acronym suffixes, matched with or without periods
|
|
@@ -658,6 +671,17 @@ class Lexicon:
|
|
|
658
671
|
#: Full default list:
|
|
659
672
|
#: :data:`~nameparser.config.bound_given_names.BOUND_GIVEN_NAMES`.
|
|
660
673
|
bound_given_names: frozenset[str] = frozenset()
|
|
674
|
+
#: Given names written short ("chas" for Charles, "wm" for William,
|
|
675
|
+
#: Filipino "ma" for María). Period-marked and opening the part of
|
|
676
|
+
#: the name that carries the given name, an entry reads as that
|
|
677
|
+
#: given name, where an unlisted abbreviation there reads as a
|
|
678
|
+
#: title by its shape (rules/H2): "Chas. Smith" reads given
|
|
679
|
+
#: "Chas.", "Xyz. Smith" title "Xyz.". An entry also in ``titles``
|
|
680
|
+
#: is a title there, which is what listing it as one asks; anywhere
|
|
681
|
+
#: else an entry is read as it would be unlisted. Full default
|
|
682
|
+
#: list:
|
|
683
|
+
#: :data:`~nameparser.config.given_names.GIVEN_NAME_ABBREVIATIONS`.
|
|
684
|
+
given_name_abbreviations: frozenset[str] = frozenset()
|
|
661
685
|
#: Marker words introducing a birth surname, routed to the maiden
|
|
662
686
|
#: field ("née", "geb.", "rozená", ...). An entry may be a PHRASE
|
|
663
687
|
#: ("z domu"): entries are matched by lookahead, longest first, so
|
|
@@ -692,6 +716,73 @@ class Lexicon:
|
|
|
692
716
|
#: vocabulary is. Full default list:
|
|
693
717
|
#: :data:`~nameparser.config.suffixes.GLUED_HONORIFICS`.
|
|
694
718
|
honorific_tails: frozenset[str] = frozenset()
|
|
719
|
+
#: Salutations that read as a title where one ENDS the given part
|
|
720
|
+
#: after a family comma, written bare: "Doe, Jane Mrs" reads title
|
|
721
|
+
#: "Mrs" where an office or rank word there stays a middle name
|
|
722
|
+
#: ("Doe, John Pope"), rules/H8. A period-marked title word reads as
|
|
723
|
+
#: a title there whether listed here or not (rules/H5). Entries need
|
|
724
|
+
#: not also be in ``titles``; one that is not is never consulted.
|
|
725
|
+
#: Full default list:
|
|
726
|
+
#: :data:`~nameparser.config.titles.AFTER_GIVEN_TITLES`.
|
|
727
|
+
after_given_titles: frozenset[str] = frozenset()
|
|
728
|
+
#: Subset of suffix_words that are also borne as names ("fils",
|
|
729
|
+
#: "neto"). Read as a suffix only where the name has words to
|
|
730
|
+
#: spare, as an ambiguous acronym is: "Pedro Alves Neto" ends in a
|
|
731
|
+
#: suffix, "Ana Neto" keeps her family name, and either reading is
|
|
732
|
+
#: reported (rules/S11). Written in Title case in a name of mixed
|
|
733
|
+
#: case it leans to the name even with words to spare, and in lower
|
|
734
|
+
#: case it is the suffix after a comma that names the family,
|
|
735
|
+
#: unless it is also in ``agnomens``. Full default list:
|
|
736
|
+
#: :data:`~nameparser.config.suffixes.SUFFIX_WORDS_AMBIGUOUS`.
|
|
737
|
+
suffix_words_ambiguous: frozenset[str] = frozenset()
|
|
738
|
+
#: Subset of suffix_words_ambiguous whose writing says nothing:
|
|
739
|
+
#: an agnomen ("filho", "neto") is written in the case of the name
|
|
740
|
+
#: around it, so only the words to spare decide (rules/S11). An
|
|
741
|
+
#: entry outside suffix_words_ambiguous is never consulted. Full
|
|
742
|
+
#: default list: :data:`~nameparser.config.suffixes.AGNOMENS`.
|
|
743
|
+
agnomens: frozenset[str] = frozenset()
|
|
744
|
+
#: Titles that may also be a given name ("dean", "justice",
|
|
745
|
+
#: "major"), each read as a title only inside a title phrase: after a
|
|
746
|
+
#: modifier, a style, or a title it stands in ``title_phrases``
|
|
747
|
+
#: behind; or before "of" or a title it stands in ``title_phrases``
|
|
748
|
+
#: in front of. Elsewhere it is a name word, and where it opens the
|
|
749
|
+
#: name the parse reports ``title-or-name`` if a phrase of
|
|
750
|
+
#: ``title_phrases`` the parse can read holds it (rules/H9), so "Grace Smith", which no
|
|
751
|
+
#: phrase holds, reports nothing, and emptying ``title_phrases``
|
|
752
|
+
#: silences the report: "Dean Smith"
|
|
753
|
+
#: and "Mr Major Smith" read given "Dean" and "Major", "Dean of
|
|
754
|
+
#: Students Smith" and "Major General Smith" a title. An entry also
|
|
755
|
+
#: in ``titles`` is a title wherever it stands, which is what listing
|
|
756
|
+
#: it there asks, so the default lists these words here alone. Full
|
|
757
|
+
#: default list:
|
|
758
|
+
#: :data:`~nameparser.config.titles.TITLES_AMBIGUOUS`.
|
|
759
|
+
titles_ambiguous: frozenset[str] = frozenset()
|
|
760
|
+
#: Titles that only qualify a head title ("vice", "wing", "grand").
|
|
761
|
+
#: At the head of the name one is a title only with a head title
|
|
762
|
+
#: behind it in its run of titles, so "Wing Commander Lau" keeps the
|
|
763
|
+
#: title where "Wing Lau" reads "Wing" as the given name; after a
|
|
764
|
+
#: comma it is read as any title is (rules/H9), save one also in
|
|
765
|
+
#: ``titles_ambiguous``, which is read everywhere as at the head. One
|
|
766
|
+
#: standing before a word of ``titles_ambiguous`` puts that word in
|
|
767
|
+
#: a title phrase. Entries in neither ``titles`` nor
|
|
768
|
+
#: ``titles_ambiguous`` are never consulted. Full default
|
|
769
|
+
#: list: :data:`~nameparser.config.titles.TITLE_MODIFIERS`.
|
|
770
|
+
title_modifiers: frozenset[str] = frozenset()
|
|
771
|
+
#: Honorific styles ("hon", "excellency", "highness"): one standing
|
|
772
|
+
#: before a word of ``titles_ambiguous`` puts that word in a title
|
|
773
|
+
#: phrase, as in "The Hon. Justice Smith" (rules/H9). Entries in
|
|
774
|
+
#: neither ``titles`` nor ``titles_ambiguous`` are never consulted.
|
|
775
|
+
#: Full default list:
|
|
776
|
+
#: :data:`~nameparser.config.titles.TITLE_STYLES`.
|
|
777
|
+
title_styles: frozenset[str] = frozenset()
|
|
778
|
+
#: Two-word title phrases, a title and the title it qualifies
|
|
779
|
+
#: ("chief justice", "major general"), stored space-joined: a word
|
|
780
|
+
#: of ``titles_ambiguous`` on either side of one is in a title
|
|
781
|
+
#: phrase (rules/H9). A phrase is consulted only if it is two
|
|
782
|
+
#: words, each in ``titles`` or ``titles_ambiguous``. Full default
|
|
783
|
+
#: list:
|
|
784
|
+
#: :data:`~nameparser.config.titles.TITLE_PHRASES`.
|
|
785
|
+
title_phrases: frozenset[str] = frozenset()
|
|
695
786
|
#: Lowercase word -> case mask used by capitalized(): the word's
|
|
696
787
|
#: own letters and digits, each in the case it takes ("phd" ->
|
|
697
788
|
#: "PhD"), laid over the word as written, so the one entry
|
|
@@ -739,9 +830,10 @@ class Lexicon:
|
|
|
739
830
|
# the expensive one -- three working configurations broken
|
|
740
831
|
# across two attempts. Do not add a third.
|
|
741
832
|
#
|
|
742
|
-
# The v2 form of
|
|
743
|
-
#
|
|
744
|
-
#
|
|
833
|
+
# The v2 form of the vocabulary build's check that a bound given
|
|
834
|
+
# name which is also a particle may be given (tools/vocab/build.py,
|
|
835
|
+
# check 9). The build guards the shipped data; this guards
|
|
836
|
+
# vocabulary a caller supplies.
|
|
745
837
|
contradictory = (
|
|
746
838
|
self.bound_given_names & self.particles) - self.particles_ambiguous
|
|
747
839
|
if contradictory:
|
|
@@ -765,6 +857,22 @@ class Lexicon:
|
|
|
765
857
|
f"period gate suffix_acronyms_ambiguous exists to impose"
|
|
766
858
|
)
|
|
767
859
|
|
|
860
|
+
# the mirror of the check above: suffix_as_written ORs the
|
|
861
|
+
# unambiguous acronym branch too, so a gated suffix word listed
|
|
862
|
+
# there is claimed as a suffix with no count ('Ana Neto' would
|
|
863
|
+
# lose her family name, rules/S11)
|
|
864
|
+
word_gate_bypassed = self.suffix_words_ambiguous & (
|
|
865
|
+
self.suffix_acronyms - self.suffix_acronyms_ambiguous)
|
|
866
|
+
if word_gate_bypassed:
|
|
867
|
+
raise ValueError(
|
|
868
|
+
f"an ambiguous suffix word must not also be an unambiguous "
|
|
869
|
+
f"suffix acronym; in both: "
|
|
870
|
+
f"{', '.join(sorted(word_gate_bypassed))}. The acronym "
|
|
871
|
+
f"branch matches it outright, which bypasses the count "
|
|
872
|
+
f"suffix_words_ambiguous exists to impose; drop it from "
|
|
873
|
+
f"suffix_acronyms or from suffix_words_ambiguous"
|
|
874
|
+
)
|
|
875
|
+
|
|
768
876
|
# -- constructors ----------------------------------------------------
|
|
769
877
|
|
|
770
878
|
@classmethod
|
|
@@ -820,7 +928,10 @@ class Lexicon:
|
|
|
820
928
|
# Lexicon field layout (version skew) -- silently loading it
|
|
821
929
|
# would defer the failure to some distant attribute read.
|
|
822
930
|
# Message kept in sync with _types._guarded_setstate by design
|
|
823
|
-
# (layering keeps this module import-free of _types).
|
|
931
|
+
# (layering keeps this module import-free of _types). Unlike
|
|
932
|
+
# that guard it admits no field added later (`ADDED_IN`, 2.7):
|
|
933
|
+
# every Lexicon field is still required, so a release adding one
|
|
934
|
+
# breaks older Lexicon pickles until this learns the marker.
|
|
824
935
|
expected = {f.name for f in dataclasses.fields(Lexicon)} - {"_cap_map"}
|
|
825
936
|
if set(state) != expected:
|
|
826
937
|
missing = ", ".join(sorted(expected - set(state))) or "none"
|
|
@@ -937,17 +1048,20 @@ class Lexicon:
|
|
|
937
1048
|
def _default_lexicon() -> Lexicon:
|
|
938
1049
|
# v1 data modules are the single source of vocabulary through 2.x.
|
|
939
1050
|
from nameparser.config.bound_given_names import BOUND_GIVEN_NAMES
|
|
1051
|
+
from nameparser.config.given_names import GIVEN_NAME_ABBREVIATIONS
|
|
940
1052
|
from nameparser.config.capitalization import CAPITALIZATION_EXCEPTIONS
|
|
941
1053
|
from nameparser.config.conjunctions import (
|
|
942
1054
|
CONJUNCTIONS, CONJUNCTIONS_AMBIGUOUS)
|
|
943
1055
|
from nameparser.config.maiden_markers import MAIDEN_MARKERS
|
|
944
1056
|
from nameparser.config.particles import NON_GIVEN_NAME_PARTICLES, PARTICLES
|
|
945
1057
|
from nameparser.config.suffixes import (
|
|
946
|
-
GLUED_HONORIFICS, SUFFIX_ACRONYMS, SUFFIX_ACRONYMS_AMBIGUOUS,
|
|
947
|
-
SUFFIX_WORDS,
|
|
1058
|
+
AGNOMENS, GLUED_HONORIFICS, SUFFIX_ACRONYMS, SUFFIX_ACRONYMS_AMBIGUOUS,
|
|
1059
|
+
SUFFIX_WORDS, SUFFIX_WORDS_AMBIGUOUS,
|
|
948
1060
|
)
|
|
949
1061
|
from nameparser.config.surnames import KOREAN_SURNAMES
|
|
950
|
-
from nameparser.config.titles import
|
|
1062
|
+
from nameparser.config.titles import (
|
|
1063
|
+
AFTER_GIVEN_TITLES, GIVEN_NAME_TITLES, TITLE_MODIFIERS, TITLE_PHRASES,
|
|
1064
|
+
TITLE_STYLES, TITLES, TITLES_AMBIGUOUS)
|
|
951
1065
|
|
|
952
1066
|
# every vocabulary constant is a frozenset since #293, so each one
|
|
953
1067
|
# feeds its strictly-typed frozenset[str] field as it stands -- and
|
|
@@ -972,9 +1086,17 @@ def _default_lexicon() -> Lexicon:
|
|
|
972
1086
|
conjunctions=CONJUNCTIONS,
|
|
973
1087
|
conjunctions_ambiguous=CONJUNCTIONS_AMBIGUOUS,
|
|
974
1088
|
bound_given_names=BOUND_GIVEN_NAMES,
|
|
1089
|
+
given_name_abbreviations=GIVEN_NAME_ABBREVIATIONS,
|
|
975
1090
|
maiden_markers=MAIDEN_MARKERS,
|
|
976
1091
|
surnames=KOREAN_SURNAMES,
|
|
977
1092
|
honorific_tails=GLUED_HONORIFICS,
|
|
1093
|
+
after_given_titles=AFTER_GIVEN_TITLES,
|
|
1094
|
+
suffix_words_ambiguous=SUFFIX_WORDS_AMBIGUOUS,
|
|
1095
|
+
agnomens=AGNOMENS,
|
|
1096
|
+
titles_ambiguous=TITLES_AMBIGUOUS,
|
|
1097
|
+
title_modifiers=TITLE_MODIFIERS,
|
|
1098
|
+
title_styles=TITLE_STYLES,
|
|
1099
|
+
title_phrases=TITLE_PHRASES,
|
|
978
1100
|
# pass canonical pair-tuples so this strictly-typed call site never
|
|
979
1101
|
# feeds a Mapping to the tuple-annotated field; __post_init__
|
|
980
1102
|
# still tolerates a Mapping at runtime for interactive use
|
|
@@ -41,7 +41,7 @@ class Parser:
|
|
|
41
41
|
vocabulary DECLINES, so a locale pack's surnames always win where
|
|
42
42
|
they match; returning None declines in turn and the token stays
|
|
43
43
|
whole. Two promises narrow when one is supplied (the first is
|
|
44
|
-
rules/A1's Accepted
|
|
44
|
+
rules/A1's Accepted bullet "The one exception to totality"):
|
|
45
45
|
parse-totality gains its one exception -- an exception raised by
|
|
46
46
|
the segmenter propagates, because a user-supplied callable's own
|
|
47
47
|
error is a user-code error, not a content error -- and this Parser
|
|
@@ -215,7 +215,7 @@ class Parser:
|
|
|
215
215
|
dropped = set(state.dropped)
|
|
216
216
|
# Force the role BEFORE the entry pass: it keys on
|
|
217
217
|
# Role.SUFFIX, and a bare value's sub-parse reads its words
|
|
218
|
-
# as a name ('
|
|
218
|
+
# as a name ('RA PhD' is a title and a family there), so
|
|
219
219
|
# inside the sub-parse it joined nothing. The sub-parse's
|
|
220
220
|
# tags are KEPT, "joined" included: a within-piece mark
|
|
221
221
|
# (the Ph. D. merge) is role-blind and right for every
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Consumes: tokens (all roles set), dropped, ambiguities (by index;
|
|
4
4
|
one carrying ``field_tail`` is worded from its word's final role,
|
|
5
5
|
#626), and segments, for the part a withdrawn connective report's
|
|
6
|
-
letter ends (rules/
|
|
6
|
+
letter ends (rules/P8, #721).
|
|
7
7
|
Produces: a validated ParsedName -- the constructor re-checks every
|
|
8
8
|
invariant (span order/bounds, ambiguity subset), so a pipeline bug
|
|
9
9
|
that would produce an invalid result dies HERE, not in a renderer
|
|
@@ -21,12 +21,42 @@ tests/v2/pipeline/test_assemble.py.
|
|
|
21
21
|
"""
|
|
22
22
|
from __future__ import annotations
|
|
23
23
|
|
|
24
|
-
from nameparser._pipeline._state import
|
|
24
|
+
from nameparser._pipeline._state import (
|
|
25
|
+
FIELD_READING, NAME_ROLES, Beside, Deferred, ParseState,
|
|
26
|
+
PendingAmbiguity,
|
|
27
|
+
)
|
|
25
28
|
from nameparser._types import (
|
|
26
|
-
Ambiguity, AmbiguityKind, ParsedName, Role, Token,
|
|
29
|
+
FOLDED_TAG, Ambiguity, AmbiguityKind, ParsedName, Reading, Role, Token,
|
|
27
30
|
)
|
|
28
31
|
|
|
29
32
|
|
|
33
|
+
def _alternative(pending: PendingAmbiguity, materialized: tuple[Token, ...],
|
|
34
|
+
final: dict[int, Token],
|
|
35
|
+
middle_as_family: bool) -> Reading | None:
|
|
36
|
+
"""The public Reading of a report's PendingReading, its field named
|
|
37
|
+
from the final roles where the emitter deferred it (O5's other
|
|
38
|
+
primary field) and folded as `middle_as_family` folds every middle
|
|
39
|
+
(rules/O6's neighbour, the policy's own fold)."""
|
|
40
|
+
alt = pending.alternative
|
|
41
|
+
if alt is None or not materialized:
|
|
42
|
+
return None
|
|
43
|
+
role = alt.role
|
|
44
|
+
if role is Deferred.OTHER_PRIMARY:
|
|
45
|
+
role = (Role.FAMILY if materialized[0].role is Role.GIVEN
|
|
46
|
+
else Role.GIVEN)
|
|
47
|
+
elif isinstance(role, Beside) and role.index in final:
|
|
48
|
+
front = final[role.index]
|
|
49
|
+
role = (role.unfolded
|
|
50
|
+
if FOLDED_TAG in front.tags and role.unfolded is not None
|
|
51
|
+
else front.role)
|
|
52
|
+
elif isinstance(role, (Deferred, Beside)):
|
|
53
|
+
# assign found no name position to name it from
|
|
54
|
+
return None
|
|
55
|
+
if middle_as_family and role is Role.MIDDLE:
|
|
56
|
+
role = Role.FAMILY
|
|
57
|
+
return Reading(alt.kind, role, alt.source)
|
|
58
|
+
|
|
59
|
+
|
|
30
60
|
# rules/A2: "a name with no name content parses to the empty
|
|
31
61
|
# name — every field empty, false as a boolean — while ambiguities
|
|
32
62
|
# born from its punctuation survive on the empty result"
|
|
@@ -116,7 +146,7 @@ def assemble(state: ParseState) -> ParsedName:
|
|
|
116
146
|
and any(t.role in (Role.SUFFIX, Role.TITLE)
|
|
117
147
|
for t in materialized)):
|
|
118
148
|
continue
|
|
119
|
-
# rules/
|
|
149
|
+
# rules/P8: "unless no name word of its part stands to its
|
|
120
150
|
# right" -- nothing after the letter in its segment, or only
|
|
121
151
|
# suffixes, titles, a maiden clause or a nickname: it connects
|
|
122
152
|
# nothing, so the report goes (#721). Asked here for the reason
|
|
@@ -143,7 +173,10 @@ def assemble(state: ParseState) -> ParsedName:
|
|
|
143
173
|
continue
|
|
144
174
|
detail = (f"{detail}{FIELD_READING[materialized[0].role]}"
|
|
145
175
|
f"{pending.field_tail}")
|
|
146
|
-
ambiguities.append(Ambiguity(
|
|
176
|
+
ambiguities.append(Ambiguity(
|
|
177
|
+
pending.kind, detail, materialized,
|
|
178
|
+
_alternative(pending, materialized, final,
|
|
179
|
+
state.policy.middle_as_family)))
|
|
147
180
|
return ParsedName(original=state.original,
|
|
148
181
|
tokens=tuple(final.values()),
|
|
149
182
|
ambiguities=tuple(ambiguities))
|