nameparser 2.6.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.
Files changed (237) hide show
  1. {nameparser-2.6.0/nameparser.egg-info → nameparser-2.7.0}/PKG-INFO +9 -6
  2. {nameparser-2.6.0 → nameparser-2.7.0}/README.rst +8 -5
  3. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/__init__.py +5 -1
  4. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_config_shim.py +91 -5
  5. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_lexicon.py +122 -10
  6. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_pipeline/_assemble.py +36 -3
  7. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_pipeline/_assign.py +490 -86
  8. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_pipeline/_classify.py +154 -34
  9. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_pipeline/_comma.py +142 -52
  10. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_pipeline/_extract.py +4 -3
  11. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_pipeline/_group.py +142 -37
  12. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_pipeline/_pieces.py +405 -99
  13. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_pipeline/_post_rules.py +116 -43
  14. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_pipeline/_segment.py +4 -4
  15. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_pipeline/_state.py +93 -5
  16. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_pipeline/_vocab.py +632 -72
  17. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_policy.py +12 -8
  18. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_render.py +60 -1
  19. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_types.py +189 -30
  20. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_version.py +4 -4
  21. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/config/_compiled.py +437 -23
  22. nameparser-2.7.0/nameparser/config/_titles_2_6_0.py +826 -0
  23. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/config/capitalization.py +2 -1
  24. nameparser-2.7.0/nameparser/config/given_names.py +18 -0
  25. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/config/particles.py +8 -7
  26. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/config/suffixes.py +23 -2
  27. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/config/titles.py +42 -3
  28. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/ar.toml +25 -12
  29. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/bn.toml +1 -2
  30. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/cs.toml +1 -3
  31. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/de.toml +14 -9
  32. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/en.toml +168 -91
  33. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/es.toml +12 -10
  34. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/fil.toml +9 -1
  35. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/fr.toml +12 -12
  36. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/ga.toml +2 -2
  37. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/gd.toml +1 -1
  38. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/he.toml +3 -2
  39. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/hi.toml +1 -2
  40. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/it.toml +11 -10
  41. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/ms.toml +3 -3
  42. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/mul.toml +1 -1
  43. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/nl.toml +23 -20
  44. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/pa.toml +2 -1
  45. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/pl.toml +2 -1
  46. nameparser-2.7.0/nameparser/vocab/pt-BR.toml +49 -0
  47. nameparser-2.7.0/nameparser/vocab/pt.toml +101 -0
  48. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/sr.toml +1 -2
  49. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/sw.toml +1 -2
  50. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/tr.toml +3 -1
  51. nameparser-2.7.0/nameparser/vocab/und.toml +21 -0
  52. {nameparser-2.6.0 → nameparser-2.7.0/nameparser.egg-info}/PKG-INFO +9 -6
  53. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser.egg-info/SOURCES.txt +5 -0
  54. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_conjunctions.py +2 -2
  55. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_first_name.py +11 -2
  56. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_suffixes.py +49 -34
  57. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_titles.py +4 -4
  58. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/_differential_fixtures.py +35 -5
  59. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/cases.py +2024 -457
  60. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/pipeline/test_assemble.py +2 -1
  61. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/pipeline/test_assign.py +51 -17
  62. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/pipeline/test_classify.py +4 -4
  63. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/pipeline/test_comma.py +16 -12
  64. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/pipeline/test_group.py +36 -27
  65. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/pipeline/test_pieces.py +44 -10
  66. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/pipeline/test_post_rules.py +13 -6
  67. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/pipeline/test_script_segment.py +14 -11
  68. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/pipeline/test_state.py +7 -6
  69. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/pipeline/test_vocab.py +311 -19
  70. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/rules_doc.py +5 -4
  71. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_benchmark.py +62 -2
  72. nameparser-2.7.0/tests/v2/test_cases.py +814 -0
  73. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_config_shim.py +191 -0
  74. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_contracts.py +3 -1
  75. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_differential.py +18 -0
  76. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_facade.py +21 -7
  77. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_facade_cases.py +7 -0
  78. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_lexicon.py +22 -8
  79. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_local_agreement.py +173 -3
  80. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_local_corpora.py +404 -12
  81. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_locales.py +4 -3
  82. nameparser-2.7.0/tests/v2/test_measure.py +178 -0
  83. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_name_usage_tool.py +7 -5
  84. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_parser.py +156 -34
  85. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_policy.py +1 -1
  86. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_properties.py +293 -21
  87. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_regex_sync.py +24 -2
  88. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_render.py +53 -0
  89. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_reprs.py +6 -1
  90. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_snapshot.py +78 -4
  91. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_types.py +140 -5
  92. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_vocab.py +304 -51
  93. nameparser-2.7.0/tests/v2/test_word_report.py +309 -0
  94. nameparser-2.6.0/nameparser/vocab/pt.toml +0 -70
  95. nameparser-2.6.0/nameparser/vocab/und.toml +0 -21
  96. nameparser-2.6.0/tests/v2/test_cases.py +0 -415
  97. {nameparser-2.6.0 → nameparser-2.7.0}/AUTHORS +0 -0
  98. {nameparser-2.6.0 → nameparser-2.7.0}/LICENSE +0 -0
  99. {nameparser-2.6.0 → nameparser-2.7.0}/MANIFEST.in +0 -0
  100. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/__main__.py +0 -0
  101. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_facade.py +0 -0
  102. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_locale.py +0 -0
  103. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_parser.py +0 -0
  104. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_pipeline/__init__.py +0 -0
  105. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_pipeline/_script_segment.py +0 -0
  106. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/_pipeline/_tokenize.py +0 -0
  107. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/config/__init__.py +0 -0
  108. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/config/_deprecated.py +0 -0
  109. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/config/_invariants.py +0 -0
  110. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/config/bound_first_names.py +0 -0
  111. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/config/bound_given_names.py +0 -0
  112. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/config/conjunctions.py +0 -0
  113. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/config/maiden_markers.py +0 -0
  114. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/config/prefixes.py +0 -0
  115. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/config/regexes.py +0 -0
  116. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/config/surnames.py +0 -0
  117. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/locales/__init__.py +0 -0
  118. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/locales/ja.py +0 -0
  119. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/locales/ru.py +0 -0
  120. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/locales/tr_az.py +0 -0
  121. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/locales/zh.py +0 -0
  122. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/parser.py +0 -0
  123. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/py.typed +0 -0
  124. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/util.py +0 -0
  125. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/ak.toml +0 -0
  126. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/am.toml +0 -0
  127. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/as.toml +0 -0
  128. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/be.toml +0 -0
  129. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/bg.toml +0 -0
  130. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/bin.toml +0 -0
  131. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/bo.toml +0 -0
  132. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/bs.toml +0 -0
  133. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/ca.toml +0 -0
  134. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/da.toml +0 -0
  135. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/el.toml +0 -0
  136. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/et.toml +0 -0
  137. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/fa.toml +0 -0
  138. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/ff.toml +0 -0
  139. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/fi.toml +0 -0
  140. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/gu.toml +0 -0
  141. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/ha.toml +0 -0
  142. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/hr.toml +0 -0
  143. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/hu.toml +0 -0
  144. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/hy.toml +0 -0
  145. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/id.toml +0 -0
  146. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/is.toml +0 -0
  147. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/ja.toml +0 -0
  148. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/ka.toml +0 -0
  149. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/kk.toml +0 -0
  150. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/km.toml +0 -0
  151. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/kn.toml +0 -0
  152. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/ko.toml +0 -0
  153. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/kok.toml +0 -0
  154. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/lo.toml +0 -0
  155. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/lt.toml +0 -0
  156. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/lv.toml +0 -0
  157. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/mi.toml +0 -0
  158. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/mk.toml +0 -0
  159. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/ml.toml +0 -0
  160. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/mn.toml +0 -0
  161. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/mr.toml +0 -0
  162. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/my.toml +0 -0
  163. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/nb.toml +0 -0
  164. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/ne.toml +0 -0
  165. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/nn.toml +0 -0
  166. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/or.toml +0 -0
  167. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/ro.toml +0 -0
  168. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/ru.toml +0 -0
  169. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/sd.toml +0 -0
  170. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/shn.toml +0 -0
  171. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/si.toml +0 -0
  172. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/sk.toml +0 -0
  173. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/sl.toml +0 -0
  174. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/sv.toml +0 -0
  175. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/ta.toml +0 -0
  176. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/te.toml +0 -0
  177. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/th.toml +0 -0
  178. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/ti.toml +0 -0
  179. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/uk.toml +0 -0
  180. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/ur.toml +0 -0
  181. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/vi.toml +0 -0
  182. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/yi.toml +0 -0
  183. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/yo.toml +0 -0
  184. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/zh.toml +0 -0
  185. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser/vocab/zu.toml +0 -0
  186. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser.egg-info/dependency_links.txt +0 -0
  187. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser.egg-info/requires.txt +0 -0
  188. {nameparser-2.6.0 → nameparser-2.7.0}/nameparser.egg-info/top_level.txt +0 -0
  189. {nameparser-2.6.0 → nameparser-2.7.0}/pyproject.toml +0 -0
  190. {nameparser-2.6.0 → nameparser-2.7.0}/setup.cfg +0 -0
  191. {nameparser-2.6.0 → nameparser-2.7.0}/tests/__init__.py +0 -0
  192. {nameparser-2.6.0 → nameparser-2.7.0}/tests/base.py +0 -0
  193. {nameparser-2.6.0 → nameparser-2.7.0}/tests/conftest.py +0 -0
  194. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_bound_given_names.py +0 -0
  195. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_brute_force.py +0 -0
  196. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_capitalization.py +0 -0
  197. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_comma_variants.py +0 -0
  198. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_constants.py +0 -0
  199. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_east_slavic_patronymic_order.py +0 -0
  200. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_initials.py +0 -0
  201. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_middle_name_as_last.py +0 -0
  202. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_nicknames.py +0 -0
  203. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_output_format.py +0 -0
  204. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_particles.py +0 -0
  205. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_python_api.py +0 -0
  206. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_turkic_patronymic_order.py +0 -0
  207. {nameparser-2.6.0 → nameparser-2.7.0}/tests/test_variations.py +0 -0
  208. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/__init__.py +0 -0
  209. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/conftest.py +0 -0
  210. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/design_docs.py +0 -0
  211. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/pipeline/__init__.py +0 -0
  212. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/pipeline/test_extract.py +0 -0
  213. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/pipeline/test_segment.py +0 -0
  214. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/pipeline/test_tokenize.py +0 -0
  215. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/rules_budget.py +0 -0
  216. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/rules_skeleton.py +0 -0
  217. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_cldr_agreement.py +0 -0
  218. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_cldr_corpus.py +0 -0
  219. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_cli.py +0 -0
  220. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_config_aliases.py +0 -0
  221. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_corpora.py +0 -0
  222. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_corpus_build.py +0 -0
  223. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_design_docs.py +0 -0
  224. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_doc_citations.py +0 -0
  225. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_doc_spellings.py +0 -0
  226. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_gnd_corpus.py +0 -0
  227. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_layering.py +0 -0
  228. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_local_jrc.py +0 -0
  229. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_locale.py +0 -0
  230. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_order_correspondence.py +0 -0
  231. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_rules_budget.py +0 -0
  232. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_rules_doc.py +0 -0
  233. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_rules_doc_grammar.py +0 -0
  234. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_rules_skeleton.py +0 -0
  235. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_wikidata_corpus.py +0 -0
  236. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_wikidata_native_corpus.py +0 -0
  237. {nameparser-2.6.0 → nameparser-2.7.0}/tests/v2/test_zh_surnames_tool.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nameparser
