From 57c505b03bd1d5666f9461e29e10bdf628d8ee47 Mon Sep 17 00:00:00 2001 From: Derek Gulbranson Date: Mon, 29 Jun 2026 12:14:01 -0700 Subject: [PATCH] fix: drop Constants.__setstate__ pre-1.2.1 pickle migration shim 1.2.1 shipped with a compatibility shim that let pickles written by the old dir()-sweep __getstate__ (which included the read-only suffixes_prefixes_titles property) load without raising AttributeError. That was a one-version bridge; users who persisted pre-1.2.1 blobs can load and re-pickle under 1.2.1 before upgrading to 1.3.0. Remove the shim and its regression test, and add an upgrade note to the release log explaining the migration path. Co-Authored-By: Claude Sonnet 4.6 --- docs/release_log.rst | 6 ++++++ nameparser/config/__init__.py | 10 ---------- tests/test_constants.py | 25 ------------------------- 3 files changed, 6 insertions(+), 35 deletions(-) diff --git a/docs/release_log.rst b/docs/release_log.rst index 585ae01..c603a86 100644 --- a/docs/release_log.rst +++ b/docs/release_log.rst @@ -1,6 +1,12 @@ Release Log =========== * 1.3.0 - Unreleased + + **Upgrade note — pickle migration**: ``Constants`` pickles written before + 1.2.1 cannot be loaded under 1.3.0+. If you have persisted blobs, upgrade + to 1.2.1 first (which includes a one-version compatibility shim), load and + re-pickle under 1.2.1, then upgrade to 1.3.0. + - Add ``suffix_delimiter`` to ``Constants`` and ``HumanName`` for parsing suffixes separated by arbitrary delimiters, e.g. ``"RN - CRNA"`` (#156) - Add ``initials_separator`` to ``Constants`` and ``HumanName`` to control spacing between consecutive initials within a name group (#171) - Fix ``Constants`` customizations, singleton identity, and ``TupleManager`` subclass being lost across ``pickle``/``deepcopy`` round-trips (#167, #168, #169) diff --git a/nameparser/config/__init__.py b/nameparser/config/__init__.py index d98a914..1d5c034 100644 --- a/nameparser/config/__init__.py +++ b/nameparser/config/__init__.py @@ -383,16 +383,6 @@ def __setstate__(self, state: Mapping[str, Any]) -> None: # unpickling. self._pst = None for name, value in state.items(): - # Migration shim: pickles written before this fix used a dir() sweep - # for __getstate__, so their state carries the read-only - # ``suffixes_prefixes_titles`` property. Skip any such computed - # property rather than raising AttributeError on its missing setter; - # the real config is restored from the other keys. We don't promise - # to read pre-fix blobs forever — this only smooths migration for - # anyone persisting them, and can be dropped a release or two after - # 1.2.1 once they've re-pickled. - if isinstance(getattr(type(self), name, None), property): - continue setattr(self, name, value) # Verify each descriptor-backed attr was restored. Without this, a missing # key surfaces later as AttributeError: 'Constants' object has no attribute diff --git a/tests/test_constants.py b/tests/test_constants.py index 2f021ea..decae95 100644 --- a/tests/test_constants.py +++ b/tests/test_constants.py @@ -208,31 +208,6 @@ def test_tuplemanager_ignores_dunder_lookups(self) -> None: # A normal (non-dunder) unknown key still returns the None default. self.assertEqual(tm.unknown_key, None) - def test_unpickle_legacy_state_with_property_key(self) -> None: - """Pickles written by older versions must still load. - - The previous __getstate__ built its state from a dir() sweep, which - always included the computed `suffixes_prefixes_titles` property (no - customization required). That property has no setter, so __setstate__ - must skip such keys instead of raising AttributeError. - - Covers the temporary migration shim in __setstate__; remove this test - when that shim is dropped (a release or two after 1.2.1). - """ - c = Constants() - c.titles.add('legacytitle') - # Reproduce the legacy dir()-sweep state dict, which carries the - # read-only `suffixes_prefixes_titles` property alongside the real config. - legacy_state = { - name: getattr(c, name) for name in dir(c) if not name.startswith('_') - } - self.assertIn('suffixes_prefixes_titles', legacy_state) - - restored = Constants.__new__(Constants) - restored.__setstate__(legacy_state) - - # The real customization is recovered and the property key is ignored. - self.assertIn('legacytitle', restored.titles) def test_suffixes_prefixes_titles_reflects_add_title(self) -> None: """suffixes_prefixes_titles must include titles added after construction."""