Skip to content

Commit c4ac7e4

Browse files
authored
Merge pull request #211 from derek73/fix/restore-pickle-migration-shim
Restore Constants pickle migration shim, correct its version history
2 parents b2f0cf2 + 805c109 commit c4ac7e4

3 files changed

Lines changed: 36 additions & 6 deletions

File tree

docs/release_log.rst

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
Release Log
22
===========
33
* 1.3.0 - Unreleased
4-
5-
**Upgrade note — pickle migration**: ``Constants`` pickles written before
6-
1.2.1 cannot be loaded under 1.3.0+. If you have persisted blobs, upgrade
7-
to 1.2.1 first (which includes a one-version compatibility shim), load and
8-
re-pickle under 1.2.1, then upgrade to 1.3.0.
9-
104
- Add ``non_first_name_prefixes`` to ``Constants``: a leading particle that is never a first name (e.g. ``"de Mesnil"``, ``"dos Santos"``) now parses as a surname with an empty first name, instead of treating the particle as the first name (closes #121)
115
- Add a first-class ``maiden`` field and ``maiden_delimiters`` to ``Constants``, so a delimiter (e.g. parenthesis) can be routed to ``maiden`` instead of ``nickname`` for alternate/maiden surnames, e.g. ``"Baker (Johnson), Jenny"`` (closes #22)
126
- Fix suffix-shaped parenthesized/quoted content (e.g. ``"(Ret)"``, ``"(MBA)"``) being misclassified as a nickname instead of a suffix (closes #111)

nameparser/config/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,16 @@ def __setstate__(self, state: Mapping[str, Any]) -> None:
535535
# unpickling.
536536
self._pst = None
537537
for name, value in state.items():
538+
# Migration shim: pickles written before this fix (1.3.0 and earlier,
539+
# including 1.2.1) used a dir() sweep for __getstate__, so their state
540+
# carries the read-only ``suffixes_prefixes_titles`` property. Skip any
541+
# such computed property rather than raising AttributeError on its
542+
# missing setter; the real config is restored from the other keys. We
543+
# don't promise to read pre-fix blobs forever — this only smooths
544+
# migration for anyone persisting them, and can be dropped a release
545+
# or two after 1.3.0 once they've re-pickled.
546+
if isinstance(getattr(type(self), name, None), property):
547+
continue
538548
setattr(self, name, value)
539549
# Verify each descriptor-backed attr was restored. Without this, a missing
540550
# key surfaces later as AttributeError: 'Constants' object has no attribute

tests/test_constants.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,32 @@ def test_pickle_roundtrip_preserves_instance_scalar_override(self) -> None:
166166

167167
self.assertEqual(restored.empty_attribute_default, None)
168168

169+
def test_unpickle_legacy_state_with_property_key(self) -> None:
170+
"""Pickles written by older versions must still load.
171+
172+
The previous __getstate__ built its state from a dir() sweep, which
173+
always included the computed `suffixes_prefixes_titles` property (no
174+
customization required). That property has no setter, so __setstate__
175+
must skip such keys instead of raising AttributeError.
176+
177+
Covers the temporary migration shim in __setstate__; remove this test
178+
when that shim is dropped (a release or two after 1.3.0).
179+
"""
180+
c = Constants()
181+
c.titles.add('legacytitle')
182+
# Reproduce the legacy dir()-sweep state dict, which carries the
183+
# read-only `suffixes_prefixes_titles` property alongside the real config.
184+
legacy_state = {
185+
name: getattr(c, name) for name in dir(c) if not name.startswith('_')
186+
}
187+
self.assertIn('suffixes_prefixes_titles', legacy_state)
188+
189+
restored = Constants.__new__(Constants)
190+
restored.__setstate__(legacy_state)
191+
192+
# The real customization is recovered and the property key is ignored.
193+
self.assertIn('legacytitle', restored.titles)
194+
169195
def test_pickle_roundtrip_preserves_regex_manager_subclass(self) -> None:
170196
"""regexes must round-trip as a RegexTupleManager, not a plain TupleManager.
171197

0 commit comments

Comments
 (0)