3
- Version: 2.6.0
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
6
  License-Expression: LGPL-2.1-or-later
@@ -35,11 +35,14 @@ nameparser parses human names into seven fields — title, given, middle,
35
35
  family, suffix, nickname, maiden. Results are immutable, configuration is
36
36
  composable, and locale packs are opt-in.
37
37
 
38
- 📣 **nameparser 2.6 is out.** It keeps surnames that earlier releases
39
- read as credentials (``Luo Si``, ``Maria de Sa``, ``Ram Bahadur B.K.``),
40
- reads ``Wm`` and ``Md`` as the given names they abbreviate, recognizes more credentials after a comma
41
- (``John Smith, MBChB``), and adds an opt-in reading for names with two
42
- family names (``Gabriel García Márquez``). The `release log
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
43
46
  <https://nameparser.readthedocs.io/en/latest/release_log.html>`__ has
44
47
  the details.
45
48
 
@@ -7,11 +7,14 @@ 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.6 is out.** It keeps surnames that earlier releases
11
- read as credentials (``Luo Si``, ``Maria de Sa``, ``Ram Bahadur B.K.``),
12
- reads ``Wm`` and ``Md`` as the given names they abbreviate, recognizes more credentials after a comma
13
- (``John Smith, MBChB``), and adds an opt-in reading for names with two
14
- family names (``Gabriel García Márquez``). The `release log
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
15
18
  <https://nameparser.readthedocs.io/en/latest/release_log.html>`__ has
