From c72953cbd0fde124599f3f62ce80d094309d1482 Mon Sep 17 00:00:00 2001 From: Derek Gulbranson Date: Sun, 5 Jul 2026 15:30:28 -0700 Subject: [PATCH] Fix IndexError in initials for unnormalized *_list elements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _process_initial split name parts with split(" "), which yields empty strings for repeated spaces, and part[0] on an empty string raised IndexError. The parse path never produces such parts because parse_pieces normalizes whitespace, but first_list/middle_list/ last_list are plain attributes, so direct assignment bypasses that normalization (e.g. name.middle_list = ['Q R']). Use bare split(), which treats any whitespace run as one delimiter and never yields empty strings — identical to split(" ") for normalized input, and also tolerant of tabs, newlines, and leading/trailing whitespace in directly-assigned elements. Closes #232 Co-Authored-By: Claude Fable 5 --- docs/release_log.rst | 1 + nameparser/parser.py | 5 ++++- tests/test_initials.py | 9 +++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/release_log.rst b/docs/release_log.rst index b975e85..e405fa6 100644 --- a/docs/release_log.rst +++ b/docs/release_log.rst @@ -1,6 +1,7 @@ Release Log =========== * 1.3.0 - Unreleased + - Fix ``IndexError`` in ``initials()``/``initials_list()`` when a ``*_list`` attribute was assigned directly with an element containing unnormalized whitespace (e.g. ``name.middle_list = ['Q R']``), bypassing the parser's whitespace normalization (closes #232) - Fix ``HumanName`` acting as its own iterator with a stored cursor: breaking out of a loop, iterating in nested loops, or calling ``len(name)`` mid-loop corrupted subsequent iteration; ``iter(name)`` now returns a fresh independent iterator each time. ``next(name)`` on the instance itself (undocumented) now raises ``TypeError`` — call ``next(iter(name))`` instead (closes #225) - Fix parsing writing back into the ``Constants`` it reads (usually the shared module-level ``CONSTANTS``): pieces derived while parsing a name — period-joined titles/suffixes like ``"Lt.Gov."`` and conjunction-joined pieces like ``"Mr. and Mrs."`` or ``"von und zu"`` — are now tracked per parse instead of being permanently ``add()``-ed to the config, so parse results no longer depend on which names were parsed earlier in the process and parsing no longer mutates shared state across threads - Remove the vestigial ``unparsable`` attribute: the guard that was meant to set it has been unreachable since 2013 (v0.2.9), so it has reported ``False`` for every parsed name for over a decade; check ``len(name) == 0`` to detect an empty parse diff --git a/nameparser/parser.py b/nameparser/parser.py index 1d1d28b..e6ee2e6 100644 --- a/nameparser/parser.py +++ b/nameparser/parser.py @@ -250,7 +250,10 @@ def _process_initial(self, name_part: str, firstname: bool = False) -> str: Name parts may include prefixes or conjunctions. This function filters these from the name unless it is a first name, since first names cannot be conjunctions or prefixes. """ - parts = name_part.split(" ") + # split() rather than split(" "): *_list attributes assigned directly + # bypass parse_pieces whitespace normalization, and split(" ") yields + # empty strings for repeated spaces (#232) + parts = name_part.split() initials = [] for part in parts: if not (self.is_prefix(part) or self.is_conjunction(part)) or firstname: diff --git a/tests/test_initials.py b/tests/test_initials.py index 6e1a767..eb6d671 100644 --- a/tests/test_initials.py +++ b/tests/test_initials.py @@ -134,6 +134,15 @@ def test_str_default_behavior_unchanged(self) -> None: hn = HumanName("John Doe") self.assertEqual(str(hn), "John Doe") + def test_initials_with_doubled_space_in_list_element(self) -> None: + # direct *_list assignment bypasses parse_pieces whitespace + # normalization, so initials must tolerate unnormalized elements + # instead of raising IndexError (#232) + hn = HumanName(first="John") + hn.middle_list = ["Q R"] + self.assertEqual(hn.initials_list(), ["J", "Q R"]) + self.assertEqual(hn.initials(), "J. Q R.") + def test_constructor_first(self) -> None: hn = HumanName(first="TheName") self.m(hn.first, "TheName", hn)