Skip to content

fix(strutils): stop singularize() mangling words ending in 'ss'#418

Open
Sanjays2402 wants to merge 1 commit into
mahmoud:masterfrom
Sanjays2402:fix/singularize-double-s
Open

fix(strutils): stop singularize() mangling words ending in 'ss'#418
Sanjays2402 wants to merge 1 commit into
mahmoud:masterfrom
Sanjays2402:fix/singularize-double-s

Conversation

@Sanjays2402

Copy link
Copy Markdown

Summary

strutils.singularize() corrupts words that are already singular and end in a double s (glass, boss, class, kiss, address, business, ...), returning glas, bos, clas, kis, addres, busines.

>>> from boltons.strutils import singularize
>>> singularize('glass')
'glas'      # expected 'glass'
>>> singularize('boss')
'bos'       # expected 'boss'
>>> singularize('address')
'addres'    # expected 'address'

Root cause

singularize() ends with a catch-all that strips the final character from any word ending in s that didn't match the -ies / -ses branches:

elif word.endswith('es') and word[-3] == 's':
    singular = word[:-2]
else:
    singular = word[:-1]      # <-- 'glass' -> 'glas'

No English plural ends in ss. The plural of these words ends in sses (glass -> glasses), which is already handled correctly by the preceding word.endswith('es') and word[-3] == 's' branch. Only words that are already singular and end in ss fall through to the blind word[:-1].

This also violated the idempotency implied by the function's own docstring (singularize('Glasses') == 'Glass'): feeding that canonical singular back in produced 'Glas', and applying singularize() defensively to an already-singular word degraded it further ('boss' -> 'bos' -> 'bo').

Fix

Add an elif word.endswith('ss') guard before the final fallback that returns the word unchanged (+7 lines). Because the -sses branch runs first, real plurals are unaffected.

Before / after

input before after
glass glas glass
boss bos boss
class clas class
kiss kis kiss
address addres address
business busines business
glasses (plural) glass glass (unchanged)
bosses (plural) boss boss (unchanged)
chances, cats, activities correct correct (unchanged)

The single-s ambiguous cases (e.g. bus) are intentionally left alone — they are indistinguishable from a regular plural (cats -> cat) without a dictionary, so this PR does not touch them.

Tests

Added test_singularize_double_s covering the double-s singular words, case preservation (Glass, BOSS), the still-correct -sses plurals, and idempotency.

Proven to guard the bug (regression test fails on unpatched source, passes with the fix):

# source fix stashed, test kept:
>   assert singularize('glass') == 'glass'
E   AssertionError: assert 'glas' == 'glass'
1 failed

# fix restored:
1 passed

Full suite: 438 passed (was 437). All 28 strutils docstring doctests pass. No linter is configured in CI; the change is pure stdlib control flow valid on every supported Python (3.7-3.14).

singularize() blindly stripped the trailing 's' from any word ending in
's' that did not match the earlier '-ies'/'-ses' branches, so words that
are already singular and end in a double 's' were corrupted:

    singularize('glass')  -> 'glas'
    singularize('boss')   -> 'bos'
    singularize('class')  -> 'clas'
    singularize('kiss')   -> 'kis'
    singularize('address')-> 'addres'

No English plural ends in 'ss' -- the plural of these words ends in
'sses' (glass -> glasses), which is already handled by the preceding
`word.endswith('es') and word[-3] == 's'` branch. This also broke the
idempotency implied by the function's own docstring, which states
singularize('Glasses') == 'Glass': feeding that canonical singular back
in produced 'Glas', and applying singularize() defensively to an
already-singular word degraded it further ('boss' -> 'bos' -> 'bo').

Fix: add an `elif word.endswith('ss')` guard before the final
`word[:-1]` fallback that returns the word unchanged. Real '-sses'
plurals are unaffected because that branch runs first, so
singularize('glasses') still returns 'glass'. The single-'s' ambiguous
cases (e.g. 'bus') are intentionally left alone; they are
indistinguishable from a regular plural without a dictionary.

Adds test_singularize_double_s covering the double-'s' singular words,
case preservation, the still-correct '-sses' plurals, and idempotency.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant