Skip to content

Equality

coola.equality

Contain code to check if two objects are equal or not.

coola.equality.objects_are_allclose

objects_are_allclose(
    actual: object,
    expected: object,
    *,
    rtol: float = 1e-05,
    atol: float = 1e-08,
    equal_nan: bool = False,
    show_difference: bool = False,
    max_depth: int = 1000,
    registry: EqualityTesterRegistry | None = None
) -> bool

Indicate if two objects are equal within a tolerance.

Parameters:

Name Type Description Default
actual object

The actual input.

required
expected object

The expected input.

required
rtol float

The relative tolerance parameter. Must be non-negative.

1e-05
atol float

The absolute tolerance parameter. Must be non-negative.

1e-08
equal_nan bool

If True, then two NaNs will be considered as equal.

False
show_difference bool

If True, it shows a difference between the two objects if they are different. This parameter is useful to find the difference between two objects.

False
max_depth int

Maximum recursion depth for nested comparisons. Must be positive. Defaults to 1000.

1000
registry EqualityTesterRegistry | None

The registry with the equality tester to use.

None

Returns:

Type Description
bool

True if the two objects are (element-wise) equal within a tolerance, otherwise False

Raises:

Type Description
ValueError

if rtol or atol is negative.

RecursionError

if recursion depth exceeds max_depth.

Example
>>> import torch
>>> from coola.equality import objects_are_allclose
>>> objects_are_allclose(
...     [torch.ones(2, 3), torch.zeros(2)],
...     [torch.ones(2, 3), torch.zeros(2)],
... )
True
>>> objects_are_allclose(
...     [torch.ones(2, 3), torch.ones(2)],
...     [torch.ones(2, 3), torch.zeros(2)],
... )
False
>>> objects_are_allclose(
...     [torch.ones(2, 3) + 1e-7, torch.ones(2)],
...     [torch.ones(2, 3), torch.ones(2) - 1e-7],
...     rtol=0,
...     atol=1e-8,
... )
False

coola.equality.objects_are_equal

objects_are_equal(
    actual: object,
    expected: object,
    *,
    equal_nan: bool = False,
    show_difference: bool = False,
    max_depth: int = 1000,
    registry: EqualityTesterRegistry | None = None
) -> bool

Indicate if two objects are equal or not.

Parameters:

Name Type Description Default
actual object

The actual input.

required
expected object

The expected input.

required
equal_nan bool

If True, then two NaNs will be considered as equal.

False
show_difference bool

If True, it shows a difference between the two objects if they are different. This parameter is useful to find the difference between two objects.

False
max_depth int

Maximum recursion depth for nested comparisons. Must be positive. Defaults to 1000.

1000
registry EqualityTesterRegistry | None

The registry with the equality tester to use.

None

Returns:

Type Description
bool

True if the two nested data are equal, otherwise False.

Raises:

Type Description
RecursionError

if recursion depth exceeds max_depth.

Example
>>> import torch
>>> from coola.equality import objects_are_equal
>>> objects_are_equal(
...     [torch.ones(2, 3), torch.zeros(2)],
...     [torch.ones(2, 3), torch.zeros(2)],
... )
True
>>> objects_are_equal([torch.ones(2, 3), torch.ones(2)], [torch.ones(2, 3), torch.zeros(2)])
False

coola.equality.config

Define the equality configuration.

coola.equality.config.EqualityConfig dataclass

Define the config to control the comparison rules.

Note

This class is not thread-safe. Each comparison should create its own config instance. Do not share config instances between threads as the internal depth counter is not protected by locks.

Parameters:

Name Type Description Default
registry EqualityTesterRegistry

The registry with the equality tester to use.

create_default_registry()
equal_nan bool

If True, NaN values will be considered equal. Defaults to False.

False
atol float

The absolute tolerance parameter for floating-point comparisons. Must be non-negative. Defaults to 0.0.

0.0
rtol float

The relative tolerance parameter for floating-point comparisons. Must be non-negative. Defaults to 0.0.

0.0
show_difference bool

If True, shows differences between non-equal objects. Defaults to False.

False
max_depth int

Maximum recursion depth for nested object comparisons. Must be positive. Defaults to 1000. Set to a lower value to protect against stack overflow with extremely deeply nested structures.

1000

Raises:

Type Description
ValueError

if atol or rtol is negative, or if max_depth is not positive.

Example
>>> from coola.equality.config import EqualityConfig
>>> config = EqualityConfig()
>>> config
EqualityConfig(registry=EqualityTesterRegistry(...), equal_nan=False, atol=0.0, rtol=0.0, show_difference=False, max_depth=1000)

coola.equality.config.EqualityConfig.depth property

depth: int

Get the current depth counter.

coola.equality.config.EqualityConfig.__post_init__

__post_init__() -> None

Validate configuration parameters after initialization.

coola.equality.config.EqualityConfig.decrement_depth

decrement_depth() -> None

Increment the current depth counter.

coola.equality.config.EqualityConfig.increment_depth

increment_depth() -> None

Increment the current depth counter.

coola.equality.handler

Contain handlers to help check if two objects are equal or not.

The handlers are designed to work with the Chain of Responsibility pattern.

coola.equality.handler.BaseEqualityHandler

Bases: ABC

Define the base class to implement an equality handler.

A child class needs to implement the following methods:

  • equal
  • handle

A terminal handler has its next handler set to None.

Parameters:

Name Type Description Default
next_handler BaseEqualityHandler | None

The next handler.

None
Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import SameObjectHandler, FalseHandler
>>> config = EqualityConfig()
>>> handler = SameObjectHandler()
>>> handler.set_next_handler(FalseHandler())
>>> handler.handle("abc", "abc", config)
True
>>> handler.handle("abc", "ABC", config)
False

coola.equality.handler.BaseEqualityHandler.next_handler property

next_handler: BaseEqualityHandler | None

The next handler.

coola.equality.handler.BaseEqualityHandler.chain

chain(handler: T) -> T

Chain a handler to the current handler.

Parameters:

Name Type Description Default
handler T

The handler to chain.

required

Returns:

Type Description
T

The input handler.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import (
...     SameObjectHandler,
...     SameTypeHandler,
...     ObjectEqualHandler,
... )
>>> config = EqualityConfig()
>>> handler = SameObjectHandler()
>>> handler.chain(SameTypeHandler()).chain(ObjectEqualHandler())
>>> handler.handle([1, 2, 3], [1, 2, 3], config)
True

coola.equality.handler.BaseEqualityHandler.chain_all

chain_all(
    *handlers: BaseEqualityHandler,
) -> BaseEqualityHandler

Chain multiple handlers in sequence.

Parameters:

Name Type Description Default
*handlers BaseEqualityHandler

Variable number of handlers to chain.

()

Returns:

Type Description
BaseEqualityHandler

The last handler in the chain.

Example
>>> from coola.equality.handler import (
...     SameObjectHandler,
...     SameTypeHandler,
...     SameLengthHandler,
...     ObjectEqualHandler,
... )
>>> handler = SameObjectHandler()
>>> handler.chain_all(SameTypeHandler(), SameLengthHandler(), ObjectEqualHandler())

coola.equality.handler.BaseEqualityHandler.equal abstractmethod

equal(other: object) -> bool

Indicate if two objects are equal or not.

Parameters:

Name Type Description Default
other object

The other object.

required

Returns:

Type Description
bool

True if the two objects are equal, otherwise False.

Example
>>> from coola.equality.handler import SameObjectHandler, TrueHandler
>>> handler1 = SameObjectHandler()
>>> handler2 = SameObjectHandler()
>>> handler3 = TrueHandler()
>>> handler1.equal(handler2)
True
>>> handler1.equal(handler3)
False

coola.equality.handler.BaseEqualityHandler.get_chain_length

get_chain_length() -> int

Get the total number of handlers in the chain.

Returns:

Type Description
int

The total number of handlers in the chain.

Example
>>> from coola.equality.handler import (
...     SameObjectHandler,
...     SameTypeHandler,
...     SameLengthHandler,
...     ObjectEqualHandler,
... )
>>> handler = SameObjectHandler()
>>> handler.chain_all(SameTypeHandler(), SameLengthHandler(), ObjectEqualHandler())
>>> handler.get_chain_length()
4

coola.equality.handler.BaseEqualityHandler.handle abstractmethod

handle(
    actual: object, expected: object, config: EqualityConfig
) -> bool

Return the equality result between the two input objects.

Parameters:

