Skip to content

fix: raise NumberFormatError for invalid string in NumberPattern.apply - #1411

Open
2025sl93041-gkb wants to merge 2 commits into
python-babel:masterfrom
2025sl93041-gkb:fix/numberformaterror-invalid-decimal
Open

2025sl93041-gkb wants to merge 2 commits into
python-babel:masterfrom
2025sl93041-gkb:fix/numberformaterror-invalid-decimal

Conversation

@2025sl93041-gkb

Copy link
Copy Markdown

Summary

Closes #702.

When any formatting function (format_decimal, format_currency,
format_percent, format_compact_decimal, etc.) received an invalid
string value (e.g. "not_a_number"), the internal
decimal.Decimal(str(value)) call would raise a raw
decimal.InvalidOperation — a third-party implementation detail from the
decimal standard library module — rather than Babel's own
NumberFormatError.

This is a leaky abstraction: callers who want to catch bad-value errors
need to import and handle decimal.InvalidOperation in addition to (or
instead of) NumberFormatError.

Root cause

NumberPattern.apply() (and the internal _get_compact_format()
helper) both do:

if not isinstance(value, decimal.Decimal):
    value = decimal.Decimal(str(value))

decimal.Decimal raises decimal.InvalidOperation for strings it
cannot parse. No surrounding try/except was present.

Fix

Wrap the conversion in a try/except block and re-raise as
NumberFormatError:

if not isinstance(value, decimal.Decimal):
    try:
        value = decimal.Decimal(str(value))
    except decimal.InvalidOperation as e:
        raise NumberFormatError(f"{value!r} is not a valid number") from e

Both NumberPattern.apply() and _get_compact_format() are fixed.

Test

Added test_number_pattern_apply_invalid_string_raises_number_format_error
in tests/test_numbers.py. The test calls NumberPattern.apply()
directly (via parse_pattern('0.###')) with a mock locale so it works
without requiring the full CLDR data build.

Wrap `decimal.Decimal(str(value))` in `NumberPattern.apply()` and
`_get_compact_format()` in a try/except to catch
`decimal.InvalidOperation` and re-raise as `NumberFormatError`.

Previously, passing an invalid string like `"not_a_number"` to any
formatting function (format_decimal, format_currency, format_percent,
format_compact_decimal) would propagate a raw
`decimal.InvalidOperation` exception. The public API contract
documents `NumberFormatError` as the error type for bad input; all
other invalid-value paths already raise it.

Closes python-babel#702
@akx akx added this to the Babel 3 milestone Nov 4, 2026
@2025sl93041-gkb

Copy link
Copy Markdown
Author

Hi, following up on this PR. Let me know if there are any concerns with the fix or the test approach and I'll address them.

@chrikrah chrikrah left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@2025sl93041-gkb except ArithmeticError stops catching this, which fits the Babel 3 milestone.

non-blocking: CHANGES.rst only changes in release-prep commits. Can the hunk go?

# git archive b9fe3a4 (head) and 9a3ef47 (base), Python 3.13, locale-data copied from the distro Babel 2.x install
$ python -c "from babel import numbers
try: numbers.format_decimal('abc', locale='en_US')
except ArithmeticError as e: print('caught', type(e).__name__)
except Exception as e: print('not caught', type(e).__name__)"
not caught NumberFormatError   # base 9a3ef47: caught InvalidOperation
$ python -m pytest -q tests --ignore=tests/messages/test_setuptools_frontend.py
6 failed, 6711 passed, 1200 skipped, 1 xfailed   # base 9a3ef47: 6 failed (same six, CLDR version), 6710 passed

Maintainers update CHANGES.rst only in release-prep commits.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inconsistent exceptions

3 participants