Skip to content

Equality

coola.equality

Utilities to compare nested objects for exact or tolerant equality.

The package exports the comparison functions, the result, the configuration, the engine, the testers and the registry. The testers of the optional libraries (NumPy, PyTorch, JAX, xarray, pydantic) are imported the first time they are used.

coola.equality.AttributeName

Bases: str

Name of an attribute in the location of a difference.

It is a str that marks a path component as an attribute (a dataclass field, for example) rather than a key or an index, so a path is formatted as ['a'][2].x and not as ['a'][2]['x'].

Example
>>> from coola.equality._helpers.utils import AttributeName
>>> AttributeName("x") == "x"
True

coola.equality.BaseEqualityTester

Bases: InlineDisplayMixin, ABC, Generic[T]

Define the base class to implement an equality operator.

The engine owns the invariants of a comparison (identity, strict type, depth, cycles, location of the first difference). A tester never recurses: objects_are_equal receives the two objects, which have the same type, and the engine, and returns

  • a verdict: True, or ctx.fail(reason, *path) (which returns False) for a leaf;
  • or the child pairs (actual, expected, path part) that must all be equal, produced lazily and in the order the first difference is wanted, for a container.

Two testers are equal if they have the same type and the same attributes. A child class with parameters must override _display_fields.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import BaseEqualityTester, get_default_registry
>>> class PointTester(BaseEqualityTester[tuple]):
...     def objects_are_equal(self, actual, expected, ctx):
...         return ((actual[0], expected[0], 0), (actual[1], expected[1], 1))
...
>>> registry = get_default_registry().copy()
>>> registry.register(tuple, PointTester(), exist_ok=True)
>>> Engine(EqualityConfig(registry=registry)).run((1, 2), (1, 2))
True
>>> Engine(EqualityConfig(registry=registry)).run((1, 2), (1, 3))
False
>>> PointTester() == PointTester()
True

coola.equality.BaseEqualityTester.objects_are_equal abstractmethod

objects_are_equal(
    actual: T, expected: T, ctx: Engine
) -> bool | Iterator[tuple[object, object, object]]

Test two objects that have the same type.

Parameters:

Name Type Description Default
actual T

The actual object.

required
expected T

The expected object, with the same type.

required
ctx Engine

The Engine of the comparison.

required

Returns:

Type Description
bool | Iterator[tuple[object, object, object]]

A verdict, or the child pairs that must all be equal.

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

coola.equality.ComparisonResult dataclass

Describe the outcome of comparing two objects.

bool(result) is True if the objects are equal, so the result can be used as a drop-in replacement for the bool returned by objects_are_equal.

Attributes:

Name Type Description
equal bool

True if the objects are equal.

path tuple[object, ...]

The location of the first difference, e.g. ("a", 2) for actual["a"][2]. The attributes (the fields of a dataclass) are AttributeName instances and are formatted as .name, and the elements of a set are Element instances formatted as {value}. Empty if equal, or if the difference is at the root.

reason str | None

A description of the first difference found, or None if the objects are equal.

Example
>>> from coola.equality import compare
>>> result = compare({"a": [1, 2, 3]}, {"a": [1, 2, 4]})
>>> bool(result)
False
>>> result.path
('a', 2)
>>> result.format_path()
"['a'][2]"

coola.equality.ComparisonResult.format_message

format_message(root: str = 'actual') -> str

Format a human-readable description of the result.

Parameters:

Name Type Description Default
root str

The name used for the root of the path.

'actual'

Returns:

Type Description
str

The message.

coola.equality.ComparisonResult.format_path

format_path(root: str = '') -> str

Format the path as an indexing expression.

Parameters:

Name Type Description Default
root str

The name prepended to the path.

''

Returns:

Type Description
str

The formatted path, e.g. data['a'][2].

coola.equality.DataclassEqualityTester

Bases: BaseEqualityTester[Any]

Implement an equality tester for dataclass instances.

The two instances have the same type, and the tester compares their fields, in the order of declaration. The fields with compare=False are ignored. The fields are compared with the engine, so they support the tolerance and equal_nan, and the location of a difference is the name of the field.

Example
>>> from dataclasses import dataclass
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> @dataclass
... class Point:
...     x: float
...
>>> engine = Engine(EqualityConfig())
>>> engine.run(Point(1.0), Point(1.0))
True
>>> engine.run(Point(1.0), Point(2.0))
False

coola.equality.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 two objects of the same type. The engine checks the identity and the type before the tester is called.

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

Raises:

Type Description
TypeError

if == raises a TypeError or a ValueError, or returns a value that cannot be converted to a bool (for example an array). False would claim that the objects are different when they were not compared at all.

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

coola.equality.DefaultEqualityTester.objects_are_equal

objects_are_equal(
    actual: object, expected: object, ctx: Engine
) -> bool

Test two objects with ==.

Parameters:

Name Type Description Default
actual object

The actual object.

required
expected object

The expected object, with the same type.

required
ctx Engine

The Engine of the comparison.

required

Returns:

Type Description
bool

True if the objects are equal, otherwise False (the reason is recorded in the engine).

Raises:

Type Description
TypeError

If == raises or returns a value that cannot be converted to a bool.

coola.equality.DifferenceCloseEqualityTester

Bases: BaseEqualityTester[Any]

Implement an equality tester for decimal.Decimal, fractions.Fraction and datetime.timedelta.

These types have no allclose, so the tester compares their difference with the tolerance (see difference_close). A Decimal NaN is equal to a NaN only if equal_nan is True, and atol of a timedelta is a number of seconds.

This tester is registered for Decimal, Fraction and timedelta in the default registry.

Example
>>> from coola.equality import Tolerance
>>> from decimal import Decimal
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import DifferenceCloseEqualityTester, get_default_registry
>>> registry = get_default_registry()
>>> tester = DifferenceCloseEqualityTester()
>>> tester.objects_are_equal(
...     Decimal("1.0"), Decimal("1.00001"), Engine(EqualityConfig())
... )
False
>>> tester.objects_are_equal(
...     Decimal("1.0"), Decimal("1.00001"), Engine(EqualityConfig(tolerance=Tolerance(atol=1e-3)))
... )
True

coola.equality.Element dataclass

Location of an element of a set in the path of a difference.

Parameters:

Name Type Description Default
value object

The element.

required
Example
>>> from coola.equality import Element
>>> Element(2)
Element(value=2)

coola.equality.Engine

Run the comparison of two objects.

One engine runs one comparison. For each pair of objects, the engine checks the identity, the strict type, finds the tester (the lookup is cached for the call) and calls it. A tester that uses the engine returns either a bool (the verdict) or an iterator of (actual, expected, path part) child pairs that must all be equal. The iterators are kept on an explicit stack, depth first, so the first difference in iteration order is the one found and nothing after it is visited. The stack use is constant: there is no RecursionError whatever the nesting.

Parameters:

Name Type Description Default
config EqualityConfig

The settings of the comparison (the registry that finds the tester of a type, the tolerance, equal_nan and max_depth). A config is immutable and can be shared.

required
explaining bool

If True, the reasons are built and the first difference is recorded in failure.

False

Attributes:

Name Type Description
atol float

The absolute tolerance.

rtol float

The relative tolerance.

equal_nan bool

If True, two NaNs are equal.

explaining

If True, the reasons are built.

failure tuple[tuple[object, ...], str | None] | None

None or, after a difference was found, the pair (path, reason) of the first difference.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> Engine(EqualityConfig()).run([1, {"a": 2}], [1, {"a": 2}])
True
>>> Engine(EqualityConfig()).run([1, {"a": 2}], [1, {"a": 3}])
False

coola.equality.Engine.fail

fail(
    reason: str | Callable[[], str], *path: object
) -> bool

Record the reason of the difference and return False.

A tester writes return ctx.fail(...).

Parameters:

Name Type Description Default
reason str | Callable[[], str]

The reason, or a callable that builds it. A callable is called only if explaining is True.

required
*path object

The path parts to add after the ones the engine knows, for example the index in an array.

()

Returns:

Type Description
bool

False.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> engine = Engine(EqualityConfig())
>>> engine.fail("values differ", 3)
False

coola.equality.Engine.find

find(data_type: type) -> BaseEqualityTester[Any]

Find the tester of a type.

The registry lookup is paid once per type and per comparison.

Parameters:

Name Type Description Default
data_type type

The type of the objects.

required

Returns:

Type Description
BaseEqualityTester[Any]

The tester of the type.

coola.equality.Engine.run

run(actual: object, expected: object) -> bool

Run the comparison.

Parameters:

Name Type Description Default
actual object

The actual object.

required
expected object

The expected object.

required

Returns:

Type Description
bool

True if the objects are equal, otherwise False. In the second case, failure holds the path and the reason of the first difference.

Raises:

