From a61587867de8f1225b13366eb482ecc84fc25d08 Mon Sep 17 00:00:00 2001 From: TJ Bruno Date: Sun, 15 Jan 2023 14:49:16 -0800 Subject: [PATCH 01/21] Add del method --- can/bus.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/can/bus.py b/can/bus.py index f29b8ea6e..be9f44283 100644 --- a/can/bus.py +++ b/can/bus.py @@ -71,6 +71,7 @@ def __init__( :raises ~can.exceptions.CanInitializationError: If the bus cannot be initialized """ + self._is_shutdown: bool = False self._periodic_tasks: List[_SelfRemovingCyclicTask] = [] self.set_filters(can_filters) @@ -420,7 +421,12 @@ def shutdown(self) -> None: Called to carry out any interface specific cleanup required in shutting down a bus. """ + if self._is_shutdown: + LOG.debug(f"{self.__class__} is already shut down") + return None + self.stop_all_periodic_tasks() + self._is_shutdown = True def __enter__(self): return self @@ -428,6 +434,14 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): self.shutdown() + def __del__(self) -> None: + if self._is_shutdown: + return None + + self.shutdown() + LOG.warn(f"{self.__class__} was not properly shut down") + + @property def state(self) -> BusState: """ From f9ebbace74400fa529bc8e92ffe59f02d432df30 Mon Sep 17 00:00:00 2001 From: TJ Bruno Date: Mon, 16 Jan 2023 15:03:17 -0800 Subject: [PATCH 02/21] Add unittest --- test/test_bus.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/test_bus.py b/test/test_bus.py index e11d829d3..7d9e5f727 100644 --- a/test/test_bus.py +++ b/test/test_bus.py @@ -1,3 +1,4 @@ +import gc from unittest.mock import patch import can @@ -12,3 +13,10 @@ def test_bus_ignore_config(): _ = can.Bus(interface="virtual") assert can.util.load_config.called + +@patch.object(can.bus.BusABC, "shutdown") +def test_bus_attempts_self_cleanup(mock_shutdown): + bus = can.Bus(interface="virtual") + del bus + gc.collect() + mock_shutdown.assert_called() From 60cb71b92d609fbb1e869f734b2ab99050c67bd5 Mon Sep 17 00:00:00 2001 From: TJ Bruno Date: Mon, 16 Jan 2023 20:13:26 -0800 Subject: [PATCH 03/21] Satisfy black formatter --- can/bus.py | 3 +-- test/test_bus.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/can/bus.py b/can/bus.py index be9f44283..8b7828c2e 100644 --- a/can/bus.py +++ b/can/bus.py @@ -49,7 +49,7 @@ def __init__( self, channel: Any, can_filters: Optional[can.typechecking.CanFilters] = None, - **kwargs: object + **kwargs: object, ): """Construct and open a CAN bus instance of the specified type. @@ -441,7 +441,6 @@ def __del__(self) -> None: self.shutdown() LOG.warn(f"{self.__class__} was not properly shut down") - @property def state(self) -> BusState: """ diff --git a/test/test_bus.py b/test/test_bus.py index 7d9e5f727..24421b2fd 100644 --- a/test/test_bus.py +++ b/test/test_bus.py @@ -14,6 +14,7 @@ def test_bus_ignore_config(): _ = can.Bus(interface="virtual") assert can.util.load_config.called + @patch.object(can.bus.BusABC, "shutdown") def test_bus_attempts_self_cleanup(mock_shutdown): bus = can.Bus(interface="virtual") From 9b0587d4bf8c13ea42d2daaefc807fc61dca578b Mon Sep 17 00:00:00 2001 From: TJ Bruno Date: Mon, 16 Jan 2023 20:28:53 -0800 Subject: [PATCH 04/21] Satisfy pylint linter --- can/bus.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/can/bus.py b/can/bus.py index 8b7828c2e..851c37156 100644 --- a/can/bus.py +++ b/can/bus.py @@ -422,8 +422,8 @@ def shutdown(self) -> None: in shutting down a bus. """ if self._is_shutdown: - LOG.debug(f"{self.__class__} is already shut down") - return None + LOG.debug("%s is already shut down", self.__class__) + return self.stop_all_periodic_tasks() self._is_shutdown = True @@ -436,10 +436,10 @@ def __exit__(self, exc_type, exc_val, exc_tb): def __del__(self) -> None: if self._is_shutdown: - return None + return self.shutdown() - LOG.warn(f"{self.__class__} was not properly shut down") + LOG.warning("%s was not properly shut down", self.__class__) @property def state(self) -> BusState: From 545c2f4b392a378800ff5c3c7485fc62b4063b48 Mon Sep 17 00:00:00 2001 From: Teejay Date: Sun, 22 Jan 2023 14:36:48 -0800 Subject: [PATCH 05/21] PR feedback Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> --- can/bus.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/can/bus.py b/can/bus.py index 851c37156..6780a9bb8 100644 --- a/can/bus.py +++ b/can/bus.py @@ -435,11 +435,10 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.shutdown() def __del__(self) -> None: - if self._is_shutdown: - return - - self.shutdown() - LOG.warning("%s was not properly shut down", self.__class__) + if not self._is_shutdown: + # We do some best-effort cleanup if the user forgot to properly close the bus instance + self.shutdown() + LOG.warning("%s was not properly shut down", self.__class__) @property def state(self) -> BusState: From 16fd7b92842fd18e20b2002ca95108d282e220b0 Mon Sep 17 00:00:00 2001 From: Teejay Date: Sun, 22 Jan 2023 14:37:15 -0800 Subject: [PATCH 06/21] PR feedback Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> --- can/bus.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/can/bus.py b/can/bus.py index 6780a9bb8..394954351 100644 --- a/can/bus.py +++ b/can/bus.py @@ -418,8 +418,9 @@ def flush_tx_buffer(self) -> None: def shutdown(self) -> None: """ - Called to carry out any interface specific cleanup required - in shutting down a bus. + Called to carry out any interface specific cleanup required in shutting down a bus. + + This method can be safely called multiple times. """ if self._is_shutdown: LOG.debug("%s is already shut down", self.__class__) From 90a29da5b67cc5eec16c2107078eb2b2fe379b31 Mon Sep 17 00:00:00 2001 From: TJ Bruno Date: Sun, 22 Jan 2023 22:33:47 -0800 Subject: [PATCH 07/21] Move to cls attribute --- can/bus.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/can/bus.py b/can/bus.py index 394954351..ea9ca91b6 100644 --- a/can/bus.py +++ b/can/bus.py @@ -44,6 +44,8 @@ class BusABC(metaclass=ABCMeta): #: Log level for received messages RECV_LOGGING_LEVEL = 9 + _is_shutdown: bool = False + @abstractmethod def __init__( self, @@ -71,7 +73,6 @@ def __init__( :raises ~can.exceptions.CanInitializationError: If the bus cannot be initialized """ - self._is_shutdown: bool = False self._periodic_tasks: List[_SelfRemovingCyclicTask] = [] self.set_filters(can_filters) From 18e457efa3a61acb4082df56eb644f31c439e9cd Mon Sep 17 00:00:00 2001 From: TJ Bruno Date: Sun, 22 Jan 2023 22:34:25 -0800 Subject: [PATCH 08/21] Add unittest --- test/test_interface.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 test/test_interface.py diff --git a/test/test_interface.py b/test/test_interface.py new file mode 100644 index 000000000..acb6d0e57 --- /dev/null +++ b/test/test_interface.py @@ -0,0 +1,36 @@ +import importlib +from unittest.mock import patch + +import pytest + +import can +from can.interfaces import BACKENDS + + +@pytest.fixture(params=(BACKENDS.keys())) +def constructor(request): + mod, cls = BACKENDS[request.param] + + try: + module = importlib.import_module(mod) + constructor = getattr(module, cls) + except: + pytest.skip("Unable to load interface") + + return constructor + + +@pytest.fixture +def interface(constructor): + with patch.object(constructor, "__init__", return_value=None): + return constructor() + + +@patch.object(can.bus.BusABC, "shutdown") +def test_all_interfaces_call_parent_shutdown(mock_shutdown, interface): + try: + interface.shutdown() + except: + pass + finally: + mock_shutdown.assert_called() From a598559dbc5d8f4e1ef32cf847805bec70ea515e Mon Sep 17 00:00:00 2001 From: TJ Bruno Date: Sun, 22 Jan 2023 22:51:19 -0800 Subject: [PATCH 09/21] Call parent shutdown from socketcand --- can/interfaces/socketcand/socketcand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py index 28f0c700f..4994eff59 100644 --- a/can/interfaces/socketcand/socketcand.py +++ b/can/interfaces/socketcand/socketcand.py @@ -170,5 +170,5 @@ def send(self, msg, timeout=None): self._tcp_send(ascii_msg) def shutdown(self): - self.stop_all_periodic_tasks() + super().shutdown() self.__socket.close() From a779b91f273b18dc1df9d58934a762a7b46291bd Mon Sep 17 00:00:00 2001 From: TJ Bruno Date: Mon, 23 Jan 2023 15:42:27 -0800 Subject: [PATCH 10/21] Wrap del in try except --- can/bus.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/can/bus.py b/can/bus.py index ea9ca91b6..d99dadfe1 100644 --- a/can/bus.py +++ b/can/bus.py @@ -437,10 +437,14 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.shutdown() def __del__(self) -> None: - if not self._is_shutdown: - # We do some best-effort cleanup if the user forgot to properly close the bus instance - self.shutdown() - LOG.warning("%s was not properly shut down", self.__class__) + try: + if not self._is_shutdown: + # We do some best-effort cleanup if the user forgot to properly close the bus instance + self.shutdown() + LOG.warning("%s was not properly shut down", self.__class__) + except Exception as e: + # Prevent unwanted output from being printed to stdout/stderr + LOG.debug(e) @property def state(self) -> BusState: From 8dbd73c60ada60746824ed638948edbd9db75e70 Mon Sep 17 00:00:00 2001 From: TJ Bruno Date: Mon, 23 Jan 2023 15:47:48 -0800 Subject: [PATCH 11/21] Call parent shutdown from ixxat --- can/interfaces/ixxat/canlib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 1e4055f89..17784f8bf 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -147,6 +147,7 @@ def _send_periodic_internal(self, msgs, period, duration=None): return self.bus._send_periodic_internal(msgs, period, duration) def shutdown(self): + super().shutdown() return self.bus.shutdown() @property From 248d64cd876ad1e0c6ef84e8ec8766e787d71bd2 Mon Sep 17 00:00:00 2001 From: TJ Bruno Date: Mon, 23 Jan 2023 16:02:07 -0800 Subject: [PATCH 12/21] Black & pylint --- can/bus.py | 5 +++-- test/test_interface.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/can/bus.py b/can/bus.py index d99dadfe1..dc1a3d979 100644 --- a/can/bus.py +++ b/can/bus.py @@ -439,10 +439,11 @@ def __exit__(self, exc_type, exc_val, exc_tb): def __del__(self) -> None: try: if not self._is_shutdown: - # We do some best-effort cleanup if the user forgot to properly close the bus instance + # We do some best-effort cleanup if the user + # forgot to properly close the bus instance self.shutdown() LOG.warning("%s was not properly shut down", self.__class__) - except Exception as e: + except Exception as e: # pylint: disable=W0703 # Prevent unwanted output from being printed to stdout/stderr LOG.debug(e) diff --git a/test/test_interface.py b/test/test_interface.py index acb6d0e57..c13f17c9c 100644 --- a/test/test_interface.py +++ b/test/test_interface.py @@ -23,7 +23,7 @@ def constructor(request): @pytest.fixture def interface(constructor): with patch.object(constructor, "__init__", return_value=None): - return constructor() + return constructor() @patch.object(can.bus.BusABC, "shutdown") From 468db05799c4db2fe77211b5309e4ddc1121ea2a Mon Sep 17 00:00:00 2001 From: Teejay Date: Wed, 25 Jan 2023 11:18:55 -0800 Subject: [PATCH 13/21] PR feedback Co-authored-by: Felix Divo <4403130+felixdivo@users.noreply.github.com> --- can/bus.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/can/bus.py b/can/bus.py index dc1a3d979..acf3f77dc 100644 --- a/can/bus.py +++ b/can/bus.py @@ -437,16 +437,15 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.shutdown() def __del__(self) -> None: - try: - if not self._is_shutdown: - # We do some best-effort cleanup if the user - # forgot to properly close the bus instance + if not self._is_shutdown: + LOG.warning("%s was not properly shut down", self.__class__) + # We do some best-effort cleanup if the user + # forgot to properly close the bus instance + try: self.shutdown() - LOG.warning("%s was not properly shut down", self.__class__) - except Exception as e: # pylint: disable=W0703 - # Prevent unwanted output from being printed to stdout/stderr - LOG.debug(e) - + except Exception as e: # pylint: disable=W0703 + # Prevent unwanted output from being printed to stdout/stderr + LOG.debug(e) @property def state(self) -> BusState: """ From 692b2e516a88419fac145df66b4186620df063ce Mon Sep 17 00:00:00 2001 From: TJ Bruno Date: Wed, 25 Jan 2023 11:46:52 -0800 Subject: [PATCH 14/21] Remove try/except & fix ordering --- can/bus.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/can/bus.py b/can/bus.py index acf3f77dc..cfea440a4 100644 --- a/can/bus.py +++ b/can/bus.py @@ -427,8 +427,8 @@ def shutdown(self) -> None: LOG.debug("%s is already shut down", self.__class__) return - self.stop_all_periodic_tasks() self._is_shutdown = True + self.stop_all_periodic_tasks() def __enter__(self): return self @@ -441,11 +441,8 @@ def __del__(self) -> None: LOG.warning("%s was not properly shut down", self.__class__) # We do some best-effort cleanup if the user # forgot to properly close the bus instance - try: - self.shutdown() - except Exception as e: # pylint: disable=W0703 - # Prevent unwanted output from being printed to stdout/stderr - LOG.debug(e) + self.shutdown() + @property def state(self) -> BusState: """ From c0d5ae1b5ec6d7a1b93685571b3a8cfee3a40523 Mon Sep 17 00:00:00 2001 From: TJ Bruno Date: Wed, 25 Jan 2023 12:00:31 -0800 Subject: [PATCH 15/21] Fix unittest --- test/test_interface.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/test_interface.py b/test/test_interface.py index c13f17c9c..271e90b1b 100644 --- a/test/test_interface.py +++ b/test/test_interface.py @@ -22,12 +22,18 @@ def constructor(request): @pytest.fixture def interface(constructor): - with patch.object(constructor, "__init__", return_value=None): - return constructor() + class MockInterface(constructor): + def __init__(self): + pass + + def __del__(self): + pass + + return MockInterface() @patch.object(can.bus.BusABC, "shutdown") -def test_all_interfaces_call_parent_shutdown(mock_shutdown, interface): +def test_interface_calls_parent_shutdown(mock_shutdown, interface): try: interface.shutdown() except: From 90d10a863fed692ae4ee5c83216311db1d57f3f0 Mon Sep 17 00:00:00 2001 From: TJ Bruno Date: Wed, 25 Jan 2023 12:41:47 -0800 Subject: [PATCH 16/21] Call parent shutdown from etas --- can/interfaces/etas/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/can/interfaces/etas/__init__.py b/can/interfaces/etas/__init__.py index 3a203a50d..03dc42bc1 100644 --- a/can/interfaces/etas/__init__.py +++ b/can/interfaces/etas/__init__.py @@ -248,6 +248,7 @@ def flush_tx_buffer(self) -> None: OCI_ResetQueue(self.txQueue) def shutdown(self) -> None: + super().shutdown() # Cleanup TX if self.txQueue: OCI_DestroyCANTxQueue(self.txQueue) From e3f2f6e326762f47744b2c0494f430e30f9aa11f Mon Sep 17 00:00:00 2001 From: TJ Bruno Date: Wed, 25 Jan 2023 13:24:40 -0800 Subject: [PATCH 17/21] Add warning filter --- test/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/__init__.py b/test/__init__.py index 4265cc3e6..203e1d065 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1 +1,8 @@ #!/usr/bin/env python + +import pytest + +# Ignore exceptions being raised as a result of BusABC.__del__ +pytestmark = pytest.mark.filterwarnings( + "ignore::pytest.PytestUnraisableExceptionWarning" +) From 42c2ea45e6cfd4a45612a7487de6e6ede6529d05 Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Tue, 31 Jan 2023 18:30:27 +0000 Subject: [PATCH 18/21] Make multicast_udp back2back test more specific --- test/back2back_test.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/back2back_test.py b/test/back2back_test.py index 54d619878..0ed7b8e5f 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -12,6 +12,7 @@ import pytest import can +from can import CanInterfaceNotImplementedError from can.interfaces.udp_multicast import UdpMulticastBus from .config import ( @@ -296,7 +297,7 @@ class BasicTestUdpMulticastBusIPv4(Back2BackTestCase): CHANNEL_2 = UdpMulticastBus.DEFAULT_GROUP_IPv4 def test_unique_message_instances(self): - with self.assertRaises(NotImplementedError): + with self.assertRaises(CanInterfaceNotImplementedError): super().test_unique_message_instances() @@ -315,7 +316,7 @@ class BasicTestUdpMulticastBusIPv6(Back2BackTestCase): CHANNEL_2 = HOST_LOCAL_MCAST_GROUP_IPv6 def test_unique_message_instances(self): - with self.assertRaises(NotImplementedError): + with self.assertRaises(CanInterfaceNotImplementedError): super().test_unique_message_instances() @@ -323,7 +324,7 @@ def test_unique_message_instances(self): try: bus_class = can.interface._get_class_for_interface("etas") TEST_INTERFACE_ETAS = True -except can.exceptions.CanInterfaceNotImplementedError: +except CanInterfaceNotImplementedError: pass From 40b684d1f2b26fd2e78bed15e77f03e753ab363b Mon Sep 17 00:00:00 2001 From: Felix Divo <4403130+felixdivo@users.noreply.github.com> Date: Tue, 31 Jan 2023 18:30:54 +0000 Subject: [PATCH 19/21] clean up test_interface_canalystii.py --- test/test_interface_canalystii.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/test_interface_canalystii.py b/test/test_interface_canalystii.py index 4d1d3eb84..4f3033e10 100755 --- a/test/test_interface_canalystii.py +++ b/test/test_interface_canalystii.py @@ -1,11 +1,7 @@ #!/usr/bin/env python -""" -""" - -import time import unittest -from unittest.mock import Mock, patch, call +from unittest.mock import patch, call from ctypes import c_ubyte import canalystii as driver # low-level driver module, mock out this layer From 861980cbd582a4e66d22dbcfded13771f2e1ef1b Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 30 Mar 2023 17:02:21 +0200 Subject: [PATCH 20/21] carry over from #1519 --- can/interfaces/canalystii.py | 21 +++++++++++---------- test/__init__.py | 7 ------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/can/interfaces/canalystii.py b/can/interfaces/canalystii.py index 7150a60bd..1e6a7fea4 100644 --- a/can/interfaces/canalystii.py +++ b/can/interfaces/canalystii.py @@ -1,11 +1,11 @@ -import collections +from collections import deque from ctypes import c_ubyte import logging import time from typing import Any, Dict, Optional, Deque, Sequence, Tuple, Union from can import BitTiming, BusABC, Message, BitTimingFd -from can.exceptions import CanTimeoutError, CanInitializationError +from can.exceptions import CanTimeoutError from can.typechecking import CanFilters from can.util import deprecated_args_alias, check_or_adjust_timing_clock @@ -50,11 +50,12 @@ def __init__( If set, software received message queue can only grow to this many messages (for all channels) before older messages are dropped """ - super().__init__(channel=channel, can_filters=can_filters, **kwargs) - if not (bitrate or timing): raise ValueError("Either bitrate or timing argument is required") + # Do this after the error handling + super().__init__(channel=channel, can_filters=can_filters, **kwargs) + if isinstance(channel, str): # Assume comma separated string of channels self.channels = [int(ch.strip()) for ch in channel.split(",")] @@ -63,23 +64,23 @@ def __init__( else: # Sequence[int] self.channels = list(channel) - self.rx_queue = collections.deque( - maxlen=rx_queue_size - ) # type: Deque[Tuple[int, driver.Message]] + self.rx_queue: Deque[Tuple[int, driver.Message]] = deque(maxlen=rx_queue_size) self.channel_info = f"CANalyst-II: device {device}, channels {self.channels}" self.device = driver.CanalystDevice(device_index=device) - for channel in self.channels: + for single_channel in self.channels: if isinstance(timing, BitTiming): timing = check_or_adjust_timing_clock(timing, valid_clocks=[8_000_000]) - self.device.init(channel, timing0=timing.btr0, timing1=timing.btr1) + self.device.init( + single_channel, timing0=timing.btr0, timing1=timing.btr1 + ) elif isinstance(timing, BitTimingFd): raise NotImplementedError( f"CAN FD is not supported by {self.__class__.__name__}." ) else: - self.device.init(channel, bitrate=bitrate) + self.device.init(single_channel, bitrate=bitrate) # Delay to use between each poll for new messages # diff --git a/test/__init__.py b/test/__init__.py index 203e1d065..4265cc3e6 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,8 +1 @@ #!/usr/bin/env python - -import pytest - -# Ignore exceptions being raised as a result of BusABC.__del__ -pytestmark = pytest.mark.filterwarnings( - "ignore::pytest.PytestUnraisableExceptionWarning" -) From 3aef90513263a96e36f4c30459dd8cd8b9b82b13 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Thu, 30 Mar 2023 17:12:19 +0200 Subject: [PATCH 21/21] fix AttributeError --- can/bus.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/can/bus.py b/can/bus.py index cfea440a4..fc96f0348 100644 --- a/can/bus.py +++ b/can/bus.py @@ -1,7 +1,7 @@ """ Contains the ABC bus implementation and its documentation. """ - +import contextlib from typing import cast, Any, Iterator, List, Optional, Sequence, Tuple, Union import can.typechecking @@ -305,6 +305,10 @@ def stop_all_periodic_tasks(self, remove_tasks: bool = True) -> None: :param remove_tasks: Stop tracking the stopped tasks. """ + if not hasattr(self, "_periodic_tasks"): + # avoid AttributeError for partially initialized BusABC instance + return + for task in self._periodic_tasks: # we cannot let `task.stop()` modify `self._periodic_tasks` while we are # iterating over it (#634) @@ -441,7 +445,8 @@ def __del__(self) -> None: LOG.warning("%s was not properly shut down", self.__class__) # We do some best-effort cleanup if the user # forgot to properly close the bus instance - self.shutdown() + with contextlib.suppress(AttributeError): + self.shutdown() @property def state(self) -> BusState: