Skip to content

Commit f69ecee

Browse files
derek73claude
andcommitted
perf: restore _pst cache with proper invalidation via callbacks and __setattr__
The original cache was dropped to fix staleness, but recomputing the set union on every call is ~1000x slower. This restores the cache with a correct invalidation strategy: SetManager fires an on_change callback after add/remove, and Constants.__setattr__ clears _pst and re-wires the callback whenever one of the four contributing attrs is replaced (covering both user mutations and conftest teardown restores). The conftest manual `CONSTANTS._pst = None` reset is removed — it is now handled automatically by __setattr__ during collection restore. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e75e928 commit f69ecee

2 files changed

Lines changed: 24 additions & 8 deletions

File tree

nameparser/config/__init__.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"""
3030
import re
3131
import sys
32-
from collections.abc import Iterable, Iterator, Mapping, Set
32+
from collections.abc import Callable, Iterable, Iterator, Mapping, Set
3333
from typing import Any, TypeVar
3434

3535
if sys.version_info >= (3, 11):
@@ -62,8 +62,9 @@ class SetManager(Set):
6262
6363
'''
6464

65-
def __init__(self, elements: Iterable[str]) -> None:
65+
def __init__(self, elements: Iterable[str], on_change: Callable[[], None] | None = None) -> None:
6666
self.elements = set(elements)
67+
self._on_change = on_change
6768

6869
def __call__(self) -> Set[str]:
6970
return self.elements
@@ -93,6 +94,8 @@ def add_with_encoding(self, s: str, encoding: str | None = None) -> None:
9394
if isinstance(s, bytes):
9495
s = s.decode(encoding)
9596
self.elements.add(lc(s))
97+
if self._on_change:
98+
self._on_change()
9699

97100
def add(self, *strings: str) -> Self:
98101
"""
@@ -112,7 +115,8 @@ def remove(self, *strings: str) -> Self:
112115
for s in strings:
113116
if (lower := lc(s)) in self.elements:
114117
self.elements.remove(lower)
115-
118+
if self._on_change:
119+
self._on_change()
116120
return self
117121

118122

@@ -146,6 +150,9 @@ def __getattr__(self, attr: str) -> re.Pattern[str]:
146150
return self.get(attr, EMPTY_REGEX)
147151

148152

153+
_PST_ATTRS = frozenset(('prefixes', 'suffix_acronyms', 'suffix_not_acronyms', 'titles'))
154+
155+
149156
class Constants:
150157
"""
151158
An instance of this class hold all of the configuration constants for the parser.
@@ -178,7 +185,7 @@ class Constants:
178185
conjunctions: SetManager
179186
capitalization_exceptions: TupleManager[str]
180187
regexes: RegexTupleManager
181-
188+
_pst: Set[str] | None
182189

183190
string_format = "{title} {first} {middle} {last} {suffix} ({nickname})"
184191
"""
@@ -262,9 +269,21 @@ def __init__(self,
262269
self.capitalization_exceptions = TupleManager(capitalization_exceptions)
263270
self.regexes = RegexTupleManager(regexes)
264271

272+
def __setattr__(self, name: str, value: object) -> None:
273+
if name in _PST_ATTRS:
274+
object.__setattr__(self, '_pst', None)
275+
if isinstance(value, SetManager):
276+
value._on_change = self._invalidate_pst
277+
object.__setattr__(self, name, value)
278+
279+
def _invalidate_pst(self) -> None:
280+
self._pst = None
281+
265282
@property
266283
def suffixes_prefixes_titles(self) -> Set[str]:
267-
return self.prefixes | self.suffix_acronyms | self.suffix_not_acronyms | self.titles
284+
if not self._pst:
285+
self._pst = self.prefixes | self.suffix_acronyms | self.suffix_not_acronyms | self.titles
286+
return self._pst
268287

269288
def __repr__(self) -> str:
270289
return "<Constants() instance>"

tests/conftest.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,3 @@ def empty_attribute_default(request: pytest.FixtureRequest) -> Iterator[str | No
7575
setattr(CONSTANTS, attr, value)
7676
for attr, value in collection_snapshot.items():
7777
setattr(CONSTANTS, attr, value)
78-
# Invalidate the lazily-built suffixes/prefixes/titles cache so it is
79-
# recomputed from the restored collections rather than a mutated one.
80-
CONSTANTS._pst = None

0 commit comments

Comments
 (0)