Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions docs/release_log.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
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 ``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)
- 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)
- Fix suffix-shaped parenthesized/quoted content (e.g. ``"(Ret)"``, ``"(MBA)"``) being misclassified as a nickname instead of a suffix (closes #111)
Expand Down
10 changes: 10 additions & 0 deletions nameparser/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,16 @@ 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 (1.3.0 and earlier,
# including 1.2.1) 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.3.0 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
Expand Down
26 changes: 26 additions & 0 deletions tests/test_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,32 @@ def test_pickle_roundtrip_preserves_instance_scalar_override(self) -> None:

self.assertEqual(restored.empty_attribute_default, 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.3.0).
"""
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_pickle_roundtrip_preserves_regex_manager_subclass(self) -> None:
"""regexes must round-trip as a RegexTupleManager, not a plain TupleManager.

Expand Down