Type Description
MaxDepthExceededError

If the nesting of the objects exceeds max_depth.

coola.equality.Engine.warn_once

warn_once(key: object, message: str) -> bool

Emit a RuntimeWarning once per comparison.

Parameters:

Name Type Description Default
key object

The key that identifies the warning.

required
message str

The message of the warning.

required

Returns:

Type Description
bool

True if the warning was emitted, i.e. the first time key is given in the comparison, otherwise False.

coola.equality.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. The two objects have the same type (the engine checks it) and the tester calls the equal method of the first one. An object without a callable equal is different.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import EqualEqualityTester, get_default_registry
>>> 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
...
>>> engine = Engine(EqualityConfig())
>>> tester = EqualEqualityTester()
>>> tester.objects_are_equal(MyFloat(42), MyFloat(42), engine)
True
>>> tester.objects_are_equal(MyFloat(42), MyFloat(1), engine)
False

coola.equality.EqualNanEqualityTester

Bases: BaseEqualityTester[object]

Implement an equality tester for objects with equal method that supports NaN equality.

This tester is designed for objects that implement an equal(other, equal_nan) method. The tester calls the equal method of the first object with equal_nan of the comparison. An object without a callable equal is different.

Example
>>> import math
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import EqualNanEqualityTester, get_default_registry
>>> 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
...
>>> registry = get_default_registry()
>>> tester = EqualNanEqualityTester()
>>> nan1, nan2 = MyFloat(float("nan")), MyFloat(float("nan"))
>>> tester.objects_are_equal(nan1, nan2, Engine(EqualityConfig()))
False
>>> tester.objects_are_equal(nan1, nan2, Engine(EqualityConfig(equal_nan=True)))
True

coola.equality.EqualityConfig dataclass

Define the config to control the comparison rules.

The config is immutable: it can be shared between comparisons and threads. The state of one comparison lives in the Engine.

Parameters:

Name Type Description Default
registry EqualityTesterRegistry

The registry with the equality tester to use.

create_default_registry()
tolerance Tolerance

The tolerance of the numbers. Defaults to no tolerance (exact comparison).

Tolerance()
equal_nan bool

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

False
max_depth int | None

The nesting limit: the maximum nesting level of the compared objects. A deeper structure raises MaxDepthExceededError (it is not a display depth like the max_depth of summarize). Must be positive, or None for no limit: the engine uses an explicit stack, so a deep structure does not raise a RecursionError. See the "Common options" section of the equality user guide. Defaults to None.

None

Raises:

Type Description
TypeError

if max_depth is neither None nor an int (a bool is rejected).

ValueError

if max_depth is not positive.

Example
>>> from coola.equality import Tolerance
>>> from coola.equality.config import EqualityConfig
>>> config = EqualityConfig(tolerance=Tolerance(atol=1e-6))
>>> config.atol
1e-06
>>> config.rtol
0.0

coola.equality.EqualityConfig.atol property

atol: float

The absolute tolerance.

coola.equality.EqualityConfig.rtol property

rtol: float

The relative tolerance.

coola.equality.EqualityConfig.__post_init__

__post_init__() -> None

Validate the configuration parameters after initialization.

Raises:

Type Description
TypeError

If max_depth is not an int.

ValueError

If max_depth is not positive.

coola.equality.EqualityTesterRegistry

Bases: TypeRegistry[BaseEqualityTester[Any]]

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 internal 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, T] | 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 dict[K, V]

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(
  (<class 'object'>): DefaultEqualityTester()
  (<class 'list'>): SequenceEqualityTester()
)
>>> config = EqualityConfig()
>>> registry.objects_are_equal([1, 2, 3], [1, 2, 3], config=config)
True

coola.equality.EqualityTesterRegistry.find_equality_tester

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

Type-specific alias for find: find the appropriate equality tester for a given type.

See TypeRegistry.resolve for the full behavior description (MRO resolution, caching, and the KeyError on no match).

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())
>>> # Sequence is not in list's MRO, so the registry falls back to DefaultEqualityTester
>>> tester = registry.find_equality_tester(list)
>>> tester
DefaultEqualityTester()

coola.equality.EqualityTesterRegistry.has_equality_tester

has_equality_tester(data_type: type) -> bool

Type-specific alias for has: check if an equality tester is explicitly registered for the given type.

See Registry.has for the full behavior description.

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

coola.equality.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.JaxArrayEqualityTester

Bases: BaseEqualityTester[ndarray]

Implement an equality tester for jax.numpy.ndarray.

Two arrays are equal if they have the same data type, the same shape and equal elements. The numbers are compared with the tolerance and equal_nan of the comparison, and a boolean array is exact. The location of a difference is the index of the first element that differs. Without 64-bit mode, JAX has no float64: the tolerance is computed in the widest dtype JAX has.

Raises:

Type Description
MissingPackageError

If jax is not installed.

Example
>>> import jax.numpy as jnp
>>> from coola.equality import compare
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import JaxArrayEqualityTester, get_default_registry
>>> engine = Engine(EqualityConfig())
>>> tester = JaxArrayEqualityTester()
>>> tester.objects_are_equal(jnp.ones((2, 3)), jnp.ones((2, 3)), engine)
True
>>> tester.objects_are_equal(jnp.ones((2, 3)), jnp.zeros((2, 3)), engine)
False
>>> compare(jnp.ones((2, 3)), jnp.zeros((2, 3))).path
(0, 0)

coola.equality.MappingEqualityTester

Bases: BaseEqualityTester['Mapping[Any, Any]']

Implement a mapping equality tester.

This tester handles dictionary-like objects (dict, Mapping ABC). The two mappings must have the same keys, and the values of the same key are compared in the order of actual. The keys are compared with their type, like the elements of sets: {1: "a"} and {True: "a"} are not the same mapping. With equal_nan=True, two NaN keys are the same key.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> registry = get_default_registry()
>>> engine = Engine(EqualityConfig())
>>> engine.run({"a": 1, "b": 2}, {"a": 1, "b": 2})
True
>>> engine.run({"a": 1, "b": 2}, {"a": 1, "b": 4})
False

coola.equality.MaxDepthExceededError

Bases: Exception

Raised when the nesting of the compared objects exceeds the max_depth of the equality configuration.

coola.equality.NumpyArrayEqualityTester

Bases: BaseEqualityTester[ndarray]

Implement an equality tester for numpy.ndarray.

Two arrays are equal if they have the same data type, the same shape and equal elements. The numbers are compared with the tolerance and equal_nan of the comparison (a boolean, a string and an instant are exact; atol of a duration is a number of seconds). The elements of an object array are compared by the engine, one by one. The location of a difference is the index of the first element that differs (the position in the flattened array for an object array).

Raises:

Type Description
MissingPackageError

If numpy is not installed.

Example
>>> import numpy as np
>>> from coola.equality import compare
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import NumpyArrayEqualityTester, get_default_registry
>>> engine = Engine(EqualityConfig())
>>> tester = NumpyArrayEqualityTester()
>>> tester.objects_are_equal(np.ones((2, 3)), np.ones((2, 3)), engine)
True
>>> tester.objects_are_equal(np.ones((2, 3)), np.zeros((2, 3)), engine)
False
>>> compare(np.ones((2, 3)), np.zeros((2, 3))).path
(0, 0)

coola.equality.NumpyMaskedArrayEqualityTester

Bases: BaseEqualityTester[MaskedArray]

Implement an equality tester for numpy.ma.MaskedArray.

Two masked arrays are equal if they have the same data type, the same shape, the same data (the masked values are compared too), the same mask and the same fill_value. The location of a difference is .data, .mask or .fill_value, and then the index in the array.

Raises:

Type Description
MissingPackageError

If numpy is not installed.

Example
>>> import numpy as np
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> engine = Engine(EqualityConfig())
>>> engine.run(
...     np.ma.array([1, 2, 3], mask=[0, 0, 1]), np.ma.array([1, 2, 3], mask=[0, 0, 1])
... )
True
>>> engine.run(
...     np.ma.array([1, 2, 3], mask=[0, 0, 1]), np.ma.array([1, 2, 3], mask=[0, 1, 1])
... )
False

coola.equality.NumpyTimeScalarEqualityTester

Bases: BaseEqualityTester[object]

Implement an equality tester for numpy.timedelta64 and numpy.datetime64 scalars.

NaT is equal to NaT only if equal_nan is True. A numpy.timedelta64 is a duration: the tolerances apply (atol is a number of seconds). A numpy.datetime64 is an instant and is compared exactly.

Raises:

Type Description
MissingPackageError

If numpy is not installed.

