Skip to content

Commit 36fd2ea

Browse files
derek73claude
andcommitted
Make REGEXES a dict instead of a set of (name, pattern) tuples
A set of tuples consumed as dict(...) means a duplicate name resolves nondeterministically per process (set iteration order varies with hash randomization). A dict literal makes a duplicate key a visible overwrite at write time and is the honest structure for key/value data. The regexes parameter annotation on Constants.__init__ collapses to Mapping: both TupleManager arms were dict subclasses already covered by it, and the new plain-dict default requires it (iterating a dict yields keys, not pairs, so it never satisfied the Iterable[tuple] arm). Closes #227 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 2358c77 commit 36fd2ea

3 files changed

Lines changed: 26 additions & 25 deletions

File tree

docs/release_log.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Release Log
4343
- Add German/Austrian nobility and ecclesiastical titles to ``TITLES`` (closes #101)
4444
- Add German/Dutch last-name prefixes and title/degree suffixes; fix ``join_on_conjunctions()`` to register multi-word prefix chains (e.g. ``"von und zu"``) as prefixes, mirroring existing title handling (closes #18)
4545
- Change ``Constants.__repr__`` to report collection sizes and non-default scalar config, replacing the uninformative ``<Constants() instance>`` (#221)
46+
- Change ``REGEXES`` from a ``set`` of ``(name, pattern)`` tuples to a ``dict``, so a duplicate name is a visible overwrite in the source instead of a nondeterministic winner at import time; code iterating ``REGEXES`` directly now gets keys instead of pairs — use ``.items()`` (#227)
4647
* 1.2.1 - June 19, 2026
4748
- Fix ``initials()`` interpolating the literal ``None`` for empty name parts when ``empty_attribute_default = None`` (e.g. ``"J. None D."``); empty parts now render as an empty string and a fully-empty result returns ``empty_attribute_default``
4849
- Add ``python -m nameparser "Name String"`` command-line helper that prints a parsed name

nameparser/config/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ def __init__(self,
477477
bound_first_names: Iterable[str] = BOUND_FIRST_NAMES,
478478
non_first_name_prefixes: Iterable[str] = NON_FIRST_NAME_PREFIXES,
479479
capitalization_exceptions: TupleManager[str] | Iterable[tuple[str, str]] = CAPITALIZATION_EXCEPTIONS,
480-
regexes: RegexTupleManager | TupleManager[re.Pattern[str]] | Iterable[tuple[str, re.Pattern[str]]] = REGEXES,
480+
regexes: Mapping[str, re.Pattern[str]] | Iterable[tuple[str, re.Pattern[str]]] = REGEXES,
481481
patronymic_name_order: bool = False,
482482
middle_name_as_last: bool = False,
483483
) -> None:

nameparser/config/regexes.py

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,39 +8,39 @@
88

99
EMPTY_REGEX = re.compile('')
1010

11-
REGEXES = set([
12-
("spaces", re.compile(r"\s+")),
13-
("word", re.compile(r"(\w|\.)+")),
14-
("mac", re.compile(r'^(ma?c)(\w{2,})', re.I)),
15-
("initial", re.compile(r'^(\w\.|[A-Z])?$')),
16-
("quoted_word", re.compile(r'(?<!\w)\'([^\s]*?)\'(?!\w)')),
17-
("double_quotes", re.compile(r'\"(.*?)\"')),
18-
("parenthesis", re.compile(r'\((.*?)\)')),
19-
("roman_numeral", re.compile(r'^(X|IX|IV|V?I{0,3})$', re.I)),
20-
("no_vowels",re.compile(r'^[^aeyiuo]+$', re.I)),
21-
("period_not_at_end",re.compile(r'.*\..+$', re.I)),
22-
("emoji",re_emoji),
23-
("phd", re.compile(r'\s(ph\.?\s+d\.?)', re.I)),
24-
("space_before_comma", re.compile(r'\s+,')),
25-
("east_slavic_patronymic", re.compile(
11+
REGEXES = {
12+
"spaces": re.compile(r"\s+"),
13+
"word": re.compile(r"(\w|\.)+"),
14+
"mac": re.compile(r'^(ma?c)(\w{2,})', re.I),
15+
"initial": re.compile(r'^(\w\.|[A-Z])?$'),
16+
"quoted_word": re.compile(r'(?<!\w)\'([^\s]*?)\'(?!\w)'),
17+
"double_quotes": re.compile(r'\"(.*?)\"'),
18+
"parenthesis": re.compile(r'\((.*?)\)'),
19+
"roman_numeral": re.compile(r'^(X|IX|IV|V?I{0,3})$', re.I),
20+
"no_vowels": re.compile(r'^[^aeyiuo]+$', re.I),
21+
"period_not_at_end": re.compile(r'.*\..+$', re.I),
22+
"emoji": re_emoji,
23+
"phd": re.compile(r'\s(ph\.?\s+d\.?)', re.I),
24+
"space_before_comma": re.compile(r'\s+,'),
25+
"east_slavic_patronymic": re.compile(
2626
r'(ovich|ovna|evich|evna|ichna|ilyich|kuzmich|lukich|fomich|fokich)$',
2727
re.I,
28-
)),
29-
("east_slavic_patronymic_cyrillic", re.compile(
28+
),
29+
"east_slavic_patronymic_cyrillic": re.compile(
3030
r'(ович|овна|евич|евна|ична|ильич|кузьмич|лукич|фомич|фокич)$',
3131
re.I,
32-
)),
33-
("turkic_patronymic_marker", re.compile(
32+
),
33+
"turkic_patronymic_marker": re.compile(
3434
r"^(oglu|oğlu|ogly|ogli|o['’ʻ]g['’ʻ]li"
3535
r"|qizi|qızı|kizi|kyzy|gyzy|uly|uulu)$",
3636
re.I,
37-
)),
38-
("turkic_patronymic_marker_cyrillic", re.compile(
37+
),
38+
"turkic_patronymic_marker_cyrillic": re.compile(
3939
r'^(оглу|оглы|оғлу|ўғли|угли|кызы|гызы|қызы|қизи|улы|ұлы|уулу)$',
4040
re.I,
41-
)),
42-
("period_abbreviation", re.compile(r'^[^\W\d_]{2,}\.$')),
43-
])
41+
),
42+
"period_abbreviation": re.compile(r'^[^\W\d_]{2,}\.$'),
43+
}
4444
"""
4545
All regular expressions used by the parser are precompiled and stored in the config.
4646
"""

0 commit comments

Comments
 (0)