Show the Policy spellings that type-check - #334
Merged
Conversation
The package ships py.typed, and every Policy configuration example in
the guide needed a # type: ignore[arg-type]. Five spellings failed, not
the two first noticed: script_orders={}, segment_scripts=(),
nickname_delimiters={...}, maiden_delimiters={...} and the
dict(DEFAULT_SCRIPT_ORDERS) | {...} override. A bare SET literal for a
delimiter field is the one most likely to bite, being the spelling
anyone reaches for first.
The cause is not sloppiness and is not fixed here: a dataclass field
annotation types both the __init__ parameter and the attribute, and
Policy normalizes in __post_init__, so those two types genuinely
differ. The annotation stays the storage type, because reading the
attribute is the commoner operation and a union there would cascade to
every reader -- the same reasoning that already kept
empty_attribute_default narrow.
What changes is which spelling the docs teach. Each one has a
type-clean equivalent that means exactly the same thing, verified
under mypy. Union spellings are untouched: frozenset | set is already
frozenset, so DEFAULT_NICKNAME_DELIMITERS | {...} was always clean.
The script_orders override becomes (*DEFAULT_SCRIPT_ORDERS, (Script.HAN,
GIVEN_FIRST)), which depends on a later entry replacing an earlier one.
That was emergent from _validated_script_orders building a dict before
sorting rather than a stated contract, so prose telling readers to
append needed it pinned: a test now asserts the override, the survival
of the other entries, one entry per script, and equality with the
mapping spelling it replaces.
The note in customize.rst said the opposite of what is true now; it
explains the storage-vs-constructor split instead.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #334 +/- ##
=======================================
Coverage 98.48% 98.48%
=======================================
Files 41 41
Lines 2832 2832
=======================================
Hits 2789 2789
Misses 43 43 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
nameparser ships
py.typed, and everyPolicyconfiguration example in the docs needed a# type: ignore[arg-type]. This changes the docs to teach the spellings that check clean.Five spellings failed, not the two originally noticed:
Policy(script_orders={})dict→tuplePolicy(segment_scripts=())tuple→frozensetPolicy(nickname_delimiters={("[", "]")})set→frozensetPolicy(maiden_delimiters={("(", ")")})set→frozensetPolicy(script_orders=dict(DEFAULT_SCRIPT_ORDERS) | {...})dict→tupleThe bare set literal for a delimiter field is the one most likely to bite in practice — it is the spelling anyone reaches for first, and it appeared in
concepts.rstas the introductory example.What is not changed, and why
The annotations. A dataclass field annotation types both the
__init__parameter and the attribute, andPolicynormalizes in__post_init__(its validators takeobject), so those two types genuinely differ. Python cannot express that with one annotation.The annotation stays the storage type, because reading the attribute is the commoner operation and a union there would cascade to every reader — the same reasoning that already kept
empty_attribute_defaultnarrow. This is a real expressiveness gap, not sloppiness, and it applies to every normalizing field.Union spellings were already fine and are untouched:
frozenset | setisfrozenset, soDEFAULT_NICKNAME_DELIMITERS | {("{", "}")}always checked clean.The one spelling that needed a contract first
The
script_ordersoverride becomes:which depends on a later entry replacing an earlier one. That behavior was emergent —
_validated_script_ordersbuilds adictbefore sorting, and the comment there justifies it for hashability, not for override semantics. Prose telling readers to append is prose that depends on it, sotests/v2/test_policy.pynow pins the override, the survival of the other entries, one-entry-per-script, and equality with the mapping spelling it replaces.Verification
mypyclean.uv run sphinx-build -b doctest docs— exit 0.uv run pytest -q— 3060 passed (was 3059; one new test).uv run mypy,uv run ruff check— clean.Note for review
The
.. note::incustomize.rstpreviously told readers the readable spellings need a# type: ignore. It now explains the storage-vs-constructor split instead, and says the wider spellings still parse identically for anyone not running a type checker.I did not add a release-log entry: no behavior changed. Arguably the newly-contracted last-wins rule is a promise the library now makes and could warrant a line — happy to add one if you read it that way.
🤖 Generated with Claude Code