Name Type Description Default
actual object

The actual input.

required
expected object

The expected input.

required
config EqualityConfig

The equality configuration.

required

Returns:

Type Description
bool

True if the input objects are equal, and False otherwise.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import SameObjectHandler
>>> config = EqualityConfig()
>>> handler = SameObjectHandler()
>>> handler.handle("abc", "abc", config)
True

coola.equality.handler.BaseEqualityHandler.set_next_handler

set_next_handler(
    handler: BaseEqualityHandler | None,
) -> None

Set the next handler.

Parameters:

Name Type Description Default
handler BaseEqualityHandler | None

The next handler. None means it is a terminal handler and there is no next handler.

required
Example
>>> from coola.equality.handler import SameObjectHandler, TrueHandler
>>> handler = SameObjectHandler()
>>> handler.set_next_handler(TrueHandler())

coola.equality.handler.BaseEqualityHandler.validate_chain

validate_chain() -> None

Validate the current handler chain.

Raises:

Type Description
RuntimeError

If the current handler chain is not valid.

Example
>>> from coola.equality.handler import (
...     SameObjectHandler,
...     SameTypeHandler,
...     SameLengthHandler,
...     ObjectEqualHandler,
... )
>>> handler = SameObjectHandler()
>>> handler.chain_all(SameTypeHandler(), SameLengthHandler(), ObjectEqualHandler())
>>> handler.validate_chain()

coola.equality.handler.BaseEqualityHandler.visualize_chain

visualize_chain() -> str

Visualize the current handler chain.

Returns:

Type Description
str

A string containing the visualization of the current handler chain.

Example
>>> from coola.equality.handler import (
...     SameObjectHandler,
...     SameTypeHandler,
...     SameLengthHandler,
...     ObjectEqualHandler,
... )
>>> handler = SameObjectHandler()
>>> handler.chain_all(SameTypeHandler(), SameLengthHandler(), ObjectEqualHandler())
>>> print(handler.visualize_chain())
(0): SameObjectHandler()
(1): SameTypeHandler()
(2): SameLengthHandler()
(3): ObjectEqualHandler()

coola.equality.handler.EqualHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two objects have the same data.

This handler returns False if the two objects are different data, otherwise it returns True. The first object must have a equal attribute which indicates if the two objects are equal or not.

Example
>>> import math
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import EqualHandler
>>> class MyFloat:
...     def __init__(self, value: float) -> None:
...         self._value = float(value)
...     def equal(self, other: object) -> bool:
...         if type(other) is not type(self):
...             return False
...         return self._value == other._value
...
>>> config = EqualityConfig()
>>> handler = EqualHandler()
>>> handler.handle(MyFloat(42), MyFloat(42), config)
True
>>> handler.handle(MyFloat(42), 42, config)
False
>>> handler.handle(MyFloat(42), float("nan"), config)
False

coola.equality.handler.EqualNanHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two objects have the same data.

This handler returns False if the two objects are different data, otherwise it returns True. The first object must have a equal attribute which indicates if the two objects are equal or not.

Example
>>> import math
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import EqualNanHandler
>>> class MyFloat:
...     def __init__(self, value: float) -> None:
...         self._value = float(value)
...     def equal(self, other: object, equal_nan: bool = False) -> bool:
...         if type(other) is not type(self):
...             return False
...         if equal_nan and math.isnan(self._value) and math.isnan(other._value):
...             return True
...         return self._value == other._value
...
>>> config = EqualityConfig()
>>> handler = EqualNanHandler()
>>> handler.handle(MyFloat(42), MyFloat(42), config)
True
>>> handler.handle(MyFloat(42), 42, config)
False
>>> handler.handle(MyFloat(float("nan")), MyFloat(float("nan")), config)
False
>>> config.equal_nan = True
>>> handler.handle(MyFloat(float("nan")), MyFloat(float("nan")), config)
True

coola.equality.handler.FalseHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Implement a handler that always returns False.

This handler is designed to be used at the end of the chain of responsibility. This handler does not call the next handler.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import FalseHandler
>>> config = EqualityConfig()
>>> handler = FalseHandler()
>>> handler.handle("abc", "abc", config)
False
>>> handler.handle("abc", "ABC", config)
False

coola.equality.handler.HandlerEqualityMixin

Mixin providing a standard implementation of the equal() method.

This mixin eliminates code duplication across handlers that only need to compare their type and next_handler. Handlers using this mixin must inherit from BaseEqualityHandler to ensure the next_handler attribute is available.

Design Note

This mixin must be used with classes that inherit from BaseEqualityHandler. The type annotation self: BaseEqualityHandler on the equal() method enforces this constraint and enables type-safe access to the next_handler attribute.

Example
>>> from coola.equality.handler import BaseEqualityHandler, HandlerEqualityMixin
>>> class MyHandler(HandlerEqualityMixin, BaseEqualityHandler):
...     def handle(self, actual, expected, config):
...         return True
...
>>> handler1 = MyHandler()
>>> handler2 = MyHandler()
>>> handler1.equal(handler2)
True

coola.equality.handler.HandlerEqualityMixin.equal

equal(other: object) -> bool

Indicate if two handlers are equal.

Two handlers are equal if they are of the same type and have equal next_handler chains.

Note

The type annotation self: BaseEqualityHandler ensures this mixin is only used with BaseEqualityHandler subclasses, enabling type-safe access to next_handler.

Parameters:

Name Type Description Default
other object

The other object to compare with.

required

Returns:

Type Description
bool

True if the handlers are equal, otherwise False.

coola.equality.handler.JaxArrayEqualHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two JAX arrays are equal.

This handler returns True if the two arrays are equal, otherwise False. This handler is designed to be used at the end of the chain of responsibility. This handler does not call the next handler.

Example
>>> import jax.numpy as jnp
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import JaxArrayEqualHandler
>>> config = EqualityConfig()
>>> handler = JaxArrayEqualHandler()
>>> handler.handle(jnp.ones((2, 3)), jnp.ones((2, 3)), config)
True
>>> handler.handle(jnp.ones((2, 3)), jnp.zeros((2, 3)), config)
False

coola.equality.handler.MappingSameKeysHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two objects have the same keys.

This handler returns False if the two objects have different keys, otherwise it passes the inputs to the next handler.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import MappingSameKeysHandler
>>> config = EqualityConfig()
>>> handler = MappingSameKeysHandler()
>>> handler.handle({"a": 1, "b": 2}, {"a": 1, "b": 2, "c": 1}, config)
False

coola.equality.handler.MappingSameValuesHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the key-value pairs in the first mapping are in the second mapping.

This handler returns False if the one of the key-value pair in the first mapping is not in the second mapping, otherwise it passes the inputs to the next handler.

Notes

This handler assumes that all the keys in the first mapping are also in the second mapping. The second mapping can have more keys. To check if two mappings are equal, you can combine this handler with MappingSameKeysHandler.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import MappingSameValuesHandler, TrueHandler
>>> config = EqualityConfig()
>>> handler = MappingSameValuesHandler(next_handler=TrueHandler())
>>> handler.handle({"a": 1, "b": 2}, {"a": 1, "b": 2}, config)
True
>>> handler.handle({"a": 1, "b": 2}, {"a": 1, "b": 3}, config)
False

coola.equality.handler.NanEqualHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two NaNs are equal.

This handler returns True if the two numbers are NaNs, otherwise it passes the inputs to the next handler.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import NanEqualHandler, FalseHandler
>>> config = EqualityConfig()
>>> handler = NanEqualHandler(next_handler=FalseHandler())
>>> handler.handle(float("nan"), float("nan"), config)
False
>>> config.equal_nan = True
>>> handler.handle(float("nan"), float("nan"), config)
True

coola.equality.handler.NumpyArrayEqualHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two NumPy arrays are equal.

This handler returns True if the two arrays are equal, otherwise False. This handler is designed to be used at the end of the chain of responsibility. This handler does not call the next handler.

Example
>>> import numpy as np
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import NumpyArrayEqualHandler
>>> config = EqualityConfig()
>>> handler = NumpyArrayEqualHandler()
>>> handler.handle(np.ones((2, 3)), np.ones((2, 3)), config)
True
>>> handler.handle(np.ones((2, 3)), np.zeros((2, 3)), config)
False

coola.equality.handler.ObjectEqualHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two objects are equal using the default equality operator ==.

