Skip to content

Commit 295f907

Browse files
committed
Fix: Add missing Node arg in TestLocalNodeProcessorClass' setUp method. Add more type hints (Including: set originalMTI to Union[int, None] instead of using hasattr; mark PIP methods as @staticmethod to match usage). Fix: Make Scanner.EOF int so doesn't overlap utf-8 & find works. Remove lint or add appropriate ignores.
1 parent 3451b7c commit 295f907

33 files changed

Lines changed: 252 additions & 152 deletions

examples/tkexamples/cdiform.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def __init__(self, *args, **kwargs):
6868
raise ValueError("at least one argument (parent) is required")
6969
self.parent = args[0]
7070
self.root = args[0]
71-
self.ignore_non_gui_tags = None
71+
self.ignore_non_gui_tags = None # type: deque
7272
if hasattr(self.parent, 'root'):
7373
self.root = self.parent.root
7474
self._container = self # where to put visible widgets

openlcb/__init__.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
ORD_z = 0x7A
2424

2525

26-
def only_hex_pairs(value: str) -> bool:
26+
def only_hex_pairs(value: str) -> Union[re.Match[bytes], re.Match[str], None]:
2727
"""Check if string contains only machine-readable hex pairs.
2828
See openlcb.conventions submodule for LCC ID dot notation
2929
functions (less restrictive).
@@ -68,7 +68,8 @@ def list_type_names(values) -> List[str]:
6868
.format(type(values).__name__))
6969

7070

71-
def precise_sleep(seconds: Union[float, int], start: float = None) -> None:
71+
def precise_sleep(seconds: Union[float, int],
72+
start: Union[float, None] = None) -> None:
7273
"""Wait for a precise number of seconds
7374
(precise to hundredths approximately, depending on accuracy of
7475
platform's sleep). Since time.sleep(seconds) is generally not
@@ -91,13 +92,16 @@ def formatted_ex(ex) -> str:
9192
return "{}: {}".format(type(ex).__name__, ex)
9293

9394

94-
def from_hex_bytes(b: bytearray, start: int, stop: int, assertValid=True) -> bytearray:
95+
def from_hex_bytes(b: bytearray, start: int, stop: int,
96+
assertValid=True) -> bytearray:
9597
"""ASCII hex bytearray (even length) → binary bytearray"""
9698
# like bytearray.fromhex, except accepts bytes rather than str only
9799
r = bytearray((stop-start) // 2)
98100
if assertValid:
99101
if (stop-start) % 2 > 0:
100-
raise IndexError("Only hex pairs are accepted, got odd count: start={} stop={}".format(start, stop))
102+
raise IndexError(
103+
"Only hex pairs are accepted, got odd count: start={} stop={}"
104+
.format(start, stop))
101105
if start < 0 or start > len(b):
102106
raise IndexError("start={} len={}".format(start, len(b)))
103107
if stop < 0 or stop > len(b):
@@ -110,12 +114,12 @@ def from_hex_bytes(b: bytearray, start: int, stop: int, assertValid=True) -> byt
110114
while i < stop:
111115
x, y = b[i], b[i+1]
112116
if assertValid:
113-
if not ((x >= ORD_A and x <= ORD_F) or (x >= ORD_a and x <= ORD_f) or (x >= ORD_0 and x <= ORD_9)):
114-
raise ValueError("Got character {}, expected hex digit".format((bytearray([x])).decode("utf-8")))
115-
if not ((y >= ORD_A and y <= ORD_F) or (y >= ORD_a and y <= ORD_f) or (y >= ORD_0 and y <= ORD_9)):
116-
raise ValueError("Got character {}, expected hex digit".format((bytearray([y])).decode("utf-8")))
117+
if not ((x >= ORD_A and x <= ORD_F) or (x >= ORD_a and x <= ORD_f) or (x >= ORD_0 and x <= ORD_9)): # noqa: E501
118+
raise ValueError("Got character {}, expected hex digit".format((bytearray([x])).decode("utf-8"))) # noqa: E501
119+
if not ((y >= ORD_A and y <= ORD_F) or (y >= ORD_a and y <= ORD_f) or (y >= ORD_0 and y <= ORD_9)): # noqa: E501
120+
raise ValueError("Got character {}, expected hex digit".format((bytearray([y])).decode("utf-8"))) # noqa: E501
117121
# v =
118-
# NOTE: below will still raise exception if over 255 even if assertValid is False
122+
# NOTE: below will still raise exception if over 255 even if assertValid is False # noqa: E501
119123
r[rel] = ((x & 15) + ((x >> 6) & 1) * 9) << 4 | \
120124
((y & 15) + ((y >> 6) & 1) * 9)
121125
# assert v < 256, str(b[i:i+1])

openlcb/canbus/canframe.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22

33
from collections import OrderedDict
44
from logging import getLogger
5+
from typing import Any, Union
56

7+
from openlcb import (
8+
emit_cast,
9+
list_type_names,
10+
)
611
from openlcb.canbus.controlframe import ControlFrame
712
from openlcb.nodeid import NodeID
813

@@ -125,13 +130,13 @@ def alias(self) -> int:
125130
def __init__(self, *args, afterSendState=None, reservation=None,
126131
minimumState=None):
127132
self.afterSendState = afterSendState
128-
self.encoder = NoEncoder()
133+
self.encoder = NoEncoder() # type: Any
129134
self.reservation = reservation
130135
self.minimumState = minimumState
131136
arg1 = None
132137
arg2 = None
133138
arg3 = None
134-
self._alias = None
139+
self._alias = None # type: Union[int, None] # TODO: type: int ?
135140
if len(args) > 0:
136141
arg1 = args[0]
137142
if len(args) > 1:
@@ -142,7 +147,7 @@ def __init__(self, *args, afterSendState=None, reservation=None,
142147
arg3 = bytearray()
143148
# There are three ctor forms.
144149
# - See "Args" in class for docstring.
145-
self.header = 0
150+
self.header = 0 # type: int
146151
self.direction = None # See deque for usage
147152
self.data = bytearray()
148153
# three arguments as N_cid, nodeID, alias
@@ -153,26 +158,35 @@ def __init__(self, *args, afterSendState=None, reservation=None,
153158
# (duck typing) in this case.
154159
if len(args) < 3:
155160
args_error = "Expected alias after NodeID"
161+
assert isinstance(arg3, int), \
162+
("Expected int alias after NodeID, got"
163+
f" {emit_cast(arg3)}")
156164
# cid must be 4 to 7 inclusive (100 to 111 binary)
157165
# precondition(4 <= cid && cid <= 7)
158-
cid = arg1
159166
nodeID = arg2
160167
self._alias = arg3
168+
if not isinstance(arg1, int):
169+
args_error = \
170+
("Expected CID 4 to 7 if arg2 is NodeID,"
171+
f" got {emit_cast(arg1)}")
172+
else:
173+
cid = arg1
161174

162-
nodeCode = ((nodeID.value >> ((cid-4)*12)) & 0xFFF)
163-
# ^ cid-4 results in 0 to 3. *12 results in 0 to 36 bit shift (nodeID size) # noqa: E501
164-
self.header = ((cid << 12) | nodeCode) << 12 | (self._alias & 0xFFF) | 0x10_00_00_00 # noqa: E501
165-
# self.data = bytearray()
175+
nodeCode = ((nodeID.value >> ((cid-4)*12)) & 0xFFF)
176+
# ^ cid-4 results in 0 to 3. *12 results in 0 to 36 bit shift (nodeID size) # noqa: E501
177+
self.header = ((cid << 12) | nodeCode) << 12 | (self._alias & 0xFFF) | 0x10_00_00_00 # noqa: E501
178+
# self.data = bytearray()
166179

167180
# two arguments as header, data
168181
elif isinstance(arg2, bytearray):
169182
# TODO: decode (header?) if self._alias is necessary in this case,
170183
# otherwise is remains None!
171184
if not isinstance(arg1, int):
172-
args_error = "Expected int(header) since 2nd argument is bytearray."
185+
args_error = \
186+
"Expected int(header) since 2nd argument is bytearray."
173187
# Types of both args are enforced by this point.
174-
self.header = arg1
175-
self._alias = arg1 & 0xFFF
188+
self.header = arg1 # type: ignore
189+
self._alias = arg1 & 0xFFF # type: ignore
176190
if self._alias == 0:
177191
logger.warning("Alias is {}".format(self._alias))
178192
self.data = arg2
@@ -190,7 +204,7 @@ def __init__(self, *args, afterSendState=None, reservation=None,
190204
# three arguments as control, alias, data
191205
elif isinstance(arg2, int):
192206
# Types of all 3 are enforced by usage (duck typing) in this case.
193-
control = arg1
207+
control = arg1 # type: int # type: ignore
194208
self._alias = arg2
195209
self.header = \
196210
(control << 12) | (self._alias & 0xFFF) | 0x10_00_00_00
@@ -203,9 +217,10 @@ def __init__(self, *args, afterSendState=None, reservation=None,
203217

204218
if args_error:
205219
raise TypeError(
206-
args_error.rstrip(".") + ". Valid constructors:"
207-
+ CanFrame.constructor_help() + ". Got: "
208-
+ openlcb.list_type_names(args))
220+
"{}. Valid constructors: {}. Got: {}".format(
221+
args_error.rstrip("."),
222+
CanFrame.constructor_help(),
223+
list_type_names(args)))
209224
if self._alias is not None:
210225
if self._alias & 0xFFF != self._alias:
211226
raise ValueError(

openlcb/canbus/canlink.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from typing import (
2525
# Iterable,
2626
List, # in case list doesn't support `[` in this Python version
27-
# Union, # in case `|` doesn't support 'type' in this Python version
27+
Union, # in case `|` doesn't support 'type' in this Python version
2828
)
2929

3030
from openlcb import (
@@ -208,7 +208,7 @@ def isAllowed(self, frame: CanFrame) -> bool:
208208
return False
209209
return self.blockedFrameType(frame) is None
210210

211-
def blockedFrameType(self, frame: CanFrame) -> ControlFrame:
211+
def blockedFrameType(self, frame: CanFrame) -> Union[ControlFrame, None]:
212212
if self._state == CanLink.State.Permitted:
213213
# All frame types are allowed in this state.
214214
return None
@@ -219,7 +219,10 @@ def blockedFrameType(self, frame: CanFrame) -> ControlFrame:
219219
# - only AMR is allowed to be sent while transitioning to
220220
# Inhibited state (and in this implementation, prior
221221
# states occur before sending CID)
222-
if self.getState().value >= frame.minimumState.value:
222+
state = self.getState()
223+
if state is None:
224+
return control_frame
225+
if state.value >= frame.minimumState.value:
223226
return None
224227
if control_frame == ControlFrame.CID:
225228
return None
@@ -243,7 +246,7 @@ def isDuplicateAlias(self, alias):
243246
# link layer later switched to an Inhibited state and had to
244247
# generate a new alias
245248

246-
def blockedReason(self, frame: CanFrame) -> str:
249+
def blockedReason(self, frame: CanFrame) -> Union[str, None]:
247250
if self.isCanceled(frame):
248251
return "The frame is using an alias from a previous reservation"
249252
blocked_type = self.blockedFrameType(frame)
@@ -619,7 +622,8 @@ def handleReceivedAMR(self, frame: CanFrame):
619622
alias = frame.header & 0xFFF
620623
if (self.checkAndHandleAliasCollision(frame)):
621624
pass # return
622-
logger.warning(f"Accepting AMR after collision (alias={alias:02X})")
625+
logger.warning(
626+
f"Accepting AMR after collision (alias={alias:02X})")
623627
# Alias Map Reset - drop from maps
624628
if not frame.data:
625629
logger.warning(f"Bad AMR (no data, so no NodeID) from {alias:02X}")
@@ -645,7 +649,9 @@ def handleReceivedAMR(self, frame: CanFrame):
645649
except KeyError:
646650
pass # deleted by a concurrent process
647651
else:
648-
logger.warning(f"AMR ignored: Node {nodeID} can't delete node {storedID}'s alias {alias} which is the same")
652+
logger.warning(
653+
f"AMR ignored: Node {nodeID} can't delete node"
654+
f" {storedID}'s alias {alias} which is the same")
649655

650656
storedAlias = self.nodeIdToAlias.get(nodeID)
651657
if storedAlias == alias:
@@ -658,7 +664,9 @@ def handleReceivedAMR(self, frame: CanFrame):
658664
except KeyError:
659665
pass # deleted by a concurrent process
660666
else:
661-
logger.warning(f"AMR ignored: Alias {alias} can't delete alias {storedAlias}'s NodeID {nodeID} which is the same")
667+
logger.warning(
668+
f"AMR ignored: Alias {alias} can't delete alias"
669+
f" {storedAlias}'s NodeID {nodeID} which is the same")
662670
try:
663671
self.duplicateAliases.remove(alias)
664672
except ValueError:
@@ -1380,9 +1388,10 @@ def waitForReady(self, device: PortInterface, mode="binary",
13801388
break
13811389
# if verbose:
13821390
# print(" * state: {}".format(state))
1383-
assert self.getWaitForAliasResponseStart() is not None, \
1391+
responseStart = self.getWaitForAliasResponseStart()
1392+
assert responseStart is not None, \
13841393
"openlcb didn't send 7,6,5,4 CIDs (state={})".format(state)
1385-
if ((default_timer() - self.getWaitForAliasResponseStart())
1394+
if ((default_timer() - responseStart)
13861395
> CanLink.ALIAS_RESPONSE_DELAY):
13871396
# 200ms = standard wait time for responses
13881397
pass # no collisions (fail collision test if doing that)

openlcb/canbus/canlinklayersimulation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77

88

99
class CanLinkLayerSimulation(CanLink):
10-
pass
10+
pass

openlcb/canbus/canphysicallayer.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
and is subclassed.
66
'''
77
from logging import getLogger
8-
from typing import Callable
8+
from typing import Callable, Union
99
import warnings
1010

1111
from openlcb.canbus.canframe import CanFrame
@@ -50,7 +50,7 @@ def sendFrameAfter(self, frame: CanFrame):
5050
frame.encoder = self
5151
PhysicalLayer.sendFrameAfter(self, frame) # calls onQueuedFrame if set
5252

53-
def pollFrame(self) -> CanFrame:
53+
def pollFrame(self) -> Union[CanFrame, None]:
5454
frame = super().pollFrame()
5555
if frame is None:
5656
return None
@@ -68,8 +68,8 @@ def registerFrameReceivedListener(self,
6868
" You don't really need to listen to packets."
6969
" Use pollFrame instead, which will collect and decode"
7070
" packets into frames (this layer communicates to upper layers"
71-
" using physicalLayer.onFrameReceived set by LinkLayer/subclass"
72-
" constructor).")
71+
" using physicalLayer.onFrameReceived set by"
72+
" LinkLayer/subclass constructor).")
7373
self._frameReceivedListeners.append(listener)
7474

7575
def fireFrameReceived(self, frame: CanFrame):

openlcb/canbus/canphysicallayergridconnect.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ def receiveAll(self, device: PortInterface, verbose=False) -> int:
105105
pass
106106
return count
107107

108-
def sendAll(self, device: PortInterface, mode="binary", verbose=False) -> int:
108+
def sendAll(self, device: PortInterface, mode="binary",
109+
verbose=False) -> int:
109110
"""Send all queued frames using the given device.
110111
111112
Args:
@@ -169,7 +170,8 @@ def handleDataString(self, string: str) -> int:
169170

170171
@classmethod
171172
def nextPacketRange(cls, data: Union[bytes, bytearray],
172-
start: int = 0) -> Tuple[int, int]:
173+
start: int = 0) -> Union[Tuple[int, int],
174+
Tuple[None, None]]:
173175
"""Get the packet slice if any.
174176
Returns:
175177
tuple(int, int): Position of ':' and position *after* ';' or
@@ -187,7 +189,8 @@ def nextPacketRange(cls, data: Union[bytes, bytearray],
187189
return (firstI, lastI+1)
188190

189191
@classmethod
190-
def nextPacket(cls, data: Union[bytes, bytearray]) -> bytearray:
192+
def nextPacket(cls, data: Union[bytes, bytearray]) -> Union[bytearray,
193+
bytes, None]:
191194
"""Get the packet including ':' and ';'."""
192195
start, end = cls.nextPacketRange(data)
193196
if start is None:
@@ -197,7 +200,8 @@ def nextPacket(cls, data: Union[bytes, bytearray]) -> bytearray:
197200
@staticmethod
198201
def readInt32(data, start):
199202
# chunk = data[start:start+8]
200-
# return int.from_bytes(chunk, 'big') - 0x30303030 + ((chunk & 0x40404040) >> 6) * 9
203+
# return (int.from_bytes(chunk, 'big') - 0x30303030 +
204+
# ((chunk & 0x40404040) >> 6) * 9)
201205
"""Fast hex bytearray → 32-bit int (8 hex chars, ASCII)"""
202206
# branchless conversion for uppercase/lowercase or digits:
203207
# - (data[i] & 15): Gives the low 4 bits (0-9 for digits
@@ -320,7 +324,7 @@ def handleData(self, data: Union[bytes, bytearray],
320324
if first is None:
321325
break
322326
# else last is not None guaranteed
323-
semi = (end - 2) if (self.inboundBuffer[end-1] == 0x0a) else (end - 1)
327+
semi = (end - 2) if (self.inboundBuffer[end-1] == 0x0a) else (end - 1) # noqa: E501
324328
if semi - first < 11: # len(":X") + 8 data + len("N")
325329
logger.warning(
326330
"[handleData] Skipped malformed {} "
@@ -352,17 +356,19 @@ def handleData(self, data: Union[bytes, bytearray],
352356
dataEnd = semi
353357
if self.assertValidData and (dataEnd - dataI > 0):
354358
assert only_hex_pairs(self.inboundBuffer[headerI:headerEnd]), \
355-
self.inboundBuffer[headerI:headerEnd] # show the non-hex data
359+
self.inboundBuffer[headerI:headerEnd] # show non-hex data
356360
assert only_hex_pairs(self.inboundBuffer[dataI:dataEnd]), \
357361
self.inboundBuffer[dataI:dataEnd] # show the non-hex data
358-
header_bytes = from_hex_bytes(self.inboundBuffer, headerI, headerEnd,
362+
header_bytes = from_hex_bytes(self.inboundBuffer, headerI,
363+
headerEnd,
359364
assertValid=self.assertValidData)
360365
if dataEnd - dataI > 0:
361366
if (dataEnd - dataI) % 2 > 0:
362367
logger.warning(
363368
"[handleData] Skipped malformed packet"
364369
" (Incomplete pair in {} (range {},{}) in {})"
365-
.format(repr(self.inboundBuffer[dataI:dataEnd]), dataI, dataEnd, repr(self.inboundBuffer[first:end])))
370+
.format(repr(self.inboundBuffer[dataI:dataEnd]), dataI,
371+
dataEnd, repr(self.inboundBuffer[first:end])))
366372
start = end
367373
continue
368374
outData = from_hex_bytes(self.inboundBuffer, dataI, dataEnd,

openlcb/canbus/canphysicallayersimulation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def sendAll(self, device, mode="binary", verbose=False) -> int:
6060
blockedMsg = self.linkLayer.blockedReason(frame)
6161
if blockedMsg:
6262
if verbose:
63-
print("Skipping sending frame: {}".format(blockedMsg))
63+
print(f"Skipping sending frame: {blockedMsg}")
6464
continue
6565
# data = self.encodeFrameAsData(frame)
6666
# device.send(data) # commented since simulation

openlcb/canbus/seriallink.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def _settimeout(self, seconds: float):
2828
logger.warning("settimeout is not implemented for SerialLink")
2929
pass
3030

31-
def _connect(self, _, port: str, device: serial.Serial = None,
31+
def _connect(self, _, port: str, device: Union[serial.Serial, None] = None,
3232
baudrate: int = 230400):
3333
"""Connect to a serial port.
3434
@@ -51,7 +51,7 @@ def _connect(self, _, port: str, device: serial.Serial = None,
5151
self._device = device
5252
self._device.reset_input_buffer() # drop anything that's just sitting there already # noqa: E501
5353

54-
def _send(self, msg: Union[bytes, bytearray]):
54+
def _send(self, data: Union[bytes, bytearray]) -> None:
5555
"""send bytes
5656
5757
Args:
@@ -62,8 +62,8 @@ def _send(self, msg: Union[bytes, bytearray]):
6262
RuntimeError: If the string couldn't be written to the port.
6363
"""
6464
total_sent = 0
65-
while total_sent < len(msg[total_sent:]):
66-
sent = self._device.write(msg[total_sent:])
65+
while total_sent < len(data[total_sent:]):
66+
sent = self._device.write(data[total_sent:])
6767
if sent == 0:
6868
self.setOpen(False)
6969
raise RuntimeError("socket connection broken")

0 commit comments

Comments
 (0)