Skip to content

Commit 0689042

Browse files
committed
Merge branch 'master' into fix/mypy-type-fixes
2 parents b43cf9f + c05b49d commit 0689042

10 files changed

Lines changed: 116 additions & 74 deletions

File tree

canopen/lss.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def __init__(self) -> None:
8585
self.network: canopen.network.Network = canopen.network._UNINITIALIZED_NETWORK
8686
self._node_id = 0
8787
self._data = None
88-
self.responses = queue.Queue()
88+
self.responses: queue.Queue[bytes] = queue.Queue()
8989

9090
def send_switch_state_global(self, mode):
9191
"""switch mode to CONFIGURATION_STATE or WAITING_STATE

canopen/node/local.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import logging
4+
from collections.abc import Callable
45
from typing import Union
56

67
import canopen.network
@@ -26,8 +27,8 @@ def __init__(
2627
super(LocalNode, self).__init__(node_id, object_dictionary)
2728

2829
self.data_store: dict[int, dict[int, bytes]] = {}
29-
self._read_callbacks = []
30-
self._write_callbacks = []
30+
self._read_callbacks: list[Callable] = []
31+
self._write_callbacks: list[Callable] = []
3132

3233
self.sdo = SdoServer(0x600 + self.id, 0x580 + self.id, self)
3334
self.tpdo = TPDO(self)
@@ -62,10 +63,10 @@ def remove_network(self) -> None:
6263
self.nmt.network = canopen.network._UNINITIALIZED_NETWORK
6364
self.emcy.network = canopen.network._UNINITIALIZED_NETWORK
6465

65-
def add_read_callback(self, callback):
66+
def add_read_callback(self, callback: Callable):
6667
self._read_callbacks.append(callback)
6768

68-
def add_write_callback(self, callback):
69+
def add_write_callback(self, callback: Callable):
6970
self._write_callbacks.append(callback)
7071

7172
def get_data(

canopen/node/remote.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def __init__(
3939
#: Enable WORKAROUND for reversed PDO mapping entries
4040
self.curtis_hack = False
4141

42-
self.sdo_channels = []
42+
self.sdo_channels: list[SdoClient] = []
4343
self.sdo = self.add_sdo(0x600 + self.id, 0x580 + self.id)
4444
self.tpdo = TPDO(self)
4545
self.rpdo = RPDO(self)

canopen/objectdictionary/__init__.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def __iter__(self) -> Iterator[int]:
163163
def __len__(self) -> int:
164164
return len(self.indices)
165165

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

169169
def add_object(self, obj: Union[ODArray, ODRecord, ODVariable]) -> None:
@@ -211,8 +211,8 @@ def __init__(self, name: str, index: int):
211211
self.name = name
212212
#: Storage location of index
213213
self.storage_location = None
214-
self.subindices = {}
215-
self.names = {}
214+
self.subindices: dict[int, ODVariable] = {}
215+
self.names: dict[str, ODVariable] = {}
216216

217217
def __repr__(self) -> str:
218218
return f"<{type(self).__qualname__} {self.name!r} at {pretty_index(self.index)}>"
@@ -238,10 +238,12 @@ def __len__(self) -> int:
238238
def __iter__(self) -> Iterator[int]:
239239
return iter(sorted(self.subindices))
240240

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

244-
def __eq__(self, other: ODRecord) -> bool:
244+
def __eq__(self, other: object) -> bool:
245+
if not isinstance(other, ODRecord):
246+
return NotImplemented
245247
return self.index == other.index
246248

247249
def add_member(self, variable: ODVariable) -> None:
@@ -270,8 +272,8 @@ def __init__(self, name: str, index: int):
270272
self.name = name
271273
#: Storage location of index
272274
self.storage_location = None
273-
self.subindices = {}
274-
self.names = {}
275+
self.subindices: dict[int, ODVariable] = {}
276+
self.names: dict[str, ODVariable] = {}
275277

276278
def __repr__(self) -> str:
277279
return f"<{type(self).__qualname__} {self.name!r} at {pretty_index(self.index)}>"
@@ -302,7 +304,9 @@ def __len__(self) -> int:
302304
def __iter__(self) -> Iterator[int]:
303305
return iter(sorted(self.subindices))
304306

305-
def __eq__(self, other: ODArray) -> bool:
307+
def __eq__(self, other: object) -> bool:
308+
if not isinstance(other, ODArray):
309+
return NotImplemented
306310
return self.index == other.index
307311

308312
def add_member(self, variable: ODVariable) -> None:
@@ -391,7 +395,9 @@ def qualname(self) -> str:
391395
return f"{self.parent.name}.{self.name}"
392396
return self.name
393397

394-
def __eq__(self, other: ODVariable) -> bool:
398+
def __eq__(self, other: object) -> bool:
399+
if not isinstance(other, ODVariable):
400+
return NotImplemented
395401
return (self.index == other.index and
396402
self.subindex == other.subindex)
397403

@@ -417,7 +423,7 @@ def add_value_description(self, value: int, descr: str) -> None:
417423
"""
418424
self.value_descriptions[value] = descr
419425

