Skip to content

Commit 0019438

Browse files
karldinghardbyte
authored andcommitted
Add typing annotations for functions in can.bus (#652)
This adds typing annotations for use via mypy for all functions under can.bus. This works towards PEP 561 compatibility. * Add file for type-checking specific code. Add a Type Alias for CAN Filters used by can.bus * Fix pylint unused import error * Switch CAN Filter to TypedDict * Add mypy_extensions dependency to install_requires * Remove types generated by sphinx-autodoc-typehints With the introduction of the sphinx-autodoc-typehints extension, we don't need to duplicate typing information in the docstring as well as the function signature.
1 parent cb315b2 commit 0019438

File tree

3 files changed

+61
-40
lines changed

3 files changed

+61
-40
lines changed

can/bus.py

Lines changed: 50 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
Contains the ABC bus implementation and its documentation.
55
"""
66

7+
from typing import Any, Iterator, List, Optional, Sequence, Tuple, Union
8+
9+
import can.typechecking
10+
711
from abc import ABCMeta, abstractmethod
812
import can
913
import logging
@@ -38,7 +42,12 @@ class BusABC(metaclass=ABCMeta):
3842
RECV_LOGGING_LEVEL = 9
3943

4044
@abstractmethod
41-
def __init__(self, channel, can_filters=None, **kwargs):
45+
def __init__(
46+
self,
47+
channel: Any,
48+
can_filters: Optional[can.typechecking.CanFilters] = None,
49+
**kwargs: object
50+
):
4251
"""Construct and open a CAN bus instance of the specified type.
4352
4453
Subclasses should call though this method with all given parameters
@@ -47,26 +56,24 @@ def __init__(self, channel, can_filters=None, **kwargs):
4756
:param channel:
4857
The can interface identifier. Expected type is backend dependent.
4958
50-
:param list can_filters:
59+
:param can_filters:
5160
See :meth:`~can.BusABC.set_filters` for details.
5261
5362
:param dict kwargs:
5463
Any backend dependent configurations are passed in this dictionary
5564
"""
56-
self._periodic_tasks = []
65+
self._periodic_tasks: List[can.broadcastmanager.CyclicSendTaskABC] = []
5766
self.set_filters(can_filters)
5867

59-
def __str__(self):
68+
def __str__(self) -> str:
6069
return self.channel_info
6170

62-
def recv(self, timeout=None):
71+
def recv(self, timeout: Optional[float] = None) -> Optional[can.Message]:
6372
"""Block waiting for a message from the Bus.
6473
65-
:type timeout: float or None
6674
:param timeout:
6775
seconds to wait for a message or None to wait indefinitely
6876
69-
:rtype: can.Message or None
7077
:return:
7178
None on timeout or a :class:`can.Message` object.
7279
:raises can.CanError:
@@ -100,7 +107,9 @@ def recv(self, timeout=None):
100107
else:
101108
return None
102109

103-
def _recv_internal(self, timeout):
110+
def _recv_internal(
111+
self, timeout: Optional[float]
112+
) -> Tuple[Optional[can.Message], bool]:
104113
"""
105114
Read a message from the bus and tell whether it was filtered.
106115
This methods may be called by :meth:`~can.BusABC.recv`
@@ -128,7 +137,6 @@ def _recv_internal(self, timeout):
128137
:param float timeout: seconds to wait for a message,
129138
see :meth:`~can.BusABC.send`
130139
131-
:rtype: tuple[can.Message, bool] or tuple[None, bool]
132140
:return:
133141
1. a message that was read or None on timeout
134142
2. a bool that is True if message filtering has already
@@ -144,14 +152,13 @@ def _recv_internal(self, timeout):
144152
raise NotImplementedError("Trying to read from a write only bus?")
145153

146154
@abstractmethod
147-
def send(self, msg, timeout=None):
155+
def send(self, msg: can.Message, timeout: Optional[float] = None):
148156
"""Transmit a message to the CAN bus.
149157
150158
Override this method to enable the transmit path.
151159
152160
:param can.Message msg: A message object.
153161
154-
:type timeout: float or None
155162
:param timeout:
156163
If > 0, wait up to this many seconds for message to be ACK'ed or
157164
for transmit queue to be ready depending on driver implementation.
@@ -164,7 +171,13 @@ def send(self, msg, timeout=None):
164171
"""
165172
raise NotImplementedError("Trying to write to a readonly bus?")
166173

167-
def send_periodic(self, msgs, period, duration=None, store_task=True):
174+
def send_periodic(
175+
self,
176+
msgs: Union[Sequence[can.Message], can.Message],
177+
period: float,
178+
duration: Optional[float] = None,
179+
store_task: bool = True,
180+
) -> can.broadcastmanager.CyclicSendTaskABC:
168181
"""Start sending messages at a given period on this bus.
169182
170183
The task will be active until one of the following conditions are met:
@@ -175,20 +188,19 @@ def send_periodic(self, msgs, period, duration=None, store_task=True):
175188
- :meth:`BusABC.stop_all_periodic_tasks()` is called
176189
- the task's :meth:`CyclicTask.stop()` method is called.
177190
178-
:param Union[Sequence[can.Message], can.Message] msgs:
191+
:param msgs:
179192
Messages to transmit
180-
:param float period:
193+
:param period:
181194
Period in seconds between each message
182-
:param float duration:
195+
:param duration:
183196
Approximate duration in seconds to continue sending messages. If
184197
no duration is provided, the task will continue indefinitely.
185-
:param bool store_task:
198+
:param store_task:
186199
If True (the default) the task will be attached to this Bus instance.
187200
Disable to instead manage tasks manually.
188201
:return:
189202
A started task instance. Note the task can be stopped (and depending on
190203
the backend modified) by calling the :meth:`stop` method.
191-
:rtype: can.broadcastmanager.CyclicSendTaskABC
192204
193205
.. note::
194206
@@ -223,30 +235,34 @@ def wrapped_stop_method(remove_task=True):
223235
pass
224236
original_stop_method()
225237

226-
task.stop = wrapped_stop_method
238+
setattr(task, "stop", wrapped_stop_method)
227239

228240
if store_task:
229241
self._periodic_tasks.append(task)
230242

231243
return task
232244

233-
def _send_periodic_internal(self, msgs, period, duration=None):
245+
def _send_periodic_internal(
246+
self,
247+
msgs: Union[Sequence[can.Message], can.Message],
248+
period: float,
249+
duration: Optional[float] = None,
250+
) -> can.broadcastmanager.CyclicSendTaskABC:
234251
"""Default implementation of periodic message sending using threading.
235252
236253
Override this method to enable a more efficient backend specific approach.
237254
238-
:param Union[Sequence[can.Message], can.Message] msgs:
255+
:param msgs:
239256
Messages to transmit
240-
:param float period:
257+
:param period:
241258
Period in seconds between each message
242-
:param float duration:
259+
:param duration:
243260
The duration between sending each message at the given rate. If
244261
no duration is provided, the task will continue indefinitely.
245262
:return:
246263
A started task instance. Note the task can be stopped (and
247264
depending on the backend modified) by calling the :meth:`stop`
248265
method.
249-
:rtype: can.broadcastmanager.CyclicSendTaskABC
250266
"""
251267
if not hasattr(self, "_lock_send_periodic"):
252268
# Create a send lock for this bus, but not for buses which override this method
@@ -275,7 +291,7 @@ def stop_all_periodic_tasks(self, remove_tasks=True):
275291
if remove_tasks:
276292
self._periodic_tasks = []
277293

278-
def __iter__(self):
294+
def __iter__(self) -> Iterator[can.Message]:
279295
"""Allow iteration on messages as they are received.
280296
281297
>>> for msg in bus:
@@ -291,18 +307,18 @@ def __iter__(self):
291307
yield msg
292308

293309
@property
294-
def filters(self):
310+
def filters(self) -> Optional[can.typechecking.CanFilters]:
295311
"""
296312
Modify the filters of this bus. See :meth:`~can.BusABC.set_filters`
297313
for details.
298314
"""
299315
return self._filters
300316

301317
@filters.setter
302-
def filters(self, filters):
318+
def filters(self, filters: Optional[can.typechecking.CanFilters]):
303319
self.set_filters(filters)
304320

305-
def set_filters(self, filters=None):
321+
def set_filters(self, filters: Optional[can.typechecking.CanFilters] = None):
306322
"""Apply filtering to all messages received by this Bus.
307323
308324
All messages that match at least one filter are returned.
@@ -327,25 +343,24 @@ def set_filters(self, filters=None):
327343
self._filters = filters or None
328344
self._apply_filters(self._filters)
329345

330-
def _apply_filters(self, filters):
346+
def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]):
331347
"""
332348
Hook for applying the filters to the underlying kernel or
333349
hardware if supported/implemented by the interface.
334350
335-
:param Iterator[dict] filters:
351+
:param filters:
336352
See :meth:`~can.BusABC.set_filters` for details.
337353
"""
338354

