From df0620e3cbab32c524117039a4ee727687b44b31 Mon Sep 17 00:00:00 2001 From: Dani Bodor Date: Wed, 6 Dec 2023 17:48:14 +0100 Subject: [PATCH] create generic equivalence check --- eitprocessing/mixins/equality.py | 59 ++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/eitprocessing/mixins/equality.py b/eitprocessing/mixins/equality.py index 16fa9ace2..dee4ecc1e 100644 --- a/eitprocessing/mixins/equality.py +++ b/eitprocessing/mixins/equality.py @@ -1,8 +1,10 @@ +from __future__ import annotations from abc import ABC from abc import abstractmethod from dataclasses import astuple from dataclasses import is_dataclass import numpy as np +from typing_extensions import Self class Equivalence(ABC): @@ -28,3 +30,60 @@ def _array_safe_eq(a, b) -> bool: return object.__eq__(a, b) # `a == b` could trigger an infinite loop except TypeError: return NotImplemented + + @abstractmethod + def isequivalent( + self, + other: Self, + raise_: bool = False, + checks: dict[str, bool] = None, + ) -> bool: + """Test whether the data structure between two objects are equivalent. + + Equivalence in this case means that objects are equal in all respects, + except for data content. Generally data loaded from the same source with + identical preprocessing will be equivalent. + + Args: + other: object that will be compared to self. + raise_: + if False (default): return `False` if not equivalent; + if `True`: raise `EquivalenceError` if not equivalence. + checks: Dictionary of bools that will be checked. This dictionary can be + defined in each child class individually. + Defaults to None, meaning that only `type`s are compared. + + Raises: + EquivalenceError: if `raise_ == True` and the objects are not equivalent. + + Returns: + bool describing result of equivalence comparison. + """ + # TODO: find out what correct way is to send extra argument to parent class without pissing off the linter + if not checks: + checks = {} + + if self == other: + return True + try: + if type(self) is not type(other): + raise EquivalenceError( + f"Classes don't match: {type(self)}, {type(other)}" + ) + for msg, check in checks.items(): + if not check: + raise EquivalenceError(msg) + except EquivalenceError as e: + if raise_: + raise e + return False + return True + + +class EquivalenceError(TypeError, ValueError): + """Raised if objects are not equivalent. + + Equivalence in this case means that objects are equal in all respects, + except for data content. Generally data loaded from the same source with + identical preprocessing will be equivalent. + """