Example
>>> from coola.equality import Tolerance
>>> import numpy as np
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import NumpyTimeScalarEqualityTester, get_default_registry
>>> tester = NumpyTimeScalarEqualityTester()
>>> engine = Engine(EqualityConfig(tolerance=Tolerance(atol=1.0)))
>>> tester.objects_are_equal(np.timedelta64(1, "s"), np.timedelta64(2000, "ms"), engine)
True
>>> tester.objects_are_equal(
...     np.datetime64("2020-01-01"), np.datetime64("2020-01-02"), engine
... )
False

coola.equality.ObjectAttributesEqualityTester

Bases: BaseEqualityTester[Any]

Implement an equality tester for objects without value semantics.

A plain class, an exception or a functools.partial is equal to another object only if it is the same object, unless the class defines __eq__. This tester compares the attributes instead: the entries of vars, the slots, the args of an exception and the content of a functools.partial (see get_object_attributes). The attributes are compared through the registry, so the tolerance and equal_nan apply to them.

It is not registered by default, because it changes the result for the classes that rely on the identity: register it for the classes that must be compared by their attributes. The two objects have the same type, the same attribute names and equal values. The location of a difference is the name of the attribute.

Example
>>> from coola.equality import objects_are_equal
>>> from coola.equality.tester import (
...     DefaultEqualityTester,
...     EqualityTesterRegistry,
...     ObjectAttributesEqualityTester,
... )
>>> class Point:
...     def __init__(self, x):
...         self.x = x
...
>>> registry = EqualityTesterRegistry({object: DefaultEqualityTester()})
>>> objects_are_equal(Point(1), Point(1), registry=registry)
False
>>> registry.register(Point, ObjectAttributesEqualityTester())
>>> objects_are_equal(Point(1), Point(1), registry=registry)
True
>>> objects_are_equal(Point(1), Point(2), registry=registry)
False

coola.equality.OrderedMappingEqualityTester

Bases: MappingEqualityTester

Implement an ordered mapping equality tester.

This tester behaves like MappingEqualityTester, and additionally requires the keys to be in the same order. It is registered for collections.OrderedDict, for which Python's == is also order-sensitive.

Example
>>> from collections import OrderedDict
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> engine = Engine(EqualityConfig())
>>> engine.run(OrderedDict(a=1, b=2), OrderedDict(a=1, b=2))
True
>>> engine.run(OrderedDict(a=1, b=2), OrderedDict(b=2, a=1))
False

coola.equality.PydanticModelEqualityTester

Bases: BaseEqualityTester[BaseModel]

Implement an equality tester for pydantic.BaseModel.

The two models must have the same type, the same field values and the same extra values. The values are compared with the registry, so they support the tolerance and equal_nan. The private attributes are not compared, as the fields are what the model validates and serializes. The other entries of the instance __dict__ (for example the value of a functools.cached_property that was read on one model only) are ignored too. The location of a difference is the name of the field, or model_extra.

Raises:

Type Description
MissingPackageError

If pydantic is not installed.

Example
>>> from pydantic import BaseModel
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import PydanticModelEqualityTester, get_default_registry
>>> class Point(BaseModel):
...     x: float
...     y: float
...
>>> engine = Engine(EqualityConfig())
>>> engine.run(Point(x=1.0, y=2.0), Point(x=1.0, y=2.0))
True
>>> engine.run(Point(x=1.0, y=2.0), Point(x=1.0, y=3.0))
False

coola.equality.ScalarEqualityTester

Bases: BaseEqualityTester[float]

Implement a scalar equality tester.

This tester handles numeric scalar types (int, float, complex) with support for NaN equality and tolerance-based comparisons. Two NaNs are equal only if equal_nan is True, a bool and an enum member are labels and are compared exactly, and the other numbers are close if |a - b| <= atol + rtol * max(|a|, |b|).

This tester is registered for int, float and complex in the default registry.

Example
>>> from coola.equality import Tolerance
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import ScalarEqualityTester, get_default_registry
>>> registry = get_default_registry()
>>> tester = ScalarEqualityTester()
>>> tester.objects_are_equal(42.0, 42.0, Engine(EqualityConfig()))
True
>>> tester.objects_are_equal(42.0, 1.0, Engine(EqualityConfig()))
False
>>> tester.objects_are_equal(42.0, 42.0001, Engine(EqualityConfig(tolerance=Tolerance(atol=1e-3))))
True

coola.equality.ScipySparseEqualityTester

Bases: BaseEqualityTester[Any]

Implement an equality tester for scipy.sparse matrices and arrays.

Two sparse objects are equal if they have the same class (a matrix is not equal to an array, and the format is part of the class: csr_matrix, csc_matrix...; the engine checks it), the same shape, the same data type, the same non-zero positions and equal values. The values are compared with the tolerance and equal_nan of the comparison. The duplicate entries are summed and the explicitly stored zeros are ignored, so two objects that represent the same matrix are equal. The tolerance applies to the stored values: a stored value is never compared with an absent one. The location of a difference is the coordinates of the first value that differs.

Raises:

Type Description
MissingPackageError

If numpy or scipy is not installed.

Example
>>> from scipy import sparse
>>> from coola.equality import compare
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import ScipySparseEqualityTester
>>> engine = Engine(EqualityConfig())
>>> tester = ScipySparseEqualityTester()
>>> tester.objects_are_equal(sparse.eye(3, format="csr"), sparse.eye(3, format="csr"), engine)
True
>>> tester.objects_are_equal(
...     sparse.eye(3, format="csr"), 2 * sparse.eye(3, format="csr"), engine
... )
False
>>> compare(sparse.eye(3, format="csr"), 2 * sparse.eye(3, format="csr")).path
(0, 0)

coola.equality.SequenceEqualityTester

Bases: BaseEqualityTester['Sequence[Any]']

Implement a sequence equality tester.

This tester handles sequence types (list, tuple, deque, Sequence ABC). The two sequences must have the same length, and the items are compared in order. A sequence of exact scalars (int, str, bytes, bool and None) is compared at once.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> engine = Engine(EqualityConfig())
>>> engine.run([1, 2, 3], [1, 2, 3])
True
>>> engine.run([1, 2, 3], [1, 2, 4])
False
>>> engine.run([1, 2, 3], [1, 2])
False

coola.equality.SetEqualityTester

Bases: BaseEqualityTester['AbstractSet[Any]']

Implement a set equality tester.

This tester handles set and frozenset. Unlike ==, the elements are matched by hash and type, so equal_nan applies to them (the tolerance does not). The location of a difference is the element of actual that has no match in expected.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> engine = Engine(EqualityConfig())
>>> engine.run({1, 2, 3}, {1, 2, 3})
True
>>> engine.run({1, 2, 3}, {1, 2, 4})
False

coola.equality.TableEqualityTester

Bases: BaseEqualityTester[Any]

Implement an equality tester for the tables of pandas, polars and pyarrow.

The structure is compared first, then the columns one by one, each by its kind (see Column). The comparison stops at the first column that differs. A table is a leaf for the engine: the difference is reported as a whole. The reason says what differs in the structure (the shapes, the data types, the index, the names of the columns, ...) or, for the values, the name of the first column that differs, the first row that differs and the two values. The row is found only when the difference is explained.

Parameters:

Name Type Description Default
adapter Callable[[Any], Table]

The function that converts an object to a Table.

required
Example
>>> import pandas as pd
>>> from coola.equality import compare
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import TableEqualityTester, get_default_registry
>>> from coola.equality.tester.pandas import pandas_frame
>>> engine = Engine(EqualityConfig())
>>> tester = TableEqualityTester(pandas_frame)
>>> tester.objects_are_equal(
...     pd.DataFrame({"a": [1, 2]}), pd.DataFrame({"a": [1, 2]}), engine
... )
True
>>> tester.objects_are_equal(
...     pd.DataFrame({"a": [1, 2]}), pd.DataFrame({"a": [1, 3]}), engine
... )
False
>>> print(compare(pd.DataFrame({"a": [1, 2]}), pd.DataFrame({"a": [1, 3]})).reason)
tables have different values: column 'a' differs at row 1: 2 vs 3

coola.equality.Tolerance dataclass

Define the tolerance of a comparison.

Two numbers actual and expected are close if |actual - expected| <= atol + rtol * max(|actual|, |expected|) (the numpy.isclose rule made symmetric). The default is no tolerance (exact comparison).

Parameters:

Name Type Description Default
rtol float

The relative tolerance parameter. Must be non-negative and finite. Defaults to 0.0.

0.0
atol float

The absolute tolerance parameter. Must be non-negative and finite. Defaults to 0.0.

0.0

Raises:

Type Description
TypeError

If rtol or atol is not a real number (a bool is rejected).

ValueError

If rtol or atol is negative or not finite.

Example
>>> from coola.equality import Tolerance
>>> Tolerance()
Tolerance(rtol=0.0, atol=0.0)
>>> Tolerance.numpy_default()
Tolerance(rtol=1e-05, atol=1e-08)

coola.equality.Tolerance.numpy_default classmethod

numpy_default() -> Tolerance

Create the tolerance used by objects_are_allclose.

The values (rtol=1e-5, atol=1e-8) are the defaults of numpy.allclose.

Returns:

Type Description
Tolerance

The tolerance Tolerance(rtol=1e-5, atol=1e-8).

Example
>>> from coola.equality import Tolerance
>>> Tolerance.numpy_default()
Tolerance(rtol=1e-05, atol=1e-08)

coola.equality.TolerantEqualEqualityTester

Bases: BaseEqualityTester[object]

Implement an equality tester for objects that support both allclose and equal methods.

This tester is designed for objects that implement both an allclose(other, rtol, atol, equal_nan) method for tolerance-based comparison and an equal(other, equal_nan) method for exact comparison. The tester calls allclose when a tolerance is not zero, otherwise equal. An object that does not have both methods is different.

Example
>>> from coola.equality import Tolerance
>>> import math
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import TolerantEqualEqualityTester, get_default_registry
>>> class MyFloat:
...     def __init__(self, value: float) -> None:
...         self._value = float(value)
...     def allclose(
...         self,
...         other: object,
...         rtol: float = 1e-5,
...         atol: float = 1e-8,
...         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 math.isclose(self._value, other._value, rel_tol=rtol, abs_tol=atol)
...     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
...
>>> registry = get_default_registry()
>>> tester = TolerantEqualEqualityTester()
>>> tester.objects_are_equal(MyFloat(42), MyFloat(42), Engine(EqualityConfig()))
True
>>> tester.objects_are_equal(MyFloat(1.0), MyFloat(1.4), Engine(EqualityConfig()))
False
>>> tester.objects_are_equal(
...     MyFloat(1.0), MyFloat(1.4), Engine(EqualityConfig(tolerance=Tolerance(atol=0.5)))
... )
True

coola.equality.TorchPackedSequenceEqualityTester

Bases: BaseEqualityTester[PackedSequence]

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

This tester compares the data tensor and the metadata of two packed sequences: batch_sizes, sorted_indices and unsorted_indices. The location of a difference is the name of the attribute.

Raises:

Type Description
MissingPackageError

If torch is not installed.

Example
>>> import torch
>>> from torch.nn.utils.rnn import pack_padded_sequence
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> engine = Engine(EqualityConfig())
>>> def make(value):
...     return pack_padded_sequence(
...         torch.tensor([[1.0, value], [3.0, 4.0]]),
...         lengths=torch.tensor([2, 1]),
...         batch_first=True,
...         enforce_sorted=False,
...     )
...
>>> engine.run(make(2.0), make(2.0))
True
>>> engine.run(make(2.0), make(5.0))
False

coola.equality.TorchTensorEqualityTester

Bases: BaseEqualityTester[Tensor]

Implement an equality tester for torch.Tensor.

Two tensors are equal if they have the same data type, shape, device, layout and equal elements. The numbers are compared with the tolerance and equal_nan of the comparison, and a boolean tensor is exact. The tensors on the meta device have no data: only the metadata is compared. The location of a difference is the index of the first element that differs.

Raises:

Type Description
MissingPackageError

If torch is not installed.

Example
>>> import torch
>>> from coola.equality import compare
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import TorchTensorEqualityTester, get_default_registry
>>> engine = Engine(EqualityConfig())
>>> tester = TorchTensorEqualityTester()
>>> tester.objects_are_equal(torch.ones(2, 3), torch.ones(2, 3), engine)
True
>>> tester.objects_are_equal(torch.ones(2, 3), torch.zeros(2, 3), engine)
False
>>> compare(torch.ones(2, 3), torch.zeros(2, 3)).path
(0, 0)

coola.equality.XarrayDataArrayEqualityTester

Bases: BaseEqualityTester[DataArray]

Implement an equality tester for xarray.DataArray.

This tester compares the variable (data, dimensions and attributes), the name and the coordinate variables of two DataArrays. The location of a difference is the name of the attribute.

Raises:

Type Description
MissingPackageError

If xarray is not installed.

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

coola.equality.XarrayDatasetEqualityTester

Bases: BaseEqualityTester[Dataset]

Implement an equality tester for xarray.Dataset.

This tester compares the data variables, the coordinates and the attributes of two Datasets. The location of a difference is the name of the attribute.

Raises:

Type Description
MissingPackageError

If xarray is not installed.

Example
>>> import numpy as np
>>> import xarray as xr
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> engine = Engine(EqualityConfig())
>>> engine.run(
...     xr.Dataset({"x": ("z", np.arange(6))}), xr.Dataset({"x": ("z", np.arange(6))})
... )
True
>>> engine.run(
...     xr.Dataset({"x": ("z", np.ones(6))}), xr.Dataset({"x": ("z", np.zeros(6))})
... )
False

coola.equality.XarrayVariableEqualityTester

Bases: BaseEqualityTester[Variable]

Implement an equality tester for xarray.Variable.

This tester compares the data, the dimension names and the attributes of two Variables. The location of a difference is the name of the attribute.

Raises:

Type Description
MissingPackageError

If xarray is not installed.

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

coola.equality.assert_objects_allclose

assert_objects_allclose(
    actual: object,
    expected: object,
    *,
    rtol: float = 1e-05,
    atol: float = 1e-08,
    equal_nan: bool = False,
    max_depth: int | None = None,
    registry: EqualityTesterRegistry | None = None,
    root: str = "actual",
) -> None

Assert that two objects are equal within a tolerance.

Parameters:

Name Type Description Default
actual object

The actual object.

required
expected object

The expected object.

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, treat two NaN values as equal.

False
max_depth int | None

The nesting limit: the maximum nesting level of the compared objects. A deeper structure raises MaxDepthExceededError (it is not a display depth like the max_depth of summarize). Must be positive, or None for no limit: the engine uses an explicit stack, so a deep structure does not raise a RecursionError. See the "Common options" section of the equality user guide. Defaults to None.

None
registry EqualityTesterRegistry | None

Registry used to resolve type-specific equality testers. If None, the default registry is used.

None
root str

The name used for the root of the path in the message.

'actual'

Raises:

Type Description
AssertionError

If the objects are not equal within the tolerances. The message contains the path to the first difference.

ValueError

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

MaxDepthExceededError

If the nesting of the objects exceeds max_depth.

Example
>>> from coola.equality import assert_objects_allclose
>>> assert_objects_allclose({"a": [1.0, 2.0]}, {"a": [1.0, 2.0 + 1e-9]})
>>> assert_objects_allclose([1.0], [1.5], atol=0.1, root="data")
Traceback (most recent call last):
    ...
AssertionError: objects are not equal at data[0]
numbers are different:
  actual   : 1.0
  expected : 1.5

coola.equality.assert_objects_equal

assert_objects_equal(
    actual: object,
    expected: object,
    *,
    equal_nan: bool = False,
    max_depth: int | None = None,
    registry: EqualityTesterRegistry | None = None,
    root: str = "actual",
) -> None

Assert that two objects are equal.

Parameters:

Name Type Description Default
actual object

The actual object.

required
expected object

The expected object.

required
equal_nan bool

If True, treat two NaN values as equal.

False
max_depth int | None

The nesting limit: the maximum nesting level of the compared objects. A deeper structure raises MaxDepthExceededError (it is not a display depth like the max_depth of summarize). Must be positive, or None for no limit: the engine uses an explicit stack, so a deep structure does not raise a RecursionError. See the "Common options" section of the equality user guide. Defaults to None.

None
registry EqualityTesterRegistry | None

Registry used to resolve type-specific equality testers. If None, the default registry is used.

None
root str

The name used for the root of the path in the message.

'actual'

Raises:

Type Description
AssertionError

If the objects are not equal. The message contains the path to the first difference.

ValueError

If max_depth is not positive.

MaxDepthExceededError

If the nesting of the objects exceeds max_depth.

Example
>>> from coola.equality import assert_objects_equal
>>> assert_objects_equal({"a": [1, 2]}, {"a": [1, 2]})
>>> assert_objects_equal({"a": [1, 2]}, {"a": [1, 3]}, root="data")
Traceback (most recent call last):
    ...
AssertionError: objects are not equal at data['a'][1]
numbers are different:
  actual   : 2
  expected : 3

coola.equality.compare

compare(
    actual: object,
    expected: object,
    *,
    rtol: float = 0.0,
    atol: float = 0.0,
    equal_nan: bool = False,
    max_depth: int | None = None,
    registry: EqualityTesterRegistry | None = None,
) -> ComparisonResult

Compare two objects and explain the first difference.

Exact equality is used by default. Set rtol or atol to compare within a tolerance: rtol=1e-5 and atol=1e-8 are the defaults of objects_are_allclose. Comparison stops at the first difference, and only this difference is explained.

Note

The difference is the first failure recorded by the engine (ctx.fail of a tester). Nothing is logged, and no global state is modified, so this function can be called concurrently from several threads.

Parameters:

Name Type Description Default
actual object

The actual object.

required
expected object

The expected object.

required
rtol float

The relative tolerance parameter. Must be non-negative.

0.0
atol float

The absolute tolerance parameter. Must be non-negative.

0.0
equal_nan bool

If True, treat two NaN values as equal.

False
max_depth int | None

The nesting limit: the maximum nesting level of the compared objects. A deeper structure raises MaxDepthExceededError (it is not a display depth like the max_depth of summarize). Must be positive, or None for no limit: the engine uses an explicit stack, so a deep structure does not raise a RecursionError. See the "Common options" section of the equality user guide. Defaults to None.

None
registry EqualityTesterRegistry | None

Registry used to resolve type-specific equality testers. If None, the default registry is used.

None

Returns:

Type Description
ComparisonResult

The comparison result.

Raises:

Type Description
ValueError

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

MaxDepthExceededError

If the nesting of the objects exceeds max_depth.

Example
>>> from coola.equality import compare
>>> compare([1, 2], [1, 2]).equal
True
>>> result = compare({"a": 1}, {"a": 2})
>>> result.path
('a',)
>>> result.equal
False
>>> compare(1.0, 1.0 + 1e-9).equal
False
>>> compare(1.0, 1.0 + 1e-9, rtol=1e-5, atol=1e-8).equal
True

coola.equality.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, array.array, Sequence ABC) - Mappings (dict, OrderedDict, MappingProxyType, 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.objects_are_allclose

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

Indicate if two objects are equal within a tolerance.

Note

The types are compared first, so 1 and 1.0 are different (as are [1] and [1.0]), even with a tolerance. A boolean is a label, not a quantity: True and False are never close, even with atol=1 (also for numpy.bool_ and for boolean arrays).

An object is always equal to itself, even if it contains NaN and equal_nan=False: [n] equals [n] for n = float("nan"), but not [float("nan")]. Two distinct objects that contain NaN are equal only with equal_nan=True.

This also holds for np.nan and math.nan, which are single objects: [math.nan] equals [math.nan], while [float("nan")] does not equal another [float("nan")]. Use equal_nan=True if the data can contain a NaN.

Parameters:

Name Type Description Default
actual object

The actual object.

required
expected object

The expected object.

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, treat two NaN values as equal.

False
max_depth int | None

The nesting limit: the maximum nesting level of the compared objects. A deeper structure raises MaxDepthExceededError (it is not a display depth like the max_depth of summarize). Must be positive, or None for no limit: the engine uses an explicit stack, so a deep structure does not raise a RecursionError. See the "Common options" section of the equality user guide. Defaults to None.

None
registry EqualityTesterRegistry | None

Registry used to resolve type-specific equality testers. If None, the default registry is used.

None

Returns:

Type Description
bool

True if the two objects are equal within tolerances, otherwise

bool

False.

Raises:

Type Description
TypeError

If rtol or atol is not a real number, or if max_depth is not an int (a bool is rejected).

ValueError

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

MaxDepthExceededError

If the nesting of the objects exceeds max_depth.

Example
>>> from coola.equality import objects_are_allclose
>>> objects_are_allclose([1.0, 2.0], [1.0 + 1e-8, 2.0])
True
>>> objects_are_allclose([1.0, 2.0], [1.0, 2.1], atol=1e-3, rtol=0.0)
False
>>> objects_are_allclose([float("nan")], [float("nan")], equal_nan=False)
False
>>> objects_are_allclose([float("nan")], [float("nan")], equal_nan=True)
True

coola.equality.objects_are_equal

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

Indicate if two objects are equal or not.

Note

The types are compared first, so 1 and 1.0 are different (as are [1] and [1.0]). There is no tolerance: use objects_are_allclose to compare numbers within a tolerance.

An object is always equal to itself, even if it contains NaN and equal_nan=False: [n] equals [n] for n = float("nan"), but not [float("nan")]. Two distinct objects that contain NaN are equal only with equal_nan=True.

This also holds for np.nan and math.nan, which are single objects: [math.nan] equals [math.nan], while [float("nan")] does not equal another [float("nan")]. Use equal_nan=True if the data can contain a NaN.

Parameters:

Name Type Description Default
actual object

The actual object.

required
expected object

The expected object.

required
equal_nan bool

If True, treat two NaN values as equal.

False
max_depth int | None

The nesting limit: the maximum nesting level of the compared objects. A deeper structure raises MaxDepthExceededError (it is not a display depth like the max_depth of summarize). Must be positive, or None for no limit: the engine uses an explicit stack, so a deep structure does not raise a RecursionError. See the "Common options" section of the equality user guide. Defaults to None.

None
registry EqualityTesterRegistry | None

Registry used to resolve type-specific equality testers. If None, the default registry is used.

None

Returns:

Type Description
bool

True if the two objects are exactly equal, otherwise False.

Raises:

Type Description
TypeError

If max_depth is not an int (a bool is rejected).

ValueError

If max_depth is not positive.

MaxDepthExceededError

If the nesting of the objects exceeds max_depth.

Example
>>> from coola.equality import objects_are_equal
>>> objects_are_equal([1, {"a": 2}], [1, {"a": 2}])
True
>>> objects_are_equal([1, {"a": 2}], [1, {"a": 3}])
False

coola.equality.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 objects_are_equal(self, actual: MyType, expected: MyType, ctx) -> bool:
...         return actual.value == expected.value or ctx.fail("values differ")
...
>>> register_equality_testers({MyType: MyEqualityTester()})

coola.equality.config

Define the equality configuration.

coola.equality.config.EqualityConfig dataclass

Define the config to control the comparison rules.

The config is immutable: it can be shared between comparisons and threads. The state of one comparison lives in the Engine.

Parameters:

Name Type Description Default
registry EqualityTesterRegistry

The registry with the equality tester to use.

create_default_registry()
tolerance Tolerance

The tolerance of the numbers. Defaults to no tolerance (exact comparison).

Tolerance()
equal_nan bool

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

False
max_depth int | None

The nesting limit: the maximum nesting level of the compared objects. A deeper structure raises MaxDepthExceededError (it is not a display depth like the max_depth of summarize). Must be positive, or None for no limit: the engine uses an explicit stack, so a deep structure does not raise a RecursionError. See the "Common options" section of the equality user guide. Defaults to None.

None

Raises:

Type Description
TypeError

if max_depth is neither None nor an int (a bool is rejected).

ValueError

if max_depth is not positive.

Example
>>> from coola.equality import Tolerance
>>> from coola.equality.config import EqualityConfig
>>> config = EqualityConfig(tolerance=Tolerance(atol=1e-6))
>>> config.atol
1e-06
>>> config.rtol
0.0

coola.equality.config.EqualityConfig.atol property

atol: float

The absolute tolerance.

coola.equality.config.EqualityConfig.rtol property

rtol: float

The relative tolerance.

coola.equality.config.EqualityConfig.__post_init__

__post_init__() -> None

Validate the configuration parameters after initialization.

Raises:

Type Description
TypeError

If max_depth is not an int.

ValueError

If max_depth is not positive.

coola.equality.config.create_default_registry

create_default_registry() -> EqualityTesterRegistry

Create the default equality tester registry.

This helper is used as a dataclasses.field default factory to avoid importing the registry interface at module import time.

Returns:

Type Description
EqualityTesterRegistry

The default equality tester registry.

coola.equality.engine

Implement the engine that runs a comparison with an explicit stack.

The engine owns the invariants of a comparison: identity, strict type, depth, cycles and the location of the first difference. A tester that uses the engine never recurses: it returns a verdict, or the child pairs that must all be equal.

coola.equality.engine.Element dataclass

Location of an element of a set in the path of a difference.

Parameters:

Name Type Description Default
value object

The element.

required
Example
>>> from coola.equality import Element
>>> Element(2)
Element(value=2)

coola.equality.engine.Engine

Run the comparison of two objects.

One engine runs one comparison. For each pair of objects, the engine checks the identity, the strict type, finds the tester (the lookup is cached for the call) and calls it. A tester that uses the engine returns either a bool (the verdict) or an iterator of (actual, expected, path part) child pairs that must all be equal. The iterators are kept on an explicit stack, depth first, so the first difference in iteration order is the one found and nothing after it is visited. The stack use is constant: there is no RecursionError whatever the nesting.

Parameters:

Name Type Description Default
config EqualityConfig

The settings of the comparison (the registry that finds the tester of a type, the tolerance, equal_nan and max_depth). A config is immutable and can be shared.

required
explaining bool

If True, the reasons are built and the first difference is recorded in failure.

False

Attributes:

Name Type Description
atol float

The absolute tolerance.

rtol float

The relative tolerance.

equal_nan bool

If True, two NaNs are equal.

explaining

If True, the reasons are built.

failure tuple[tuple[object, ...], str | None] | None

None or, after a difference was found, the pair (path, reason) of the first difference.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> Engine(EqualityConfig()).run([1, {"a": 2}], [1, {"a": 2}])
True
>>> Engine(EqualityConfig()).run([1, {"a": 2}], [1, {"a": 3}])
False

coola.equality.engine.Engine.fail

fail(
    reason: str | Callable[[], str], *path: object
) -> bool

Record the reason of the difference and return False.

A tester writes return ctx.fail(...).

Parameters:

Name Type Description Default
reason str | Callable[[], str]

The reason, or a callable that builds it. A callable is called only if explaining is True.

required
*path object

The path parts to add after the ones the engine knows, for example the index in an array.

()

Returns:

Type Description
bool

False.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> engine = Engine(EqualityConfig())
>>> engine.fail("values differ", 3)
False

coola.equality.engine.Engine.find

find(data_type: type) -> BaseEqualityTester[Any]

Find the tester of a type.

The registry lookup is paid once per type and per comparison.

Parameters:

Name Type Description Default
data_type type

The type of the objects.

required

Returns:

Type Description
BaseEqualityTester[Any]

The tester of the type.

coola.equality.engine.Engine.run

run(actual: object, expected: object) -> bool

Run the comparison.

Parameters:

Name Type Description Default
actual object

The actual object.

required
expected object

The expected object.

required

Returns:

Type Description
bool

True if the objects are equal, otherwise False. In the second case, failure holds the path and the reason of the first difference.

Raises:

Type Description
MaxDepthExceededError

If the nesting of the objects exceeds max_depth.

coola.equality.engine.Engine.warn_once

warn_once(key: object, message: str) -> bool

Emit a RuntimeWarning once per comparison.

Parameters:

Name Type Description Default
key object

The key that identifies the warning.

required
message str

The message of the warning.

required

Returns:

Type Description
bool

True if the warning was emitted, i.e. the first time key is given in the comparison, otherwise False.

coola.equality.tester

Type-aware equality testers used by coola.equality.

This package exposes a registry-based dispatch system with built-in testers for common Python containers and optional scientific types. Extend the default behavior by registering custom testers in the default registry.

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: InlineDisplayMixin, ABC, Generic[T]

Define the base class to implement an equality operator.

The engine owns the invariants of a comparison (identity, strict type, depth, cycles, location of the first difference). A tester never recurses: objects_are_equal receives the two objects, which have the same type, and the engine, and returns

  • a verdict: True, or ctx.fail(reason, *path) (which returns False) for a leaf;
  • or the child pairs (actual, expected, path part) that must all be equal, produced lazily and in the order the first difference is wanted, for a container.

Two testers are equal if they have the same type and the same attributes. A child class with parameters must override _display_fields.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import BaseEqualityTester, get_default_registry
>>> class PointTester(BaseEqualityTester[tuple]):
...     def objects_are_equal(self, actual, expected, ctx):
...         return ((actual[0], expected[0], 0), (actual[1], expected[1], 1))
...
>>> registry = get_default_registry().copy()
>>> registry.register(tuple, PointTester(), exist_ok=True)
>>> Engine(EqualityConfig(registry=registry)).run((1, 2), (1, 2))
True
>>> Engine(EqualityConfig(registry=registry)).run((1, 2), (1, 3))
False
>>> PointTester() == PointTester()
True

coola.equality.tester.BaseEqualityTester.objects_are_equal abstractmethod

objects_are_equal(
    actual: T, expected: T, ctx: Engine
) -> bool | Iterator[tuple[object, object, object]]

Test two objects that have the same type.

Parameters:

Name Type Description Default
actual T

The actual object.

required
expected T

The expected object, with the same type.

required
ctx Engine

The Engine of the comparison.

required

Returns:

Type Description
bool | Iterator[tuple[object, object, object]]

A verdict, or the child pairs that must all be equal.

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

coola.equality.tester.DataclassEqualityTester

Bases: BaseEqualityTester[Any]

Implement an equality tester for dataclass instances.

The two instances have the same type, and the tester compares their fields, in the order of declaration. The fields with compare=False are ignored. The fields are compared with the engine, so they support the tolerance and equal_nan, and the location of a difference is the name of the field.

Example
>>> from dataclasses import dataclass
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> @dataclass
... class Point:
...     x: float
...
>>> engine = Engine(EqualityConfig())
>>> engine.run(Point(1.0), Point(1.0))
True
>>> engine.run(Point(1.0), Point(2.0))
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 two objects of the same type. The engine checks the identity and the type before the tester is called.

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

Raises:

Type Description
TypeError

if == raises a TypeError or a ValueError, or returns a value that cannot be converted to a bool (for example an array). False would claim that the objects are different when they were not compared at all.

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

coola.equality.tester.DefaultEqualityTester.objects_are_equal

objects_are_equal(
    actual: object, expected: object, ctx: Engine
) -> bool

Test two objects with ==.

Parameters:

Name Type Description Default
actual object

The actual object.

required
expected object

The expected object, with the same type.

required
ctx Engine

The Engine of the comparison.

required

Returns:

Type Description
bool

True if the objects are equal, otherwise False (the reason is recorded in the engine).

Raises:

Type Description
TypeError

If == raises or returns a value that cannot be converted to a bool.

coola.equality.tester.DifferenceCloseEqualityTester

Bases: BaseEqualityTester[Any]

Implement an equality tester for decimal.Decimal, fractions.Fraction and datetime.timedelta.

These types have no allclose, so the tester compares their difference with the tolerance (see difference_close). A Decimal NaN is equal to a NaN only if equal_nan is True, and atol of a timedelta is a number of seconds.

This tester is registered for Decimal, Fraction and timedelta in the default registry.

Example
>>> from coola.equality import Tolerance
>>> from decimal import Decimal
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import DifferenceCloseEqualityTester, get_default_registry
>>> registry = get_default_registry()
>>> tester = DifferenceCloseEqualityTester()
>>> tester.objects_are_equal(
...     Decimal("1.0"), Decimal("1.00001"), Engine(EqualityConfig())
... )
False
>>> tester.objects_are_equal(
...     Decimal("1.0"), Decimal("1.00001"), Engine(EqualityConfig(tolerance=Tolerance(atol=1e-3)))
... )
True

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. The two objects have the same type (the engine checks it) and the tester calls the equal method of the first one. An object without a callable equal is different.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import EqualEqualityTester, get_default_registry
>>> 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
...
>>> engine = Engine(EqualityConfig())
>>> tester = EqualEqualityTester()
>>> tester.objects_are_equal(MyFloat(42), MyFloat(42), engine)
True
>>> tester.objects_are_equal(MyFloat(42), MyFloat(1), engine)
False

coola.equality.tester.EqualNanEqualityTester

Bases: BaseEqualityTester[object]

Implement an equality tester for objects with equal method that supports NaN equality.

This tester is designed for objects that implement an equal(other, equal_nan) method. The tester calls the equal method of the first object with equal_nan of the comparison. An object without a callable equal is different.

Example
>>> import math
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import EqualNanEqualityTester, get_default_registry
>>> 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
...
>>> registry = get_default_registry()
>>> tester = EqualNanEqualityTester()
>>> nan1, nan2 = MyFloat(float("nan")), MyFloat(float("nan"))
>>> tester.objects_are_equal(nan1, nan2, Engine(EqualityConfig()))
False
>>> tester.objects_are_equal(nan1, nan2, Engine(EqualityConfig(equal_nan=True)))
True

coola.equality.tester.EqualityTesterRegistry

Bases: TypeRegistry[BaseEqualityTester[Any]]

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 internal 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, T] | 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 dict[K, V]

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(
  (<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]

Type-specific alias for find: find the appropriate equality tester for a given type.

See TypeRegistry.resolve for the full behavior description (MRO resolution, caching, and the KeyError on no match).

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())
>>> # Sequence is not in list's MRO, so the registry falls back to DefaultEqualityTester
>>> tester = registry.find_equality_tester(list)
>>> tester
DefaultEqualityTester()

coola.equality.tester.EqualityTesterRegistry.has_equality_tester

has_equality_tester(data_type: type) -> bool

Type-specific alias for has: check if an equality tester is explicitly registered for the given type.

See Registry.has for the full behavior description.

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

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.JaxArrayEqualityTester

Bases: BaseEqualityTester[ndarray]

Implement an equality tester for jax.numpy.ndarray.

Two arrays are equal if they have the same data type, the same shape and equal elements. The numbers are compared with the tolerance and equal_nan of the comparison, and a boolean array is exact. The location of a difference is the index of the first element that differs. Without 64-bit mode, JAX has no float64: the tolerance is computed in the widest dtype JAX has.

Raises:

Type Description
MissingPackageError

If jax is not installed.

Example
>>> import jax.numpy as jnp
>>> from coola.equality import compare
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import JaxArrayEqualityTester, get_default_registry
>>> engine = Engine(EqualityConfig())
>>> tester = JaxArrayEqualityTester()
>>> tester.objects_are_equal(jnp.ones((2, 3)), jnp.ones((2, 3)), engine)
True
>>> tester.objects_are_equal(jnp.ones((2, 3)), jnp.zeros((2, 3)), engine)
False
>>> compare(jnp.ones((2, 3)), jnp.zeros((2, 3))).path
(0, 0)

coola.equality.tester.MappingEqualityTester

Bases: BaseEqualityTester['Mapping[Any, Any]']

Implement a mapping equality tester.

This tester handles dictionary-like objects (dict, Mapping ABC). The two mappings must have the same keys, and the values of the same key are compared in the order of actual. The keys are compared with their type, like the elements of sets: {1: "a"} and {True: "a"} are not the same mapping. With equal_nan=True, two NaN keys are the same key.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> registry = get_default_registry()
>>> engine = Engine(EqualityConfig())
>>> engine.run({"a": 1, "b": 2}, {"a": 1, "b": 2})
True
>>> engine.run({"a": 1, "b": 2}, {"a": 1, "b": 4})
False

coola.equality.tester.NumpyArrayEqualityTester

Bases: BaseEqualityTester[ndarray]

Implement an equality tester for numpy.ndarray.

Two arrays are equal if they have the same data type, the same shape and equal elements. The numbers are compared with the tolerance and equal_nan of the comparison (a boolean, a string and an instant are exact; atol of a duration is a number of seconds). The elements of an object array are compared by the engine, one by one. The location of a difference is the index of the first element that differs (the position in the flattened array for an object array).

Raises:

Type Description
MissingPackageError

If numpy is not installed.

Example
>>> import numpy as np
>>> from coola.equality import compare
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import NumpyArrayEqualityTester, get_default_registry
>>> engine = Engine(EqualityConfig())
>>> tester = NumpyArrayEqualityTester()
>>> tester.objects_are_equal(np.ones((2, 3)), np.ones((2, 3)), engine)
True
>>> tester.objects_are_equal(np.ones((2, 3)), np.zeros((2, 3)), engine)
False
>>> compare(np.ones((2, 3)), np.zeros((2, 3))).path
(0, 0)

