Skip to content

Commit 05329f8

Browse files
derek73claude
andcommitted
Fix comment rot and citation precision from the toolkit review
- _group.py's docstring no longer claims Policy is not consulted (stale since the M5 delimiter wiring) - the additional_parts_count citation attaches to the clauses it supports (1313/1369 for the =1 sites, 1333 for the 0 site); the 'lastname part may have suffixes' quote cites 1368 - the EAST_SLAVIC pinned-live note carries its date (2026-07-12) - AGENTS.md: the CLI line describes the 2.0 parse() output + --json, and the facade-layer paragraph records that parser.py:NNNN citations resolve via git show 2d5d8c2:nameparser/parser.py Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent b0099a9 commit 05329f8

6 files changed

Lines changed: 17 additions & 8 deletions

File tree

AGENTS.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ uv run mypy
2929
# Lint
3030
uv run ruff check nameparser/
3131

32-
# Debug how a specific name string is parsed (prints HumanName repr)
32+
# Debug how a specific name string is parsed: 2.0 core parse() --
33+
# prints the ParsedName repr raw and capitalized, then initials;
34+
# --json emits the component dict
3335
uv run python -m nameparser "Dr. Juan Q. Xavier de la Vega III"
3436

3537
# Build docs
@@ -112,7 +114,7 @@ Each named attribute (`title`, `first`, etc.) is a `@property` that joins its co
112114
The 2.0 rewrite lands as underscore-private modules alongside the v1 code. These conventions apply to all new-API code and are stricter than the v1 sections above. The full design record (rationale, settled-decision logs, dated amendments) lives in untracked `docs/superpowers/specs/`; this section is the enforceable subset. **A commit that establishes or amends one of these conventions must update this section in the same commit** — grep-driven staleness sweeps miss paraphrased prose (see Workflow above), so write-time maintenance is the mechanism, audits are the backstop.
113115