This handler returns True if the two objects are equal, otherwise False. This handler is designed to be used at the end of the chain of responsibility. This handler does not call the next handler.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import ObjectEqualHandler
>>> config = EqualityConfig()
>>> handler = ObjectEqualHandler()
>>> handler.handle(1, 1, config)
True
>>> handler.handle(1, "abc", config)
False

coola.equality.handler.PandasDataFrameEqualHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two pandas.DataFrames are equal.

This handler returns True if the two pandas.DataFrames are equal, otherwise False. This handler is designed to be used at the end of the chain of responsibility. This handler does not call the next handler.

Example
>>> import pandas
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import PandasDataFrameEqualHandler
>>> config = EqualityConfig()
>>> handler = PandasDataFrameEqualHandler()
>>> handler.handle(
...     pandas.DataFrame({"col": [1, 2, 3]}),
...     pandas.DataFrame({"col": [1, 2, 3]}),
...     config,
... )
True
>>> handler.handle(
...     pandas.DataFrame({"col": [1, 2, 3]}),
...     pandas.DataFrame({"col": [1, 2, 4]}),
...     config,
... )
False

coola.equality.handler.PandasSeriesEqualHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two pandas.Serieses are equal.

This handler returns True if the two pandas.Serieses are equal, otherwise False. This handler is designed to be used at the end of the chain of responsibility. This handler does not call the next handler.

Example
>>> import pandas
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import PandasSeriesEqualHandler
>>> config = EqualityConfig()
>>> handler = PandasSeriesEqualHandler()
>>> handler.handle(pandas.Series([1, 2, 3]), pandas.Series([1, 2, 3]), config)
True
>>> handler.handle(pandas.Series([1, 2, 3]), pandas.Series([1, 2, 4]), config)
False

coola.equality.handler.PolarsDataFrameEqualHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two polars.DataFrames are equal.

This handler returns True if the two polars.DataFrames are equal, otherwise False. This handler is designed to be used at the end of the chain of responsibility. This handler does not call the next handler.

Example
>>> import polars as pl
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import PolarsDataFrameEqualHandler
>>> config = EqualityConfig()
>>> handler = PolarsDataFrameEqualHandler()
>>> handler.handle(
...     pl.DataFrame({"col": [1, 2, 3]}),
...     pl.DataFrame({"col": [1, 2, 3]}),
...     config,
... )
True
>>> handler.handle(
...     pl.DataFrame({"col": [1, 2, 3]}),
...     pl.DataFrame({"col": [1, 2, 4]}),
...     config,
... )
False

coola.equality.handler.PolarsLazyFrameEqualHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two polars.LazyFrames are equal.

This handler returns True if the two polars.LazyFrames are equal, otherwise False. This handler is designed to be used at the end of the chain of responsibility. This handler does not call the next handler.

Example
>>> import polars as pl
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import PolarsLazyFrameEqualHandler
>>> config = EqualityConfig()
>>> handler = PolarsLazyFrameEqualHandler()
>>> handler.handle(
...     pl.LazyFrame({"col": [1, 2, 3]}),
...     pl.LazyFrame({"col": [1, 2, 3]}),
...     config,
... )
True
>>> handler.handle(
...     pl.LazyFrame({"col": [1, 2, 3]}),
...     pl.LazyFrame({"col": [1, 2, 4]}),
...     config,
... )
False

coola.equality.handler.PolarsSeriesEqualHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two polars.Serieses are equal.

This handler returns True if the two polars.Serieses are equal, otherwise False. This handler is designed to be used at the end of the chain of responsibility. This handler does not call the next handler.

Example
>>> import polars as pl
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import PolarsSeriesEqualHandler
>>> config = EqualityConfig()
>>> handler = PolarsSeriesEqualHandler()
>>> handler.handle(pl.Series([1, 2, 3]), pl.Series([1, 2, 3]), config)
True
>>> handler.handle(pl.Series([1, 2, 3]), pl.Series([1, 2, 4]), config)
False

coola.equality.handler.PyarrowEqualHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two pyarrow arrays or tables are equal.

This handler returns True if the two arrays or tables are equal, otherwise False. This handler is designed to be used at the end of the chain of responsibility. This handler does not call the next handler.

Note that config.equal_nan, config.atol and config.rtol arguments are ignored.

Example
>>> import pyarrow
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import PyarrowEqualHandler
>>> config = EqualityConfig()
>>> handler = PyarrowEqualHandler()
>>> handler.handle(pyarrow.array([1, 2, 3]), pyarrow.array([1, 2, 3]), config)
True
>>> handler.handle(pyarrow.array([1, 2, 3]), pyarrow.array([1, 2, 4]), config)
False

coola.equality.handler.SameAttributeHandler

Bases: BaseEqualityHandler

Check if the two objects have the same attribute.

This handler returns False if the two objects have different attributes, otherwise it passes the inputs to the next handler. The objects must have the attribute.

Example
>>> import numpy as np
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import SameAttributeHandler, TrueHandler
>>> config = EqualityConfig()
>>> handler = SameAttributeHandler(name="shape", next_handler=TrueHandler())
>>> handler.handle(np.ones((2, 3)), np.ones((2, 3)), config)
True
>>> handler.handle(np.ones((2, 3)), np.ones((3, 2)), config)
False

coola.equality.handler.SameDTypeHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two objects have the same data type.

This handler returns False if the two objects have different data types, otherwise it passes the inputs to the next handler. The objects must have a dtype attribute (e.g. object.dtype) which returns the data type of the object. This handler works on numpy.ndarrays and torch.Tensors objects.

Example
>>> import numpy as np
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import SameDTypeHandler, TrueHandler
>>> config = EqualityConfig()
>>> handler = SameDTypeHandler(next_handler=TrueHandler())
>>> handler.handle(np.ones((2, 3)), np.ones((2, 3)), config)
True
>>> handler.handle(np.ones((2, 3), dtype=float), np.ones((2, 3), dtype=int), config)
False

coola.equality.handler.SameDataHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two objects have the same data.

This handler returns False if the two objects have different data, otherwise it passes the inputs to the next handler. The objects must have a data attribute (e.g. object.data) which returns the data of the object.

Example
>>> import numpy as np
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import SameDataHandler, TrueHandler
>>> config = EqualityConfig()
>>> handler = SameDataHandler(next_handler=TrueHandler())
>>> handler.handle(np.ones((2, 3)), np.ones((2, 3)), config)
True
>>> handler.handle(np.ones((2, 3)), np.zeros((2, 3)), config)
False

coola.equality.handler.SameLengthHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two objects have the same length.

This handler returns False if the two objects have different lengths, otherwise it passes the inputs to the next handler.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import SameLengthHandler
>>> config = EqualityConfig()
>>> handler = SameLengthHandler()
>>> handler.handle([1, 2, 3], [1, 2, 3, 4], config)
False

coola.equality.handler.SameObjectHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two objects refer to the same object.

This handler returns True if the two objects refer to the same object, otherwise it passes the inputs to the next handler.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import SameObjectHandler
>>> config = EqualityConfig()
>>> handler = SameObjectHandler()
>>> handler.handle("abc", "abc", config)
True

coola.equality.handler.SameShapeHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two objects have the same shape.

This handler returns False if the two objects have different shapes, otherwise it passes the inputs to the next handler. The objects must have a shape attribute (e.g. object.shape) which returns the shape of the object. This handler works on jax.numpy.ndarrays, numpy.ndarrays, pandas.DataFrame, polars.DataFrame and torch.Tensors objects.

Example
>>> import numpy as np
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import SameShapeHandler, TrueHandler
>>> config = EqualityConfig()
>>> handler = SameShapeHandler(next_handler=TrueHandler())
>>> handler.handle(np.ones((2, 3)), np.ones((2, 3)), config)
True
>>> handler.handle(np.ones((2, 3)), np.ones((3, 2)), config)
False

coola.equality.handler.SameTypeHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two objects have the same type.

This handler returns False if the two objects have different types, otherwise it passes the inputs to the next handler.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import SameTypeHandler
>>> config = EqualityConfig()
>>> handler = SameTypeHandler()
>>> handler.handle(1, "abc", config)
False

coola.equality.handler.ScalarEqualHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two numbers are equal or not.

This handler returns False if the two numbers are different, otherwise it returns True. It is possible to control the tolerance by using atol and rtol. By default, the tolerances are set to 0.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import ScalarEqualHandler
>>> config = EqualityConfig()
>>> handler = ScalarEqualHandler()
>>> handler.handle(42.0, 42.0, config)
True
>>> config.atol = 1e-3
>>> handler.handle(42.0, 42.0001, config)
True
>>> handler.handle(float("nan"), float("nan"), config)
False

