|
3 | 3 | import re |
4 | 4 | import timeit |
5 | 5 |
|
| 6 | +import pytest |
| 7 | + |
6 | 8 | from nameparser import HumanName |
7 | 9 | from nameparser.config import Constants, RegexTupleManager, SetManager, TupleManager |
8 | 10 | from nameparser.config.regexes import EMPTY_REGEX |
@@ -445,6 +447,45 @@ def test_replaced_manager_no_longer_invalidates_cache(self) -> None: |
445 | 447 | self.assertIs(c.suffixes_prefixes_titles, primed) |
446 | 448 | self.assertNotIn('ghost', c.suffixes_prefixes_titles) |
447 | 449 |
|
| 450 | + def test_tuplemanager_delattr_removes_dict_entry(self) -> None: |
| 451 | + """Deleting a non-dunder attribute must remove the dict entry. |
| 452 | +
|
| 453 | + TupleManager routes non-dunder attribute deletion to ``del self[attr]`` |
| 454 | + (the mirror of its dot-notation __setattr__), so ``del tm.key`` and |
| 455 | + ``del tm['key']`` are the same operation. |
| 456 | + """ |
| 457 | + c = Constants() |
| 458 | + c.nickname_delimiters['curly_braces'] = re.compile(r'\{(.*?)\}') |
| 459 | + self.assertIn('curly_braces', c.nickname_delimiters) |
| 460 | + del c.nickname_delimiters.curly_braces # type: ignore[attr-defined] |
| 461 | + self.assertNotIn('curly_braces', c.nickname_delimiters) |
| 462 | + |
| 463 | + def test_assigning_non_setmanager_to_cached_union_member_raises(self) -> None: |
| 464 | + """A cached-union attribute rejects a plain iterable, demanding a SetManager. |
| 465 | +
|
| 466 | + The four ``_CachedUnionMember`` attributes wire an on-change callback into |
| 467 | + the assigned manager; a bare list would silently break cache invalidation, |
| 468 | + so __set__ fails loud with a message telling the caller to wrap it. |
| 469 | + """ |
| 470 | + c = Constants() |
| 471 | + with pytest.raises(TypeError, match='SetManager'): |
| 472 | + c.titles = ['mr', 'ms'] # type: ignore[assignment] |
| 473 | + |
| 474 | + def test_setstate_raises_on_missing_descriptor_field(self) -> None: |
| 475 | + """Unpickling a state blob missing a cached-union collection must fail loudly. |
| 476 | +
|
| 477 | + __setstate__ verifies every ``_CachedUnionMember``-backed attribute was |
| 478 | + restored; a missing one would otherwise surface much later as an |
| 479 | + AttributeError on the private mangled name (e.g. ``_titles``), which is |
| 480 | + far harder to diagnose than a named ValueError here. |
| 481 | + """ |
| 482 | + c = Constants() |
| 483 | + state = dict(c.__getstate__()) |
| 484 | + del state['titles'] |
| 485 | + restored = Constants.__new__(Constants) |
| 486 | + with pytest.raises(ValueError, match='titles'): |
| 487 | + restored.__setstate__(state) |
| 488 | + |
448 | 489 |
|
449 | 490 | class SuffixesPrefixesTitlesPerformanceTests(HumanNameTestBase): |
450 | 491 | """Guard against accidental cache removal on suffixes_prefixes_titles. |
|
0 commit comments