This is a utility class for safely checking dataclass structures when they may contain circular references. Such as in the case of parent child relationships where parents have references to their children and the children have references to all of their parents.

2 min read Original article ↗

This is a utility class for safely checking dataclass structures when they may contain circular references. Such as in the case of parent child relationships where parents have references to their children and the children have references to all of their parents.

from dataclasses import (
dataclass,
fields,
)
@dataclass(eq=False)
class RecursiveCompareMixin:
"""A utility class to provide recursion safe dataclass comparison.
"""
def __eq__(self, __o: object) -> bool:
# Check if we are are already checking, if we are, return True.
# Returning True here is fine because the only case where we recurse
# into the same object when checking equality is when examining an
# objects parent list. As we are already checking the object's
# equality, the actual comparison value will be established higher up
# the stack.
if getattr(self, '__already_checking__', False):
return True
# Set the recursion guard to prevent infinite recursion.
object.__setattr__(self, '__already_checking__', True)
# Get our fields and the other object's
s_fields = fields(self)
o_fields = fields(__o)
# If the fields are different we are not the same.
if s_fields != o_fields:
return False
# Compare the fields of each object. If there is a difference, return
# False.
for s_field, o_field in zip(s_fields, o_fields):
if getattr(self, s_field.name) != getattr(__o, o_field.name):
return False
# After finishing the comparison set the recursion guard to False to
# allow for the object to be checked again.
object.__setattr__(self, '__already_checking__', False)
return True