coola.equality.handler.SequenceSameValuesHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two sequences have the same values.

This handler returns False if the two sequences have at least one different value, otherwise it passes the inputs to the next handler. If the sequences have different length, this handler checks only the values of the shortest sequence.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import SequenceSameValuesHandler, TrueHandler
>>> config = EqualityConfig()
>>> handler = SequenceSameValuesHandler(next_handler=TrueHandler())
>>> handler.handle([1, 2, 3], [1, 2, 3], config)
True
>>> handler.handle([1, 2, 3], [1, 2, 4], config)
False

coola.equality.handler.TorchTensorEqualHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two tensors are equal.

This handler returns True if the two tensors are equal, otherwise False. This handler is designed to be used at the end of the chain of responsibility. This handler does not call the next handler.

Example
>>> import torch
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import TorchTensorEqualHandler
>>> config = EqualityConfig()
>>> handler = TorchTensorEqualHandler()
>>> handler.handle(torch.ones(2, 3), torch.ones(2, 3), config)
True
>>> handler.handle(torch.ones(2, 3), torch.zeros(2, 3), config)
False

coola.equality.handler.TorchTensorSameDeviceHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Check if the two tensors have the same device.

This handler returns False if the two objects have different devices, otherwise it passes the inputs to the next handler.

Example
>>> import torch
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import TrueHandler, TorchTensorSameDeviceHandler
>>> config = EqualityConfig()
>>> handler = TorchTensorSameDeviceHandler(next_handler=TrueHandler())
>>> handler.handle(torch.ones(2, 3), torch.ones(3, 2), config)
True

coola.equality.handler.TrueHandler

Bases: HandlerEqualityMixin, BaseEqualityHandler

Implement a handler that always returns True.

This handler is designed to be used at the end of the chain of responsibility. This handler does not call the next handler.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import TrueHandler
>>> config = EqualityConfig()
>>> handler = TrueHandler()
>>> handler.handle("abc", "abc", config)
True
>>> handler.handle("abc", "ABC", config)
True

coola.equality.handler.check_recursion_depth

check_recursion_depth(
    config: EqualityConfig,
) -> Generator[None, None, None]

Context manager to track and enforce recursion depth limits.

This context manager increments the recursion depth counter on entry and decrements it on exit (even if an exception occurs). It raises a RecursionError if the maximum depth is exceeded.

Parameters:

Name Type Description Default
config EqualityConfig

The equality configuration containing depth settings.

required

Raises:

Type Description
RecursionError

if the current depth exceeds max_depth.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import check_recursion_depth
>>> config = EqualityConfig(max_depth=5)
>>> with check_recursion_depth(config):
...     print(config._current_depth)
...
1

coola.equality.handler.create_chain

create_chain(
    *handlers: BaseEqualityHandler,
) -> BaseEqualityHandler

Create a chain of handlers and return the first handler.

Parameters:

Name Type Description Default
handlers BaseEqualityHandler

Handlers to chain.

()

Returns:

Type Description
BaseEqualityHandler

The first handler of the chain.

Example
>>> from coola.equality.handler import (
...     create_chain,
...     SameObjectHandler,
...     SameTypeHandler,
...     ObjectEqualHandler,
... )
>>> handler = create_chain(SameObjectHandler(), SameTypeHandler(), ObjectEqualHandler())
>>> print(handler.visualize_chain())
(0): SameObjectHandler()
(1): SameTypeHandler()
(2): ObjectEqualHandler()

coola.equality.handler.handlers_are_equal

handlers_are_equal(
    handler1: BaseEqualityHandler | None,
    handler2: BaseEqualityHandler | None,
) -> bool

Indicate whether two handlers are equal.

Parameters:

Name Type Description Default
handler1 BaseEqualityHandler | None

The first handler.

required
handler2 BaseEqualityHandler | None

The second handler.

required

Returns:

Type Description
bool

True if both handlers are equal, otherwise False.

Example
>>> from coola.equality.handler import SameObjectHandler, FalseHandler, handlers_are_equal
>>> handlers_are_equal(SameObjectHandler(), SameObjectHandler())
True
>>> handlers_are_equal(SameObjectHandler(), FalseHandler())
False
>>> handlers_are_equal(None, SameObjectHandler())
False
>>> handlers_are_equal(SameObjectHandler(), None)
False
>>> handlers_are_equal(None, None)
True

coola.equality.tester

Contain the equality testers to check if two objects are equal or not.

This module provides a comprehensive system for comparing objects of different types using a registry-based approach with specialized equality testers. The testers use the chain of responsibility pattern with handlers to perform various equality checks.

Key components
  • BaseEqualityTester: Abstract base class for all equality testers
  • EqualityTesterRegistry: Registry that dispatches to appropriate testers by type
  • Specialized testers for Python built-ins (list, dict, int, float, etc.)
  • Testers for third-party libraries (NumPy, PyTorch, Pandas, Polars, JAX, xarray, PyArrow)

The default registry is pre-configured with testers for common types and can be extended with custom testers using register_equality_testers().

Example
>>> from coola.equality.tester import get_default_registry
>>> from coola.equality.config import EqualityConfig
>>> registry = get_default_registry()
>>> config = EqualityConfig()
>>> # Compare lists with nested structures
>>> registry.objects_are_equal([1, {"a": 2}], [1, {"a": 2}], config)
True

coola.equality.tester.BaseEqualityTester

Bases: ABC, Generic[T]

Define the base class to implement an equality operator.

This abstract base class defines the interface for all equality testers in coola. Equality testers are responsible for comparing objects of a specific type using a chain of handlers that implement the chain of responsibility pattern.

The generic type parameter T indicates the primary type this tester is designed to handle, though the actual implementation may handle related types as well.

Subclasses must implement
  • equal(): Check if another tester is of the same type
  • objects_are_equal(): Check if two objects are equal using handler chain
Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import DefaultEqualityTester
>>> config = EqualityConfig()
>>> tester = DefaultEqualityTester()
>>> tester.objects_are_equal(42, 42, config)
True
>>> tester.objects_are_equal("meow", "meov", config)
False

coola.equality.tester.BaseEqualityTester.equal abstractmethod

equal(other: object) -> bool

Indicate if two equality testers are equal (same type).

This method checks if another object is an equality tester of the same type as this one. It's used for comparing equality tester instances themselves, not the objects they test.

Parameters:

Name Type Description Default
other object

The other object to compare against.

required

Returns:

Type Description
bool

True if the other object is an equality tester of the same type,

bool

otherwise False.

Example
>>> from coola.equality.tester import DefaultEqualityTester, MappingEqualityTester
>>> tester1 = DefaultEqualityTester()
>>> tester2 = DefaultEqualityTester()
>>> tester3 = MappingEqualityTester()
>>> tester1.equal(tester2)
True
>>> tester1.equal(tester3)
False

coola.equality.tester.BaseEqualityTester.objects_are_equal abstractmethod

objects_are_equal(
    actual: T, expected: object, config: EqualityConfig
) -> bool

Indicate if two objects are equal or not.

This method delegates equality checking to a chain of handlers that implement various checks (e.g., same object, same type, same values). The handler chain is typically set up in the tester's init method.

Parameters:

Name Type Description Default
actual T

The actual object to compare.

required
expected object

The expected object to compare against.

required
config EqualityConfig

The equality configuration controlling comparison behavior (e.g., tolerance for floating point, whether to treat NaN as equal).

required

Returns:

Type Description
bool

True if the two objects are equal according to this tester's

bool

logic and the provided configuration, otherwise False.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import DefaultEqualityTester
>>> config = EqualityConfig()
>>> tester = DefaultEqualityTester()
>>> tester.objects_are_equal(42, 42, config)
True
>>> tester.objects_are_equal("meow", "meov", config)
False

coola.equality.tester.DefaultEqualityTester

Bases: BaseEqualityTester[object]

Implement a default equality tester.

This tester serves as the fallback for types without specialized equality testers. It uses Python's built-in == operator to test equality between objects. The tester uses a handler chain to perform checks in order: 1. SameObjectHandler: Check if objects are the same instance (identity) 2. SameTypeHandler: Check if objects have the same type 3. ObjectEqualHandler: Use == operator for equality comparison

This tester is registered for the object type in the default registry, making it the catch-all for unregistered types via Python's MRO.

Example

Basic usage with primitives:

>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import DefaultEqualityTester
>>> config = EqualityConfig()
>>> tester = DefaultEqualityTester()
>>> tester.objects_are_equal(42, 42, config=config)
True
>>> tester.objects_are_equal("meow", "meov", config)
False

Different types are not equal:

>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import DefaultEqualityTester
>>> config = EqualityConfig()
>>> tester = DefaultEqualityTester()
>>> tester.objects_are_equal(42, "42", config)
False

coola.equality.tester.EqualEqualityTester

Bases: BaseEqualityTester[object]

Implement an equality tester for objects with equal method.

This tester is designed for objects that implement an equal(other) method for equality comparison. It uses a handler chain that checks: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify same type 3. EqualHandler: Call the object's equal() method

This tester is used for comparing BaseEqualityTester instances themselves in the default registry.

Example

Custom class with equal method:

>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import EqualEqualityTester
>>> class MyFloat:
...     def __init__(self, value: float) -> None:
...         self._value = float(value)
...     def equal(self, other: object) -> bool:
...         if type(other) is not type(self):
...             return False
...         return self._value == other._value
...
>>> config = EqualityConfig()
>>> tester = EqualEqualityTester()
>>> tester.objects_are_equal(MyFloat(42), MyFloat(42), config=config)
True
>>> tester.objects_are_equal(MyFloat(42), MyFloat(1), config=config)
False

coola.equality.tester.EqualNanEqualityTester

Bases: BaseEqualityTester[object]

Implement an equality tester for objects with NaN-aware equal method.

This tester is designed for objects that implement an equal(other, equal_nan) method that supports NaN equality. When config.equal_nan is True, NaN values are considered equal to each other. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify same type 3. EqualNanHandler: Call object's equal() with equal_nan parameter

Example

Custom class with NaN-aware equal method:

>>> import math
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import EqualNanEqualityTester
>>> class MyFloat:
...     def __init__(self, value: float) -> None:
...         self._value = float(value)
...     def equal(self, other: object, equal_nan: bool = False) -> bool:
...         if type(other) is not type(self):
...             return False
...         if equal_nan and math.isnan(self._value) and math.isnan(other._value):
...             return True
...         return self._value == other._value
...
>>> config = EqualityConfig()
>>> tester = EqualNanEqualityTester()
>>> tester.objects_are_equal(MyFloat(42), MyFloat(42), config=config)
True
>>> tester.objects_are_equal(MyFloat(float("nan")), MyFloat(float("nan")), config=config)
False
>>> config.equal_nan = True
>>> tester.objects_are_equal(MyFloat(float("nan")), MyFloat(float("nan")), config=config)
True

coola.equality.tester.EqualityTesterRegistry

Registry that manages and dispatches equality testers based on data type.

This registry maintains a mapping from Python types to equality tester instances and uses the Method Resolution Order (MRO) for type lookup. When checking equality, it automatically selects the most specific registered equality tester for the data's type, falling back to parent types or a default tester if needed.

The registry includes an LRU cache for type lookups to optimize performance in applications that repeatedly check equality of similar data structures.

Parameters:

Name Type Description Default
initial_state dict[type, BaseEqualityTester[Any]] | None

Optional initial mapping of types to equality testers. If provided, the state is copied to prevent external mutations.

None

Attributes:

Name Type Description
_state TypeRegistry[BaseEqualityTester]

Internal mapping of registered types to equality testers

Example

Basic usage:

>>> from coola.equality.tester import (
...     EqualityTesterRegistry,
...     SequenceEqualityTester,
...     DefaultEqualityTester,
... )
>>> from coola.equality.config import EqualityConfig
>>> registry = EqualityTesterRegistry(
...     {object: DefaultEqualityTester(), list: SequenceEqualityTester()}
... )
>>> registry
EqualityTesterRegistry(
  (state): TypeRegistry(
      (<class 'object'>): DefaultEqualityTester()
      (<class 'list'>): SequenceEqualityTester()
    )
)
>>> config = EqualityConfig()
>>> registry.objects_are_equal([1, 2, 3], [1, 2, 3], config=config)
True

coola.equality.tester.EqualityTesterRegistry.find_equality_tester

find_equality_tester(
    data_type: type,
) -> BaseEqualityTester[Any]

Find the appropriate equality tester for a given type.

Uses the Method Resolution Order (MRO) to find the most specific registered equality tester. For example, if you register a tester for Sequence but not for list, lists will use the Sequence tester.

Results are cached using an LRU cache (256 entries) for performance, as tester lookup is a hot path in recursive equality checking.

Parameters:

Name Type Description Default
data_type type

The Python type to find an equality tester for

required

Returns:

Type Description
BaseEqualityTester[Any]

The most specific registered equality tester for this type, a parent

BaseEqualityTester[Any]

type's tester via MRO, or the default tester

Example
>>> from collections.abc import Sequence
>>> from coola.equality.tester import (
...     EqualityTesterRegistry,
...     SequenceEqualityTester,
...     DefaultEqualityTester,
... )
>>> registry = EqualityTesterRegistry({object: DefaultEqualityTester()})
>>> registry.register(Sequence, SequenceEqualityTester())
>>> # list does not inherit from Sequence, so it uses DefaultEqualityTester
>>> tester = registry.find_equality_tester(list)
>>> tester
DefaultEqualityTester()

coola.equality.tester.EqualityTesterRegistry.has_equality_tester

has_equality_tester(data_type: type) -> bool

Check if an equality tester is explicitly registered for the given type.

Note that this only checks for direct registration. Even if this returns False, find_equality_tester() may still return a tester via MRO lookup or the default tester.

Parameters:

Name Type Description Default
data_type type

The type to check

required

Returns:

Type Description
bool

True if an equality tester is explicitly registered for this type,

bool

False otherwise

Example
>>> from coola.equality.tester import EqualityTesterRegistry, SequenceEqualityTester
>>> registry = EqualityTesterRegistry()
>>> registry.register(list, SequenceEqualityTester())
>>> registry.has_equality_tester(list)
True
>>> registry.has_equality_tester(tuple)
False

coola.equality.tester.EqualityTesterRegistry.objects_are_equal

objects_are_equal(
    actual: object, expected: object, config: EqualityConfig
) -> bool

Check if two objects are equal by recursively comparing their structure.

This is the main entry point for equality checking. It automatically: 1. Determines the actual object's type 2. Finds the appropriate equality tester 3. Delegates to that tester's objects_are_equal method 4. The tester recursively processes nested structures

Parameters:

Name Type Description Default
actual object

The actual object.

required
expected object

The expected object.

required
config EqualityConfig

The equality configuration.

required

Returns:

Type Description
bool

True if the objects are equal according to the registered testers,

bool

False otherwise

Example

Checking if two lists of integers are equal:

>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import get_default_registry
>>> registry = get_default_registry()
>>> config = EqualityConfig()
>>> registry.objects_are_equal([1, 2, 3], [1, 2, 3], config=config)
True

Checking if two lists of tensors are equal:

>>> import torch
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import get_default_registry
>>> registry = get_default_registry()
>>> config = EqualityConfig()
>>> registry.objects_are_equal(
...     [torch.ones(2, 3), torch.zeros(2)],
...     [torch.ones(2, 3), torch.zeros(2)],
...     config=config,
... )
True

coola.equality.tester.EqualityTesterRegistry.register

register(
    data_type: type,
    tester: BaseEqualityTester[Any],
    exist_ok: bool = False,
) -> None

Register an equality tester for a given data type.

This method associates an equality tester instance with a specific Python type. When checking equality for data of this type, the registered tester will be used. The cache is automatically cleared after registration to ensure consistency.

Parameters:

Name Type Description Default
data_type type

The Python type to register (e.g., list, dict, custom classes)

required
tester BaseEqualityTester[Any]

The equality tester instance that handles this type

required
exist_ok bool

If False (default), raises an error if the type is already registered. If True, overwrites the existing registration silently.

False

Raises:

Type Description
RuntimeError

If the type is already registered and exist_ok is False

Example
>>> from coola.equality.tester import EqualityTesterRegistry, SequenceEqualityTester
>>> registry = EqualityTesterRegistry()
>>> registry.register(list, SequenceEqualityTester())
>>> registry.has_equality_tester(list)
True

coola.equality.tester.EqualityTesterRegistry.register_many

register_many(
    mapping: Mapping[type, BaseEqualityTester[Any]],
    exist_ok: bool = False,
) -> None

