mmachenry
mmachenry

Reputation: 1962

Why is Pytest trying to subtract a float from a dict?

I am attempting to write a nested pytest approx class to compare complex structures while allowing floats to be compared with approximation.

import pytest
from _pytest.python_api import ApproxMapping
from collections.abc import Mapping

def nested_approx(expected, rel=None, abs=None, nan_ok=False):
    if isinstance(expected, Mapping):
        return ApproxNestedMapping(expected, rel, abs, nan_ok)
    else:
        return pytest.approx(expected, rel=rel, abs=abs, nan_ok=nan_ok)

class ApproxNestedMapping(ApproxMapping):
    def _yield_comparisons(self, actual):
        if not isinstance(actual, Mapping):
            raise TypeError(f"Expected a mapping but got {type(actual)}")

        # Ensure all keys match exactly
        expected_keys = set(self.expected.keys())
        actual_keys = set(actual.keys())

        if expected_keys != actual_keys:
            missing = expected_keys - actual_keys
            extra = actual_keys - expected_keys
            raise AssertionError(f"Key mismatch:\n  Missing: {missing}\n  Extra: {extra}")

        # Compare values, ensuring key order doesn't affect comparison
        for k in sorted(expected_keys):  # Sorting ensures stable comparisons
            yield actual[k], nested_approx(
                self.expected[k],
                rel=self.rel,
                abs=self.abs,
                nan_ok=self.nan_ok
            )

    def _check_type(self):
        if not isinstance(self.expected, Mapping):
            raise TypeError(f"Expected a mapping but got {type(self.expected)}")


def test_redux_this_fails():
    actual = {
        'trail_angle': 10.593548226756798,
        'joint': { },
    }

    expected = {
        "joint": { },
        "trail_angle": 10.463173570164972,
    }
    assert actual == nested_approx(expected)

The result of this pytest is this:

_______________________________ test_redux_this_fails ________________________________

    def test_redux_this_fails():
        actual = {
            'trail_angle': 10.593548226756798,
            'joint': { },
        }
    
        expected = {
            "joint": { },
            "trail_angle": 10.463173570164972,
        }
>       assert actual == nested_approx(expected)
E       AssertionError: assert {'trail_angle': 10.593548226756798, 'joint': {}} == approx({'joint': {}, 'trail_angle': 10.463173570164972 ± 1.0e-05})
E         
E         (pytest_assertion plugin: representation of details failed: /home/mmachenry/src/keystone/ScheduleServer/services/rollingmodel/.tox/py39/lib/python3.9/site-packages/_pytest/python_api.py:267: TypeError: unsupported operand type(s) for -: 'dict' and 'float'.
E          Probably an object has a faulty __repr__.)

This unit test works just fine when the floating point numbers are the same number

def test_redux_this_passes():
    actual = { 
        'trail_angle': 10.593548226756798,
        'joint': { },
    }   

    expected = { 
        "joint": { },
        'trail_angle': 10.593548226756798,
    }   
    assert actual == nested_approx(expected)

And it fails with an appropriate message about how trial_angle is not a close enough number when the dict is in order:

def test_redux_this_fails_with_a_reasonable_error():
    actual = { 
        'joint': { },
        "trail_angle": 10.463173570164972,
    }   

    expected = { 
        "joint": { },
        'trail_angle': 10.593548226756798,
    }   
    assert actual == nested_approx(expected)

But when the dict is in order and the numbers are failing, I get the really undesirable effect of comparing a float to a dict and I'm not sure why because I have done a pair-wise comparison over each of the keys.

Upvotes: 0

Views: 25

Answers (0)

Related Questions