pytest-dev/pytest · error · TypeError

relative tolerance for a scalar value must be an int, float…

Error message

relative tolerance for a scalar value must be an int, float or Decimal, got {type(rel).__name__}

What it means

ApproxScalar.__init__ validates that when a relative tolerance (rel) is supplied, it must be an int, float, or Decimal. Any other type (string, timedelta, None-as-sentinel misuse, custom object) is rejected with the offending type name. This is enforced because relative tolerance is multiplied by abs(expected); non-numeric types would produce a TypeError later in a confusing place, so pytest fails fast.

Solutions

  1. Pass rel as a plain float: `approx(1.0, rel=0.01)`.
  2. If the tolerance comes from config/env, cast explicitly: `rel=float(cfg['rel'])`.
  3. For Decimal comparisons, pass a Decimal rel: `approx(Decimal('1'), rel=Decimal('0.01'))`.

Example fix

# before
approx(1.0, rel='0.01')
# after
approx(1.0, rel=0.01)
Defensive patterns

Strategy: validation

Validate before calling

from decimal import Decimal
def is_valid_rel(rel) -> bool:
    return rel is None or isinstance(rel, (int, float, Decimal))
# usage
rel = float(config['rel']) if isinstance(config['rel'], str) else config['rel']
assert is_valid_rel(rel)
assert actual == approx(expected, rel=rel)

Type guard

from decimal import Decimal
def is_scalar_rel(v) -> bool:
    return v is None or isinstance(v, (int, float, Decimal))

Prevention

When it happens

Trigger: Passing rel='0.1', rel=timedelta(seconds=1) to a scalar approx, rel=np.float64(...) (this one is accepted), or rel=[0.1]. Triggered at ApproxScalar.__init__ (src/_pytest/approx.py:448-453). Note: bool is a subclass of int, so rel=True/False passes but yields nonsensical results.

Common situations: Copy-pasting tolerance syntax from datetime examples into scalar comparisons; reading tolerances from config files as strings; mixing up rel and abs argument order.

Related errors


AI-assisted analysis of pytest-dev/pytest@0d6fbdeffa (2026-08-11). Data as JSON: /api/errors/7daa572e18071a41. Report an issue: GitHub.

Appendix: source

Thrown at src/_pytest/approx.py:450

    # Using Real should be better than this Union, but not possible yet:
    # https://github.com/python/typeshed/pull/3108
    DEFAULT_ABSOLUTE_TOLERANCE: float | Decimal = 1e-12
    DEFAULT_RELATIVE_TOLERANCE: float | Decimal = 1e-6
    rel: float | Decimal | None
    abs: float | Decimal | None

    def __init__(
        self,
        expected: ExpectedT,
        rel: float | Decimal | timedelta | None,
        abs: float | Decimal | timedelta | None,
        nan_ok: bool,
    ) -> None:
        __tracebackhide__ = True
        if rel is not None:
            if not isinstance(rel, (int, float, Decimal)):
                raise TypeError(
                    f"relative tolerance for a scalar value must be an int, float or Decimal, "
                    f"got {type(rel).__name__}"
                )
            if not isinstance(expected, SupportsAbs):
                raise TypeError(
                    f"expected value must support abs(...) when relative tolerance is used, "
                    f"got {type(expected).__name__}"
                )
        if abs is not None and not isinstance(abs, (int, float, Decimal)):
            raise TypeError(
                f"absolute tolerance for a scalar value must be an int, float or Decimal, "
                f"got {type(abs).__name__}"
            )
        super().__init__(expected, rel=rel, abs=abs, nan_ok=nan_ok)

    def __repr__(self) -> str:
        """Return a string communicating both the expected value and the
        tolerance for the comparison being made.

View on GitHub (pinned to 0d6fbdeffa)