1

I'm creating a library of matchers that are used by comparing against values with ==. Unfortunately, when comparing a compound type containing a matcher instance against a value with a non-Any type, like so:

assert {"foo": SomeMatcher(arg)} == {"foo": 3}

then mypy with the --strict-equality flag complains about a "Non-overlapping equality check". For now, I'm working around this by calling the constructors via functions whose return types are annotated as Any, but I'm wondering if it's possible to make mypy treat my matcher instances as always Any automatically. I know something like this is possible, as mypy allows unittest.mock.Mock instances to be passed in place of non-Mock arguments, as though the Mock instances all had type Any — or is that something hardcoded into mypy?

I've already tried getting this to work by giving my matchers a trivial __new__ that's annotated with return type Any, as well as using a metaclass whose __new__ had return type Any, but neither worked.

An MVCE of a failing matcher:

from typing import Any

class InRange:
    def __init__(self, start: int, end: int) -> None:
        self.start = start
        self.end = end

    def __eq__(self, other: Any) -> bool:
        try:
            return bool(self.start <= other <= self.end)
        except (TypeError, ValueError):
            return False


assert {"foo": InRange(1, 5)} == {"foo": 3}
assert {"foo": 3} == {"foo": InRange(1, 5)}
jwodder
  • 54,758
  • 12
  • 108
  • 124
  • My first thought here would be about why you are using `--strict-equality`. Is it because you want strict equality toggled for other comparisons, but then want to make the matcher comparisons an exception? – Mario Ishac Jun 24 '21 at 21:56
  • @MarioIshac: Yes, and I also want my library to not raise errors for other users who use `--strict-equality`. – jwodder Jun 24 '21 at 22:12

1 Answers1

1

Mypy does have some hardcoded exceptions for certain cases, for example set and frozenset. Although not for unittest.mock.Mock, there it's solved by subclassing Any.

Subclassing Any works for type stubs, but at runtime python throws an error: TypeError: Cannot subclass typing.Any. The following solves that problem:

from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    Base = Any
else:
    Base = object

class InRange(Base):
    def __init__(self, start: int, end: int) -> None:
        self.start = start
        self.end = end

    def __eq__(self, other: Any) -> bool:
        try:
            return bool(self.start <= other <= self.end)
        except (TypeError, ValueError):
            return False


assert {"foo": InRange(1, 5)} == {"foo": 3}
assert {"foo": 3} == {"foo": InRange(1, 5)}
mihi
  • 3,097
  • 16
  • 26