Register multiple equality testers at once.

This is a convenience method for bulk registration that internally calls register() for each type-tester pair.

Parameters:

Name Type Description Default
mapping Mapping[type, BaseEqualityTester[Any]]

Dictionary mapping Python types to equality tester instances

required
exist_ok bool

If False (default), raises an error if any type is already registered. If True, overwrites existing registrations silently.

False

Raises:

Type Description
RuntimeError

If any type is already registered and exist_ok is False

Example
>>> from coola.equality.tester import (
...     EqualityTesterRegistry,
...     SequenceEqualityTester,
...     MappingEqualityTester,
... )
>>> registry = EqualityTesterRegistry()
>>> registry.register_many(
...     {
...         list: SequenceEqualityTester(),
...         dict: MappingEqualityTester(),
...     }
... )
>>> registry
EqualityTesterRegistry(
  (state): TypeRegistry(
      (<class 'list'>): SequenceEqualityTester()
      (<class 'dict'>): MappingEqualityTester()
    )
)

coola.equality.tester.HandlerEqualityTester

Bases: BaseEqualityTester[T]

Implement an equality tester that uses an equality handler.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.handler import (
...     ObjectEqualHandler,
...     SameObjectHandler,
...     SameTypeHandler,
...     create_chain,
... )
>>> from coola.equality.tester import HandlerEqualityTester
>>> config = EqualityConfig()
>>> handler = create_chain(SameObjectHandler(), SameTypeHandler(), ObjectEqualHandler())
>>> tester = HandlerEqualityTester(handler)
>>> print(tester)
HandlerEqualityTester(
  (0): SameObjectHandler()
  (1): SameTypeHandler()
  (2): ObjectEqualHandler()
)
>>> tester.objects_are_equal(42, 42, config=config)
True
>>> tester.objects_are_equal("meow", "meov", config)
False

coola.equality.tester.JaxArrayEqualityTester

Bases: BaseEqualityTester[ndarray]

Implement an equality tester for jax.numpy.ndarray.

This tester compares JAX arrays element-wise with support for NaN equality and tolerance-based comparisons. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify both are JAX arrays 3. SameDTypeHandler: Check arrays have the same dtype 4. SameShapeHandler: Verify arrays have the same shape 5. JaxArrayEqualHandler: Element-wise comparison with tolerance support

The tester respects config.equal_nan for NaN comparisons and config.atol/rtol for floating-point tolerance.

Example

Basic array comparison:

>>> import jax.numpy as jnp
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import JaxArrayEqualityTester
>>> config = EqualityConfig()
>>> tester = JaxArrayEqualityTester()
>>> tester.objects_are_equal(jnp.ones((2, 3)), jnp.ones((2, 3)), config)
True
>>> tester.objects_are_equal(jnp.ones((2, 3)), jnp.zeros((2, 3)), config)
False

coola.equality.tester.MappingEqualityTester

Bases: BaseEqualityTester[Mapping[Any, Any]]

Implement a mapping equality tester.

This tester handles dictionary-like objects (dict, Mapping ABC) by recursively comparing their keys and values. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify same type 3. SameLengthHandler: Check both mappings have same number of keys 4. MappingSameKeysHandler: Verify both have the same keys 5. MappingSameValuesHandler: Recursively compare values using registry 6. TrueHandler: Return True if all checks pass

The values are compared recursively, so nested dictionaries, lists, and other complex structures are handled correctly.

Example

Basic mapping comparison:

>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import MappingEqualityTester
>>> config = EqualityConfig()
>>> tester = MappingEqualityTester()
>>> tester.objects_are_equal({"a": 1, "b": 2}, {"a": 1, "b": 2}, config)
True
>>> tester.objects_are_equal({"a": 1, "b": 2}, {"a": 1, "b": 4}, config)
False

Nested mapping comparison:

>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import MappingEqualityTester
>>> config = EqualityConfig()
>>> tester = MappingEqualityTester()
>>> tester.objects_are_equal(
...     {"a": {"x": 1}, "b": [1, 2]},
...     {"a": {"x": 1}, "b": [1, 2]},
...     config,
... )
True

coola.equality.tester.NumpyArrayEqualityTester

Bases: BaseEqualityTester[ndarray]

Implement an equality tester for numpy.ndarray.

This tester compares NumPy arrays element-wise with support for NaN equality and tolerance-based comparisons. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify both are numpy arrays 3. SameDTypeHandler: Check arrays have the same dtype 4. SameShapeHandler: Verify arrays have the same shape 5. NumpyArrayEqualHandler: Element-wise comparison with tolerance support

The tester respects config.equal_nan for NaN comparisons and config.atol/rtol for floating-point tolerance.

Example

Basic array comparison:

>>> import numpy as np
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import NumpyArrayEqualityTester
>>> config = EqualityConfig()
>>> tester = NumpyArrayEqualityTester()
>>> tester.objects_are_equal(np.ones((2, 3)), np.ones((2, 3)), config)
True
>>> tester.objects_are_equal(np.ones((2, 3)), np.zeros((2, 3)), config)
False

NaN comparison with equal_nan:

>>> import numpy as np
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import NumpyArrayEqualityTester
>>> config = EqualityConfig(equal_nan=True)
>>> tester = NumpyArrayEqualityTester()
>>> tester.objects_are_equal(
...     np.array([1.0, float("nan")]),
...     np.array([1.0, float("nan")]),
...     config,
... )
True

coola.equality.tester.NumpyMaskedArrayEqualityTester

Bases: BaseEqualityTester[MaskedArray]

Implement an equality tester for numpy.ma.MaskedArray.

This tester compares NumPy masked arrays by checking data, mask, and fill_value. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify both are masked arrays 3. SameDTypeHandler: Check arrays have the same dtype 4. SameShapeHandler: Verify arrays have the same shape 5. SameDataHandler: Compare the underlying data arrays 6. SameAttributeHandler("mask"): Compare the mask arrays 7. SameAttributeHandler("fill_value"): Compare fill values 8. TrueHandler: Return True if all checks pass

Example

Basic masked array comparison:

>>> import numpy as np
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import NumpyMaskedArrayEqualityTester
>>> config = EqualityConfig()
>>> tester = NumpyMaskedArrayEqualityTester()
>>> tester.objects_are_equal(
...     np.ma.array(data=[0.0, 1.0, 1.2], mask=[0, 1, 0]),
...     np.ma.array(data=[0.0, 1.0, 1.2], mask=[0, 1, 0]),
...     config,
... )
True
>>> tester.objects_are_equal(
...     np.ma.array(data=[0.0, 1.0, 1.2], mask=[0, 1, 0]),
...     np.ma.array(data=[0.0, 1.0, 2.0], mask=[0, 1, 0]),
...     config,
... )
False

Different masks are not equal:

>>> import numpy as np
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import NumpyMaskedArrayEqualityTester
>>> config = EqualityConfig()
>>> tester = NumpyMaskedArrayEqualityTester()
>>> tester.objects_are_equal(
...     np.ma.array(data=[0.0, 1.0, 1.2], mask=[0, 1, 0]),
...     np.ma.array(data=[0.0, 1.0, 1.2], mask=[1, 1, 0]),
...     config,
... )
False

coola.equality.tester.PandasDataFrameEqualityTester

Bases: BaseEqualityTester[DataFrame]

Implement an equality tester for pandas.DataFrame.

This tester uses pandas' DataFrame equality testing which compares shape, column names, data types, index, and values. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify both are pandas DataFrames 3. PandasDataFrameEqualHandler: Use pandas' assert_frame_equal internally

Note

The tester uses pandas' internal comparison logic which handles NaN values and performs comprehensive DataFrame equality checking.

Example

Basic DataFrame comparison:

>>> import pandas as pd
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import PandasDataFrameEqualityTester
>>> config = EqualityConfig()
>>> tester = PandasDataFrameEqualityTester()
>>> tester.objects_are_equal(
...     pd.DataFrame({"col": [1, 2, 3]}),
...     pd.DataFrame({"col": [1, 2, 3]}),
...     config,
... )
True
>>> tester.objects_are_equal(
...     pd.DataFrame({"col": [1, 2, 3]}),
...     pd.DataFrame({"col": [1, 2, 4]}),
...     config,
... )
False

Different column names are not equal:

