Skip to content

Commit ba6e8e6

Browse files
derek73claude
andcommitted
Restore pickle-state verification loop; trim duplicated dunder comments
Review of #204 flagged the __setstate__ verification loop drop as an undisclosed diagnostic regression (truncated/incompatible pickle state would surface as a confusing AttributeError instead of a clear ValueError), so it's restored. Also trims the two __getattr__ comments that duplicated _is_dunder()'s rationale instead of stating only the site-specific consequence. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 37d5c69 commit ba6e8e6

1 file changed

Lines changed: 13 additions & 7 deletions

File tree

nameparser/config/__init__.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,7 @@ class TupleManager(dict[str, T]):
156156
'''
157157

158158
def __getattr__(self, attr: str) -> T | None:
159-
# Report dunders as genuinely absent so hasattr() is honest and
160-
# protocol probes work; otherwise the dict default is mistaken for a
161-
# real protocol hook. See RegexTupleManager.__getattr__ for the
162-
# concrete failure this prevents.
159+
# Otherwise the dict default (None) is mistaken for a real protocol hook.
163160
if _is_dunder(attr):
164161
raise AttributeError(attr)
165162
return self.get(attr)
@@ -201,9 +198,8 @@ def __reduce__(self) -> tuple[type, tuple[()], Mapping[str, T]]:
201198

202199
class RegexTupleManager(TupleManager[re.Pattern[str]]):
203200
def __getattr__(self, attr: str) -> re.Pattern[str]:
204-
# Report dunders as genuinely absent; otherwise the EMPTY_REGEX
205-
# default is mistaken for a real protocol hook — e.g. copy.deepcopy
206-
# tries to call the returned re.Pattern and raises TypeError.
201+
# Otherwise EMPTY_REGEX is returned for a dunder probe; copy.deepcopy
202+
# then tries to call the returned re.Pattern and raises TypeError.
207203
if _is_dunder(attr):
208204
raise AttributeError(attr)
209205
return self.get(attr, EMPTY_REGEX)
@@ -541,6 +537,16 @@ def __setstate__(self, state: Mapping[str, Any]) -> None:
541537
self._pst = None
542538
for name, value in state.items():
543539
setattr(self, name, value)
540+
# Verify each descriptor-backed attr was restored. Without this, a missing
541+
# key surfaces later as AttributeError: 'Constants' object has no attribute
542+
# '_prefixes' — the private mangled name, not the public one, making it
543+
# very hard to diagnose.
544+
for attr in (n for n, v in vars(type(self)).items() if isinstance(v, _CachedUnionMember)):
545+
if not hasattr(self, '_' + attr):
546+
raise ValueError(
547+
f"Pickle state is missing required field {attr!r}. "
548+
"The state blob may be truncated or from an incompatible version."
549+
)
544550

545551
def __getstate__(self) -> Mapping[str, Any]:
546552
# Pickle the instance's own configuration: the collections built in

0 commit comments

Comments
 (0)