Skip to content

Add maiden name field with delimiter routing (#22)#199

Merged
derek73 merged 10 commits into
masterfrom
worktree-maiden-name-bucket
Jul 3, 2026
Merged

Add maiden name field with delimiter routing (#22)#199
derek73 merged 10 commits into
masterfrom
worktree-maiden-name-bucket

Conversation

@derek73

@derek73 derek73 commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds a first-class maiden field to HumanName, mirroring nickname (property, setter, as_dict/iteration/slicing).
  • Replaces the unreleased extra_nickname_delimiters with per-bucket nickname_delimiters/maiden_delimiters on Constants, so a delimiter (e.g. parenthesis) can be routed to maiden instead of nickname for alternate/maiden surnames, e.g. "Baker (Johnson), Jenny"first="Jenny", last="Baker", maiden="Johnson".
  • The three built-in delimiters resolve live against Constants.regexes via a string-sentinel mechanism, preserving the existing, documented CONSTANTS.regexes.parenthesis = ... customization path.
  • HumanName now raises ValueError if nickname_delimiters/maiden_delimiters contains a string that doesn't name a real Constants.regexes key (e.g. a typo, or a key missing from a custom regexes override). Previously this silently fell back to EMPTY_REGEX, which doesn't just skip the delimiter — it matches at every character position and corrupts the parse (injects a whitespace-only truthy value into nickname/maiden, leaves the real delimiter content unstripped elsewhere). Constants(regexes=<minimal custom set>) still works: the built-in sentinels are only seeded for names actually present in the regexes passed to the constructor.
  • Along the way, fixes a latent TupleManager bug: constructing a subscripted generic (TupleManager[X](...)) was silently corrupting the dict via __orig_class__ leaking through __setattr__.
  • Default (unconfigured) parsing behavior is unchanged; maiden is off by default and not included in the default string_format.
  • Documents the new API in docs/customize.rst/docs/release_log.rst, and updates AGENTS.md (architecture notes, extension patterns, new gotchas) and docs/usage.rst (repr/as_dict examples) accordingly.

Closes #22.

Test plan

  • uv run pytest — 1196 passed, 22 xfailed
  • uv run mypy nameparser/ — clean
  • uv run ruff check nameparser/ tests/ — clean
  • sphinx-build -b doctest spot-checked for the new/changed doc examples (not CI-covered, but verified manually); total doc-tree failure count reduced from a 26 baseline to 15, none maiden-related
  • Both written forms tested end-to-end: "Baker (Johnson), Jenny" and "Jenny Baker (Johnson)"
  • Default-off behavior confirmed unchanged (parens still route to nickname unless explicitly reconfigured)
  • Unresolvable delimiter sentinel raises ValueError instead of silently corrupting output; Constants(regexes=<minimal set>) still works unaffected

See the review comment below for the full multi-agent review this branch went through after the initial commits, and what it found/fixed.

derek73 added 6 commits July 3, 2026 14:18
Mirrors the existing nickname field at the data-model level: property,
setter, _members entry, constructor kwarg, and as_dict/iteration/slicing
participation. Delimiter routing (parse_nicknames generalization) is not
part of this change.

Also fixes tests/test_python_api.py::test_slice, whose hardcoded slice
indices/lengths depended on the exact size of _members and shifted by
one now that maiden has been added.
…en_delimiters buckets

parse_nicknames() now iterates two named delimiter buckets instead of the
unreleased extra_nickname_delimiters: nickname_delimiters (holds the three
built-ins by default, plus any caller-added patterns) and maiden_delimiters
(empty by default). Built-in entries store the string name of a regexes
entry rather than a copied pattern, so overriding e.g.
CONSTANTS.regexes.parenthesis keeps affecting nickname/maiden parsing;
moving a key between the two dicts routes it to a different bucket without
losing that live link.

Also fixes a latent bug in TupleManager surfaced by this task: constructing
a subscripted generic like TupleManager[re.Pattern[str] | str](...) causes
typing's GenericAlias.__call__ to set __orig_class__ on the new instance,
which TupleManager's dict-backed __setattr__ silently turned into a bogus
dict entry. __setattr__/__delattr__ now guard dunder names the same way
__getattr__ already does.
Closes the gap the final feature review flagged: repr() and as_dict()
now always include maiden, but usage.rst's walkthrough examples
predate that field and were never updated. Also corrects the
as_dict() example's key order to match actual _members iteration
order (was already stale/mismatched before this branch, unrelated to
maiden -- fixed here since the line was already being touched).

Verified via `sphinx-build -b doctest`: failure count for the whole
docs/ tree drops from 26 to 21 with this change, and none of the
remaining failures reference maiden -- they're pre-existing
cross-example CONSTANTS state leaks already noted in AGENTS.md's
doctest gotcha, unrelated to this feature.
@derek73 derek73 self-assigned this Jul 3, 2026
@derek73 derek73 added this to the v1.3.0 milestone Jul 3, 2026
derek73 added 3 commits July 3, 2026 15:44
…elimiter sentinel

nickname_delimiters/maiden_delimiters string values that don't resolve
to a real Constants.regexes key used to fall back to RegexTupleManager's
EMPTY_REGEX default. That's not a harmless no-op: EMPTY_REGEX matches
at every character position, so handle_match() fired repeatedly and
appended '' into the bucket's list each time, producing a truthy
whitespace-only nickname/maiden while leaving the intended delimiter's
content (e.g. literal parentheses) unstripped elsewhere in the name.

Found independently by two review agents with working reproductions
while auditing PR #199. Now raises ValueError naming the bad key
instead. To keep Constants(regexes=<minimal custom set>) working
(confirmed via the existing test_override_regex), nickname_delimiters
is only seeded with a built-in name if it's actually present in the
regexes passed to the constructor -- so a caller who deliberately
drops e.g. "parenthesis" from a custom regexes set doesn't end up with
a dangling sentinel that looks like a mistake.
nickname_delimiters/maiden_delimiters' construction already fixed a
latent bug where TupleManager[X](...) leaked __orig_class__ into the
dict itself (commit 5bd82b2), but nothing in the suite pinned it down
directly -- a future "simplification" of __setattr__/__delattr__ back
to bare dict.__setitem__/__delitem__ aliases would silently corrupt
nickname/maiden parsing again with no test catching it. Flagged by a
review agent auditing PR #199.
Six repr() examples in customize.rst (Hon Solo x2, dean title x3, the
suffix-attribute-list example) never got the maiden: '' line every
real HumanName.__repr__() now emits -- these predate the maiden field
and are unrelated to the nickname-delimiter sections this feature
actually touched, so they were missed. Also fixes usage.rst's
name[1:-2] slice example, which needed to become name[1:-3] once
_members grew by one (the corresponding test_python_api.py::test_slice
update was already made in this branch; the doc example was not).

Verified via sphinx-build -b doctest: total failure count across docs/
drops from 21 (after the earlier usage.rst-only fix) to 15, with no
remaining maiden-related mismatches. The rest are pre-existing
cross-example CONSTANTS state leaks already documented in AGENTS.md's
doctest gotcha, unrelated to this change.

Found by review agents auditing PR #199.
@derek73

derek73 commented Jul 3, 2026

Copy link
Copy Markdown
Owner Author

Ran a multi-agent review (code, tests, comments, silent-failures, type-design) against this branch. Summary and fixes pushed:

Fixed:

  • Critical: nickname_delimiters/maiden_delimiters silently corrupted output on an unresolvable string sentinel (typo, or a key missing from a custom regexes override) — EMPTY_REGEX's zero-width match fired repeatedly via handle_match(), producing a truthy whitespace-only nickname/maiden while leaving the real delimiter content unstripped elsewhere. Now raises ValueError naming the bad key. Independently found by two review agents with working reproductions. (8412d3f)
  • Added a regression test for the TupleManager.__setattr__/__delattr__ dunder-name guard, which had no direct test despite already having caused one real bug during this feature's development. (7b14aa9)
  • Fixed remaining stale repr()/slice doctest examples in customize.rst (6 blocks) and usage.rst (name[1:-2]name[1:-3]) that were missed when maiden was added to _members. (be65195)

Reviewed and judged not worth acting on:

  • Duplication of the string-sentinel and dunder-guard explanations across code comments/docstrings/AGENTS.md/customize.rst — judged intentional and audience-appropriate (contributor vs. end-user vs. future-maintainer), not careless copy-paste.
  • Type design of TupleManager[re.Pattern[str] | str] — judged appropriately minimal for this codebase's YAGNI stance; a wrapper class or Literal type was considered and rejected as either leaking into every caller-visible dict or falsely constraining the intentionally-open regexes key space.

Full suite: 1196 passed, 22 xfailed. mypy and ruff clean.

…string

Not added as :param: entries since they aren't __init__ arguments
(always set internally, unlike every other entry in that block) --
that would make Sphinx's generated docs imply Constants(nickname_delimiters=...)
works. Documented as a plain note instead, pointing at the
customization docs for the full mechanism.
@derek73 derek73 merged commit d46e167 into master Jul 3, 2026
8 checks passed
@derek73 derek73 deleted the worktree-maiden-name-bucket branch July 3, 2026 23:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Alternate last name in parentheses is parsed into nickname field

1 participant