Skip to content

Commit 6688d63

Browse files
authored
Clarify ODVariable.data_type cannot be None (#666)
Use a simple integer zero as dummy value for "unset" in instances. This fixes some type checker errors. Add type hints for functions involving those values. Fix test coverage for unset and unknown data type.
1 parent fc5621b commit 6688d63

3 files changed

Lines changed: 16 additions & 7 deletions

File tree

canopen/objectdictionary/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ def __init__(self, name: str, index: int, subindex: int = 0):
366366
#: The value of this variable stored in the object dictionary
367367
self.value: Optional[int] = None
368368
#: Data type according to the standard as an :class:`int`
369-
self.data_type: Optional[int] = None
369+
self.data_type: int = 0
370370
#: Access type, should be "rw", "ro", "wo", or "const"
371371
self.access_type: str = "rw"
372372
#: The variable represents a DOMAIN ObjectType
@@ -480,7 +480,7 @@ def encode_raw(self, value: Union[int, float, str, bytes, bytearray]) -> bytes:
480480
return self.STRUCT_TYPES[self.data_type].pack(value)
481481
except struct.error:
482482
raise ValueError("Value does not fit in specified type")
483-
elif self.data_type is None:
483+
elif not self.data_type:
484484
raise ObjectDictionaryError("Data type has not been specified")
485485
else:
486486
raise TypeError(

canopen/objectdictionary/eds.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import logging
55
import re
66
from configparser import NoOptionError, NoSectionError, RawConfigParser
7-
from typing import TYPE_CHECKING
7+
from typing import Any, TYPE_CHECKING
88

99
from canopen.objectdictionary import (
1010
ODArray,
@@ -204,7 +204,7 @@ def import_from_node(node_id: int, network: canopen.network.Network):
204204
return od
205205

206206

207-
def _calc_bit_length(data_type):
207+
def _calc_bit_length(data_type: int) -> int:
208208
if data_type == datatypes.INTEGER8:
209209
return 8
210210
elif data_type == datatypes.INTEGER16:
@@ -215,7 +215,7 @@ def _calc_bit_length(data_type):
215215
return 64
216216
else:
217217
raise ValueError(
218-
f"Invalid data_type '{data_type}', expecting a signed integer data_type."
218+
f"Invalid data_type 0x{data_type:04X}, expecting an integer data_type."
219219
)
220220

221221

@@ -229,7 +229,7 @@ def _signed_int_from_hex(hex_str, bit_length):
229229
return number
230230

231231

232-
def _convert_variable(node_id, var_type, value):
232+
def _convert_variable(node_id: int, var_type: int, value: Any) -> Any:
233233
if var_type in (datatypes.OCTET_STRING, datatypes.DOMAIN):
234234
return bytes.fromhex(value)
235235
elif var_type in (datatypes.VISIBLE_STRING, datatypes.UNICODE_STRING):
@@ -245,7 +245,7 @@ def _convert_variable(node_id, var_type, value):
245245
return int(value, 0)
246246

247247

248-
def _revert_variable(var_type, value):
248+
def _revert_variable(var_type: int, value: Any) -> Any:
249249
if value is None:
250250
return None
251251
if var_type in (datatypes.OCTET_STRING, datatypes.DOMAIN):

test/test_od.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,15 @@ def test_domain(self):
171171
self.assertEqual(var.decode_raw(b"zero terminated\x00"), b"zero terminated\x00")
172172
self.assertEqual(var.encode_raw(b"testing"), b"testing")
173173

174+
def test_unknown_data_type(self):
175+
var = od.ODVariable("Test unknown", 0x1000)
176+
# data_type intentionally left at default (unset)
177+
with self.assertRaises(od.ObjectDictionaryError):
178+
var.encode_raw(42)
179+
var.data_type = 0x7F # from Device profile specific Standard Data types
180+
with self.assertRaises(TypeError):
181+
var.encode_raw(42)
182+
174183

175184
class TestAlternativeRepresentations(unittest.TestCase):
176185

0 commit comments

Comments
 (0)