Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions AGENTS.md

Large diffs are not rendered by default.

12 changes: 5 additions & 7 deletions docs/customize.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,11 @@ the same words, and :doc:`migrate` has the table.
The default word lists themselves — ``TITLES``, ``PARTICLES`` and the
other frozensets in ``nameparser.config`` — are the default lexicon's
compiled sets, frozen and read-only, so a runtime addition belongs on a
:class:`~nameparser.Lexicon` as above. ``REGEXES`` and
``CAPITALIZATION_EXCEPTIONS`` are the two members the freeze does not
cover — they are still plain dicts. Editing one at runtime is not a
supported override. The parser does not read ``REGEXES`` at all, and
``Lexicon.default()`` is cached, so an edit to
``CAPITALIZATION_EXCEPTIONS`` after the first parse never reaches it;
extend a :class:`~nameparser.Lexicon`'s pairs instead
:class:`~nameparser.Lexicon` as above. ``CAPITALIZATION_EXCEPTIONS`` is
the one member the freeze does not cover — it is still a plain dict.
Editing it at runtime is not a supported override:
``Lexicon.default()`` is cached, so an edit after the first parse never
reaches it; extend a :class:`~nameparser.Lexicon`'s pairs instead
(:ref:`case-exceptions`).

Five of these lists, ``PARTICLES``, ``NON_GIVEN_NAME_PARTICLES``,
Expand Down
5 changes: 3 additions & 2 deletions docs/design/decisions/3-0-reevaluations.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Promoted 2026-08-15 from session memory (Derek's 2026-07-30 ask; promotion appro
- 3.0 carries no v1 compatibility layer: `HumanName`, `nameparser.parser`, `nameparser.util`, `nameparser.config`'s five shim names and the five 1.x word-list names 2.2 retired are gone, so every surface speaks `given` and `family`, a configuration is a `Lexicon` and a `Policy` handed to a `Parser`, and a pickle is a core type's — 2026-10-28 #357, REMOVAL.
- Render's default no longer space-joins its fields as v1's did: 3.0's default follows the order a name was read in and keeps its written separators (`str(parse("威廉·莎士比亚"))` is `'威廉·莎士比亚'`, where 2.x gave `'威廉 莎士比亚'`), decided in decisions/vocabulary-model's #754 output-model entry, item 6, and stated as rules/R6 — 2026-08-15; 2026-10-28 #754 (Derek), THE RENDER SIGNATURE.
- A `.pyi` stub typing `Policy.__init__` wide and its attributes narrow is a 3.0 candidate, deferred from #334 for want of a signature list with its own sync test — 2026-08-15.
- The vocabulary DATA modules keep `nameparser.config` as their home in 3.0, and it holds them and nothing else — 2026-10-28 #357 (Derek); the five shim names left it, and the five 1.x word-list names 2.2 retired went with their alias bridge — 2026-10-28 #357, REMOVAL.
- The vocabulary DATA modules keep `nameparser.config` as their home in 3.0, and it holds them and nothing else — 2026-10-28 #357 (Derek); the five shim names left it, and the five 1.x word-list names 2.2 retired went with their alias bridge — 2026-10-28 #357, REMOVAL; `regexes`, which configured only the v1 parser, left it too, announced by 2.8.1 — 2026-11-02 #1008.
- Pickle-guard layout breaks landing in a minor release are shim-era friction; the guarded raise stands on its own — 2026-08-15.
- Reading positionally where no script licenses family-first is the safe direction on its own, family-first being the marked case that needs affirmative evidence — 2026-08-15.
- From 2.8, the last 2.x feature release, the uses 3.0 removes warn with a `DeprecationWarning` on every call at the caller's line naming code that runs: constructing `HumanName` or a `Constants`, loading either from a pickle, mutating the shared `CONSTANTS`, and reading `nameparser.config`'s three managers or `nameparser.util.lc`; importing `nameparser`, `nameparser.parser` or `HumanName` stays quiet, and so does reading the shared `CONSTANTS`, which no warning reaches — 2026-10-28 #925 (Derek), WARNINGS.; 3.0 removes those uses and their warnings with them — 2026-10-28 #357, REMOVAL.
Expand Down Expand Up @@ -63,7 +63,7 @@ Open: #753 (phase 3, language packs; the three pack bullets carried out by its 3
- (B) 2026-09-23 #459 — THE SHIM RAISES on a v1 config it once accepted: a `capitalization_exceptions` value that does not spell its key's letters and digits raises `ValueError` at the first parse (decisions/R4, 2026-09-23). Cited against the shim's never-raise rule and taken anyway, so it is recorded here: the raise stands on its own — a case mask has no reading for a value that re-spells its word, and the core `Lexicon` raises the same way with no shim involved — and only where it LANDS for a v1 caller, at the first parse rather than at construction, is shim-era. 3.0 has nothing left to decide.
- (B) 2026-10-02 #541 — THE SHIM DROPS, with a warning, every v1 `Constants` entry v1 could never match (decisions/R4, 2026-10-02). Cited from 1.4 parity, and it dies with the shim: the core `Lexicon` normalizes edge whitespace away and has no v1 reading to reproduce, so 3.0 has nothing left to decide.
- (B) 2026-10-26 #752 — THE SHIM KEEPS 2.6.0'S TITLE LIST as v1's default `titles`, frozen in `nameparser/config/_titles_2_6_0.py`, so a title a later release made phrase-only (decisions/H9, 2026-10-26, the v1 title list) is still listed and removable. Cited from v1 parity, and it dies with the shim: the core `Lexicon` keeps the two sets apart and has no v1 list to reproduce, so 3.0 deletes the file with the facade and decides nothing.
- 2026-10-28 #357 (Derek) (its five shim names as all 3.0 removes from the package, and its standing alias messages, superseded by 2026-10-28 #357, REMOVAL, below) — THE VOCABULARY DATA MODULES KEEP `nameparser.config` IN 3.0. SUPERSEDES: the 2026-08-15 bundle's nameparser.config removal-scope bullet. What 3.0 removes from the package is the five shim names (`Constants`, `CONSTANTS`, `SetManager`, `TupleManager`, `RegexTupleManager`); `titles`, `particles`, `suffixes`, `regexes` and the rest stay where `Lexicon`'s field docs already point, the core keeps importing them from there, and so 2.8 needs no bridge for them. The five 2.2 alias messages (`prefixes.PREFIXES` → `particles.PARTICLES` and the rest, #357's subject) therefore stand as written: each names a path 3.0 keeps. Decided in chat with the orchestrating session of the 2.8 deprecation work, answering the 2.8 deprecation inventory's first question.
- 2026-10-28 #357 (Derek) (its five shim names as all 3.0 removes from the package, and its standing alias messages, superseded by 2026-10-28 #357, REMOVAL, below; its `regexes` among the modules that stay, superseded by 2026-11-02 #1008, below) — THE VOCABULARY DATA MODULES KEEP `nameparser.config` IN 3.0. SUPERSEDES: the 2026-08-15 bundle's nameparser.config removal-scope bullet. What 3.0 removes from the package is the five shim names (`Constants`, `CONSTANTS`, `SetManager`, `TupleManager`, `RegexTupleManager`); `titles`, `particles`, `suffixes`, `regexes` and the rest stay where `Lexicon`'s field docs already point, the core keeps importing them from there, and so 2.8 needs no bridge for them. The five 2.2 alias messages (`prefixes.PREFIXES` → `particles.PARTICLES` and the rest, #357's subject) therefore stand as written: each names a path 3.0 keeps. Decided in chat with the orchestrating session of the 2.8 deprecation work, answering the 2.8 deprecation inventory's first question.
- 2026-10-28 #925 (Derek), WARNINGS — 2.8 WARNS WHERE CODE USES WHAT 3.0 REMOVES, and is the last 2.x feature release; after it master is 3.0 development and 2.x takes fixes only. SUPERSEDES: the 2026-08-29 entry's removal candidacy for `force_mixed_case_capitalization` (last paragraph). This keeps the 2.0 RFC's promise that a `HumanName` construction warning ships in a 2.x minor before 3.0 (docs/design/nameparser-2.0-rfc.md), which no release had kept: until 2.8 a caller who never mutated the shared `CONSTANTS` was never warned. What warns, and how:
- Constructing `HumanName` (a subclass included) and a caller-built `Constants`, `Constants.copy()` included; the import-time `CONSTANTS` singleton is built past `__init__`, so `import nameparser` never warns. The cost of exempting it: reading the shared `CONSTANTS` (`'dr' in CONSTANTS.titles`) warns nowhere, so code that only reads it passes `-W error::DeprecationWarning` on 2.8 and breaks in 3.0; docs/migrate.rst says so.
- Loading a `HumanName` or `Constants` pickle, from `__setstate__`, since unpickling skips `__init__` and the load is the one use construction never sees; each names what to store instead (`parse(text).as_dict()` or the text; a pickled `Parser`). `copy.copy` and `copy.deepcopy` go through the same hooks, a subclass's own included, with only the load warning filtered, a copy being no load; `copy.copy(CONSTANTS)` stays quiet with them, and a `HumanName` built on the copy warns.
Expand All @@ -81,3 +81,4 @@ Open: #753 (phase 3, language packs; the three pack bullets carried out by its 3
Left open: render's space-joined default (the 2026-08-15 bundle's render bullet), which the removal does not touch: `str(parse("威廉·莎士比亚"))` is still `'威廉 莎士比亚'`. The mechanisms.md entries that described the layer, FACADE-CONTRACT and CONFIG-SHIM-SNAPSHOT, are retired with it; LEGACY-STATE-SIGNATURE, whose one instance was the shim's `_LEGACY_DEAD_ENTRIES`, stays as a shape that lives in nothing.
Measured 2026-10-28 on the removal branch: the call budget's `facade` rows and the facade's thousand-names timing row leave tests/v2/test_benchmark.py (decisions/parse-cost); `tools/differential/snapshot.jsonl` loses its `facade` key, three rows, the `Ph. D.` initials 'J. P D.'; and `compare.py --baseline 1.4.0`, now comparing 1.4.0's `HumanName` with the tree's `parse()`, reports 1,749 moved entries where it reported 1,751: two `Ph. D.` names, `Ph. D., John` ('J. P. D.') and `Ph. D., Prof. Dr. Alejandro Manuel César Mamani de la Cruz Jr.` ('A. M. C. M. C. P. D.'), whose initials 1.4.0 and `parse()` both write 'P. D.' for the merged element where the facade wrote 'P D.'; the second is a CLDR fictional rendering, so the count of moved names only `corpus_cldr.jsonl` holds goes from 179 to 178. One behavior lived only in the facade: its initials fell back to the vocabulary for a spliced field (`john smith` with middle `e` spliced in gave 'j. s.'), where the core's `replace()` then `initials()` gives 'j. e. s.' (rules/R3's Accepted bullet on a field set as raw text; decisions/R3, 2026-09-13 #528).
- 2026-10-29 #753, CARRIED OUT — (A) phase 3b answers SHOULD A LOCALE PACK BE ABLE TO REMOVE BASE VOCABULARY? (the 2026-09-13 bullet) yes, by selection. `Locale.languages` is a choice of BCP 47 tags; `parser_for` unions the packs' choices and compiles the reading index under it, where a chosen language's `default = false` readings join and, on a word a chosen language holds a vocabulary reading of, other languages' vocabulary readings leave, save `mul.toml`'s. The 2026-08-17 bullet's "overrides rather than additions" is met the same way, with no removal list on `Locale`. What 3b built and measured is decisions/vocabulary-model's 2026-10-29 #753 phase 3b entry.
- 2026-11-02 #1008 — `nameparser.config.regexes` IS REMOVED IN 3.0, ANNOUNCED BY A 2.8.1 `DeprecationWarning`. SUPERSEDES: the 2026-10-28 #357 (Derek) entry's listing of `regexes` among the `nameparser.config` modules that stay. The module's `REGEXES` table configured the v1 parser through `CONSTANTS.regexes`, and the 2.0 pipeline never read it: parsing is configured by named `Policy` fields, the two v1 switches that survived being `Policy(strip_bidi=...)` and `Policy(strip_emoji=...)`. What it still did was hold a third copy of every pattern the pipeline and render hand-copy, which `tests/v2/test_regex_sync.py` held both copies to as the source, backwards, since the pipeline's copy is the one that parses (#994 edited the East Slavic pattern in three files where two would have done). The test now pins each hand copy to the pipeline's own (`_render._ROMAN` to `_vocab._ROMAN`, `_render._INITIAL` to `_vocab._INITIAL`); a pattern whose only other copy was the config table has no promise left to check: `_render._SPACES`, `_SPACE_BEFORE_COMMA`, `_MAC` and `_WORD`, `_vocab._PERIOD_NOT_AT_END` and `_tokenize._BIDI` are declared with no source, and the dedicated tests holding `_vocab._MARKS` and `_tokenize._EMOJI_RANGES`, which are not compiled patterns and so not in the roster, are gone. Three of them had no other tripwire, measured 2026-11-02 by mutation: dropping U+1AB0-1AFF from `_MARKS`, or narrowing `_SPACES` or `_SPACE_BEFORE_COMMA` to a literal space, failed nothing; behavior tests in tests/v2/test_parser.py and tests/v2/test_render.py now catch each. The freeze's exemption list (#293) loses `REGEXES` and names `CAPITALIZATION_EXCEPTIONS` alone. Declined: warning on import in 3.0 and removing in 4.0, which keeps the third copy and the backwards test anchor for a whole major version; and removing in 3.0 with no warning, since 2.8.0 gave none and every 3.0 removal warns in a released version first (this file's standing discipline). 3.0 is not released before 2.8.1, which warns of it. A caller who reused a pattern copies it (docs/migrate.rst's removals table).
1 change: 1 addition & 0 deletions docs/design/mechanisms.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ A second axis cuts across the jobs: whether the names are real or invented, labe
- Release notes describe intent; TAGS are ground truth. Before trusting a changelog cross-reference about what shipped where, run `git diff <tagA> <tagB> -- <file>` — the "1.2.1 pickle shim" claim recurred twice from a changelog line describing a thing that never shipped.
- Never pipe a check's output when its exit code matters. Under zsh, `snapshot.py | tail` makes `$?` tail's status. Redirect to a file and read the file.
- Mutation-test a new guard before believing it, and mutate the thing the guard watches — a survivor usually means the fixture satisfies the invariant for free. A guard over a HAND COPY of a pipeline set has two such holes, and #744's review found both in one test of tools/vocab/build.py's check 2, which copies the tokenizer's sets because the tool cannot import the package it writes. Pinning a copy to its source sees only the classes someone copied: the first test pinned `DIVIDERS` alone, and emoji, which the tokenizer strips under `Policy.strip_emoji`, had no copy and no check, so `dr★` compiled and the test passed. Its replacement swept every codepoint `_tokenize._ignorable` drops but accepted any refusal short of a TOML error, so the space in `dr x` passed because the phrase-title check refused it, and a class check 2 never learned would pass too wherever a later check happened to refuse its test text. Drive the sweep from the pipeline's own predicate, match the refusal by the checked rule's own messages, and record the control (hiding the emoji check behind its pinned constant fails the sweep: `test_every_character_the_tokenizer_ignores_is_refused` in tests/v2/test_vocab.py, which now leaves the space out, a phrase's word separator). The predicate reaches only what it decides, though: the interpunct between East Asian characters (rules/T3) is divided by context in `_tokenize_region`, never by `_ignorable`, so hiding that check passes the sweep (measured in #746's review, 2026-10-19). What holds it is its `MUTATIONS` row (`text-with-an-east-asian-interpunct`, input `先·生`); the `EAST_ASIAN_RANGES` and `INTERPUNCT` pins hold only the copies, and stay green with the check gone. Name such a class where the guard stands, and the test that holds it, rather than let the sweep imply it.
- Before deleting a pin, mutate each pattern it pins and run the suite: a pin can be a pattern's only tripwire. #1008 deleted `nameparser.config.regexes`, which `tests/v2/test_regex_sync.py` held the pipeline's copies to, and dropping U+1AB0-1AFF from `_vocab._MARKS`, or narrowing `_render._SPACES` or `_SPACE_BEFORE_COMMA` to a literal space, then failed nothing, until behavior tests in tests/v2/test_parser.py and tests/v2/test_render.py took each over.
- A claim about what a vocabulary entry DOES is a claim about the parse WITHOUT it, so take that parse (`Parser(lexicon=Lexicon.default().remove(w, kind=...))`) and compare: the parse with the entry present shows only that the output is consistent with it. #773's first note on `wm` said that removing it would move `Wm. Smith`, read off the parse with the word listed; with the word removed, `Wm. Smith` read exactly as before, rules/H2's leading-abbreviation title then claiming `Wm.` either way. The corrected note then said only the bare `Wm Smith` moves, and that was wrong too (#789, 2026-10-24): it took the off-switch parse at the head of the name only, and a trailing `Wm.` (rules/H5) and a `Wm.` before a family comma (rules/H6), which read vocabulary rather than shape, moved as well. A shape rule shadows an entry only in the spelling and the slot it reads, so take the off-switch parse for each spelling, bare and period-marked, in each position a rule reads the word in.
- Verify the restore, not just the mutation: diff against the pre-mutation copy; do not trust the harness's own restore report.
- Run all the gates, not the ones you remember: ruff runs before mypy and pytest in CI, and each has caught what the others passed.
Expand Down
24 changes: 18 additions & 6 deletions docs/migrate.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ https://nameparser.readthedocs.io/en/v1.4.0/
What 3.0 removes
----------------

