commit: a02ebef1465c64b16b8a5dc8c6c55524ac8e89ed
Author: Brian Harring <ferringb <AT> gmail <DOT> com>
AuthorDate: Mon Oct 27 15:17:57 2025 +0000
Commit: Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
CommitDate: Mon Oct 27 21:54:07 2025 +0000
URL:
https://gitweb.gentoo.org/proj/pkgcore/snakeoil.git/commit/?id=a02ebef1
feat: Add GenericRichComparison
This is to help w/ migration away from inject_richcmp_from_cmp
for instances that use __attr_comparison__ logic.
Signed-off-by: Brian Harring <ferringb <AT> gmail.com>
src/snakeoil/klass/__init__.py | 69 ++++++++++++++++++++++++++++++++++++++----
tests/klass/test_init.py | 40 ++++++++++++++++++++++++
2 files changed, 103 insertions(+), 6 deletions(-)
diff --git a/src/snakeoil/klass/__init__.py b/src/snakeoil/klass/__init__.py
index 9691dd9..68d610a 100644
--- a/src/snakeoil/klass/__init__.py
+++ b/src/snakeoil/klass/__init__.py
@@ -42,7 +42,6 @@ from collections import deque
from functools import wraps
from importlib import import_module
from operator import attrgetter
-from typing import Any
from snakeoil.deprecation import deprecated as warn_deprecated
@@ -167,14 +166,22 @@ class GenericEquality(abc.ABC):
__attr_comparison__: tuple[str, ...]
- def __eq__(self, other: Any) -> bool:
+ def __eq__(
+ self, value, /, attr_comparison_override: tuple[str, ...] | None = None
+ ) -> bool:
"""
- Comparison is down via comparing attributes listed in
self.__attr_comparison__
+ Comparison is down via comparing attributes listed in
self.__attr_comparison__,
+ or via the passed in attr_comparison_override. That exists
specifically to
+ simplify subclass partial reuse of the class when logic gets complex.
"""
- if self is other:
+ if self is value:
return True
- for attr in self.__attr_comparison__:
- if getattr(self, attr, sentinel) != getattr(other, attr, sentinel):
+ for attr in (
+ self.__attr_comparison__
+ if attr_comparison_override is None
+ else attr_comparison_override
+ ):
+ if getattr(self, attr, sentinel) != getattr(value, attr, sentinel):
return False
return True
@@ -195,6 +202,56 @@ class GenericEquality(abc.ABC):
return super().__init_subclass__()
+class GenericRichComparison(GenericEquality):
+ __slots__ = ()
+
+ def __lt__(self, value, attr_comparison_override: tuple[str, ...] | None =
None):
+ if self is value:
+ return False
+ attrlist = (
+ self.__attr_comparison__
+ if attr_comparison_override is None
+ else attr_comparison_override
+ )
+ for attr in attrlist:
+ obj1, obj2 = getattr(self, attr, sentinel), getattr(value, attr,
sentinel)
+ if obj1 is sentinel:
+ if obj2 is sentinel:
+ continue
+ return True
+ elif obj2 is sentinel:
+ return False
+ if not (obj1 >= obj2): # pyright: ignore[reportOperatorIssue]
+ return True
+ return False
+
+ def __le__(self, value, attr_comparison_override: tuple[str, ...] | None =
None):
+ if self is value:
+ return True
+ attrlist = (
+ self.__attr_comparison__
+ if attr_comparison_override is None
+ else attr_comparison_override
+ )
+ for attr in attrlist:
+ obj1, obj2 = getattr(self, attr, sentinel), getattr(value, attr,
sentinel)
+ if obj1 is sentinel:
+ if obj2 is sentinel:
+ continue
+ return True
+ elif obj2 is sentinel:
+ return False
+ if not (obj1 > obj2): # pyright: ignore[reportOperatorIssue]
+ return True
+ return False
+
+ def __gt__(self, value, attr_comparison_override: tuple[str, ...] | None =
None):
+ return not self.__le__(value,
attr_comparison_override=attr_comparison_override)
+
+ def __ge__(self, value, attr_comparison_override: tuple[str, ...] | None =
None):
+ return not self.__lt__(value,
attr_comparison_override=attr_comparison_override)
+
+
@warn_deprecated(
"generic_equality metaclass usage is deprecated; inherit from
snakeoil.klass.GenericEquality instead."
)
diff --git a/tests/klass/test_init.py b/tests/klass/test_init.py
index 32500dd..d39cabe 100644
--- a/tests/klass/test_init.py
+++ b/tests/klass/test_init.py
@@ -661,3 +661,43 @@ class TestGenericEquality:
subclass_disabling_must_be_allowed.__annotations__["__attr_comparison__"]
is None
), "annotations weren't updated"
+
+
+class TestGenericRichComparison:
+ def test_it(self):
+ class kls(klass.GenericRichComparison):
+ __attr_comparison__ = ("x", "y")
+
+ def __init__(self, x=1, y=2):
+ self.x, self.y = x, y
+
+ obj1 = kls()
+ # these use identity check shortcuts, validate how it handles
+ # the same instance.
+ assert obj1 == obj1
+ assert obj1 <= obj1
+ assert obj1 >= obj1
+ assert not (obj1 < obj1)
+ assert not (obj1 > obj1)
+ assert not (obj1 != obj1)
+
+ # validate the usual scenarios
+ obj2 = kls()
+ assert not (obj1 < obj2)
+ assert obj1 <= obj2
+ assert not (obj1 != obj2)
+ assert obj1 >= obj2
+ assert not (obj1 > obj2)
+
+ obj1.x = 0
+ assert obj1 < obj2
+ assert obj1 <= obj2
+ assert not (obj1 > obj2)
+ assert not (obj1 >= obj2)
+
+ del obj1.x
+ assert obj1 != obj2
+ assert obj1 < obj2
+ assert obj1 <= obj2
+ assert not (obj1 > obj2)
+ assert not (obj1 >= obj2)