coola.equality.tester.NumpyMaskedArrayEqualityTester

Bases: BaseEqualityTester[MaskedArray]

Implement an equality tester for numpy.ma.MaskedArray.

Two masked arrays are equal if they have the same data type, the same shape, the same data (the masked values are compared too), the same mask and the same fill_value. The location of a difference is .data, .mask or .fill_value, and then the index in the array.

Raises:

Type Description
MissingPackageError

If numpy is not installed.

Example
>>> import numpy as np
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> engine = Engine(EqualityConfig())
>>> engine.run(
...     np.ma.array([1, 2, 3], mask=[0, 0, 1]), np.ma.array([1, 2, 3], mask=[0, 0, 1])
... )
True
>>> engine.run(
...     np.ma.array([1, 2, 3], mask=[0, 0, 1]), np.ma.array([1, 2, 3], mask=[0, 1, 1])
... )
False

coola.equality.tester.NumpyTimeScalarEqualityTester

Bases: BaseEqualityTester[object]

Implement an equality tester for numpy.timedelta64 and numpy.datetime64 scalars.

NaT is equal to NaT only if equal_nan is True. A numpy.timedelta64 is a duration: the tolerances apply (atol is a number of seconds). A numpy.datetime64 is an instant and is compared exactly.

Raises:

Type Description
MissingPackageError

If numpy is not installed.

Example
>>> from coola.equality import Tolerance
>>> import numpy as np
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import NumpyTimeScalarEqualityTester, get_default_registry
>>> tester = NumpyTimeScalarEqualityTester()
>>> engine = Engine(EqualityConfig(tolerance=Tolerance(atol=1.0)))
>>> tester.objects_are_equal(np.timedelta64(1, "s"), np.timedelta64(2000, "ms"), engine)
True
>>> tester.objects_are_equal(
...     np.datetime64("2020-01-01"), np.datetime64("2020-01-02"), engine
... )
False

coola.equality.tester.ObjectAttributesEqualityTester

Bases: BaseEqualityTester[Any]

Implement an equality tester for objects without value semantics.

A plain class, an exception or a functools.partial is equal to another object only if it is the same object, unless the class defines __eq__. This tester compares the attributes instead: the entries of vars, the slots, the args of an exception and the content of a functools.partial (see get_object_attributes). The attributes are compared through the registry, so the tolerance and equal_nan apply to them.

It is not registered by default, because it changes the result for the classes that rely on the identity: register it for the classes that must be compared by their attributes. The two objects have the same type, the same attribute names and equal values. The location of a difference is the name of the attribute.

Example
>>> from coola.equality import objects_are_equal
>>> from coola.equality.tester import (
...     DefaultEqualityTester,
...     EqualityTesterRegistry,
...     ObjectAttributesEqualityTester,
... )
>>> class Point:
...     def __init__(self, x):
...         self.x = x
...
>>> registry = EqualityTesterRegistry({object: DefaultEqualityTester()})
>>> objects_are_equal(Point(1), Point(1), registry=registry)
False
>>> registry.register(Point, ObjectAttributesEqualityTester())
>>> objects_are_equal(Point(1), Point(1), registry=registry)
True
>>> objects_are_equal(Point(1), Point(2), registry=registry)
False

coola.equality.tester.OrderedMappingEqualityTester

Bases: MappingEqualityTester

Implement an ordered mapping equality tester.

This tester behaves like MappingEqualityTester, and additionally requires the keys to be in the same order. It is registered for collections.OrderedDict, for which Python's == is also order-sensitive.

Example
>>> from collections import OrderedDict
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> engine = Engine(EqualityConfig())
>>> engine.run(OrderedDict(a=1, b=2), OrderedDict(a=1, b=2))
True
>>> engine.run(OrderedDict(a=1, b=2), OrderedDict(b=2, a=1))
False

coola.equality.tester.PydanticModelEqualityTester

Bases: BaseEqualityTester[BaseModel]

Implement an equality tester for pydantic.BaseModel.

The two models must have the same type, the same field values and the same extra values. The values are compared with the registry, so they support the tolerance and equal_nan. The private attributes are not compared, as the fields are what the model validates and serializes. The other entries of the instance __dict__ (for example the value of a functools.cached_property that was read on one model only) are ignored too. The location of a difference is the name of the field, or model_extra.

Raises:

Type Description
MissingPackageError

If pydantic is not installed.

Example
>>> from pydantic import BaseModel
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import PydanticModelEqualityTester, get_default_registry
>>> class Point(BaseModel):
...     x: float
...     y: float
...
>>> engine = Engine(EqualityConfig())
>>> engine.run(Point(x=1.0, y=2.0), Point(x=1.0, y=2.0))
True
>>> engine.run(Point(x=1.0, y=2.0), Point(x=1.0, y=3.0))
False

coola.equality.tester.ScalarEqualityTester

Bases: BaseEqualityTester[float]

Implement a scalar equality tester.

This tester handles numeric scalar types (int, float, complex) with support for NaN equality and tolerance-based comparisons. Two NaNs are equal only if equal_nan is True, a bool and an enum member are labels and are compared exactly, and the other numbers are close if |a - b| <= atol + rtol * max(|a|, |b|).

This tester is registered for int, float and complex in the default registry.

Example
>>> from coola.equality import Tolerance
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import ScalarEqualityTester, get_default_registry
>>> registry = get_default_registry()
>>> tester = ScalarEqualityTester()
>>> tester.objects_are_equal(42.0, 42.0, Engine(EqualityConfig()))
True
>>> tester.objects_are_equal(42.0, 1.0, Engine(EqualityConfig()))
False
>>> tester.objects_are_equal(42.0, 42.0001, Engine(EqualityConfig(tolerance=Tolerance(atol=1e-3))))
True

coola.equality.tester.ScipySparseEqualityTester

Bases: BaseEqualityTester[Any]

Implement an equality tester for scipy.sparse matrices and arrays.

Two sparse objects are equal if they have the same class (a matrix is not equal to an array, and the format is part of the class: csr_matrix, csc_matrix...; the engine checks it), the same shape, the same data type, the same non-zero positions and equal values. The values are compared with the tolerance and equal_nan of the comparison. The duplicate entries are summed and the explicitly stored zeros are ignored, so two objects that represent the same matrix are equal. The tolerance applies to the stored values: a stored value is never compared with an absent one. The location of a difference is the coordinates of the first value that differs.

