Return NotImplemented from comparisons with non-Ksuid objects#62
Conversation
|
good catch, but I'm not sure I entirely agree with the logic for cross-class comparisons; I kind of feel like def __eq__(self, other: object) -> bool:
if not isinstance(other, Ksuid) or self.TIMESTAMP_LENGTH_IN_BYTES != other.TIMESTAMP_LENGTH_IN_BYTES:
return NotImplemented
return self._uid == other._uidAlong with a test confirming that a carefully-constructed |
|
Good point, and agreed. I've updated Added |
|
Please also run |
svix-jbrown
left a comment
There was a problem hiding this comment.
Sounds good to me! Thanks for your contribution!
Ksuid.__eq__ asserted isinstance(other, self.__class__), so comparing a Ksuid with any other type raised AssertionError instead of returning False. This broke ordinary operations such as 'ksuid == None', 'ksuid in some_list', and comparing a Ksuid with a KsuidMs, and the assert is stripped under 'python -O' (turning the error into an AttributeError). __lt__ had the same problem. Follow the data model and return NotImplemented when the other operand is not a Ksuid, so equality falls back to False and ordering raises a proper TypeError.
A Ksuid and a KsuidMs are both 20 bytes and can share a byte representation, but they decode their timestamps differently. Guard __eq__ on TIMESTAMP_LENGTH_IN_BYTES so the two subclasses never compare equal just because their raw bytes coincide, and add a test that constructs both from the same bytes and asserts inequality.
57a032c to
7f8f53e
Compare
Problem
Ksuid.__eq__assertsisinstance(other, self.__class__), so comparing aKsuidagainst any other type raisesAssertionErrorinstead of returningFalse:This breaks ordinary operations —
ksuid == None,ksuid in some_list(list membership uses==), comparing aKsuidwith aKsuidMs, and dict/set use with mixed types. It is also fragile underpython -O, whereassertis stripped and the same comparison instead raisesAttributeError('NoneType' object has no attribute '_uid').__lt__has the same problem.Fix
Per the Python data model, rich comparison methods must return
NotImplemented(never raise) when the other operand is of an unsupported type.__eq__and__lt__now returnNotImplementedwhenotheris not aKsuid, so equality falls back toFalseand ordering raises a properTypeError. Comparison betweenKsuidandKsuidMs(both 20-byte values) now compares by raw bytes, consistent with__hash__.Tests
Added
test_eq_with_non_ksuid_does_not_raise,test_eq_by_raw_bytes, andtest_ordering_with_non_ksuid_raises_type_error. The full suite passes (2021), andty/ruff check/ruff format --checkare clean on theksuidpackage.I worked on this with AI assistance under my direction and reviewed the change myself.