Skip to content

Commit 0fd386c

Browse files
derek73claude
andcommitted
fix: point patronymic_rules=True at the v1 migration path
v1's patronymic_name_order was a bool flag, so True is the likeliest wrong value a migrator passes to the 2.0 field; it failed loudly but with the raw "'bool' object is not iterable". Probe with iter() -- NOT a try/except around tuple()/frozenset(), which would also rewrite exceptions raised inside a caller's generator (pinned property) -- and raise a message naming the v1 flag and both working replacements. Same guard on PolicyPatch's union fields, with the v1 hint only where it applies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent d4aaafa commit 0fd386c

2 files changed

Lines changed: 47 additions & 6 deletions

File tree

nameparser/_policy.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,24 @@ def __post_init__(self) -> None:
8585
f"patronymic_rules must be an iterable of rule names, "
8686
f"not a bare string: {self.patronymic_rules!r}"
8787
)
88-
# Materialize before converting (the _normset pattern): a
89-
# non-iterable raises its natural TypeError here, and an exception
90-
# raised inside a caller's generator propagates untouched instead
91-
# of being rewritten as an unknown-rule error. Only the enum
92-
# lookup itself gets the enriched message, naming the offender.
93-
items = tuple(self.patronymic_rules)
88+
# Probe with iter() rather than wrapping tuple(): non-iterables
89+
# (True especially -- v1's patronymic_name_order was a bool flag,
90+
# so it's the likeliest wrong value here) get the migration-
91+
# pointing message, while an exception raised inside a caller's
92+
# generator still propagates untouched from the tuple() below
93+
# instead of being rewritten. Only the enum lookup itself gets
94+
# the unknown-rule message, naming the offender.
95+
try:
96+
rule_iter = iter(self.patronymic_rules)
97+
except TypeError:
98+
raise TypeError(
99+
f"patronymic_rules must be an iterable of PatronymicRule "
100+
f"names, got {self.patronymic_rules!r}; v1's "
101+
f"patronymic_name_order=True becomes "
102+
f"patronymic_rules={{PatronymicRule.EAST_SLAVIC}} "
103+
f"(or parser_for(locales.RU))"
104+
) from None
105+
items = tuple(rule_iter)
94106
rules = set()
95107
for r in items:
96108
try:
@@ -232,6 +244,21 @@ def __post_init__(self) -> None:
232244
f"{f.name} must be an iterable, "
233245
f"not a bare string: {value!r}"
234246
)
247+
# same iter() probe as Policy: curated message for
248+
# non-iterables (with the v1-flag hint where it applies),
249+
# caller-generator exceptions propagate from frozenset()
250+
try:
251+
iter(value)
252+
except TypeError:
253+
hint = (
254+
"; v1's patronymic_name_order=True becomes "
255+
"patronymic_rules={PatronymicRule.EAST_SLAVIC} "
256+
"(or parser_for(locales.RU))"
257+
if f.name == "patronymic_rules" else ""
258+
)
259+
raise TypeError(
260+
f"{f.name} must be an iterable, got {value!r}{hint}"
261+
) from None
235262
object.__setattr__(self, f.name, frozenset(value))
236263

237264

tests/v2/test_policy.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,20 @@ def test_patronymic_rules_rejects_bare_string_and_non_iterable() -> None:
7676
Policy(patronymic_rules=5) # type: ignore[arg-type]
7777

7878

79+
def test_patronymic_rules_true_points_at_the_v1_migration() -> None:
80+
# v1's patronymic_name_order was a BOOL flag, so True is the single
81+
# likeliest wrong value a migrator passes here -- the message must
82+
# name the v1 flag and the working replacements, not just say
83+
# "not iterable". Same guard on the PolicyPatch path (packs).
84+
with pytest.raises(TypeError, match="patronymic_name_order"):
85+
Policy(patronymic_rules=True) # type: ignore[arg-type]
86+
with pytest.raises(TypeError, match="patronymic_name_order"):
87+
PolicyPatch(patronymic_rules=True) # type: ignore[arg-type]
88+
# non-patronymic union fields get the generic curated message
89+
with pytest.raises(TypeError, match="extra_suffix_delimiters"):
90+
PolicyPatch(extra_suffix_delimiters=True) # type: ignore[arg-type]
91+
92+
7993
def test_policy_delimiters_coerce_to_frozensets() -> None:
8094
p = Policy(nickname_delimiters=[("(", ")")]) # type: ignore[arg-type]
8195
assert isinstance(p.nickname_delimiters, frozenset)

0 commit comments

Comments
 (0)