420-
def add_bit_definition(self, name: str, bits: List[int]) -> None:
426+
def add_bit_definition(self, name: str, bits: list[int]) -> None:
421427
"""Associate bit(s) with a string description.
422428
423429
:param name: Name of bit(s)
@@ -511,7 +517,7 @@ def encode_desc(self, desc: str) -> int:
511517
raise ValueError(
512518
f"No value corresponds to '{desc}'. Valid values are: {valid_values}")
513519

514-
def decode_bits(self, value: int, bits: List[int]) -> int:
520+
def decode_bits(self, value: int, bits: list[int]) -> int:
515521
try:
516522
bits = self.bit_definitions[bits]
517523
except (TypeError, KeyError):
@@ -521,7 +527,7 @@ def decode_bits(self, value: int, bits: List[int]) -> int:
521527
mask |= 1 << bit
522528
return (value & mask) >> min(bits)
523529

524-
def encode_bits(self, original_value: int, bits: List[int], bit_value: int):
530+
def encode_bits(self, original_value: int, bits: list[int], bit_value: int):
525531
try:
526532
bits = self.bit_definitions[bits]
527533
except (TypeError, KeyError):

canopen/pdo/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def export(self, filename):
9898
:rtype: canmatrix.canmatrix.CanMatrix
9999
"""
100100
try:
101-
from canmatrix import canmatrix
101+
from canmatrix import canmatrix # type:ignore # (typing still in progress)
102102
from canmatrix import formats
103103
except ImportError:
104104
raise NotImplementedError("This feature requires the 'canopen[db_export]' feature")

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(
@@ -114,7 +114,7 @@ def __len__(self) -> int:
114114
# Skip the "highest subindex" entry, which is not part of the data
115115
return len(self.od) - int(0 in self.od)
116116

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

120120

@@ -137,7 +137,9 @@ def __iter__(self) -> Iterator[int]:
137137
def __len__(self) -> int:
138138
return self[0].raw # type: ignore[return-value]
139139

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

143145

canopen/variable.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ def read(self, fmt: str = "raw") -> Union[int, bool, float, str, bytes]:
135135
136136
:returns:
137137
The value of the variable.
138+
:raises ValueError: For unsupported fmt values.
138139
"""
139140
if fmt == "raw":
140141
return self.raw

test/test_od.py

Lines changed: 13 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -288,61 +288,22 @@ def test_subindexes(self):
288288
self.assertEqual(array[3].name, "Test Variable_3")
289289

290290

291-
class _StubVariable(Variable):
292-
"""Minimal concrete Variable for testing read/write/bits."""
291+
class TestEquality(unittest.TestCase):
293292

294-
def __init__(self, od_var):
295-
super().__init__(od_var)
296-
self._data = od_var.encode_raw(od_var.default)
297-
298-
def get_data(self):
299-
return self._data
300-
301-
def set_data(self, data):
302-
self._data = data
303-
304-
305-
class TestVariable(unittest.TestCase):
306-
307-
def test_read_invalid_format(self):
308-
var = od.ODVariable("Test UNSIGNED8", 0x1000)
309-
var.data_type = od.UNSIGNED8
310-
var.default = 0
311-
v = _StubVariable(var)
312-
with self.assertRaises(ValueError):
313-
v.read(fmt="invalid")
293+
def test_record_eq_wrong_type(self):
294+
record = od.ODRecord("Test Record", 0x1001)
295+
self.assertNotEqual(record, "not a record")
296+
self.assertNotEqual(record, 42)
314297

315-
def test_write_desc(self):
316-
var = od.ODVariable("Test UNSIGNED8", 0x1000)
317-
var.data_type = od.UNSIGNED8
318-
var.default = 0
319-
var.add_value_description(0, "Off")
320-
var.add_value_description(1, "On")
321-
v = _StubVariable(var)
322-
v.write("On", fmt="desc")
323-
self.assertEqual(v.raw, 1)
324-
325-
def test_raw_with_string_value(self):
326-
var = od.ODVariable("Test VISIBLE_STRING", 0x1000)
327-
var.data_type = od.VISIBLE_STRING
328-
var.default = "hello"
329-
var.add_value_description(0, "Off")
330-
v = _StubVariable(var)
331-
# String value must not be looked up in value_descriptions
332-
self.assertEqual(v.raw, "hello")
298+
def test_array_eq_wrong_type(self):
299+
array = od.ODArray("Test Array", 0x1002)
300+
self.assertNotEqual(array, "not an array")
301+
self.assertNotEqual(array, 42)
333302

334-
def test_bits(self):
335-
var = od.ODVariable("Test UNSIGNED8", 0x1000)
336-
var.data_type = od.UNSIGNED8
337-
var.default = 0
338-
var.add_bit_definition("BIT 0", [0])
339-
var.add_bit_definition("BIT 2 and 3", [2, 3])
340-
v = _StubVariable(var)
341-
v.raw = 5
342-
bits = v.bits
343-
self.assertEqual(bits[0], 1)
344-
bits[0] = 0
345-
self.assertEqual(v.raw, 4)
303+
def test_variable_eq_wrong_type(self):
304+
var = od.ODVariable("Test Variable", 0x1000, 0)
305+
self.assertNotEqual(var, "not a variable")
306+
self.assertNotEqual(var, 42)
346307

347308

348309
if __name__ == "__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
def test_get_variable_not_found(self):
5258
self.assertIsNone(self.sdo_node.get_variable(0x9999))
5359

test/test_variable.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import unittest
2+
3+
from canopen import objectdictionary as od
4+
from canopen.variable import Variable
5+
6+
7+
class _StubVariable(Variable):
8+
"""Minimal concrete Variable for testing read/write/bits."""
9+
10+
def __init__(self, od_var):
11+
super().__init__(od_var)
12+
self._data = od_var.encode_raw(od_var.default)
13+
14+
def get_data(self):
15+
return self._data
16+
17+
def set_data(self, data):
18+
self._data = data
19+
20+
21+
class TestVariable(unittest.TestCase):
22+
23+
def test_read_invalid_format(self):
24+
var = od.ODVariable("Test UNSIGNED8", 0x1000)
25+
var.data_type = od.UNSIGNED8
26+
var.default = 0
27+
v = _StubVariable(var)
28+
with self.assertRaises(ValueError):
29+
v.read(fmt="invalid")
30+
31+
def test_write_desc(self):
32+
var = od.ODVariable("Test UNSIGNED8", 0x1000)
33+
var.data_type = od.UNSIGNED8
34+
var.default = 0
35+
var.add_value_description(0, "Off")
36+
var.add_value_description(1, "On")
37+
v = _StubVariable(var)
38+
v.write("On", fmt="desc")
39+
self.assertEqual(v.raw, 1)
40+
41+
def test_raw_with_string_value(self):
42+
var = od.ODVariable("Test VISIBLE_STRING", 0x1000)
43+
var.data_type = od.VISIBLE_STRING
44+
var.default = "hello"
45+
var.add_value_description(0, "Off")
46+
v = _StubVariable(var)
47+
# String value must not be looked up in value_descriptions
48+
self.assertEqual(v.raw, "hello")
49+
50+
def test_bits(self):
51+
var = od.ODVariable("Test UNSIGNED8", 0x1000)
52+
var.data_type = od.UNSIGNED8
53+
var.default = 0
54+
var.add_bit_definition("BIT 0", [0])
55+
var.add_bit_definition("BIT 2 and 3", [2, 3])
56+
v = _StubVariable(var)
57+
v.raw = 5
58+
bits = v.bits
59+
self.assertEqual(bits[0], 1)
60+
bits[0] = 0
61+
self.assertEqual(v.raw, 4)
62+
63+
64+
if __name__ == "__main__":
65+
unittest.main()

0 commit comments

Comments
 (0)