>>> import pandas as pd
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import PandasDataFrameEqualityTester
>>> config = EqualityConfig()
>>> tester = PandasDataFrameEqualityTester()
>>> tester.objects_are_equal(
...     pd.DataFrame({"col1": [1, 2, 3]}),
...     pd.DataFrame({"col2": [1, 2, 3]}),
...     config,
... )
False

coola.equality.tester.PandasSeriesEqualityTester

Bases: BaseEqualityTester[Series]

Implement an equality tester for pandas.Series.

This tester uses pandas' Series equality testing which compares length, data type, index, and values. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify both are pandas Series 3. PandasSeriesEqualHandler: Use pandas' assert_series_equal internally

Note

The tester uses pandas' internal comparison logic which handles NaN values and performs comprehensive Series equality checking.

Example

Basic Series comparison:

>>> import pandas as pd
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import PandasSeriesEqualityTester
>>> config = EqualityConfig()
>>> tester = PandasSeriesEqualityTester()
>>> tester.objects_are_equal(pd.Series([1, 2, 3]), pd.Series([1, 2, 3]), config)
True
>>> tester.objects_are_equal(pd.Series([1, 2, 3]), pd.Series([1, 2, 4]), config)
False

Different index values are not equal:

>>> import pandas as pd
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import PandasSeriesEqualityTester
>>> config = EqualityConfig()
>>> tester = PandasSeriesEqualityTester()
>>> tester.objects_are_equal(
...     pd.Series([1, 2, 3], index=["a", "b", "c"]),
...     pd.Series([1, 2, 3], index=["x", "y", "z"]),
...     config,
... )
False

coola.equality.tester.PolarsDataFrameEqualityTester

Bases: BaseEqualityTester[DataFrame]

Implement an equality tester for polars.DataFrame.

This tester uses Polars' DataFrame equality testing. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify both are polars DataFrames 3. PolarsDataFrameEqualHandler: Use Polars' internal equality testing

Example

Basic DataFrame comparison:

>>> import polars as pl
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import PolarsDataFrameEqualityTester
>>> config = EqualityConfig()
>>> tester = PolarsDataFrameEqualityTester()
>>> tester.objects_are_equal(
...     pl.DataFrame({"col": [1, 2, 3]}),
...     pl.DataFrame({"col": [1, 2, 3]}),
...     config,
... )
True
>>> tester.objects_are_equal(
...     pl.DataFrame({"col": [1, 2, 3]}),
...     pl.DataFrame({"col": [1, 2, 4]}),
...     config,
... )
False

coola.equality.tester.PolarsLazyFrameEqualityTester

Bases: BaseEqualityTester[LazyFrame]

Implement an equality tester for polars.LazyFrame.

This tester uses Polars' LazyFrame equality testing. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify both are polars LazyFrames 3. PolarsLazyFrameEqualHandler: Use Polars' internal equality testing

Note

LazyFrames represent query plans and are collected (materialized) for comparison, which may have performance implications for large datasets.

Example

Basic LazyFrame comparison:

>>> import polars as pl
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import PolarsLazyFrameEqualityTester
>>> config = EqualityConfig()
>>> tester = PolarsLazyFrameEqualityTester()
>>> tester.objects_are_equal(
...     pl.LazyFrame({"col": [1, 2, 3]}),
...     pl.LazyFrame({"col": [1, 2, 3]}),
...     config,
... )
True
>>> tester.objects_are_equal(
...     pl.LazyFrame({"col": [1, 2, 3]}),
...     pl.LazyFrame({"col": [1, 2, 4]}),
...     config,
... )
False

coola.equality.tester.PolarsSeriesEqualityTester

Bases: BaseEqualityTester[Series]

Implement an equality tester for polars.Series.

This tester uses Polars' Series equality testing. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify both are polars Series 3. PolarsSeriesEqualHandler: Use Polars' internal equality testing

Example

Basic Series comparison:

>>> import polars as pl
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import PolarsSeriesEqualityTester
>>> config = EqualityConfig()
>>> tester = PolarsSeriesEqualityTester()
>>> tester.objects_are_equal(pl.Series([1, 2, 3]), pl.Series([1, 2, 3]), config)
True
>>> tester.objects_are_equal(pl.Series([1, 2, 3]), pl.Series([1, 2, 4]), config)
False

coola.equality.tester.PyarrowEqualityTester

Bases: BaseEqualityTester[Array]

Implement an equality tester for pyarrow.Arrays and pyarrow.Tables.

This tester uses PyArrow's equals() method for comparison. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify both are pyarrow objects 3. PyarrowEqualHandler: Use PyArrow's equals() method

Note

The config.equal_nan, config.atol, and config.rtol arguments are ignored as PyArrow's equals() method does not support these parameters. PyArrow performs its own equality checking logic.

Example

Basic array comparison:

>>> import pyarrow as pa
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import PyarrowEqualityTester
>>> config = EqualityConfig()
>>> tester = PyarrowEqualityTester()
>>> tester.objects_are_equal(pa.array([1, 2, 3]), pa.array([1, 2, 3]), config)
True
>>> tester.objects_are_equal(pa.array([1, 2, 3]), pa.array([1, 2, 4]), config)
False

Table comparison:

>>> import pyarrow as pa
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import PyarrowEqualityTester
>>> config = EqualityConfig()
>>> tester = PyarrowEqualityTester()
>>> table1 = pa.table({"col": [1, 2, 3]})
>>> table2 = pa.table({"col": [1, 2, 3]})
>>> tester.objects_are_equal(table1, table2, config)
True

coola.equality.tester.ScalarEqualityTester

Bases: BaseEqualityTester[float]

Implement a scalar equality tester.

This tester handles numeric scalar types (int, float) with support for NaN equality and tolerance-based comparisons. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify same type 3. NanEqualHandler: Handle NaN comparisons based on config.equal_nan 4. ScalarEqualHandler: Compare values with tolerance (config.atol, config.rtol)

This tester is registered for both int and float types in the default registry.

Example

Basic scalar comparison:

>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import ScalarEqualityTester
>>> config = EqualityConfig()
>>> tester = ScalarEqualityTester()
>>> tester.objects_are_equal(42.0, 42.0, config)
True
>>> tester.objects_are_equal(42.0, 1.0, config)
False

NaN comparison with equal_nan enabled:

>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import ScalarEqualityTester
>>> config = EqualityConfig(equal_nan=True)
>>> tester = ScalarEqualityTester()
>>> tester.objects_are_equal(float("nan"), float("nan"), config)
True

Tolerance-based comparison:

>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import ScalarEqualityTester
>>> config = EqualityConfig(atol=1e-6)
>>> tester = ScalarEqualityTester()
>>> tester.objects_are_equal(1.0, 1.0000001, config)
True

coola.equality.tester.SequenceEqualityTester

Bases: BaseEqualityTester[Sequence[Any]]

Implement a sequence equality tester.

This tester handles sequence types (list, tuple, deque, Sequence ABC) by recursively comparing their elements. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify same type 3. SameLengthHandler: Check both sequences have same length 4. SequenceSameValuesHandler: Recursively compare elements using registry 5. TrueHandler: Return True if all checks pass

Elements are compared in order and recursively, so nested lists, dicts, and other complex structures are handled correctly.

Example

Basic sequence comparison:

>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import SequenceEqualityTester
>>> config = EqualityConfig()
>>> tester = SequenceEqualityTester()
>>> tester.objects_are_equal([1, 2, 3], [1, 2, 3], config)
True
>>> tester.objects_are_equal([1, 2, 3], [1, 2, 4], config)
False

Nested sequence comparison:

>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import SequenceEqualityTester
>>> config = EqualityConfig()
>>> tester = SequenceEqualityTester()
>>> tester.objects_are_equal(
...     [[1, 2], {"a": 3}],
...     [[1, 2], {"a": 3}],
...     config,
... )
True

coola.equality.tester.TorchPackedSequenceEqualityTester

Bases: BaseEqualityTester[PackedSequence]

Implement an equality tester for torch.nn.utils.rnn.PackedSequence.

This tester compares PyTorch packed sequences by checking data tensor and metadata attributes. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify both are PackedSequence 3. SameDataHandler: Compare the data tensors 4. SameAttributeHandler("batch_sizes"): Compare batch_sizes tensors 5. SameAttributeHandler("sorted_indices"): Compare sorted_indices (if present) 6. SameAttributeHandler("unsorted_indices"): Compare unsorted_indices (if present) 7. TrueHandler: Return True if all checks pass

Example

Basic packed sequence comparison:

