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
4 changes: 4 additions & 0 deletions docs/release_log.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
Release Log
===========
* 2.5.0 - Unreleased

nameparser 2.5 is under development.

* 2.4.0 - October 11, 2026

nameparser 2.4 reads post-nominal credentials much more reliably, recognizes more naming conventions, and writes credentials the usual way when it repairs case. Highlights:
Expand Down
10 changes: 5 additions & 5 deletions nameparser/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
#:
#: Bumped when a cycle OPENS, not when it ships, so the tree says what
#: it is building. Note the cost of the tuple being purely numeric: it
#: cannot carry the dev marker, so `VERSION >= (2, 4, 0)` is already
#: true here while 2.4.0 is unreleased. Compare `__version__` instead
#: cannot carry the dev marker, so `VERSION >= (2, 5, 0)` is already
#: true here while 2.5.0 is unreleased. Compare `__version__` instead
#: where that distinction matters.
VERSION = (2, 4, 0)
VERSION = (2, 5, 0)
#: PEP 440 pre-release/dev segment appended to the numeric version, or
#: "" for a final release. Joined WITHOUT a dot ("2.0.0rc1", not
#: "2.0.0.rc1"); setuptools reads __version__ as the package version.
#:
#: "dev" through the cycle, cleared at release. It normalizes to
#: 2.4.0.dev0, which sorts above 2.3.0 and BELOW 2.4.0, so an install
#: 2.5.0.dev0, which sorts above 2.4.0 and BELOW 2.5.0, so an install
#: from master can never masquerade as the release it precedes.
PRE_RELEASE = ""
PRE_RELEASE = "dev"
__version__ = ".".join(map(str, VERSION)) + PRE_RELEASE
16 changes: 10 additions & 6 deletions tests/v2/test_ledger_guards.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,9 +358,10 @@ def test_script_ranges_membership_is_decided() -> None:
}),
"expected_since_2.1.0.toml": frozenset(), # 2.2 cycle: no span-bearing rule
"expected_since_2.2.0.toml": frozenset(), # 2.3 cycle: no span-bearing rule
# The open cycle's rules (#383/#479, 2026-09-13) are literal
# The 2.4 cycle's rules (#383/#479, 2026-09-13) are literal
# names or alternations of names, and copy no script range.
"expected_since_2.3.0.toml": frozenset(),
"expected_since_2.4.0.toml": frozenset(), # open cycle, no rules yet
}

#: The leading `fix(...)`/`feat(...)` tag of a rule's `issue`, which is
Expand Down Expand Up @@ -6987,6 +6988,7 @@ def _claim(rule: dict) -> _Claim:
"fix(#614) a trailing title is read before the particle chain":
_Claim(1, ('family', 'title'), "4536c33ed2a2", None),
},
"expected_since_2.4.0.toml": {}, # open cycle, no rules yet
}


Expand Down Expand Up @@ -7088,11 +7090,12 @@ def test_every_rule_claims_the_recorded_share_of_the_corpus() -> None:
#: of each fact, since two means one of them goes quietly stale.
_CROSS_RULE_WINNERS: dict[str, dict[str, str]] = {
"expected_since_2.2.0.toml": {},
# The open cycle carries #383/#479's three rules since
# 2026-09-13 and still no contest: literal names or alternations
# of names, reaching no corpus name in common, so no diff is
# claimed twice.
# The 2.4 cycle carried #383/#479's three rules from 2026-09-13
# and no contest: literal names or alternations of names, reaching
# no corpus name in common, so no diff is claimed twice.
"expected_since_2.3.0.toml": {},
# open cycle: no rules, so no contest
"expected_since_2.4.0.toml": {},
"expected_since_1.4.0.toml": {
# Spelled out since #508: the bare `fix(comma-family)` this row
# carried is a prefix of THREE rules in this ledger (measured
Expand Down Expand Up @@ -8799,10 +8802,11 @@ def test_a_rule_reaching_no_corpus_name_says_why_it_is_kept() -> None:
],
"expected_since_2.1.0.toml": [],
"expected_since_2.2.0.toml": [],
# The open cycle's three rules (#383/#479, 2026-09-13) nest with
# The 2.4 cycle's three rules (#383/#479, 2026-09-13) nest with
# nothing: no two of them share a corpus name, so no pair is a
# contest at all, let alone a wide-first one.
"expected_since_2.3.0.toml": [],
"expected_since_2.4.0.toml": [], # open cycle, no rules yet
}


