Skip to content

Commit e75e928

Browse files
committed
fix: suffixes_prefixes_titles always reflects current set state
The `suffixes_prefixes_titles` property on `Constants` cached its result in `_pst` after the first access. Any subsequent `add()` or `remove()` call on `titles`, `prefixes`, `suffix_acronyms`, or `suffix_not_acronyms` was silently ignored by the cache, so `is_rootname()` kept returning the stale result. Concretely, a word added to `C.titles` after the property was first accessed would still be treated as a rootname (first/middle/last) by `join_on_conjunctions`, even though `is_title()` correctly returned `True` for it. This contradicts the documented behaviour of per-instance config customisation described in AGENTS.md. Fix: drop the `_pst` cache entirely and compute the union fresh on every access. The four-set union is cheap and the simplest correct approach. Add five tests that assert the property and `is_rootname` stay consistent with the live sets after `add()`/`remove()` calls.
1 parent 1f46568 commit e75e928

2 files changed

Lines changed: 33 additions & 5 deletions

File tree

‎nameparser/config/__init__.py‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,6 @@ class Constants:
179179
capitalization_exceptions: TupleManager[str]
180180
regexes: RegexTupleManager
181181

182-
_pst: Set[str] | None
183182

184183
string_format = "{title} {first} {middle} {last} {suffix} ({nickname})"
185184
"""
@@ -262,13 +261,10 @@ def __init__(self,
262261
self.conjunctions = SetManager(conjunctions)
263262
self.capitalization_exceptions = TupleManager(capitalization_exceptions)
264263
self.regexes = RegexTupleManager(regexes)
265-
self._pst = None
266264

267265
@property
268266
def suffixes_prefixes_titles(self) -> Set[str]:
269-
if not self._pst:
270-
self._pst = self.prefixes | self.suffix_acronyms | self.suffix_not_acronyms | self.titles
271-
return self._pst
267+
return self.prefixes | self.suffix_acronyms | self.suffix_not_acronyms | self.titles
272268

273269
def __repr__(self) -> str:
274270
return "<Constants() instance>"

‎tests/test_constants.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,35 @@ def test_add_constant_with_explicit_encoding(self) -> None:
104104
c = Constants()
105105
c.titles.add_with_encoding(b'b\351ck', encoding='latin_1')
106106
self.assertIn('béck', c.titles)
107+
108+
def test_suffixes_prefixes_titles_reflects_add_title(self) -> None:
109+
"""suffixes_prefixes_titles must include titles added after construction."""
110+
c = Constants()
111+
c.titles.add('emerita')
112+
self.assertIn('emerita', c.suffixes_prefixes_titles)
113+
114+
def test_suffixes_prefixes_titles_reflects_add_prefix(self) -> None:
115+
"""suffixes_prefixes_titles must include prefixes added after construction."""
116+
c = Constants()
117+
c.prefixes.add('xpfx')
118+
self.assertIn('xpfx', c.suffixes_prefixes_titles)
119+
120+
def test_suffixes_prefixes_titles_reflects_remove_title(self) -> None:
121+
"""suffixes_prefixes_titles must not include a word that was only in titles and is then removed."""
122+
c = Constants()
123+
c.titles.add('emerita')
124+
self.assertIn('emerita', c.suffixes_prefixes_titles)
125+
c.titles.remove('emerita')
126+
self.assertFalse('emerita' in c.suffixes_prefixes_titles)
127+
128+
def test_is_rootname_consistent_with_is_title(self) -> None:
129+
"""is_rootname must return False for words recognised by is_title."""
130+
hn = HumanName("", constants=None)
131+
hn.C.titles.add('emerita')
132+
self.assertFalse(hn.is_rootname('emerita'))
133+
134+
def test_is_rootname_consistent_with_is_prefix(self) -> None:
135+
"""is_rootname must return False for words recognised by is_prefix."""
136+
hn = HumanName("", constants=None)
137+
hn.C.prefixes.add('xpfx')
138+
self.assertFalse(hn.is_rootname('xpfx'))

0 commit comments

Comments
 (0)