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
15 changes: 8 additions & 7 deletions AGENTS.md

Large diffs are not rendered by default.

13 changes: 8 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ compatibility layer.** ``HumanName``, ``Constants``, the shared
names of the word lists; ``parse()`` and ``Parser`` are the
replacement. 2.8 is the last release that has them, and it warns at
each use 3.0 removes; `Coming from 1.x or 2.x`_ below says where to
start. The `release log
start. 3.0 also configures a ``Lexicon`` by reading
(``Lexicon.default().add("leed", kind="credential")``), renders a name
in the order it was written (``str(parse("김민준"))`` is ``'김민준'``),
and adds packs that choose the languages a parse reads. The `release log
<https://nameparser.readthedocs.io/en/latest/release_log.html>`__ has
the details. Please `open an
issue <https://github.com/derek73/python-nameparser/issues>`__ for anything
Expand Down Expand Up @@ -98,11 +101,11 @@ Coming from 1.x or 2.x
3.0 has no ``HumanName``: ``parse(text)`` reads the same seven fields,
named ``given`` and ``family`` where ``HumanName`` had ``first`` and
``last``, and a ``Lexicon`` and ``Policy`` handed to a ``Parser``
replace ``Constants``. Upgrade to 2.8 first and run your tests under
replace ``Constants``. Upgrade to 2.8.2 first and run your tests under
``python -W error::DeprecationWarning``: it stops at every use 3.0
removes except a read of the shared ``CONSTANTS`` and the imports
`Migrating to 3.0`_ lists, which 2.8 does not warn on, so search for
those too. A pickled ``HumanName`` or
removes except a read of the shared ``CONSTANTS`` or of a ``Lexicon``
set field and the imports `Migrating to 3.0`_ lists, which 2.8.2 does
not warn on, so search for those too. A pickled ``HumanName`` or
``Constants`` does not load in 3.0; store ``parse(text).as_dict()`` or
the original string instead.
`Migrating to 3.0`_ maps ``HumanName``'s attributes and the
Expand Down
4 changes: 2 additions & 2 deletions docs/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The rules below were distilled from PR #588, which restructured `customize.rst`