Expand Down
13 changes: 8 additions & 5 deletions tools/differential/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
FIELDS = ("title", "first", "middle", "last", "suffix", "nickname",
"maiden")

DEFAULT_BASELINE = "2.3.0"
DEFAULT_BASELINE = "2.4.0"
REPO_ROOT = HERE.parents[1]
#: The v2 API's names for the same seven roles FIELDS names in v1
#: vocabulary. Both are compared from baseline 2.0 on.
Expand Down Expand Up @@ -1748,11 +1748,13 @@ class _ShapeMismatch(NamedTuple):
#: diffs from nothing.
_RECORDED_DIFFS: dict[str, dict[str, tuple[str, ...]]] = {
"expected_since_2.2.0.toml": {},
# The open cycle carries #383/#479's three rules since
# 2026-09-13, and no contest among them: each is a literal name
# or alternation of names, and no two reach the same corpus
# name, so the run finds no contested diff to adjudicate.
# The 2.4 cycle carried #383/#479's three rules from 2026-09-13,
# and no contest among them: each is a literal name or
# alternation of names, and no two reach the same corpus name,
# so the run finds no contested diff to adjudicate.
"expected_since_2.3.0.toml": {},
# open cycle: no rules, so nothing for a second one to contest
"expected_since_2.4.0.toml": {},
"expected_since_1.4.0.toml": {
"Andrews, M.D.": ("given", "suffix"),
# #613's PR review (2026-10-06): a tolerated CJK case row, H1's
Expand Down Expand Up @@ -2324,6 +2326,7 @@ class _ShapeMismatch(NamedTuple):
# cases.py row, a render pin or a v1 bank -- so not one of them
# meets the population's literal clause and none takes a row here.
"expected_since_2.3.0.toml": {},
"expected_since_2.4.0.toml": {}, # open cycle, no watched name yet
}


Expand Down
34 changes: 34 additions & 0 deletions tools/differential/expected_since_2.4.0.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Ledger for baseline 2.4.0 -- what changes for a user upgrading from
# the previous minor. Same rule grammar as the other ledgers: `issue`,
# `name_regex` and `fields` are every one of them required. Neither
# narrowing key may stand on its own -- #451 banned a rule with
# `fields` and no regex, #456 banned the reverse -- so a rule here
# narrows by name AND by role, never by just one.
#
# Opened empty the day 2.4.0 shipped (AGENTS.md release step 8), and it
# is opened before it has anything to say because DEFAULT_BASELINE and
# the ledger are coupled: _allowlist_for treats a missing file as a
# hard error, on the reasoning that an absent ledger classifies nothing
# and would make every diff report as unexplained. So the baseline
# could not advance to 2.4.0 without this file.
#
# Do not copy rules across from expected_since_2.3.0.toml: that ledger
# classifies what 2.4 changed on top of 2.3, and a rule copied without
# checking either over-matches -- hiding a real 2.5 regression behind a
# 2.4-era label -- or never fires at all. Where a 2.5 change is visible
# from several baselines the rule may legitimately appear in more than
# one file, but each copy is checked against its own run rather than
# assumed.
#
# tests/v2/test_differential.py permits exactly one empty ledger, the
# one DEFAULT_BASELINE names -- which is this file.
#
# tests/v2/test_ledger_guards.py and tools/differential/compare.py
# enroll this file, empty, in every roster keyed by ledger filename
# (AGENTS.md release step 8 lists them). The first rule that copies a
# script range, a CJK honorific or a Latin vocabulary has to be
# recorded in its roster or the sweep fails.
#
# There is deliberately no `change = []` line. TOML forbids appending a
# [[change]] table to a statically defined array, so that line would
# block the first entry.
Loading