Skip to content

Commit 83615bc

Browse files
committed
fix: add re.I to east_slavic_patronymic_cyrillic regex
Unlike its Latin sibling and the new turkic_patronymic_marker_cyrillic pattern, this pattern had no re.I flag. The irregular-form alternatives (ильич, кузьмич, лукич, фомич, фокич) are short enough that the capitalized first letter falls within the matched suffix itself, so capitalized real-world patronymics like "Ильич" failed to match and HumanName("Иванов Иван Ильич", constants=Constants(patronymic_name_order=True)) did not rotate, while the equivalent Latin-script name did. Also strengthens test_no_regex_collision_latin/_cyrillic with positive sanity assertions confirming each word list actually matches its own family's regex, so the non-collision assertions are non-vacuous.
1 parent 1a29c38 commit 83615bc

3 files changed

Lines changed: 30 additions & 0 deletions

File tree

nameparser/config/regexes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
)),
2929
("east_slavic_patronymic_cyrillic", re.compile(
3030
r'(ович|овна|евич|евна|ична|ильич|кузьмич|лукич|фомич|фокич)$',
31+
re.I,
3132
)),
3233
("turkic_patronymic_marker", re.compile(
3334
r"^(oglu|oğlu|ogly|ogli|o['’ʻ]g['’ʻ]li"

tests/test_patronymic_order.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ def test_cyrillic_patronymic_matches() -> None:
3131
assert C.regexes.east_slavic_patronymic_cyrillic.search("ильич")
3232

3333

34+
def test_cyrillic_patronymic_matches_capitalized_irregular_forms() -> None:
35+
# The irregular forms (Ильич, Кузьмич, ...) are short enough that the
36+
# capitalized first letter falls within the matched suffix itself, unlike
37+
# the common suffixes (-ович, -евна, ...) where only the surname root is
38+
# capitalized. Case-insensitivity is required for these to match.
39+
C = Constants()
40+
assert C.regexes.east_slavic_patronymic_cyrillic.search("Ильич")
41+
assert C.regexes.east_slavic_patronymic_cyrillic.search("Кузьмич")
42+
assert C.regexes.east_slavic_patronymic_cyrillic.search("Лукич")
43+
assert C.regexes.east_slavic_patronymic_cyrillic.search("Фомич")
44+
assert C.regexes.east_slavic_patronymic_cyrillic.search("Фокич")
45+
46+
3447
def test_cyrillic_patronymic_rejects_non_patronymic() -> None:
3548
C = Constants()
3649
assert not C.regexes.east_slavic_patronymic_cyrillic.search("Иванов")
@@ -77,6 +90,14 @@ def test_cyrillic(self) -> None:
7790
assert n.middle == "Иванович"
7891
assert n.last == "Иванов"
7992

93+
def test_cyrillic_capitalized_irregular_form(self) -> None:
94+
# "Ильич" is short enough that the capitalized first letter falls
95+
# within the irregular suffix itself; requires case-insensitive match.
96+
n = self.hn("Иванов Иван Ильич")
97+
assert n.first == "Иван"
98+
assert n.middle == "Ильич"
99+
assert n.last == "Иванов"
100+
80101
def test_title_preserved(self) -> None:
81102
n = self.hn("Dr. Ivanov Ivan Ivanovich")
82103
assert n.title == "Dr."

tests/test_turkic_patronymic_order.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,12 @@ def test_no_regex_collision_latin(self) -> None:
274274
"qizi", "qızı", "kizi", "kyzy", "gyzy", "uly", "uulu",
275275
]
276276
for word in east_slavic_examples:
277+
# Sanity check: each word actually matches its own family's
278+
# regex, so the non-collision assertion below is non-vacuous.
279+
assert C.regexes.east_slavic_patronymic.search(word), word
277280
assert not C.regexes.turkic_patronymic_marker.match(word), word
278281
for word in turkic_examples:
282+
assert C.regexes.turkic_patronymic_marker.match(word), word
279283
assert not C.regexes.east_slavic_patronymic.search(word), word
280284

281285
def test_no_regex_collision_cyrillic(self) -> None:
@@ -289,6 +293,10 @@ def test_no_regex_collision_cyrillic(self) -> None:
289293
"кызы", "гызы", "қызы", "қизи", "улы", "ұлы", "уулу",
290294
]
291295
for word in east_slavic_examples:
296+
# Sanity check: each word actually matches its own family's
297+
# regex, so the non-collision assertion below is non-vacuous.
298+
assert C.regexes.east_slavic_patronymic_cyrillic.search(word), word
292299
assert not C.regexes.turkic_patronymic_marker_cyrillic.match(word), word
293300
for word in turkic_examples:
301+
assert C.regexes.turkic_patronymic_marker_cyrillic.match(word), word
294302
assert not C.regexes.east_slavic_patronymic_cyrillic.search(word), word

0 commit comments

Comments
 (0)