16
19
  the details.
17
20
 
@@ -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", "ParsedName",
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.titles import GIVEN_NAME_TITLES, TITLES
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
- "titles": TITLES,
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,10 +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 GLUED_HONORIFICS
1056
+ from nameparser.config.suffixes import (
1057
+ AGNOMENS, GLUED_HONORIFICS, SUFFIX_WORDS_AMBIGUOUS)
1050
1058
  from nameparser.config.surnames import KOREAN_SURNAMES
1051
- from nameparser.config.titles import AFTER_GIVEN_TITLES
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)
1052
1063
  # Every set field and every capitalization_exceptions key passes
1053
1064
  # _v1_matchable BEFORE any set algebra below: an entry v1 could
1054
1065
  # never match, or one Lexicon folds to empty (#582), is dropped
@@ -1071,6 +1082,27 @@ class Constants:
1071
1082
  conjunctions = matchable("conjunctions")
1072
1083
  bound = matchable("bound_first_names")
1073
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)
1074
1106
  first_name_titles = matchable("first_name_titles")
1075
1107
  non_given = matchable("non_first_name_prefixes")
1076
1108
  ambiguous_acronyms = matchable("suffix_acronyms_ambiguous") & acronyms
@@ -1136,7 +1168,7 @@ class Constants:
1136
1168
  # v1 also had, later, at capitalize().