2.8 warned at the line in your code that used each of these, with the
2.8 warned at the line in your code that used each of these, and 2.8.1
where code reads a constant of ``nameparser.config.regexes``, with the
exceptions `Finding the sites`_ names:

.. list-table::
Expand Down Expand Up @@ -60,6 +61,11 @@ exceptions `Finding the sites`_ names:
deprecated since 2.2
- Their 2.2 names, which the 2.2.0 entry of :doc:`release_log`
maps
* - ``nameparser.config.regexes`` and its ``REGEXES``, which the
parser has not read since 2.0
- Copy the pattern you reused into your own code; the bidi and
emoji strip switches are ``Policy(strip_bidi=...)`` and
``Policy(strip_emoji=...)`` (:ref:`strip-flags`)

The 2.8 deprecation warnings go with the code they warned about. In 3.0
importing a removed name raises ``ImportError``, reading one as an
Expand All @@ -69,7 +75,7 @@ a ``Constants`` cannot load, because there is no class to load it into.
Finding the sites
~~~~~~~~~~~~~~~~~

Upgrade to 2.8 first. A ``DeprecationWarning`` is hidden by default
Upgrade to 2.8.1 first. A ``DeprecationWarning`` is hidden by default
outside ``__main__``, so run your tests with warnings turned into
errors, and each use 3.0 removes stops the run at the line that makes
it:
Expand All @@ -79,7 +85,9 @@ it:
python -W error::DeprecationWarning -m pytest

