Skip to content

Fix pytest.approx mapping details for non-numeric values - #15014

Open
deepak7lal wants to merge 1 commit into
pytest-dev:mainfrom
deepak7lal:fix/approx-mapping-non-numeric
Open

Fix pytest.approx mapping details for non-numeric values#15014
deepak7lal wants to merge 1 commit into
pytest-dev:mainfrom
deepak7lal:fix/approx-mapping-non-numeric

Conversation

@deepak7lal

Copy link
Copy Markdown

Closes #15009.

Problem

ApproxMapping._repr_compare guards its diff arithmetic with:

except ZeroDivisionError:
    pass

while ApproxSequenceLike._repr_compare guards the equivalent block with except TypeError, added in #13012 for #13010.

Subtracting two strings raises TypeError, so on the mapping path it escapes into the assertion-repr hook and the mismatch table is replaced by a "representation of details failed" message. The same comparison one type over still prints its table:

assert {"item": "a"} == pytest.approx({"item": "b"})
# (pytest_assertion plugin: representation of details failed: ...
#  TypeError: unsupported operand type(s) for -: 'str' and 'str'.)

assert [1.1, "a"] == pytest.approx([1.0, "b"])
# comparison failed. Mismatched elements: 2 / 2:
# Index | Obtained | Expected
# 1     | a        | b

pytest.approx documents support for non-numeric mapping values, and test_dict_nonnumeric already covers them for equality - only the failure-detail formatter is affected.

Change

Widen the guard to except (ZeroDivisionError, TypeError), so non-numeric values are skipped for the max-absolute and max-relative calculations but still reported as mismatches. This mirrors what #13012 did for sequences.

Values that compare equal, and mappings that are wholly numeric, are unaffected.

Tests

Two tests in testing/python/approx.py, next to the existing dict cases:

  • test_mixed_dict - a mapping mixing numbers and strings, mirroring test_mixed_sequence
  • test_dict_of_strings - the all-non-numeric mapping from the issue, where Max absolute/relative difference remain -inf

Both fail on main with the TypeError above. testing/python/approx.py passes in full (151 tests).

Out of scope: #15010, which concerns the Decimal tolerance repr rather than the formatter raising.

@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Sep 13, 2026
@torjan0

torjan0 commented Sep 13, 2026

Copy link
Copy Markdown

This branch is based on ff42276a, 11 commits behind main. src/_pytest/approx.py changed +168/-42 in that range (#15006), including the sequence guard this PR is modelled on, so except (ZeroDivisionError, TypeError) is no longer safe here.

decimal.FloatOperation subclasses TypeError. On main the sequence path re-raises it before catching TypeError; catching it in the mapping path masks it instead, and _max_diff compares a Decimal against a float whenever a mapping mixes types.

with decimal.localcontext() as ctx:
    ctx.traps[decimal.FloatOperation] = True
    assert {"a": Decimal(8), "b": 1.0} == approx({"a": Decimal(9), "b": 9.0})

This reports Max absolute difference: 1, where the true maximum is 8.0. It is insertion-order dependent, so swapping the two keys prints the correct number by coincidence. Ints reach the same path through the relative division.

testing/python/approx.py passes either way, 179 on main both with and without this patch, so nothing in the suite catches the wrong maximum.

Rebasing and mirroring the sequence guard fixes #15009 and keeps that path loud:

except FloatOperation:
    raise
except (ZeroDivisionError, TypeError):
    pass

ApproxMapping._repr_compare guards its diff arithmetic with
`except ZeroDivisionError`, so an unequal pair of non-numeric values
under the same key raises TypeError into the assertion-repr hook and the
mismatch table is replaced by "representation of details failed". The
sequence path already handles this.

Catching TypeError alone is not enough here. decimal.FloatOperation
subclasses it, and pytest-dev#15006 made the sequence path re-raise that ahead of
the non-number handler so a mapping mixing Decimals with floats cannot
report the smaller of two differences as the maximum. Mirror both
clauses rather than only the second.

Adds the mapping counterpart of
test_mixed_decimal_and_float_sequence_does_not_hide_float_operation,
which fails without the re-raise, so the wrong maximum is now caught by
the suite.

Closes pytest-dev#15009

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@deepak7lal
deepak7lal force-pushed the fix/approx-mapping-non-numeric branch from 26036af to 01281a1 Compare September 14, 2026 07:06
@deepak7lal

Copy link
Copy Markdown
Author

You are right on every point, and thank you for the detail. Rebased onto main and pushed.

I reproduced it before changing anything. Applying the original except (ZeroDivisionError, TypeError) to main's mapping path gives exactly what you described:

Max absolute difference: 1
Index | Obtained | Expected
a     | 8        | 9 ± 9.0e-6
b     | 1.0      | 9.0 ± 9.0e-06

Reporting 1 while the table shows a difference of 8.0 is worse than the missing detail table this PR set out to fix, so the narrow guard was the wrong thing to ship.

The mapping path now mirrors the sequence path rather than copying only its second clause:

except FloatOperation:
    raise
except (ZeroDivisionError, TypeError):
    pass

On your other observation, that the suite passes either way: I added the mapping counterpart of test_mixed_decimal_and_float_sequence_does_not_hide_float_operation. It asserts Max absolute difference: 8 and then that the trap still raises, and it fails without the re-raise, so the wrong maximum is caught now rather than only the missing table.

testing/python/approx.py is at 182 passed, 179 on main plus the three added here.

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

Labels

bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pytest.approx loses mismatch details for same-key mappings with unequal strings

2 participants