From 64a789e70cd51d19dcac068545773470764cfdb5 Mon Sep 17 00:00:00 2001 From: Derek Gulbranson Date: Sun, 28 Jun 2026 22:25:37 -0700 Subject: [PATCH 1/2] fix: honor single-char symbol conjunctions regardless of name length (closes #151) The `join_on_conjunction` heuristic skipped single-character conjunctions when `total_length < 4` to avoid treating alphabetic initials like `e` or `y` as conjunctions. This inadvertently also skipped non-alphabetic symbols like `&`, which can never be initials. Fix: only apply the "treat as initial" fallback when the character is alphabetic. Non-alphabetic conjunctions (e.g. `&`) are now always joined regardless of name length, so `"Mr. & Mrs. John Smith"` correctly parses the title as `"Mr. & Mrs."`. Co-Authored-By: Claude Sonnet 4.6 --- nameparser/parser.py | 2 +- tests/test_conjunctions.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/nameparser/parser.py b/nameparser/parser.py index f5cde59..18dd7c3 100644 --- a/nameparser/parser.py +++ b/nameparser/parser.py @@ -870,7 +870,7 @@ def join_on_conjunctions(self, pieces: list[str], additional_parts_count: int = conj_index = [i for i, piece in enumerate(pieces) if self.is_conjunction(piece)] for i in conj_index: - if len(pieces[i]) == 1 and total_length < 4: + if len(pieces[i]) == 1 and total_length < 4 and pieces[i].isalpha(): # if there are only 3 total parts (minus known titles, suffixes # and prefixes) and this conjunction is a single letter, prefer # treating it as an initial rather than a conjunction. diff --git a/tests/test_conjunctions.py b/tests/test_conjunctions.py index 73ee3cc..f98acaf 100644 --- a/tests/test_conjunctions.py +++ b/tests/test_conjunctions.py @@ -111,6 +111,14 @@ def test_couple_titles(self) -> None: self.m(hn.first, "John and Jane", hn) self.m(hn.last, "Smith", hn) + def test_couple_titles_ampersand_conjunction(self) -> None: + # issue 151: single-char conjunctions in the conjunctions list should + # be honored even when total_length < 4 + hn = HumanName('Mr. & Mrs. John Smith') + self.m(hn.title, "Mr. & Mrs.", hn) + self.m(hn.first, "John", hn) + self.m(hn.last, "Smith", hn) + def test_title_with_three_part_name_last_initial_is_suffix_uppercase_no_period(self) -> None: hn = HumanName("King John Alexander V") self.m(hn.title, "King", hn) From be0b617321a4493f26405aa8a6f244a222894fa3 Mon Sep 17 00:00:00 2001 From: Derek Gulbranson Date: Sun, 28 Jun 2026 22:36:48 -0700 Subject: [PATCH 2/2] test: add boundary tests for single-char alpha vs symbol conjunction handling Documents the intentional asymmetry introduced in #151: non-alpha symbols like & are always honored as conjunctions, while single-char alpha conjunctions (e, y) still fall back to initial treatment in short names. Co-Authored-By: Claude Sonnet 4.6 --- tests/test_conjunctions.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_conjunctions.py b/tests/test_conjunctions.py index f98acaf..94215c3 100644 --- a/tests/test_conjunctions.py +++ b/tests/test_conjunctions.py @@ -119,6 +119,20 @@ def test_couple_titles_ampersand_conjunction(self) -> None: self.m(hn.first, "John", hn) self.m(hn.last, "Smith", hn) + def test_ampersand_conjunction_short_name_no_titles(self) -> None: + # & is non-alpha so it should always be honored as a conjunction, + # even when total_length < 4 (no titles to inflate the count) + hn = HumanName('John & Jane') + self.m(hn.first, "John & Jane", hn) + + def test_single_char_alpha_conjunction_still_treated_as_initial_when_short(self) -> None: + # single-char alpha conjunctions (e, y) are still treated as initials + # when total_length < 4; only non-alpha symbols like & bypass this guard + hn = HumanName('John y Jane') + self.m(hn.first, "John", hn) + self.m(hn.middle, "y", hn) + self.m(hn.last, "Jane", hn) + def test_title_with_three_part_name_last_initial_is_suffix_uppercase_no_period(self) -> None: hn = HumanName("King John Alexander V") self.m(hn.title, "King", hn)