1137
1169
  try:
1138
1170
  lexicon = Lexicon(
1139
- titles=titles,
1171
+ titles=titles - kept_moved,
1140
1172
  # TRANSLATE, do not filter. The two versions build the same
1141
1173
  # lookup key differently: v1 joins the raw title run and
1142
1174
  # then applies lc(), which strips only the whole string's
@@ -1193,6 +1225,15 @@ class Constants:
1193
1225
  # through this intersection.
1194
1226
  conjunctions_ambiguous=CONJUNCTIONS_AMBIGUOUS & conjunctions,
1195
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,
1196
1237
  # v1 Constants has no manager for these (#274 is 2.0
1197
1238
  # behavior); the data module is the only source
1198
1239
  maiden_markers=MAIDEN_MARKERS,
@@ -1216,6 +1257,51 @@ class Constants:
1216
1257
  # so the intersection is kept for `_snapshot() ==
1217
1258
  # Lexicon.default()` legibility, as conjunctions_ambiguous'.
1218
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(" "))),
1219
1305
  # TupleManager is dict[str, object] (v1 parity: values were
1220
1306
  # never statically str-typed); every real entry is a str,
1221
1307
  # same assumption _DelimiterManager's sentinel lookup makes.
@@ -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
+ "given_name_abbreviations",
28
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
@@ -68,6 +71,14 @@ _VOCAB_FIELDS = (
68
71
  #: decides nothing, and AGENTS.md's invariants rule guards harm, not
69
72
  #: no-ops. after_given_titles -- the same: classify tags a word from it
70
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.
71
82
  _SUBSET_FIELDS = (
72
83
  ("particles_ambiguous", "particles",
73
84
  "an orphan emits a spurious particle-or-given ambiguity"),
@@ -75,6 +86,8 @@ _SUBSET_FIELDS = (
75
86
  "an orphan silently becomes a period-gated suffix"),
76
87
  ("honorific_tails", "suffix_words",
77
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"),
78
91
  )
79
92
 
80
93
 
@@ -98,7 +111,7 @@ _SUBSET_FIELDS = (
98
111
  #: by a genuine multi-token lookahead instead --
99
112
  #: _pipeline._vocab.maiden_marker_run, longest first -- and only the
100
113
  #: STORAGE rule is shared with titles.
101
- _PHRASE_FIELDS = ("given_name_titles", "maiden_markers")
114
+ _PHRASE_FIELDS = ("given_name_titles", "maiden_markers", "title_phrases")
102
115
 
103
116
 
104
117
  #: Every character the parser reads as a full stop at a WORD'S EDGE
@@ -567,12 +580,14 @@ class Lexicon:
567
580
  Entries are normalized at construction -- lowercased, NFC-composed,
568
581
  edge full stops stripped -- so matching is case-insensitive.
569
582
  Vocabulary entries are single words -- a multi-word entry warns at
570
- construction and can never match. Two fields are exempt, and they
583
+ construction and can never match. Three fields are exempt, and they
571
584
  differ in HOW they match: ``given_name_titles`` is looked up
572
585
  against the run of words the parse has ALREADY read as titles --
573
- the whole run space-joined, or that run's last word -- while
586
+ the whole run space-joined, or that run's last word --
574
587
  ``maiden_markers`` is matched by lookahead, longest first, over
575
- 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"``).
576
591
  Field docs below show examples, not full
577
592
  contents; inspect any field's shipped vocabulary directly, e.g.
578
593
  ``Lexicon.default().conjunctions``."""
@@ -581,7 +596,7 @@ class Lexicon:
581
596
  #: list: :data:`~nameparser.config.titles.TITLES`.
582
597
  titles: frozenset[str] = frozenset()
583
598
  #: Titles whose single name word, before or after them, reads as a
584
- #: GIVEN name ("sheikh", "sister", ...) rather than a family name. Full
599
+ #: GIVEN name ("sir", "sister", ...) rather than a family name. Full
585
600
  #: default list: :data:`~nameparser.config.titles.GIVEN_NAME_TITLES`.
586
601
  given_name_titles: frozenset[str] = frozenset()
587
602
  #: Post-nominal acronym suffixes, matched with or without periods
@@ -656,6 +671,17 @@ class Lexicon:
656
671
  #: Full default list:
657
672
  #: :data:`~nameparser.config.bound_given_names.BOUND_GIVEN_NAMES`.
658
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()
659
685
  #: Marker words introducing a birth surname, routed to the maiden
660
686
  #: field ("née", "geb.", "rozená", ...). An entry may be a PHRASE
661
687
  #: ("z domu"): entries are matched by lookahead, longest first, so
@@ -693,12 +719,70 @@ class Lexicon:
693
719
  #: Salutations that read as a title where one ENDS the given part
694
720
  #: after a family comma, written bare: "Doe, Jane Mrs" reads title
695
721
  #: "Mrs" where an office or rank word there stays a middle name
696
- #: ("Doe, John King"), rules/H8. A period-marked title word reads as
722
+ #: ("Doe, John Pope"), rules/H8. A period-marked title word reads as
697
723
  #: a title there whether listed here or not (rules/H5). Entries need
698
724
  #: not also be in ``titles``; one that is not is never consulted.
699
725
  #: Full default list:
700
726
  #: :data:`~nameparser.config.titles.AFTER_GIVEN_TITLES`.
701
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()
702
786
  #: Lowercase word -> case mask used by capitalized(): the word's
703
787
  #: own letters and digits, each in the case it takes ("phd" ->
704
788
  #: "PhD"), laid over the word as written, so the one entry
@@ -773,6 +857,22 @@ class Lexicon:
773
857
  f"period gate suffix_acronyms_ambiguous exists to impose"
774
858
  )
775
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
+
776
876
  # -- constructors ----------------------------------------------------
777
877
 
778
878
  @classmethod
@@ -828,7 +928,10 @@ class Lexicon:
828
928
  # Lexicon field layout (version skew) -- silently loading it
829
929
  # would defer the failure to some distant attribute read.
830
930
  # Message kept in sync with _types._guarded_setstate by design
831
- # (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.
832
935
  expected = {f.name for f in dataclasses.fields(Lexicon)} - {"_cap_map"}
833
936
  if set(state) != expected:
834
937
  missing = ", ".join(sorted(expected - set(state))) or "none"
@@ -945,18 +1048,20 @@ class Lexicon:
945
1048
  def _default_lexicon() -> Lexicon:
946
1049
  # v1 data modules are the single source of vocabulary through 2.x.
947
1050
  from nameparser.config.bound_given_names import BOUND_GIVEN_NAMES
1051
+ from nameparser.config.given_names import GIVEN_NAME_ABBREVIATIONS
948
1052
  from nameparser.config.capitalization import CAPITALIZATION_EXCEPTIONS
949
1053
  from nameparser.config.conjunctions import (
950
1054
  CONJUNCTIONS, CONJUNCTIONS_AMBIGUOUS)
951
1055
  from nameparser.config.maiden_markers import MAIDEN_MARKERS
952
1056
  from nameparser.config.particles import NON_GIVEN_NAME_PARTICLES, PARTICLES
953
1057
  from nameparser.config.suffixes import (
954
- GLUED_HONORIFICS, SUFFIX_ACRONYMS, SUFFIX_ACRONYMS_AMBIGUOUS,
955
- SUFFIX_WORDS,
1058
+ AGNOMENS, GLUED_HONORIFICS, SUFFIX_ACRONYMS, SUFFIX_ACRONYMS_AMBIGUOUS,
1059
+ SUFFIX_WORDS, SUFFIX_WORDS_AMBIGUOUS,
956
1060
  )
957
1061
  from nameparser.config.surnames import KOREAN_SURNAMES
958
1062
  from nameparser.config.titles import (
959
- AFTER_GIVEN_TITLES, GIVEN_NAME_TITLES, TITLES)
1063
+ AFTER_GIVEN_TITLES, GIVEN_NAME_TITLES, TITLE_MODIFIERS, TITLE_PHRASES,
1064
+ TITLE_STYLES, TITLES, TITLES_AMBIGUOUS)
960
1065
 
961
1066
  # every vocabulary constant is a frozenset since #293, so each one
962
1067
  # feeds its strictly-typed frozenset[str] field as it stands -- and
@@ -981,10 +1086,17 @@ def _default_lexicon() -> Lexicon:
981
1086
  conjunctions=CONJUNCTIONS,
982
1087
  conjunctions_ambiguous=CONJUNCTIONS_AMBIGUOUS,
983
1088
  bound_given_names=BOUND_GIVEN_NAMES,
1089
+ given_name_abbreviations=GIVEN_NAME_ABBREVIATIONS,
984
1090
  maiden_markers=MAIDEN_MARKERS,
985
1091
  surnames=KOREAN_SURNAMES,
986
1092
  honorific_tails=GLUED_HONORIFICS,
987
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,
988
1100
  # pass canonical pair-tuples so this strictly-typed call site never
989
1101
  # feeds a Mapping to the tuple-annotated field; __post_init__
990
1102
  # still tolerates a Mapping at runtime for interactive use
@@ -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 FIELD_READING, NAME_ROLES, ParseState
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"
@@ -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(pending.kind, detail, materialized))
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))