Skip to content

Commit fc5621b

Browse files
authored
variable: Fix type-related issues with desc property (#667)
* variable: Value description in log message only for integers. * Raise TypeError when requesting value description of non-int data. According to the type hints, the mapping mechanism was only ever intended for integers (emulating an enumeration). Avoids a later DictionaryError for the missing key and some type checker errors because decode_desc() shall only accept int. Add the same check in the write()-based variable access API. * Style fix (black). Better overview if not on one line. * Avoid else after raise. Simplify by using the walrus operator. * Extend test coverage for ODVariable. * Extend test coverage for Variable.
1 parent 1d6bd8d commit fc5621b

4 files changed

Lines changed: 37 additions & 9 deletions

File tree

canopen/objectdictionary/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -500,11 +500,10 @@ def encode_phys(self, value: Union[int, bool, float, str, bytes]) -> int:
500500
def decode_desc(self, value: int) -> str:
501501
if not self.value_descriptions:
502502
raise ObjectDictionaryError("No value descriptions exist")
503-
elif value not in self.value_descriptions:
503+
elif (desc := self.value_descriptions.get(value)) is None:
504504
raise ObjectDictionaryError(
505505
f"No value description exists for {value}")
506-
else:
507-
return self.value_descriptions[value]
506+
return desc
508507

509508
def encode_desc(self, desc: str) -> int:
510509
if not self.value_descriptions:

canopen/variable.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,11 @@ def raw(self) -> Union[int, bool, float, str, bytes]:
7777
"""
7878
value = self.od.decode_raw(self.data)
7979
text = f"Value of {self.name!r} ({pretty_index(self.index, self.subindex)}) is {value!r}"
80-
if value in self.od.value_descriptions:
81-
text += f" ({self.od.value_descriptions[value]})"
80+
if (
81+
isinstance(value, int)
82+
and (desc := self.od.value_descriptions.get(value)) is not None
83+
):
84+
text += f" ({desc})"
8285
logger.debug(text)
8386
return value
8487

@@ -108,8 +111,14 @@ def phys(self, value: Union[int, bool, float, str, bytes]):
108111

109112
@property
110113
def desc(self) -> str:
111-
"""Converts to and from a description of the value as a string."""
112-
value = self.od.decode_desc(self.raw)
114+
"""Convert to and from a description of the value as a string.
115+
116+
:raises TypeError: If the received raw data was anything but an integer value.
117+
"""
118+
raw_int = self.raw
119+
if not isinstance(raw_int, int):
120+
raise TypeError("Description of values only supported for integer objects")
121+
value = self.od.decode_desc(raw_int)
113122
logger.debug("Description is '%s'", value)
114123
return value
115124

@@ -146,7 +155,9 @@ def read(self, fmt: str = "raw") -> Union[int, bool, float, str, bytes]:
146155
raise ValueError(f"Invalid format '{fmt}'")
147156

148157
def write(
149-
self, value: Union[int, bool, float, str, bytes], fmt: str = "raw"
158+
self,
159+
value: Union[int, bool, float, str, bytes],
160+
fmt: str = "raw",
150161
) -> None:
151162
"""Alternative way of writing using a function instead of attributes.
152163
@@ -157,12 +168,15 @@ def write(
157168
- 'raw'
158169
- 'phys'
159170
- 'desc'
171+
:raises TypeError: If the "desc" format was specified with anything but a string value.
160172
"""
161173
if fmt == "raw":
162174
self.raw = value
163175
elif fmt == "phys":
164176
self.phys = value
165177
elif fmt == "desc":
178+
if not isinstance(value, str):
179+
raise TypeError("fmt=desc requires a string value")
166180
self.desc = value
167181

168182

test/test_od.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,23 @@ def test_phys_float_factor_decodes_to_float(self):
221221
def test_desc(self):
222222
var = od.ODVariable("Test UNSIGNED8", 0x1000)
223223
var.data_type = od.UNSIGNED8
224+
with self.assertRaises(od.ObjectDictionaryError):
225+
var.decode_desc(0)
226+
with self.assertRaises(od.ObjectDictionaryError):
227+
var.encode_desc("")
228+
224229
var.add_value_description(0, "Value 0")
225230
var.add_value_description(1, "Value 1")
226231
var.add_value_description(3, "Value 3")
227232

228233
self.assertEqual(var.decode_desc(0), "Value 0")
229234
self.assertEqual(var.decode_desc(3), "Value 3")
235+
with self.assertRaises(od.ObjectDictionaryError):
236+
var.decode_desc(2)
237+
230238
self.assertEqual(var.encode_desc("Value 1"), 1)
239+
with self.assertRaises(ValueError):
240+
var.encode_desc("UNDEFINED")
231241

232242
def test_bits(self):
233243
var = od.ODVariable("Test UNSIGNED8", 0x1000)

test/test_variable.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,20 @@ def test_write_desc(self):
3737
v = _StubVariable(var)
3838
v.write("On", fmt="desc")
3939
self.assertEqual(v.raw, 1)
40+
self.assertEqual(v.desc, "On")
41+
with self.assertRaises(TypeError):
42+
v.write(b"", fmt="desc")
4043

4144
def test_raw_with_string_value(self):
4245
var = od.ODVariable("Test VISIBLE_STRING", 0x1000)
4346
var.data_type = od.VISIBLE_STRING
4447
var.default = "hello"
4548
var.add_value_description(0, "Off")
4649
v = _StubVariable(var)
47-
# String value must not be looked up in value_descriptions
4850
self.assertEqual(v.raw, "hello")
51+
# String value must not be looked up in value_descriptions
52+
with self.assertRaises(TypeError):
53+
_ = v.desc
4954

5055
def test_bits(self):
5156
var = od.ODVariable("Test UNSIGNED8", 0x1000)

0 commit comments

Comments
 (0)