The parse decides; the render views honor those decisions and never re-evaluate them. capitalized() breaks that rule — it re-runs the conjunction-versus-initial decision at render time from the word's spelling, rather than reading the tag the parser already set.
# nameparser/_render.py, _cap_word
if (... or (normalized in lex.conjunctions and not _INITIAL.fullmatch(word))):
return word.lower()
classify has already answered this. Token.tags carries conjunction and initial, both stable API, and initials() already honors them through _SKIP_TAGS. Only case repair asks again.
Two copies that ask different questions
|
predicate |
scope |
_classify.py:80 |
is_initial(token.text) |
shape and repertoire (_in_initialless_script, #320) |
_render.py |
_INITIAL.fullmatch(word) |
shape only |
_render.py says so in its own comment: "the two copies keep identical PATTERNS and divergent PREDICATES; test_regex_sync pins the patterns, which is the promise being kept." 씨. answers yes to the bare pattern and no to is_initial.
The same comment argues the divergence is harmless — no shipped CJK conjunction exists, and CJK is caseless, so both branches return the same string — while noting that this is "a property of the shipped DATA, not an invariant — conjunctions is public, configurable API."
Reading the tag makes the question disappear rather than arguing it is harmless.
Measured: this is a pure refactor
Replacing the re-derivation with a "conjunction" in tags test moves 0 of 1094 differential-corpus names, under both capitalize() and capitalize(force=True).
The plumbing is already in place: #407 threads Token.tags into _cap_word so the particle branch can consult UNJOINED_TAG, and the conjunction branch is the next consumer of the same argument.
It also retires a hand-sync obligation the module currently carries in a comment: "keep in sync with nameparser/_pipeline/_vocab.py by hand." A tag read has nothing to keep in sync.
Scope
This changes where the decision is read, not how it is made. The parser's own conjunction-versus-initial classification is untouched, and no output moves.
Not proposed here
Changing _INITIAL's pattern — test_regex_sync pins it against nameparser/_pipeline/_vocab.py, and REGEXES["initial"] is public v1 API that cannot narrow — or removing e / y from the default conjunction vocabulary.
Found while measuring #407's capitalization behavior.
The parse decides; the render views honor those decisions and never re-evaluate them.
capitalized()breaks that rule — it re-runs the conjunction-versus-initial decision at render time from the word's spelling, rather than reading the tag the parser already set.classifyhas already answered this.Token.tagscarriesconjunctionandinitial, both stable API, andinitials()already honors them through_SKIP_TAGS. Only case repair asks again.Two copies that ask different questions
_classify.py:80is_initial(token.text)_in_initialless_script, #320)_render.py_INITIAL.fullmatch(word)_render.pysays so in its own comment: "the two copies keep identical PATTERNS and divergent PREDICATES; test_regex_sync pins the patterns, which is the promise being kept."씨.answers yes to the bare pattern and no tois_initial.The same comment argues the divergence is harmless — no shipped CJK conjunction exists, and CJK is caseless, so both branches return the same string — while noting that this is "a property of the shipped DATA, not an invariant —
conjunctionsis public, configurable API."Reading the tag makes the question disappear rather than arguing it is harmless.
Measured: this is a pure refactor
Replacing the re-derivation with a
"conjunction" in tagstest moves 0 of 1094 differential-corpus names, under bothcapitalize()andcapitalize(force=True).The plumbing is already in place: #407 threads
Token.tagsinto_cap_wordso the particle branch can consultUNJOINED_TAG, and the conjunction branch is the next consumer of the same argument.It also retires a hand-sync obligation the module currently carries in a comment: "keep in sync with
nameparser/_pipeline/_vocab.pyby hand." A tag read has nothing to keep in sync.Scope
This changes where the decision is read, not how it is made. The parser's own conjunction-versus-initial classification is untouched, and no output moves.
Not proposed here
Changing
_INITIAL's pattern —test_regex_syncpins it againstnameparser/_pipeline/_vocab.py, andREGEXES["initial"]is public v1 API that cannot narrow — or removinge/yfrom the default conjunction vocabulary.Found while measuring #407's capitalization behavior.