114116
- **Module layout**: every new module is underscore-private (`_types.py`, `_lexicon.py`, `_policy.py`, `_locale.py`, `_render.py`, `_pipeline/`, `_parser.py`, plus the facade layer: `_facade.py`, `_config_shim.py`). The public import surface is exactly `nameparser` (and later `nameparser.locales`); `nameparser/__init__.py` holds re-exports and `__all__` only — no logic. Since the M11 swap, the old paths are import-path-preserving re-exports: `nameparser.parser` re-exports the `_facade` `HumanName`, `nameparser.config` re-exports the `_config_shim` names (`Constants`, `CONSTANTS`, `SetManager`, `TupleManager`, `RegexTupleManager`); the `config/` DATA modules stay the vocabulary source through 2.x. The whole facade layer is deleted in 3.0.
115-
- **Facade layer** (`_facade.py`, `_config_shim.py`): the v1-compat `HumanName`/`Constants` over the core. Key mechanisms: `Constants._generation` dirty-tracking (every mutation bumps; facades resolve their `Parser` lazily via `_cached_parser(lexicon, policy)`); `Constants._snapshot()` mirrors `_lexicon._default_lexicon()` (equality-pinned); the facade pickles v1-SHAPED state (component lists, one `__setstate__` path for 1.4 and 2.x blobs; components rebuild via `replace()`, never a re-parse); `_V1_HOOKS` overrides warn once per subclass (#280). The compat contract is the migration spec's promise: warning-free 1.4 code behaves identically except release-log-classified fixes — `tools/differential/` (dev-only, not shipped) verifies this against 1.4-on-PyPI over a 486-name corpus.
117+
- **Facade layer** (`_facade.py`, `_config_shim.py`): the v1-compat `HumanName`/`Constants` over the core. Key mechanisms: `Constants._generation` dirty-tracking (every mutation bumps; facades resolve their `Parser` lazily via `_cached_parser(lexicon, policy)`); `Constants._snapshot()` mirrors `_lexicon._default_lexicon()` (equality-pinned); the facade pickles v1-SHAPED state (component lists, one `__setstate__` path for 1.4 and 2.x blobs; components rebuild via `replace()`, never a re-parse); `_V1_HOOKS` overrides warn once per subclass (#280). The compat contract is the migration spec's promise: warning-free 1.4 code behaves identically except release-log-classified fixes — `tools/differential/` (dev-only, not shipped) verifies this against 1.4-on-PyPI over a 486-name corpus. `parser.py:NNNN` citations throughout the 2.0 code refer to the PRE-swap v1 file, deleted at the M11 swap; resolve them with `git show 2d5d8c2:nameparser/parser.py`.
116118
- **Layering is enforced by `tests/v2/test_layering.py`** (exact-module matching; `if TYPE_CHECKING:` imports don't count): `_types` imports nothing internal at module level — its rendering delegates import `_render` at call time; `_lexicon` and `_policy` sit above `_types` independently (`_lexicon` may import `nameparser.config.*` DATA modules only — vocabulary is single-sourced from the v1 data modules through 2.x, e.g. `config/maiden_markers.py`); `_locale` sits on `_lexicon`+`_policy` (plus `_types` for the shared pickle mixin); `_render` imports `_types` and `_lexicon` (for `Lexicon.default()` and `_normalize`); `_pipeline/*` imports `_types`+`_lexicon`+`_policy` plus in-package `_pipeline` helpers; `_parser` sits on everything except `_render`; the facade layer (`_facade`, `_config_shim`, `parser`, `config/__init__`, `__main__`) may import anything public plus `_render`. Extend the test's `ALLOWED` table when adding a module.
117119
- **Canonical field order** — the seven roles in `Role` enum declaration order, defined once and derived everywhere (properties, `as_dict`, reprs, `comparison_key`). Never restate the order literally.
118120
- **Method organization**, fixed section order in every class: fields + `__post_init__` validation → alternative constructors → dunders (construction/equality → protocol → operators) → properties → public methods by concern (access → editing → comparison → rendering delegates) → private helpers last, except a helper serving exactly one section may sit at that section's head.

nameparser/_config_shim.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -845,7 +845,12 @@ def __setattr__(self, name: str, value: object) -> None:
845845
if name in _MANAGER_FIELDS or name in _SCALAR_DEFAULTS:
846846
self._bump()
847847

848-
def copy(self) -> Constants: # #260
848+
def copy(self) -> Constants:
849+
"""Independent copy (#260), subclass-preserving. Divergence from
850+
v1 (which deepcopied __dict__): attributes OUTSIDE the known
851+
field surface -- e.g. ad-hoc names stashed on the instance --
852+
are not carried by copy() or pickling; only the enumerated
853+
fields survive.""" # #260
849854
# An independent instance with its own generation counter and
850855
# its own manager callbacks -- not a shared-state alias like a
851856
# naive attribute-for-attribute copy would produce. v1's copy()

nameparser/_pipeline/_assign.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def assign(state: ParseState) -> ParseState:
173173
# PARTICLE_OR_GIVEN is deliberately not emitted here: after a
174174
# comma the family is already fixed, so a leading given-position
175175
# particle is not meaningfully ambiguous.
176-
# v1: "lastname part may have suffixes in it" (parser.py:1370)
176+
# v1: "lastname part may have suffixes in it" (parser.py:1368)
177177
# -- the first piece is always the family even if suffix-shaped;
178178
# any later strict-suffix piece goes to SUFFIX per piece
179179
# ('Smith Jr., John' -> family=Smith, suffix=Jr.)

nameparser/_pipeline/_group.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
Produces: pieces + piece_tags per segment (runs of token indices --
55
tokens are NEVER joined into strings: the anti-#100 invariant); maiden
66
tail tokens get role=MAIDEN; marker tokens land in dropped.
7-
Reads: token tags (from classify); Policy is not consulted. The v1
7+
Reads: token tags (from classify), and Policy.extra_suffix_delimiters
8+
for the tail-segment handling below -- no other Policy field. The v1
89
"derived titles/prefixes" registration becomes piece_tags entries --
910
per-parse state that dissolves with the state (v1 kept per-parse sets
1011
for the same reason). Reads Policy.extra_suffix_delimiters: tail
@@ -192,7 +193,8 @@ def group(state: ParseState) -> ParseState:
192193
all_pieces: list[tuple[tuple[int, ...], ...]] = []
193194
all_ptags: list[tuple[frozenset[str], ...]] = []
194195
# v1 parity: additional_parts_count=1 applies only to FAMILY_COMMA
195-
# parts (parser.py:1333); the SUFFIX_COMMA pre-comma segment gets 0.
196+
# parts (parser.py:1313, 1369); the SUFFIX_COMMA pre-comma segment
197+
# gets 0 (parser.py:1333).
196198
additional = 1 if state.structure is Structure.FAMILY_COMMA else 0
197199
# v1 expand_suffix_delimiter parity (#191): tail segments (wholly
198200
# consumed as suffixes by assign) drop delimiter-core tokens, the

nameparser/_pipeline/_post_rules.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
patronymic ending, and the MIDDLE-position token does NOT (given +
1414
patronymic + patronymic-derived surname like Abramovich must not
1515
rotate) -> rotate: given<-old MIDDLE, middle<-old FAMILY (the
16-
patronymic), family<-old GIVEN (v1 parity, pinned live).
16+
patronymic), family<-old GIVEN (v1 parity, pinned live 2026-07-12).
1717
3. TURKIC (opt-in): exactly 1 GIVEN + 2 MIDDLE + 1 FAMILY tokens and
1818
the FAMILY-position token is a standalone Turkic marker ->
1919
given<-first MIDDLE, middle<-(second MIDDLE, marker), family<-old

tests/v2/cases.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ class Case:
119119
Case("family_segment_trailing_suffix", "Smith Jr., John",
120120
{"given": "John", "family": "Smith", "suffix": "Jr."},
121121
notes="v1: the family part may have suffixes in it "
122-
"(parser.py:1370); the first piece is always the family "
122+
"(parser.py:1368); the first piece is always the family "
123123
"(pinned live 2026-07-17)"),
124124
Case("family_segment_multiple_suffixes", "Smith Jr. MD, John",
125125
{"given": "John", "family": "Smith", "suffix": "Jr., MD"}),

0 commit comments

Comments
 (0)