339-
def _matches_filters(self, msg):
355+
def _matches_filters(self, msg: can.Message) -> bool:
340356
"""Checks whether the given message matches at least one of the
341357
current filters. See :meth:`~can.BusABC.set_filters` for details
342358
on how the filters work.
343359
344360
This method should not be overridden.
345361
346-
:param can.Message msg:
362+
:param msg:
347363
the message to check if matching
348-
:rtype: bool
349364
:return: whether the given message matches at least one filter
350365
"""
351366

@@ -388,33 +403,28 @@ def __exit__(self, exc_type, exc_val, exc_tb):
388403
self.shutdown()
389404

390405
@property
391-
def state(self):
406+
def state(self) -> BusState:
392407
"""
393408
Return the current state of the hardware
394-
395-
:type: can.BusState
396409
"""
397410
return BusState.ACTIVE
398411

399412
@state.setter
400-
def state(self, new_state):
413+
def state(self, new_state: BusState):
401414
"""
402415
Set the new state of the hardware
403-
404-
:type: can.BusState
405416
"""
406417
raise NotImplementedError("Property is not implemented.")
407418

408419
@staticmethod
409-
def _detect_available_configs():
420+
def _detect_available_configs() -> Iterator[dict]:
410421
"""Detect all configurations/channels that this interface could
411422
currently connect with.
412423
413424
This might be quite time consuming.
414425
415426
May not to be implemented by every interface on every platform.
416427
417-
:rtype: Iterator[dict]
418428
:return: an iterable of dicts, each being a configuration suitable
419429
for usage in the interface's bus constructor.
420430
"""

can/typechecking.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""Types for mypy type-checking
2+
"""
3+
import typing
4+
5+
import mypy_extensions
6+
7+
CanFilter = mypy_extensions.TypedDict(
8+
"CanFilter", {"can_id": int, "can_mask": int, "extended": bool}
9+
)
10+
CanFilters = typing.Iterable[CanFilter]

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
"aenum",
104104
'windows-curses;platform_system=="Windows"',
105105
"filelock",
106+
"mypy_extensions >= 0.4.0, < 0.5.0",
106107
],
107108
setup_requires=pytest_runner,
108109
extras_require=extras_require,

0 commit comments

Comments
 (0)