Skip to content

Commit 3950f99

Browse files
derek73claude
andauthored
Open the 3.1 cycle after 3.0.0 (#1175)
* Open the 3.1 cycle after 3.0.0 VERSION (3, 1, 0) and PRE_RELEASE "dev", so the tree reports 3.1.0dev, above 3.0.0 and below 3.1.0, with the version comments naming the new versions; and a 3.1.0 Unreleased entry in the release log. CITATION.cff stays at 3.0.0, the last released version. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Compare 3.0's views against a baseline that has them compare.py's baseline worker recorded the seven fields, the reports, initials() and the case views, but not the generation, credentials, honorifics, patronymic and surname2 views snapshot.read() records since 3.0, so against 3.0.0 every name filling one read as moved: a bare run reported 669 of 7,107 entries in a tree that moved none. The worker now reads each view as the tree side does, says which views the wheel's ParsedName has, and unrecorded() leaves out the ones a 2.x baseline lacks, as it does the alternatives before 2.7 (--baseline 2.8.0: 1,669 entries with the views compared, 1,155 without). DEFAULT_BASELINE is 3.0.0, against which the tree now reads 0 moved. A test ties the worker's views to snapshot.RECORDED_LATER, so a view added to the snapshot and not the worker fails there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hold each view row to the view it is there for The case-view rows assert they still reach their arm; the three 3.0 view rows passed an empty set and checked nothing of the kind, so a vocabulary change that stopped 田中さん filling honorifics would leave the row passing while testing nothing. Each now names its views. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent fdc3842 commit 3950f99

5 files changed

Lines changed: 72 additions & 10 deletions

File tree

‎docs/release_log.rst‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
Release Log
22
===========
3+
* 3.1.0 - Unreleased
4+
5+
nameparser 3.1 is under development. It refuses a ``Lexicon``, ``Parser`` or locale pack pickled by 2.x, which 3.0 loads with a ``DeprecationWarning``.
6+
37
* 3.0.0 - November 8, 2026
48

59
**nameparser 3.0** removes ``HumanName``, ``Constants`` and the rest of the 1.x compatibility layer, which 2.8 deprecates, and rebuilds the vocabulary as readings of words, kept in files by language. A parser can be built for a choice of languages, the suffix splits by kind and the family name into two surnames, and every parsed word records the reading it took. :doc:`migrate` gives the 3.0 call for each 1.x and 2.x one. Highlights:

‎nameparser/_version.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
#:
55
#: Bumped when a cycle OPENS, not when it ships, so the tree says what
66
#: it is building. Note the cost of the tuple being purely numeric: it
7-
#: cannot carry the dev marker, so `VERSION >= (3, 0, 0)` is already
8-
#: true here while 3.0.0 is unreleased. Compare `__version__` instead
7+
#: cannot carry the dev marker, so `VERSION >= (3, 1, 0)` is already
8+
#: true here while 3.1.0 is unreleased. Compare `__version__` instead
99
#: where that distinction matters.
10-
VERSION = (3, 0, 0)
10+
VERSION = (3, 1, 0)
1111
#: PEP 440 pre-release/dev segment appended to the numeric version, or
1212
#: "" for a final release. Joined WITHOUT a dot ("2.0.0rc1", not
1313
#: "2.0.0.rc1"); setuptools reads __version__ as the package version.
1414
#:
1515
#: "dev" through the cycle, cleared at release. It normalizes to
16-
#: 3.0.0.dev0, which sorts above 2.8.0 and BELOW 3.0.0, so an install
16+
#: 3.1.0.dev0, which sorts above 3.0.0 and BELOW 3.1.0, so an install
1717
#: from master can never masquerade as the release it precedes.
18-
PRE_RELEASE = ""
18+
PRE_RELEASE = "dev"
1919
__version__ = ".".join(map(str, VERSION)) + PRE_RELEASE

‎tests/v2/test_differential.py‎

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,14 @@ def _run_template(line: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]
156156
# a report's alternative (#752 phase 2m), which the worker records
157157
# in its own copy of snapshot.alternatives()
158158
("Jack", None, frozenset()),
159+
# 3.0's views, one row per view the default parse fills (surname2
160+
# needs a policy the snapshot does not parse under): with none, the
161+
# worker recorded no view and a bare compare.py against 3.0.0
162+
# reported 669 of 7,107 entries moved in a tree that moved none
163+
# (2026-11-08)
164+
("John Smith Jr., MD", None, frozenset({"generation", "credentials"})),
165+
("田中さん", None, frozenset({"honorifics"})),
166+
("Mahathir bin Mohamad", None, frozenset({"patronymic"})),
159167
])
160168
def test_the_worker_reads_a_line_as_the_tree_does(
161169
name: str, order: str | None, views: frozenset[str]) -> None:
@@ -166,7 +174,9 @@ def test_the_worker_reads_a_line_as_the_tree_does(
166174
assert set(raw) == {"v2"}
167175
tree = snapshot.read([(name, order)])[0]
168176
# the row still reaches the arm it is here for
169-
assert set(snapshot.CASE_VIEWS) & set(tree) == views
177+
every_view = {*snapshot.CASE_VIEWS,
178+
*(k for keys in compare.VIEW_KEYS.values() for k in keys)}
179+
assert every_view & set(tree) == views
170180
assert snapshot.row(name, order, raw["v2"]) == tree
171181
if name == "Jack":
172182
assert tree["alternatives"] == [
@@ -182,7 +192,29 @@ def test_a_baseline_without_alternatives_is_not_compared_on_them() -> None:
182192
assert compare.unrecorded(tell) == ()
183193
assert compare.unrecorded({**tell, "alternatives": False}) == (
184194
"alternatives",)
185-
assert compare.unrecorded({"__version__": "2.6.0"}) == ("alternatives",)
195+
assert compare.unrecorded({"__version__": "2.6.0"}) == (
196+
"alternatives", "generation", "credentials", "honorifics",
197+
"patronymic", "surname2")
198+
199+
200+
def test_a_baseline_without_the_views_is_not_compared_on_them() -> None:
201+
# A 2.x wheel's ParsedName has no generation, honorifics, patronymic
202+
# or surname2, so every name the tree fills one for would read as
203+
# moved; a 3.0 wheel has all four and is compared on each.
204+
tell, _ = _run_template({"name": "John Smith Jr., MD", "order": None})
205+
assert tell["views"] == list(compare.VIEW_KEYS)
206+
assert compare.unrecorded(tell) == ()
207+
assert compare.unrecorded({**tell, "views": ["generation"]}) == (
208+
"honorifics", "patronymic", "surname2")
209+
210+
211+
def test_the_worker_views_are_every_view_a_snapshot_records() -> None:
212+
# A view snapshot.row records that the worker leaves out reads as a
213+
# move in every name holding it, the shape that hid until 3.0.0
214+
# became the baseline.
215+
later = {k for keys, _ in snapshot.RECORDED_LATER for k in keys}
216+
views = {k for keys in compare.VIEW_KEYS.values() for k in keys}
217+
assert views == later - set(snapshot.CASE_VIEWS) - {"alternatives"}
186218

187219

188220
_WHEEL = "/Users/x/.cache/uv/environments-v2/w/lib/python3.11/" \

‎tools/differential/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ both sides. A snapshot without the header records no alternatives, so
7777
says it has no `Ambiguity.alternative`: a name the tree reports one
7878
for is not a name that moved (867 of the 1,088 entries `--baseline
7979
2.6.0` reported on 2026-10-26 were only that).
80+
3.0's views are left out the same way against a release whose `ParsedName` lacks them, the worker naming the views it has: on 2026-11-08 `--baseline 2.8.0` reported 1,669 of 7,107 entries moved with them compared and 1,155 without, and until the worker read them at all, a bare run against 3.0.0 reported 669 entries moved in a tree that moved none.
8081

8182
`--tree PATH` reads with another checkout's `nameparser` (the corpus is
8283
always this checkout's), and checks that the import really came from

‎tools/differential/compare.py‎

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from typing import NamedTuple
3535

3636
HERE = Path(__file__).resolve().parent
37-
DEFAULT_BASELINE = "2.8.0"
37+
DEFAULT_BASELINE = "3.0.0"
3838
REPO_ROOT = HERE.parents[1]
3939

4040

@@ -124,10 +124,15 @@ def _surfaces_for(version: str) -> frozenset[str]:
124124
# whether this release reports the reading a fork did not take (2.7,
125125
# #752 phase 2m): compare() compares the alternatives only where it does
126126
_AMBIGUITY = getattr(nameparser, "Ambiguity", None)
127+
# the views this release's ParsedName has (3.0's suffix, patronymic and
128+
# second-surname views): compare() compares a view only where it does
129+
_PARSED = getattr(nameparser, "ParsedName", None)
127130
print(json.dumps({"__version__": nameparser.__version__,
128131
"__file__": nameparser.__file__,
129132
"alternatives": "alternative" in getattr(
130-
_AMBIGUITY, "__dataclass_fields__", {})}), flush=True)
133+
_AMBIGUITY, "__dataclass_fields__", {}),
134+
"views": [v for v in @@VIEWS@@
135+
if hasattr(_PARSED, v)]}), flush=True)
131136
132137
if WANT_V2:
133138
from nameparser import Parser, Policy, parse
@@ -163,6 +168,9 @@ def _v2_row(p):
163168
forced = str(p.capitalized(force=True))
164169
row["_capitalized"] = plain if plain != rendered else ""
165170
row["_capitalized_force"] = forced if forced != plain else ""
171+
# 3.0's views, as snapshot.read() records them: empty before 3.0
172+
for key in @@VIEW_ROW_KEYS@@:
173+
row[f"_{key}"] = getattr(p, key, "") or ""
166174
return row
167175
168176
@@ -201,6 +209,9 @@ def _worker_source(version: str, want_v2: bool) -> str:
201209
"""
202210
return (_WORKER_TEMPLATE
203211
.replace("@@VERSION@@", version)
212+
.replace("@@VIEWS@@", repr(tuple(VIEW_KEYS)))
213+
.replace("@@VIEW_ROW_KEYS@@",
214+
repr(tuple(k for ks in VIEW_KEYS.values() for k in ks)))
204215
.replace("@@WANT_V2@@", "True" if want_v2 else "False"))
205216

206217

@@ -329,11 +340,25 @@ class Result(NamedTuple):
329340
unrecorded: tuple[str, ...] = () # keys the baseline cannot read
330341

331342

343+
#: The ParsedName views a row records (3.0), each with the row keys it
344+
#: fills: `generation` fills `credentials` beside it (snapshot.row).
345+
#: test_differential holds this to snapshot.RECORDED_LATER.
346+
VIEW_KEYS = {"generation": ("generation", "credentials"),
347+
"honorifics": ("honorifics",),
348+
"patronymic": ("patronymic",),
349+
"surname2": ("surname2",)}
350+
351+
332352
def unrecorded(tell: Mapping[str, object]) -> tuple[str, ...]:
333353
"""The row keys the baseline worker's release cannot record, which
334354
compare() leaves out of both sides: the alternatives before 2.7,
355+
and each view before the release whose ParsedName has it (3.0),
335356
since a tree reporting one is not a name that moved."""
336-
return () if tell.get("alternatives") else ("alternatives",)
357+
has = tell.get("views") or ()
358+
assert isinstance(has, (list, tuple))
359+
return ((() if tell.get("alternatives") else ("alternatives",))
360+
+ tuple(k for view, keys in VIEW_KEYS.items()
361+
if view not in has for k in keys))
337362

338363

339364
def compare(baseline: str, corpora: list[Path] | None = None) -> Result:

0 commit comments

Comments
 (0)