Skip to content

Commit 79f8bf8

Browse files
committed
Correct type handling in decode_bits() and encode_bits().
Contrary to the existing type hints, the methods can handle strings for lookup as well as an arbitrary collection of integer bit offsets. Correct the hints to reflect that and check at runtime to only lookup from strings. Correct return type for encode_bits(), as it works only for integers. Document both functions to explain the intention and caveats to consider, especially the lookup KeyError exception. Extend test to cover an iterable and a failed lookup.
1 parent 62cf726 commit 79f8bf8

2 files changed

Lines changed: 32 additions & 9 deletions

File tree

canopen/objectdictionary/__init__.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import logging
88
import struct
9-
from collections.abc import Iterator, Mapping, MutableMapping
9+
from collections.abc import Collection, Iterator, Mapping, MutableMapping
1010
from typing import Optional, TextIO, Union
1111

1212
from canopen.objectdictionary.datatypes import *
@@ -516,21 +516,38 @@ def encode_desc(self, desc: str) -> int:
516516
raise ValueError(
517517
f"No value corresponds to '{desc}'. Valid values are: {valid_values}")
518518

519-
def decode_bits(self, value: int, bits: list[int]) -> int:
520-
try:
519+
def decode_bits(self, value: int, bits: Union[str, Collection[int]]) -> int:
520+
"""Isolate and right-shift the specified bits from a given integer.
521+
522+
:param value: Variable value holding the bits
523+
:param bits: Registered lookup name or concrete list of bit offsets
524+
:return: Extracted bits, right-shifted to cut off to lowest specified offset
525+
:raises KeyError: For unknown lookup names
526+
"""
527+
if isinstance(bits, str):
521528
bits = self.bit_definitions[bits]
522-
except (TypeError, KeyError):
523-
pass
524529
mask = 0
525530
for bit in bits:
526531
mask |= 1 << bit
527532
return (value & mask) >> min(bits)
528533

529-
def encode_bits(self, original_value: int, bits: list[int], bit_value: int):
530-
try:
534+
def encode_bits(
535+
self, original_value: int, bits: Union[str, Collection[int]], bit_value: int
536+
) -> int:
537+
"""Replace the specified bits with the given (unshifted) pattern.
538+
539+
The bit offsets sequence may be non-contiguous, but the replacement pattern
540+
must specify all bits including the "holes". It is only shifted once, so the
541+
LSB lands at the lowest specified bit offset.
542+
543+
:param original_value: Variable value holding the bits
544+
:param bits: Registered lookup name or concrete list of bit offsets
545+
:param bit_value: Source pattern to overwrite with
546+
:return: Merged value with the bits replaced
547+
:raises KeyError: For unknown lookup names
548+
"""
549+
if isinstance(bits, str):
531550
bits = self.bit_definitions[bits]
532-
except (TypeError, KeyError):
533-
pass
534551
temp = original_value
535552
mask = 0
536553
for bit in bits:

test/test_od.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,16 @@ def test_bits(self):
238238
self.assertEqual(var.decode_bits(1, "BIT 0"), 1)
239239
self.assertEqual(var.decode_bits(1, [1]), 0)
240240
self.assertEqual(var.decode_bits(0xf, [0, 1, 2, 3]), 15)
241+
self.assertEqual(var.decode_bits(0xf, range(4)), 15)
241242
self.assertEqual(var.decode_bits(8, "BIT 2 and 3"), 2)
242243
self.assertEqual(var.encode_bits(0xf, [1], 0), 0xd)
243244
self.assertEqual(var.encode_bits(0, "BIT 0", 1), 1)
244245

246+
with self.assertRaises(KeyError):
247+
var.decode_bits(0, "DOES NOT EXIST")
248+
with self.assertRaises(KeyError):
249+
var.encode_bits(0, "DOES NOT EXIST", 0)
250+
245251

246252
class TestObjectDictionary(unittest.TestCase):
247253

0 commit comments

Comments
 (0)