Skip to content

Commit 3e4453a

Browse files
committed
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.
1 parent 4e789fe commit 3e4453a

4 files changed

Lines changed: 40 additions & 8 deletions

File tree

canopen/objectdictionary/__init__.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ def __iter__(self) -> Iterator[int]:
160160
def __len__(self) -> int:
161161
return len(self.indices)
162162

163-
def __contains__(self, index: Union[int, str]):
163+
def __contains__(self, index: object) -> bool:
164164
return index in self.names or index in self.indices
165165

166166
def add_object(self, obj: Union[ODArray, ODRecord, ODVariable]) -> None:
@@ -234,10 +234,12 @@ def __len__(self) -> int:
234234
def __iter__(self) -> Iterator[int]:
235235
return iter(sorted(self.subindices))
236236

237-
def __contains__(self, subindex: Union[int, str]) -> bool:
237+
def __contains__(self, subindex: object) -> bool:
238238
return subindex in self.names or subindex in self.subindices
239239

240-
def __eq__(self, other: ODRecord) -> bool:
240+
def __eq__(self, other: object) -> bool:
241+
if not isinstance(other, ODRecord):
242+
return NotImplemented
241243
return self.index == other.index
242244

243245
def add_member(self, variable: ODVariable) -> None:
@@ -298,7 +300,9 @@ def __len__(self) -> int:
298300
def __iter__(self) -> Iterator[int]:
299301
return iter(sorted(self.subindices))
300302

301-
def __eq__(self, other: ODArray) -> bool:
303+
def __eq__(self, other: object) -> bool:
304+
if not isinstance(other, ODArray):
305+
return NotImplemented
302306
return self.index == other.index
303307

304308
def add_member(self, variable: ODVariable) -> None:
@@ -387,7 +391,9 @@ def qualname(self) -> str:
387391
return f"{self.parent.name}.{self.name}"
388392
return self.name
389393

390-
def __eq__(self, other: ODVariable) -> bool:
394+
def __eq__(self, other: object) -> bool:
395+
if not isinstance(other, ODVariable):
396+
return NotImplemented
391397
return (self.index == other.index and
392398
self.subindex == other.subindex)
393399

canopen/sdo/base.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def __iter__(self) -> Iterator[int]:
6464
def __len__(self) -> int:
6565
return len(self.od)
6666

67-
def __contains__(self, key: Union[int, str]) -> bool:
67+
def __contains__(self, key: object) -> bool:
6868
return key in self.od
6969

7070
def get_variable(
@@ -113,7 +113,7 @@ def __len__(self) -> int:
113113
# Skip the "highest subindex" entry, which is not part of the data
114114
return len(self.od) - int(0 in self.od)
115115

116-
def __contains__(self, subindex: Union[int, str]) -> bool:
116+
def __contains__(self, subindex: object) -> bool:
117117
return subindex in self.od
118118

119119

@@ -136,7 +136,9 @@ def __iter__(self) -> Iterator[int]:
136136
def __len__(self) -> int:
137137
return self[0].raw
138138

139-
def __contains__(self, subindex: int) -> bool:
139+
def __contains__(self, subindex: object) -> bool:
140+
if not isinstance(subindex, int):
141+
return False
140142
return 0 <= subindex <= len(self)
141143

142144

test/test_od.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,5 +276,23 @@ def test_subindexes(self):
276276
self.assertEqual(array[3].name, "Test Variable_3")
277277

278278

279+
class TestEquality(unittest.TestCase):
280+
281+
def test_record_eq_wrong_type(self):
282+
record = od.ODRecord("Test Record", 0x1001)
283+
self.assertNotEqual(record, "not a record")
284+
self.assertNotEqual(record, 42)
285+
286+
def test_array_eq_wrong_type(self):
287+
array = od.ODArray("Test Array", 0x1002)
288+
self.assertNotEqual(array, "not an array")
289+
self.assertNotEqual(array, 42)
290+
291+
def test_variable_eq_wrong_type(self):
292+
var = od.ODVariable("Test Variable", 0x1000, 0)
293+
self.assertNotEqual(var, "not a variable")
294+
self.assertNotEqual(var, 42)
295+
296+
279297
if __name__ == "__main__":
280298
unittest.main()

test/test_sdo.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ def test_array_members_dynamic(self):
4848
for var in array.values():
4949
self.assertIsInstance(var, canopen.sdo.SdoVariable)
5050

51+
def test_array_contains_non_int(self):
52+
"""SdoArray.__contains__ should handle non-int types gracefully."""
53+
array = self.sdo_node[0x1003]
54+
self.assertNotIn("not an int", array)
55+
self.assertNotIn(None, array)
56+
5157

5258
class TestSDO(unittest.TestCase):
5359
"""

0 commit comments

Comments
 (0)