From 8e28cc587e864384b6c8b1aa07eb0c85f4950e72 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 27 Jan 2022 08:12:50 +0100 Subject: [PATCH 01/13] Fix deprecation in test case --- test/notifier_test.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/test/notifier_test.py b/test/notifier_test.py index c9d8f4a27..4336944a5 100644 --- a/test/notifier_test.py +++ b/test/notifier_test.py @@ -40,17 +40,19 @@ def test_multiple_bus(self): class AsyncNotifierTest(unittest.TestCase): def test_asyncio_notifier(self): - loop = asyncio.get_event_loop() - bus = can.Bus("test", bustype="virtual", receive_own_messages=True) - reader = can.AsyncBufferedReader() - notifier = can.Notifier(bus, [reader], 0.1, loop=loop) - msg = can.Message() - bus.send(msg) - future = asyncio.wait_for(reader.get_message(), 1.0) - recv_msg = loop.run_until_complete(future) - self.assertIsNotNone(recv_msg) - notifier.stop() - bus.shutdown() + async def run_it(): + loop = asyncio.get_running_loop() + bus = can.Bus("test", bustype="virtual", receive_own_messages=True) + reader = can.AsyncBufferedReader() + notifier = can.Notifier(bus, [reader], 0.1, loop=loop) + msg = can.Message() + bus.send(msg) + recv_msg = await asyncio.wait_for(reader.get_message(), 1.0) + self.assertIsNotNone(recv_msg) + notifier.stop() + bus.shutdown() + + asyncio.run(run_it()) if __name__ == "__main__": From b250424b0c9ffdfd886805aa0b16cf93c5881279 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 27 Jan 2022 08:13:18 +0100 Subject: [PATCH 02/13] Modernize example --- examples/asyncio_demo.py | 75 ++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/examples/asyncio_demo.py b/examples/asyncio_demo.py index d501d1aaf..d8e1def1c 100755 --- a/examples/asyncio_demo.py +++ b/examples/asyncio_demo.py @@ -8,54 +8,45 @@ import can -def print_message(msg): +def print_message(msg: can.Message) -> None: """Regular callback function. Can also be a coroutine.""" print(msg) -async def main(): +async def main() -> None: """The main function that runs in the loop.""" - bus = can.Bus("vcan0", bustype="virtual", receive_own_messages=True) - reader = can.AsyncBufferedReader() - logger = can.Logger("logfile.asc") - - listeners = [ - print_message, # Callback function - reader, # AsyncBufferedReader() listener - logger, # Regular Listener object - ] - # Create Notifier with an explicit loop to use for scheduling of callbacks - loop = asyncio.get_event_loop() - notifier = can.Notifier(bus, listeners, loop=loop) - # Start sending first message - bus.send(can.Message(arbitration_id=0)) - - print("Bouncing 10 messages...") - for _ in range(10): - # Wait for next message from AsyncBufferedReader - msg = await reader.get_message() - # Delay response - await asyncio.sleep(0.5) - msg.arbitration_id += 1 - bus.send(msg) - # Wait for last message to arrive - await reader.get_message() - print("Done!") - - # Clean-up - notifier.stop() - bus.shutdown() + with can.Bus("vcan0", bustype="virtual", receive_own_messages=True) as bus: + reader = can.AsyncBufferedReader() + logger = can.Logger("logfile.asc") + + listeners = [ + print_message, # Callback function + reader, # AsyncBufferedReader() listener + logger, # Regular Listener object + ] + # Create Notifier with an explicit loop to use for scheduling of callbacks + loop = asyncio.get_running_loop() + notifier = can.Notifier(bus, listeners, loop=loop) + # Start sending first message + bus.send(can.Message(arbitration_id=0)) + + print("Bouncing 10 messages...") + for _ in range(10): + # Wait for next message from AsyncBufferedReader + msg = await reader.get_message() + # Delay response + await asyncio.sleep(0.5) + msg.arbitration_id += 1 + bus.send(msg) + + # Wait for last message to arrive + await reader.get_message() + print("Done!") + + # Clean-up + notifier.stop() if __name__ == "__main__": - try: - # Get the default event loop - LOOP = asyncio.get_event_loop() - # Run until main coroutine finishes - LOOP.run_until_complete(main()) - finally: - LOOP.close() - - # or on Python 3.7+ simply - # asyncio.run(main()) + asyncio.run(main()) From b83bb55458a14a13dfbf2cf605b6c7461b699f57 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 27 Jan 2022 08:20:34 +0100 Subject: [PATCH 03/13] Better test_asyncio_notifier() --- test/notifier_test.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/test/notifier_test.py b/test/notifier_test.py index 4336944a5..a7a236bf4 100644 --- a/test/notifier_test.py +++ b/test/notifier_test.py @@ -41,16 +41,13 @@ def test_multiple_bus(self): class AsyncNotifierTest(unittest.TestCase): def test_asyncio_notifier(self): async def run_it(): - loop = asyncio.get_running_loop() - bus = can.Bus("test", bustype="virtual", receive_own_messages=True) - reader = can.AsyncBufferedReader() - notifier = can.Notifier(bus, [reader], 0.1, loop=loop) - msg = can.Message() - bus.send(msg) - recv_msg = await asyncio.wait_for(reader.get_message(), 1.0) - self.assertIsNotNone(recv_msg) - notifier.stop() - bus.shutdown() + with can.Bus("test", bustype="virtual", receive_own_messages=True) as bus: + reader = can.AsyncBufferedReader() + notifier = can.Notifier(bus, [reader], 0.1, loop=asyncio.get_running_loop()) + bus.send(can.Message()) + recv_msg = await asyncio.wait_for(reader.get_message(), 0.5) + self.assertIsNotNone(recv_msg) + notifier.stop() asyncio.run(run_it()) From 79d37c133ff4b977516bc43f215c54056a7a6203 Mon Sep 17 00:00:00 2001 From: felixdivo Date: Thu, 27 Jan 2022 07:21:19 +0000 Subject: [PATCH 04/13] Format code with black --- test/notifier_test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/notifier_test.py b/test/notifier_test.py index a7a236bf4..c2466e80c 100644 --- a/test/notifier_test.py +++ b/test/notifier_test.py @@ -43,7 +43,9 @@ def test_asyncio_notifier(self): async def run_it(): with can.Bus("test", bustype="virtual", receive_own_messages=True) as bus: reader = can.AsyncBufferedReader() - notifier = can.Notifier(bus, [reader], 0.1, loop=asyncio.get_running_loop()) + notifier = can.Notifier( + bus, [reader], 0.1, loop=asyncio.get_running_loop() + ) bus.send(can.Message()) recv_msg = await asyncio.wait_for(reader.get_message(), 0.5) self.assertIsNotNone(recv_msg) From fb2bf9413a69dbec0193902931b332bac83da3d0 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 27 Jan 2022 08:31:37 +0100 Subject: [PATCH 05/13] Attempt to fix typing; improve docs --- can/notifier.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/can/notifier.py b/can/notifier.py index 2554fb4fc..3ba7b4578 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -2,7 +2,7 @@ This module contains the implementation of :class:`~can.Notifier`. """ -from typing import Any, cast, Iterable, List, Optional, Union, Awaitable +from typing import Any, Callable, cast, Iterable, List, Optional, Union, Awaitable from can.bus import BusABC from can.listener import Listener @@ -16,15 +16,18 @@ logger = logging.getLogger("can.Notifier") +Listenable = Union[Listener, Callable[[Message], None]] + + class Notifier: def __init__( self, bus: Union[BusABC, List[BusABC]], - listeners: Iterable[Listener], + listeners: Iterable[Listenable], timeout: float = 1.0, loop: Optional[asyncio.AbstractEventLoop] = None, ) -> None: - """Manages the distribution of :class:`can.Message` instances to listeners. + """Manages the distribution of :class:`~can.Message` instances to listeners. Supports multiple buses and listeners. @@ -35,11 +38,12 @@ def __init__( :param bus: A :ref:`bus` or a list of buses to listen to. - :param listeners: An iterable of :class:`~can.Listener` - :param timeout: An optional maximum number of seconds to wait for any message. - :param loop: An :mod:`asyncio` event loop to schedule listeners in. + :param listeners: + An iterable of :class:`~can.Listener` or callables that receive a :class:`~can.Message` and return nothing. + :param timeout: An optional maximum number of seconds to wait for any :class:`~can.Message`. + :param loop: An :mod:`asyncio` event loop to schedule the ``listeners`` in. """ - self.listeners: List[Listener] = list(listeners) + self.listeners: List[Listenable] = list(listeners) self.bus = bus self.timeout = timeout self._loop = loop From 09c0e1a32046f1077e25ac0bebdc57d53cffa441 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 27 Jan 2022 08:37:09 +0100 Subject: [PATCH 06/13] try to fix mypy failure with type annotation --- examples/asyncio_demo.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/asyncio_demo.py b/examples/asyncio_demo.py index d8e1def1c..f1326e202 100755 --- a/examples/asyncio_demo.py +++ b/examples/asyncio_demo.py @@ -5,7 +5,10 @@ """ import asyncio +from typing import List + import can +from can.notifier import Listenable def print_message(msg: can.Message) -> None: @@ -20,7 +23,7 @@ async def main() -> None: reader = can.AsyncBufferedReader() logger = can.Logger("logfile.asc") - listeners = [ + listeners: List[Listenable] = [ print_message, # Callback function reader, # AsyncBufferedReader() listener logger, # Regular Listener object From aa1b163f68cd711eb20119295433a55314bea511 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:42:56 +0100 Subject: [PATCH 07/13] Better bus handling --- examples/asyncio_demo.py | 2 +- examples/serial_com.py | 6 ++--- test/notifier_test.py | 49 +++++++++++++++++++--------------------- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/examples/asyncio_demo.py b/examples/asyncio_demo.py index f1326e202..272cc59b8 100755 --- a/examples/asyncio_demo.py +++ b/examples/asyncio_demo.py @@ -19,7 +19,7 @@ def print_message(msg: can.Message) -> None: async def main() -> None: """The main function that runs in the loop.""" - with can.Bus("vcan0", bustype="virtual", receive_own_messages=True) as bus: + with can.Bus(interface="virtual", channel="my_channel_0", receive_own_messages=True) as bus: reader = can.AsyncBufferedReader() logger = can.Logger("logfile.asc") diff --git a/examples/serial_com.py b/examples/serial_com.py index 1fbc997b2..c57207a77 100755 --- a/examples/serial_com.py +++ b/examples/serial_com.py @@ -47,9 +47,9 @@ def receive(bus, stop_event): def main(): - """Controles the sender and receiver.""" - with can.interface.Bus(bustype="serial", channel="/dev/ttyS10") as server: - with can.interface.Bus(bustype="serial", channel="/dev/ttyS11") as client: + """Controls the sender and receiver.""" + with can.interface.Bus(interface="serial", channel="/dev/ttyS10") as server: + with can.interface.Bus(interface="serial", channel="/dev/ttyS11") as client: tx_msg = can.Message( arbitration_id=0x01, diff --git a/test/notifier_test.py b/test/notifier_test.py index c2466e80c..ca2093f55 100644 --- a/test/notifier_test.py +++ b/test/notifier_test.py @@ -9,39 +9,36 @@ class NotifierTest(unittest.TestCase): def test_single_bus(self): - bus = can.Bus("test", bustype="virtual", receive_own_messages=True) - reader = can.BufferedReader() - notifier = can.Notifier(bus, [reader], 0.1) - msg = can.Message() - bus.send(msg) - self.assertIsNotNone(reader.get_message(1)) - notifier.stop() - bus.shutdown() + with can.Bus("test", interface="virtual", receive_own_messages=True) as bus: + reader = can.BufferedReader() + notifier = can.Notifier(bus, [reader], 0.1) + msg = can.Message() + bus.send(msg) + self.assertIsNotNone(reader.get_message(1)) + notifier.stop() def test_multiple_bus(self): - bus1 = can.Bus(0, bustype="virtual", receive_own_messages=True) - bus2 = can.Bus(1, bustype="virtual", receive_own_messages=True) - reader = can.BufferedReader() - notifier = can.Notifier([bus1, bus2], [reader], 0.1) - msg = can.Message() - bus1.send(msg) - time.sleep(0.1) - bus2.send(msg) - recv_msg = reader.get_message(1) - self.assertIsNotNone(recv_msg) - self.assertEqual(recv_msg.channel, 0) - recv_msg = reader.get_message(1) - self.assertIsNotNone(recv_msg) - self.assertEqual(recv_msg.channel, 1) - notifier.stop() - bus1.shutdown() - bus2.shutdown() + with can.Bus(0, interface="virtual", receive_own_messages=True) as bus1: + with can.Bus(1, interface="virtual", receive_own_messages=True) as bus2: + reader = can.BufferedReader() + notifier = can.Notifier([bus1, bus2], [reader], 0.1) + msg = can.Message() + bus1.send(msg) + time.sleep(0.1) + bus2.send(msg) + recv_msg = reader.get_message(1) + self.assertIsNotNone(recv_msg) + self.assertEqual(recv_msg.channel, 0) + recv_msg = reader.get_message(1) + self.assertIsNotNone(recv_msg) + self.assertEqual(recv_msg.channel, 1) + notifier.stop() class AsyncNotifierTest(unittest.TestCase): def test_asyncio_notifier(self): async def run_it(): - with can.Bus("test", bustype="virtual", receive_own_messages=True) as bus: + with can.Bus("test", interface="virtual", receive_own_messages=True) as bus: reader = can.AsyncBufferedReader() notifier = can.Notifier( bus, [reader], 0.1, loop=asyncio.get_running_loop() From a041db2815cd4ffdf4ba5f5cc940b1cc26466e55 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:43:08 +0100 Subject: [PATCH 08/13] Doc fix --- can/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interface.py b/can/interface.py index 5282d77bf..3e24eb4eb 100644 --- a/can/interface.py +++ b/can/interface.py @@ -106,7 +106,7 @@ def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg # resolve the bus class to use for that interface cls = _get_class_for_interface(kwargs["interface"]) - # remove the 'interface' key so it doesn't get passed to the backend + # remove the "interface" key, so it doesn't get passed to the backend del kwargs["interface"] # make sure the bus can handle this config format From d5f18a97703114d0055d4e349578f43ff3101e66 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:43:24 +0100 Subject: [PATCH 09/13] Fix type errors in notifier.py --- can/notifier.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/can/notifier.py b/can/notifier.py index 3ba7b4578..06dead79a 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -2,30 +2,28 @@ This module contains the implementation of :class:`~can.Notifier`. """ +import asyncio +import logging +import threading +import time from typing import Any, Callable, cast, Iterable, List, Optional, Union, Awaitable from can.bus import BusABC from can.listener import Listener from can.message import Message -import threading -import logging -import time -import asyncio - logger = logging.getLogger("can.Notifier") - Listenable = Union[Listener, Callable[[Message], None]] class Notifier: def __init__( - self, - bus: Union[BusABC, List[BusABC]], - listeners: Iterable[Listenable], - timeout: float = 1.0, - loop: Optional[asyncio.AbstractEventLoop] = None, + self, + bus: Union[BusABC, List[BusABC]], + listeners: Iterable[Listenable], + timeout: float = 1.0, + loop: Optional[asyncio.AbstractEventLoop] = None, ) -> None: """Manages the distribution of :class:`~can.Message` instances to listeners. @@ -105,8 +103,8 @@ def stop(self, timeout: float = 5) -> None: # reader is a file descriptor self._loop.remove_reader(reader) for listener in self.listeners: - if hasattr(listener, "stop"): - listener.stop() + # Mypy prefers this over a hasattr(...) check + getattr(listener, "stop", lambda: None)() def _rx_thread(self, bus: BusABC) -> None: msg = None @@ -154,9 +152,10 @@ def _on_error(self, exc: Exception) -> bool: was_handled = False for listener in self.listeners: - if hasattr(listener, "on_error"): + on_error = getattr(listener, "on_error", None) # Mypy prefers this over hasattr(...) + if on_error is not None: try: - listener.on_error(exc) + on_error(exc) except NotImplementedError: pass else: From b99cc29f95c7c700dc244e2b340deba19516f3e3 Mon Sep 17 00:00:00 2001 From: felixdivo Date: Thu, 27 Jan 2022 13:44:36 +0000 Subject: [PATCH 10/13] Format code with black --- can/notifier.py | 14 ++++++++------ examples/asyncio_demo.py | 4 +++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/can/notifier.py b/can/notifier.py index 06dead79a..8814e48b6 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -19,11 +19,11 @@ class Notifier: def __init__( - self, - bus: Union[BusABC, List[BusABC]], - listeners: Iterable[Listenable], - timeout: float = 1.0, - loop: Optional[asyncio.AbstractEventLoop] = None, + self, + bus: Union[BusABC, List[BusABC]], + listeners: Iterable[Listenable], + timeout: float = 1.0, + loop: Optional[asyncio.AbstractEventLoop] = None, ) -> None: """Manages the distribution of :class:`~can.Message` instances to listeners. @@ -152,7 +152,9 @@ def _on_error(self, exc: Exception) -> bool: was_handled = False for listener in self.listeners: - on_error = getattr(listener, "on_error", None) # Mypy prefers this over hasattr(...) + on_error = getattr( + listener, "on_error", None + ) # Mypy prefers this over hasattr(...) if on_error is not None: try: on_error(exc) diff --git a/examples/asyncio_demo.py b/examples/asyncio_demo.py index 272cc59b8..f934b2c23 100755 --- a/examples/asyncio_demo.py +++ b/examples/asyncio_demo.py @@ -19,7 +19,9 @@ def print_message(msg: can.Message) -> None: async def main() -> None: """The main function that runs in the loop.""" - with can.Bus(interface="virtual", channel="my_channel_0", receive_own_messages=True) as bus: + with can.Bus( + interface="virtual", channel="my_channel_0", receive_own_messages=True + ) as bus: reader = can.AsyncBufferedReader() logger = can.Logger("logfile.asc") From f21987a3e703f2e182635e6f034b275251c0b699 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:48:45 +0100 Subject: [PATCH 11/13] Fix example typing --- examples/asyncio_demo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/asyncio_demo.py b/examples/asyncio_demo.py index f934b2c23..bf07d6164 100755 --- a/examples/asyncio_demo.py +++ b/examples/asyncio_demo.py @@ -19,7 +19,7 @@ def print_message(msg: can.Message) -> None: async def main() -> None: """The main function that runs in the loop.""" - with can.Bus( + with can.Bus( # type: ignore interface="virtual", channel="my_channel_0", receive_own_messages=True ) as bus: reader = can.AsyncBufferedReader() From b6f05e9f596055e67fc0c36fef0e110790a3c600 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:56:32 +0100 Subject: [PATCH 12/13] Fix too long line in doc --- can/notifier.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/can/notifier.py b/can/notifier.py index 8814e48b6..df67c5df0 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -37,7 +37,8 @@ def __init__( :param bus: A :ref:`bus` or a list of buses to listen to. :param listeners: - An iterable of :class:`~can.Listener` or callables that receive a :class:`~can.Message` and return nothing. + An iterable of :class:`~can.Listener` or callables that receive a :class:`~can.Message` + and return nothing. :param timeout: An optional maximum number of seconds to wait for any :class:`~can.Message`. :param loop: An :mod:`asyncio` event loop to schedule the ``listeners`` in. """ From 7c0388b098b8b2d038ce3f2332345dae5cf43721 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Thu, 27 Jan 2022 16:38:16 +0100 Subject: [PATCH 13/13] Switch name from Listenable to MessageRecipient --- can/notifier.py | 6 +++--- examples/asyncio_demo.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/can/notifier.py b/can/notifier.py index df67c5df0..f7c004c4e 100644 --- a/can/notifier.py +++ b/can/notifier.py @@ -14,14 +14,14 @@ logger = logging.getLogger("can.Notifier") -Listenable = Union[Listener, Callable[[Message], None]] +MessageRecipient = Union[Listener, Callable[[Message], None]] class Notifier: def __init__( self, bus: Union[BusABC, List[BusABC]], - listeners: Iterable[Listenable], + listeners: Iterable[MessageRecipient], timeout: float = 1.0, loop: Optional[asyncio.AbstractEventLoop] = None, ) -> None: @@ -42,7 +42,7 @@ def __init__( :param timeout: An optional maximum number of seconds to wait for any :class:`~can.Message`. :param loop: An :mod:`asyncio` event loop to schedule the ``listeners`` in. """ - self.listeners: List[Listenable] = list(listeners) + self.listeners: List[MessageRecipient] = list(listeners) self.bus = bus self.timeout = timeout self._loop = loop diff --git a/examples/asyncio_demo.py b/examples/asyncio_demo.py index bf07d6164..0f37d6573 100755 --- a/examples/asyncio_demo.py +++ b/examples/asyncio_demo.py @@ -8,7 +8,7 @@ from typing import List import can -from can.notifier import Listenable +from can.notifier import MessageRecipient def print_message(msg: can.Message) -> None: @@ -25,7 +25,7 @@ async def main() -> None: reader = can.AsyncBufferedReader() logger = can.Logger("logfile.asc") - listeners: List[Listenable] = [ + listeners: List[MessageRecipient] = [ print_message, # Callback function reader, # AsyncBufferedReader() listener logger, # Regular Listener object