2.8 warns where code *uses* a removed name, not where it imports one,
so some code passes that check and still fails on 3.0:
and 2.8.1 where code reads a constant of ``nameparser.config.regexes``
(``from nameparser.config.regexes import REGEXES`` included), so some
code passes that check and still fails on 3.0:

* *reading* the shared ``CONSTANTS`` (``"dr" in CONSTANTS.titles``, or
``copy.copy(CONSTANTS)``), which 2.8 left quiet so that importing
Expand All @@ -91,10 +99,13 @@ so some code passes that check and still fails on 3.0:
importing ``nameparser.parser`` or the two 1.x word-list modules,
``nameparser.config.prefixes`` and
``nameparser.config.bound_first_names``
* importing ``nameparser.config.regexes`` without reading a constant
from it (``import nameparser.config.regexes`` or ``from
nameparser.config import regexes``)

So search your code for ``CONSTANTS``, ``HumanName``, ``Constants``,
``nameparser.parser``, ``nameparser.util``, ``config.prefixes`` and
``config.bound_first_names`` as well.
``nameparser.parser``, ``nameparser.util``, ``config.prefixes``,
``config.bound_first_names`` and ``config.regexes`` as well.

Coming from 1.x
~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -546,7 +557,8 @@ rendering argument, where the 3.0 equivalent isn't config at all):
Every other ``regexes.*`` entry (``word``, ``spaces``, and the rest of
the compiled-pattern proxy) has no 3.0 replacement — parsing behavior
is configured entirely through named ``Policy`` fields now, not by
handing the parser a regex.
handing the parser a regex — and the ``nameparser.config.regexes``
module those entries came from is gone too (`What 3.0 removes`_).

Comparison
----------
Expand Down
Loading
Loading