Raises:

Type Description
MissingPackageError

If numpy or scipy is not installed.

Example
>>> from scipy import sparse
>>> from coola.equality import compare
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import ScipySparseEqualityTester
>>> engine = Engine(EqualityConfig())
>>> tester = ScipySparseEqualityTester()
>>> tester.objects_are_equal(sparse.eye(3, format="csr"), sparse.eye(3, format="csr"), engine)
True
>>> tester.objects_are_equal(
...     sparse.eye(3, format="csr"), 2 * sparse.eye(3, format="csr"), engine
... )
False
>>> compare(sparse.eye(3, format="csr"), 2 * sparse.eye(3, format="csr")).path
(0, 0)

coola.equality.tester.SequenceEqualityTester

Bases: BaseEqualityTester['Sequence[Any]']

Implement a sequence equality tester.

This tester handles sequence types (list, tuple, deque, Sequence ABC). The two sequences must have the same length, and the items are compared in order. A sequence of exact scalars (int, str, bytes, bool and None) is compared at once.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> engine = Engine(EqualityConfig())
>>> engine.run([1, 2, 3], [1, 2, 3])
True
>>> engine.run([1, 2, 3], [1, 2, 4])
False
>>> engine.run([1, 2, 3], [1, 2])
False

coola.equality.tester.SetEqualityTester

Bases: BaseEqualityTester['AbstractSet[Any]']

Implement a set equality tester.

This tester handles set and frozenset. Unlike ==, the elements are matched by hash and type, so equal_nan applies to them (the tolerance does not). The location of a difference is the element of actual that has no match in expected.

Example
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> engine = Engine(EqualityConfig())
>>> engine.run({1, 2, 3}, {1, 2, 3})
True
>>> engine.run({1, 2, 3}, {1, 2, 4})
False

coola.equality.tester.TableEqualityTester

Bases: BaseEqualityTester[Any]

Implement an equality tester for the tables of pandas, polars and pyarrow.

The structure is compared first, then the columns one by one, each by its kind (see Column). The comparison stops at the first column that differs. A table is a leaf for the engine: the difference is reported as a whole. The reason says what differs in the structure (the shapes, the data types, the index, the names of the columns, ...) or, for the values, the name of the first column that differs, the first row that differs and the two values. The row is found only when the difference is explained.

Parameters:

Name Type Description Default
adapter Callable[[Any], Table]

The function that converts an object to a Table.

required
Example
>>> import pandas as pd
>>> from coola.equality import compare
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import TableEqualityTester, get_default_registry
>>> from coola.equality.tester.pandas import pandas_frame
>>> engine = Engine(EqualityConfig())
>>> tester = TableEqualityTester(pandas_frame)
>>> tester.objects_are_equal(
...     pd.DataFrame({"a": [1, 2]}), pd.DataFrame({"a": [1, 2]}), engine
... )
True
>>> tester.objects_are_equal(
...     pd.DataFrame({"a": [1, 2]}), pd.DataFrame({"a": [1, 3]}), engine
... )
False
>>> print(compare(pd.DataFrame({"a": [1, 2]}), pd.DataFrame({"a": [1, 3]})).reason)
tables have different values: column 'a' differs at row 1: 2 vs 3

coola.equality.tester.TolerantEqualEqualityTester

Bases: BaseEqualityTester[object]

Implement an equality tester for objects that support both allclose and equal methods.

This tester is designed for objects that implement both an allclose(other, rtol, atol, equal_nan) method for tolerance-based comparison and an equal(other, equal_nan) method for exact comparison. The tester calls allclose when a tolerance is not zero, otherwise equal. An object that does not have both methods is different.

Example
>>> from coola.equality import Tolerance
>>> import math
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import TolerantEqualEqualityTester, get_default_registry
>>> class MyFloat:
...     def __init__(self, value: float) -> None:
...         self._value = float(value)
...     def allclose(
...         self,
...         other: object,
...         rtol: float = 1e-5,
...         atol: float = 1e-8,
...         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 math.isclose(self._value, other._value, rel_tol=rtol, abs_tol=atol)
...     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
...
>>> registry = get_default_registry()
>>> tester = TolerantEqualEqualityTester()
>>> tester.objects_are_equal(MyFloat(42), MyFloat(42), Engine(EqualityConfig()))
True
>>> tester.objects_are_equal(MyFloat(1.0), MyFloat(1.4), Engine(EqualityConfig()))
False
>>> tester.objects_are_equal(
...     MyFloat(1.0), MyFloat(1.4), Engine(EqualityConfig(tolerance=Tolerance(atol=0.5)))
... )
True

coola.equality.tester.TorchPackedSequenceEqualityTester

Bases: BaseEqualityTester[PackedSequence]

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

This tester compares the data tensor and the metadata of two packed sequences: batch_sizes, sorted_indices and unsorted_indices. The location of a difference is the name of the attribute.

Raises:

Type Description
MissingPackageError

If torch is not installed.

Example
>>> import torch
>>> from torch.nn.utils.rnn import pack_padded_sequence
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> engine = Engine(EqualityConfig())
>>> def make(value):
...     return pack_padded_sequence(
...         torch.tensor([[1.0, value], [3.0, 4.0]]),
...         lengths=torch.tensor([2, 1]),
...         batch_first=True,
...         enforce_sorted=False,
...     )
...
>>> engine.run(make(2.0), make(2.0))
True
>>> engine.run(make(2.0), make(5.0))
False

coola.equality.tester.TorchTensorEqualityTester

Bases: BaseEqualityTester[Tensor]

Implement an equality tester for torch.Tensor.

Two tensors are equal if they have the same data type, shape, device, layout and equal elements. The numbers are compared with the tolerance and equal_nan of the comparison, and a boolean tensor is exact. The tensors on the meta device have no data: only the metadata is compared. The location of a difference is the index of the first element that differs.

Raises:

Type Description
MissingPackageError

If torch is not installed.

Example
>>> import torch
>>> from coola.equality import compare
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import TorchTensorEqualityTester, get_default_registry
>>> engine = Engine(EqualityConfig())
>>> tester = TorchTensorEqualityTester()
>>> tester.objects_are_equal(torch.ones(2, 3), torch.ones(2, 3), engine)
True
>>> tester.objects_are_equal(torch.ones(2, 3), torch.zeros(2, 3), engine)
False
>>> compare(torch.ones(2, 3), torch.zeros(2, 3)).path
(0, 0)

coola.equality.tester.XarrayDataArrayEqualityTester

Bases: BaseEqualityTester[DataArray]

Implement an equality tester for xarray.DataArray.

This tester compares the variable (data, dimensions and attributes), the name and the coordinate variables of two DataArrays. The location of a difference is the name of the attribute.

Raises:

Type Description
MissingPackageError

If xarray is not installed.

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

coola.equality.tester.XarrayDatasetEqualityTester

Bases: BaseEqualityTester[Dataset]

Implement an equality tester for xarray.Dataset.

This tester compares the data variables, the coordinates and the attributes of two Datasets. The location of a difference is the name of the attribute.

Raises:

Type Description
MissingPackageError

If xarray is not installed.

Example
>>> import numpy as np
>>> import xarray as xr
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> engine = Engine(EqualityConfig())
>>> engine.run(
...     xr.Dataset({"x": ("z", np.arange(6))}), xr.Dataset({"x": ("z", np.arange(6))})
... )
True
>>> engine.run(
...     xr.Dataset({"x": ("z", np.ones(6))}), xr.Dataset({"x": ("z", np.zeros(6))})
... )
False

coola.equality.tester.XarrayVariableEqualityTester

Bases: BaseEqualityTester[Variable]

Implement an equality tester for xarray.Variable.

This tester compares the data, the dimension names and the attributes of two Variables. The location of a difference is the name of the attribute.

Raises:

Type Description
MissingPackageError

If xarray is not installed.

Example
>>> import numpy as np
>>> import xarray as xr
>>> from coola.equality.config import EqualityConfig
>>> from coola.equality.engine import Engine
>>> from coola.equality.tester import get_default_registry
>>> engine = Engine(EqualityConfig())
>>> engine.run(
...     xr.Variable(dims=["z"], data=np.arange(6)),
...     xr.Variable(dims=["z"], data=np.arange(6)),
... )
True
>>> engine.run(
...     xr.Variable(dims=["z"], data=np.zeros(6)), xr.Variable(dims=["z"], data=np.ones(6))
... )
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, array.array, Sequence ABC) - Mappings (dict, OrderedDict, MappingProxyType, 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 objects_are_equal(self, actual: MyType, expected: MyType, ctx) -> bool:
...         return actual.value == expected.value or ctx.fail("values differ")
...
>>> register_equality_testers({MyType: MyEqualityTester()})