From 3e4453ae1fcd43fd85bc6902d4cff2b5f8c6906c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frieder=20Sch=C3=BCler?= Date: Wed, 29 Apr 2026 20:23:17 +0200 Subject: [PATCH] Fix __contains__ and __eq__ override signatures Change method signatures to accept 'object' as required by the Liskov substitution principle (Mapping/Container/object supertypes). For __eq__, return NotImplemented for incompatible types instead of raising AttributeError. For __contains__ in SdoArray, add isinstance check to handle non-int arguments gracefully. --- canopen/objectdictionary/__init__.py | 16 +++++++++++----- canopen/sdo/base.py | 8 +++++--- test/test_od.py | 18 ++++++++++++++++++ test/test_sdo.py | 6 ++++++ 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/canopen/objectdictionary/__init__.py b/canopen/objectdictionary/__init__.py index fa694c56..96e01708 100644 --- a/canopen/objectdictionary/__init__.py +++ b/canopen/objectdictionary/__init__.py @@ -160,7 +160,7 @@ def __iter__(self) -> Iterator[int]: def __len__(self) -> int: return len(self.indices) - def __contains__(self, index: Union[int, str]): + def __contains__(self, index: object) -> bool: return index in self.names or index in self.indices def add_object(self, obj: Union[ODArray, ODRecord, ODVariable]) -> None: @@ -234,10 +234,12 @@ def __len__(self) -> int: def __iter__(self) -> Iterator[int]: return iter(sorted(self.subindices)) - def __contains__(self, subindex: Union[int, str]) -> bool: + def __contains__(self, subindex: object) -> bool: return subindex in self.names or subindex in self.subindices - def __eq__(self, other: ODRecord) -> bool: + def __eq__(self, other: object) -> bool: + if not isinstance(other, ODRecord): + return NotImplemented return self.index == other.index def add_member(self, variable: ODVariable) -> None: @@ -298,7 +300,9 @@ def __len__(self) -> int: def __iter__(self) -> Iterator[int]: return iter(sorted(self.subindices)) - def __eq__(self, other: ODArray) -> bool: + def __eq__(self, other: object) -> bool: + if not isinstance(other, ODArray): + return NotImplemented return self.index == other.index def add_member(self, variable: ODVariable) -> None: @@ -387,7 +391,9 @@ def qualname(self) -> str: return f"{self.parent.name}.{self.name}" return self.name - def __eq__(self, other: ODVariable) -> bool: + def __eq__(self, other: object) -> bool: + if not isinstance(other, ODVariable): + return NotImplemented return (self.index == other.index and self.subindex == other.subindex) diff --git a/canopen/sdo/base.py b/canopen/sdo/base.py index e4215a3a..97b0d6c5 100644 --- a/canopen/sdo/base.py +++ b/canopen/sdo/base.py @@ -64,7 +64,7 @@ def __iter__(self) -> Iterator[int]: def __len__(self) -> int: return len(self.od) - def __contains__(self, key: Union[int, str]) -> bool: + def __contains__(self, key: object) -> bool: return key in self.od def get_variable( @@ -113,7 +113,7 @@ def __len__(self) -> int: # Skip the "highest subindex" entry, which is not part of the data return len(self.od) - int(0 in self.od) - def __contains__(self, subindex: Union[int, str]) -> bool: + def __contains__(self, subindex: object) -> bool: return subindex in self.od @@ -136,7 +136,9 @@ def __iter__(self) -> Iterator[int]: def __len__(self) -> int: return self[0].raw - def __contains__(self, subindex: int) -> bool: + def __contains__(self, subindex: object) -> bool: + if not isinstance(subindex, int): + return False return 0 <= subindex <= len(self) diff --git a/test/test_od.py b/test/test_od.py index 9ab0e187..231a58dd 100644 --- a/test/test_od.py +++ b/test/test_od.py @@ -276,5 +276,23 @@ def test_subindexes(self): self.assertEqual(array[3].name, "Test Variable_3") +class TestEquality(unittest.TestCase): + + def test_record_eq_wrong_type(self): + record = od.ODRecord("Test Record", 0x1001) + self.assertNotEqual(record, "not a record") + self.assertNotEqual(record, 42) + + def test_array_eq_wrong_type(self): + array = od.ODArray("Test Array", 0x1002) + self.assertNotEqual(array, "not an array") + self.assertNotEqual(array, 42) + + def test_variable_eq_wrong_type(self): + var = od.ODVariable("Test Variable", 0x1000, 0) + self.assertNotEqual(var, "not a variable") + self.assertNotEqual(var, 42) + + if __name__ == "__main__": unittest.main() diff --git a/test/test_sdo.py b/test/test_sdo.py index 9764a6d3..a9418716 100644 --- a/test/test_sdo.py +++ b/test/test_sdo.py @@ -48,6 +48,12 @@ def test_array_members_dynamic(self): for var in array.values(): self.assertIsInstance(var, canopen.sdo.SdoVariable) + def test_array_contains_non_int(self): + """SdoArray.__contains__ should handle non-int types gracefully.""" + array = self.sdo_node[0x1003] + self.assertNotIn("not an int", array) + self.assertNotIn(None, array) + class TestSDO(unittest.TestCase): """