**A recipe is a doctest; an edge case is a unit test.** A *recipe* — how to do the thing the reader came to do — belongs in the docs as a `.. doctest::` block, which teaches and is CI-checked at once. The line between the two is whether the prose states the behavior: a claim the section makes to the reader (`edd` repairs to `EDD`, not a mask) earns a doctest beside it, which is what keeps the claim true; an edge case the prose has no reason to mention earns one sentence at most and a unit test, never a doctest (PR #216). An example sitting as prose inside a table cell is the worst of both: it teaches from the wrong place and nothing checks it.

**An example must be non-vacuous.** Show the baseline before the customization, and check that they differ: an `add(titles={"chancellor"})` example once shipped (fixed in 0ab19fdb) where `chancellor` was already a default title, so the add was a no-op and the doctest passed demonstrating nothing. Prefer a word absent from the defaults for a teachable reason (`dean`, which is also a given name).
**An example must be non-vacuous.** Show the baseline before the customization, and check that they differ: an `add(titles={"chancellor"})` example (2.x's spelling) once shipped (fixed in 0ab19fdb) where `chancellor` was already a default title, so the add was a no-op and the doctest passed demonstrating nothing. Prefer a word absent from the defaults for a teachable reason (`dean`, which is also a given name).

**An example must run warning-free.** Readers paste examples, so a warning the doctest build prints is one every reader gets. Fix the example, not the build: a `Lexicon.empty()` used only to drop titles also dropped the Korean surnames and drew the segmenterless warning, and the fix was an example that empties only the field it is about. Run the doctest build under Verifying below and expect no output at all.

Expand All @@ -44,7 +44,7 @@ The rules below were distilled from PR #588, which restructured `customize.rst`

## Linking

**Link, don't restate.** When another page already covers a topic, give one line and a `:ref:` to it; two copies drift apart. When the content is a list the code ships (the maiden markers, the delimiter pairs), link the module with `:mod:` instead of copying the list into prose, where it goes stale the next time the list changes.
**Link, don't restate.** When another page already covers a topic, give one line and a `:ref:` to it; two copies drift apart. When the content is a list the code ships (the maiden markers, the delimiter pairs), link the object that holds it (`` :data:`~nameparser.DEFAULT_NICKNAME_DELIMITERS` ``) or show the call that lists it (`Lexicon.default().words(kind="birth_marker")`) instead of copying the list into prose, where it goes stale the next time the list changes. Since 3.0 no vocabulary module is public for `:mod:` to name (#1007).

**Put a label at the heading you link to**, and link with `:ref:` to the section rather than `:doc:` to the page, which lands the reader at the top of a long page. Add the label in the same commit as the first link to it.

Expand Down
2 changes: 1 addition & 1 deletion docs/concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ threads without locking, safe to use as dict keys or cache keys, and
equality means exactly what it says — two :class:`~nameparser.Parser`
instances built from equal configuration are equal values, not merely
two objects that happen to behave the same. Every piece of
configuration in the 2.0 API is a frozen value — including the
configuration is a frozen value — including the
module-level default parser itself.

Honest ambiguity
Expand Down
9 changes: 7 additions & 2 deletions docs/customize.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1123,7 +1123,9 @@ rarely surnames tell the two shapes apart, so the switch leaves a word
listed in the lexicon's ``given_names`` where it stands, under any
``name_order``. The field is empty by default, so you supply the list
for your data, or choose ``locales.ES``, which turns the switch on and
carries 1,000 Spanish given names; an entry written without accents
carries 1,000 Spanish given names (``locales.ES_AR``, for Argentine
names, carries 5,000; see :ref:`spanish-portuguese-titles`); an entry
written without accents
matches the word with them:

.. doctest::
Expand Down Expand Up @@ -1800,7 +1802,10 @@ at the point of output, not baked into the parse. Three methods on
signatures:

* :meth:`~nameparser.ParsedName.render` fills a format spec from the
seven role fields.
seven role fields and the views derived from them (``family_base``,
``generation``, ``credentials`` and the rest). With no spec, as
``str()`` calls it, it lays the name out in the order it was read in
(since 3.0).
* :meth:`~nameparser.ParsedName.initials` is the same idea narrowed to
first letters, with its own ``delimiter``/``separator`` arguments.
* :meth:`~nameparser.ParsedName.capitalized` returns a new, case-fixed
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 @@ -6,7 +6,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.
- A `.pyi` stub typing `Policy.__init__` wide and its attributes narrow is unbuilt, a 3.x candidate (3.0 shipped without it), deferred from #334 for want of a signature list with its own sync test — 2026-08-15; 2026-11-05 #1079.
- `nameparser.config` holds data modules and nothing else in 3.0 — 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 modules re-exporting the compiled vocabulary (`titles`, `particles`, `suffixes` and five more) left it too, `Lexicon.words(kind=...)` listing a kind's words, and `regexes` left it (2026-11-02 #1008), both removals warned of by 2.8.1 — 2026-11-02 #1007 (Derek); and `surnames` became the private `_ko_surnames`, so the package holds no public module, `Lexicon.default().surnames` holding its words, warned of by 2.8.2 — 2026-11-04 #1077.
- 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.
Expand All @@ -24,7 +24,7 @@ Not standing: the harness bullet, that the snapshot and `compare.py` cannot show

Elsewhere: the explicit-comma line narrowed to a description for CJK script — decisions/cjk-comma-demotion's 2026-09-01 #469, SCOPES the FAMILY_COMMA bullet entry; the 1.4 gate the harness-baseline bullet expected 3.0 to dissolve, already retired, `compare.py --baseline 1.4.0` answering the question when asked — decisions/differential-ledger's 2026-10-13 #664 entries; the vocabulary model packs are to narrow, a strict pack dropping other languages' readings — decisions/vocabulary-model's 2026-10-15/16 entry; what the shim's filter kept and dropped until #357 — decisions/R4's 2026-10-02 #541 and 2026-10-02 #582 entries.

Open: #753 (phase 3, language packs; the three pack bullets carried out by its 3b, 2026-10-29, the packs themselves to come).
Open: #753 (phase 3, language packs; the three pack bullets carried out by its 3b, 2026-10-29, and 3.0's packs shipped after it, from `locales.NE` to `locales.ES_AR`; a pack past 3.0, as `is` is in #1074, is the issue's).

## Log

Expand Down Expand Up @@ -90,3 +90,4 @@ Open: #753 (phase 3, language packs; the three pack bullets carried out by its 3
- **Why it does not stay.** The 2026-11-02 entry kept the module because its list is data of its own, outside the per-word model; that says where the list is written, not that it needs a public import path. The words are public already as `Lexicon.default().surnames`, the field 4i-b kept public (decisions/vocabulary-model, 2026-11-02 #754), and the constant is that field's value, equal under 2.8.1 against its wheel (measured 2026-11-04), and 2.8.2's tests/v2/test_removals_2_8_1.py runs the offered code and holds it equal. A second public spelling of one set is one more surface to keep, and the only module left in a package that otherwise holds none.
- **Where it lives, and its name.** In `nameparser/config/`, because `Lexicon.default()` reads it and `_lexicon` may not import `nameparser.locales`, the layer above it (tests/v2/test_layering.py); the lists only a pack reads stay beside their packs (`locales/_es_given_names.py`, `_es_ar_given_names.py`). `_ko_` follows the BCP 47 tag naming of `vocab/ko.toml`; the constant keeps its name, `KOREAN_SURNAMES`.
- **The warning.** 2.8.1's mechanism unchanged (`config/_deprecated.py`'s `alias_getattr` with `removed=`): on 2.x the list moved to `_ko_surnames.py`, which the library reads, and `surnames.py` re-exports it under the hook, so only a caller's read warns, naming `Lexicon.default().surnames`, which runs quietly on 2.8.2 and 3.0 alike. The message cites #1007, so pyproject's filter for that family covers it. `tests/v2/test_layering.py` now refuses a public module in `nameparser/config/`.
- 2026-11-05 #1079 — THE `.pyi` STUB FOR `Policy` IS NOT BUILT FOR 3.0 AND STAYS A 3.x CANDIDATE. The 2026-08-15 bundle and decisions/differential-ledger's 2026-08-05 #334 deferral named it a 3.0 candidate; 3.0 ships without it, decided in chat during the 3.0 release sweep. It needs what #334 found missing, a signature list with its own sync test, and it adds to the API rather than changing it, so the core API freeze (2026-10-28 #925 (Derek), THE CORE API) lets any 3.x release take it.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
- A salutation that is someone's middle name reads as the title after a family comma, an accepted cost (`Doe, Jane Miss`); so does the empty head's one given word moving to the family as the period-marked spelling moves it (`, Jane Mrs` → family 'Jane') — 2026-10-24 #835.
- The rule reads the comma, not a declared order: `Doe Jane Mrs` under FAMILY_FIRST keeps middle 'Mrs', so the family-comma invariant of decisions/two-input-invariants does not hold for a trailing salutation — 2026-10-24 #835.

Open: [#877](https://github.com/derek73/python-nameparser/issues/877) (whether a bare `I` or `V` the salutation leaves ending the given part, read as the suffix by rules/C9, should report `suffix-or-name`).
Open: none in this file. Whether a bare `I` or `V` the salutation leaves ending the given part, read as the suffix by rules/C9, reports `suffix-or-name` was #877, decided in decisions/C9's 2026-11-04 #877 entry: it reports (`Doe, Jane V Mrs` → suffix 'V', reported).

## Log

Expand Down
4 changes: 3 additions & 1 deletion docs/design/decisions/H9-title-phrase.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@
- `grace` (a style) and `marshal` (a rank) are titles that may be given names, `sergeant` attaches to the word `major` and `major` to `general` (`attaches` naming a word where a kind would reach too far): `His Grace John Smith`, `Field Marshal Bernard Montgomery` and `Sergeant Major John Smith` read the title; `Mr Justice Smith` keeps given 'Justice', no exception behind a salutation — 2026-10-26 #752, phase 2i (Derek).
- Its partner is a modifier or a style before it, a title before it that attaches to it or a title after it that it attaches to (`attaches`), or `of` after it; a salutation is never one, nor any other title before it (#853's `Mr Dean Smith`), nor a title after it it does not attach to (`SMITH, DEAN MR.`) — 2026-10-25 #752.
- A modifier in the leading run of titles before the first comma is a title only with a head title after it in that run, `of` after it, or in the object of `of` or `the` there; outside that run (ending the name, H5's, or after a comma) it reads as any title does, the given part after a family comma included — 2026-10-25 #752; save a modifier that may be a given name, which the run's test reads in every part (`Lau, Wing Kai` → given 'Wing', `John Smith Wing.` → family 'Wing.') — 2026-10-26 #752, phase 2j, `wing`.
- A title that may be a given name which a caller lists in `titles` is a title wherever it stands, which keeps the 1.4 recipe `c.titles.add("dean")`; a v1 caller who empties `titles` turns the phrase reading off with them, while one who replaces the list keeps it (`c.titles.clear(); c.titles.add('dr')` still reads `Dean of Students` as the title), v1 holding no knob for the set — 2026-10-25 #752. A title 2.6.0 listed that a later batch made a title only inside a phrase stays in v1's default `titles`, read by the phrase while it is there and as no title once removed, so `c.titles.remove('king')` works as in 2.6.0; listing it in v1 cannot make it a title everywhere, which takes `Lexicon.default().add(titles={'king'})` — 2026-10-26 #752, the v1 title list.
- A title that may be a given name is a title wherever it stands once a caller takes its `given` reading out, which since 3.0 is `Lexicon.default().remove("dean", kind="given")`, the 1.4 recipe `c.titles.add("dean")` in 3.0's spelling (decisions/vocabulary-model's title warning names it) — 2026-10-25 #752.
- `maj` is a title that may be a given name, attaching to `gen` and `general`, and `major` attaches to `gen` as to `general`: `Maj Sjöwall` reads given 'Maj', reported `title-or-name`, `Maj Gen John Smith` and `Major Gen John Smith` the title; a rank written short and bare at the head reads as the given name (`MAJ Ian Stewart, MD`), no capitals signal reading it — 2026-11-03 #999, `maj`.
- `gen` is a title that may be a given name, written short, as `maj` is; the listed ranks and offices that stand before General attach to it, and `gen` and `maj` attach to `sir`: `Gen Goto` reads given 'Gen', reported `title-or-name`, `Lt Gen John Smith`, `Colonel Gen John Smith` and `Gen Sir Nick Carter` the title, `Gen. and Mrs. John Smith` the given name — 2026-11-03 #999, `gen`.
- `brig` is a title that may be a given name, written short, a title word only since this entry, attaching to `gen`, `general` and `sir`: `Brig Gen Mark Kimmitt` reads the title, `Brig Klyce` and a bare `Brig John Smith` the given name, as before — 2026-11-03 #999, `brig`.
- `hon` is a title that may be a given name only under a Chinese choice, zh.toml's `given` reading being off by default; there a style written short has its phrase partner in the article before it (`The Hon John Smith`), a rule that reaches no other word, so `The Sheikh` and `The Grace` stay epithets — 2026-11-03 #999, `hon`.
- `lady` is a title that may be a given name only under a Spanish choice, es.toml's `given` reading being off by default, and `locales.ES` makes that choice; eight more titles borne as given names, `sir`, `general`, `bishop`, `colonel`, `canon`, `author`, `empress` and `lawyer`, stay titles everywhere, their counts on their title readings — 2026-11-03 #913, the last nine.
- The article before any other title that may be a given name is no partner, save before a modifier, whose object of `the` decision 10 reads (`The Wing Smith` → title 'The Wing'), at the head, after a comma and under every order: `The Marquis de Sade` reads given 'The Marquis', `The Marquis of Tavistock` the title — 2026-11-03 #913, the article (Derek).

Not standing: what a v1 caller's `titles` list did to a title that may be a given name — emptying it, replacing it, removing a word 2.6.0 listed — overtaken by 3.0's removal of the v1 layer, #357 in decisions/3-0-reevaluations — 2026-10-25 #752, phase 2i; 2026-10-26 #752, the v1 title list.

## Log

- **2026-10-25 #752, phase 2i (its report in a family comma's given part, and its costs `His Grace John Smith` and `Field Marshal Bernard Montgomery`, superseded by 2026-10-26 #752, phase 2i (Derek); its cost `Lau, Wing`, by 2026-10-26 #752, phase 2j, `wing`) — A TITLE THAT MAY BE A GIVEN NAME IS A TITLE ONLY INSIDE A TITLE PHRASE, AND A MODIFIER AT THE HEAD OF THE NAME ONLY WITH A HEAD BESIDE IT.** Carries out items 10, 11 and 12 of decisions/vocabulary-model's 2026-10-23 #752 entry (decision 10 at the head of the name only; the phrase partner; #853's word after a title), measured here on the rule as built rather than on the spike those items were decided on.
Expand Down
Loading
Loading