From a2ce619143f3b80ab503800498df828b58db8ffc Mon Sep 17 00:00:00 2001 From: Pralav Singh <174412353+pralav-25@users.noreply.github.com> Date: Sat, 10 Oct 2026 15:57:20 +0530 Subject: [PATCH] Fix short acronym boundaries in camel_to_underscore --- _python_utils_tests/test_formatters.py | 18 ++++++++++++++++++ python_utils/formatters.py | 11 ++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/_python_utils_tests/test_formatters.py b/_python_utils_tests/test_formatters.py index e683c88..d29fade 100644 --- a/_python_utils_tests/test_formatters.py +++ b/_python_utils_tests/test_formatters.py @@ -73,6 +73,24 @@ def test_camel_to_underscore_keeps_acronyms_whole( assert formatters.camel_to_underscore(name) == expected +@pytest.mark.parametrize( + ('name', 'expected'), + [ + ('ABc', 'a_bc'), + ('ABCd', 'ab_cd'), + ('IPad', 'i_pad'), + ('AName', 'a_name'), + ('__ABc__', '__a_bc__'), + ('XMLParser', 'xml_parser'), + ], +) +def test_camel_to_underscore_splits_short_acronym_boundary( + name: str, expected: str +) -> None: + """Keep the final capital with the word following even a short acronym.""" + assert formatters.camel_to_underscore(name) == expected + + def test_timesince_date_raises_type_error() -> None: """Keep raising ``TypeError`` for a date, which has no time.""" today: typing.Any = datetime.date.today() diff --git a/python_utils/formatters.py b/python_utils/formatters.py index 71082c4..3589093 100644 --- a/python_utils/formatters.py +++ b/python_utils/formatters.py @@ -42,11 +42,12 @@ def camel_to_underscore(name: str) -> str: # Uppercase and the previous character isn't upper/underscore? # Add the underscore output.append('_') - elif i > 3 and c.islower(): - # Will return the last 3 letters to check if we are changing - # case. Only a lowercase letter ends an acronym, an underscore - # or a digit after it leaves the acronym whole. - previous = name[i - 3 : i] + elif i > 1 and c.islower(): + # Two preceding capitals are enough to mark the boundary: + # the final capital belongs to the following word. Only a + # lowercase letter ends an acronym; an underscore or digit + # after it leaves the acronym whole. + previous = name[i - 2 : i] if previous.isalpha() and previous.isupper(): output.insert(len(output) - 1, '_')