>>> import torch
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import TorchPackedSequenceEqualityTester
>>> config = EqualityConfig()
>>> tester = TorchPackedSequenceEqualityTester()
>>> seq1 = torch.nn.utils.rnn.pack_padded_sequence(
...     torch.tensor([[1.0, 2.0], [3.0, 4.0]]),
...     lengths=torch.tensor([2, 1]),
...     batch_first=True,
...     enforce_sorted=False,
... )
>>> seq2 = torch.nn.utils.rnn.pack_padded_sequence(
...     torch.tensor([[1.0, 2.0], [3.0, 4.0]]),
...     lengths=torch.tensor([2, 1]),
...     batch_first=True,
...     enforce_sorted=False,
... )
>>> tester.objects_are_equal(seq1, seq2, config)
True

coola.equality.tester.TorchTensorEqualityTester

Bases: BaseEqualityTester[Tensor]

Implement an equality tester for torch.Tensor.

This tester compares PyTorch tensors element-wise with support for device, dtype, shape checking, NaN equality, and tolerance-based comparisons. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify both are torch tensors 3. SameDTypeHandler: Check tensors have the same dtype 4. SameShapeHandler: Verify tensors have the same shape 5. TorchTensorSameDeviceHandler: Ensure tensors are on the same device 6. TorchTensorEqualHandler: Element-wise comparison with tolerance support

The tester respects config.equal_nan for NaN comparisons and config.atol/rtol for floating-point tolerance.

Example

Basic tensor comparison:

>>> import torch
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import TorchTensorEqualityTester
>>> config = EqualityConfig()
>>> tester = TorchTensorEqualityTester()
>>> tester.objects_are_equal(torch.ones(2, 3), torch.ones(2, 3), config)
True
>>> tester.objects_are_equal(torch.ones(2, 3), torch.zeros(2, 3), config)
False

NaN comparison with equal_nan:

>>> import torch
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import TorchTensorEqualityTester
>>> config = EqualityConfig(equal_nan=True)
>>> tester = TorchTensorEqualityTester()
>>> tester.objects_are_equal(
...     torch.tensor([1.0, float("nan")]),
...     torch.tensor([1.0, float("nan")]),
...     config,
... )
True

Tensors on different devices are not equal:

>>> import torch
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import TorchTensorEqualityTester
>>> config = EqualityConfig()
>>> tester = TorchTensorEqualityTester()
>>> # This example assumes CUDA is available
>>> tester.objects_are_equal(
...     torch.ones(2, 3),
...     torch.ones(2, 3).cuda() if torch.cuda.is_available() else torch.ones(2, 3),
...     config,
... )  # doctest: +SKIP
False

coola.equality.tester.XarrayDataArrayEqualityTester

Bases: BaseEqualityTester[DataArray]

Implement an equality tester for xarray.DataArray.

This tester compares xarray DataArrays by checking their variable, name, and coordinates. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify both are xarray DataArrays 3. SameAttributeHandler("variable"): Compare the underlying Variable 4. SameAttributeHandler("name"): Compare the DataArray names 5. SameAttributeHandler("_coords"): Compare the coordinate mappings 6. TrueHandler: Return True if all checks pass

Example

Basic DataArray comparison:

>>> import numpy as np
>>> import xarray as xr
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import XarrayDataArrayEqualityTester
>>> config = EqualityConfig()
>>> tester = XarrayDataArrayEqualityTester()
>>> tester.objects_are_equal(
...     xr.DataArray(np.arange(6), dims=["z"]),
...     xr.DataArray(np.arange(6), dims=["z"]),
...     config,
... )
True
>>> tester.objects_are_equal(
...     xr.DataArray(np.ones(6), dims=["z"]),
...     xr.DataArray(np.zeros(6), dims=["z"]),
...     config,
... )
False

coola.equality.tester.XarrayDatasetEqualityTester

Bases: BaseEqualityTester[Dataset]

Implement an equality tester for xarray.Dataset.

This tester compares xarray Datasets by checking their data variables, coordinates, and attributes. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify both are xarray Datasets 3. SameAttributeHandler("data_vars"): Compare data variable dictionaries 4. SameAttributeHandler("coords"): Compare coordinate dictionaries 5. SameAttributeHandler("attrs"): Compare attribute dictionaries 6. TrueHandler: Return True if all checks pass

Example

Basic Dataset comparison:

>>> import numpy as np
>>> import xarray as xr
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import XarrayDatasetEqualityTester
>>> config = EqualityConfig()
>>> tester = XarrayDatasetEqualityTester()
>>> tester.objects_are_equal(
...     xr.Dataset({"x": xr.DataArray(np.arange(6), dims=["z"])}),
...     xr.Dataset({"x": xr.DataArray(np.arange(6), dims=["z"])}),
...     config,
... )
True
>>> tester.objects_are_equal(
...     xr.Dataset({"x": xr.DataArray(np.zeros(6), dims=["z"])}),
...     xr.Dataset({"x": xr.DataArray(np.ones(6), dims=["z"])}),
...     config,
... )
False

coola.equality.tester.XarrayVariableEqualityTester

Bases: BaseEqualityTester[Variable]

Implement an equality tester for xarray.Variable.

This tester compares xarray Variables by checking their data, dimensions, and attributes. The handler chain: 1. SameObjectHandler: Check for object identity 2. SameTypeHandler: Verify both are xarray Variables 3. SameDataHandler: Compare the underlying data arrays 4. SameAttributeHandler("dims"): Compare the dimension names 5. SameAttributeHandler("attrs"): Compare attribute dictionaries 6. TrueHandler: Return True if all checks pass

Example

Basic Variable comparison:

>>> import numpy as np
>>> import xarray as xr
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.tester import XarrayVariableEqualityTester
>>> config = EqualityConfig()
>>> tester = XarrayVariableEqualityTester()
>>> tester.objects_are_equal(
...     xr.Variable(dims=["z"], data=np.arange(6)),
...     xr.Variable(dims=["z"], data=np.arange(6)),
...     config,
... )
True
>>> tester.objects_are_equal(
...     xr.Variable(dims=["z"], data=np.zeros(6)),
...     xr.Variable(dims=["z"], data=np.ones(6)),
...     config,
... )
False

coola.equality.tester.get_default_registry

get_default_registry() -> EqualityTesterRegistry

Get or create the default global registry with common Python types.

Returns a singleton registry instance that is pre-configured with equality testers for Python's built-in types including sequences (list, tuple), mappings (dict), and scalar types (int, float).

This function uses a singleton pattern to ensure the same registry instance is returned on subsequent calls, which is efficient and maintains consistency across an application.

Returns:

Type Description
EqualityTesterRegistry

A EqualityTesterRegistry instance with equality testers registered for: - Scalar types (int, float) - Sequences (list, tuple, deque, Sequence ABC) - Mappings (dict, Mapping ABC) - Third-party library types (numpy, torch, pandas, polars, jax, xarray, pyarrow)

Notes

The singleton pattern means modifications to the returned registry affect all future calls to this function. If you need an isolated registry, create a new EqualityTesterRegistry instance directly.

Example
>>> from coola.equality.tester import get_default_registry
>>> from coola.equality.config import EqualityConfig
>>> registry = get_default_registry()
>>> # Registry is ready to use with common Python types
>>> config = EqualityConfig()
>>> registry.objects_are_equal([1, 2, 3], [1, 2, 3], config)
True
>>> registry.objects_are_equal([1, 2, 3], [1, 1], config)
False

coola.equality.tester.register_equality_testers

register_equality_testers(
    mapping: Mapping[type, BaseEqualityTester[Any]],
    exist_ok: bool = False,
) -> None

Register custom equality testers to the default global registry.

This allows users to add support for custom types without modifying global state directly.

Parameters:

Name Type Description Default
mapping Mapping[type, BaseEqualityTester[Any]]

Dictionary mapping types to equality tester instances

required
exist_ok bool

If False, raises error if any type already registered

False
Example
>>> from coola.equality.tester import register_equality_testers, BaseEqualityTester
>>> class MyType:
...     def __init__(self, value):
...         self.value = value
...
>>> class MyEqualityTester(BaseEqualityTester[MyType]):
...     def equal(self, other: object) -> bool:
...         return type(other) is type(self)
...     def objects_are_equal(
...         self, actual: MyType, expected: object, config: EqualityConfig
...     ) -> bool:
...         if type(other) is not type(self):
...             return False
...         return actual.value == expected.value
...
>>> register_equality_testers({MyType: MyEqualityTester()})