Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
01b9188
Refactoring: Move exceptions to separate file.
marcel-kanter Apr 29, 2019
8697f14
Added tests for ixxat interface.
marcel-kanter Apr 29, 2019
b9bea05
Introduced CanBackEndError for exceptions related to the backend.
marcel-kanter Apr 30, 2019
938e440
Raise an CanOperationError when an exception occurs in the send metho…
marcel-kanter Apr 30, 2019
07326ef
Change intentions to 4 spaces to match remote.
marcel-kanter Apr 30, 2019
d5be879
Skip the test if there is an ImportError
marcel-kanter Apr 30, 2019
e9c7096
Merged in suggested changes from pull request.
marcel-kanter Apr 30, 2019
5d40159
Introduced CanTimeoutError.
marcel-kanter May 12, 2019
e29c162
Change version to 4.0.0-dev
hardbyte May 19, 2019
543163d
Fix imports
marcel-kanter May 19, 2019
3cf73c8
Update __init__.py
hardbyte May 19, 2019
a929ffe
Merge branch 'exception-handling' of https://github.com/marcel-kanter…
marcel-kanter May 19, 2019
739e405
Merge pull request #562 from marcel-kanter/exception-handling
felixdivo May 19, 2019
5f91225
Merge branch 'develop' into unified-exceptions
felixdivo Apr 22, 2021
21f3ae7
Format code with black
felixdivo Apr 22, 2021
cd06b77
clean up
felixdivo Apr 22, 2021
89d09ff
cleanup old unwanted changes
felixdivo Apr 22, 2021
bfbb0f1
improve docs
felixdivo Apr 22, 2021
daf6617
finalize ixxat interface
felixdivo Apr 22, 2021
a92928c
Format code with black
felixdivo Apr 22, 2021
8d0d20a
finalize adding specific exceptions to the generic part of the library
felixdivo Apr 22, 2021
dcdb3c1
Merge branch 'unified-exceptions' of github.com:hardbyte/python-can i…
felixdivo Apr 22, 2021
65d5bd2
clarify CanOperationError
felixdivo Apr 23, 2021
bcf75cb
add error code to CanError
felixdivo Apr 23, 2021
4e0ae77
improve docs
felixdivo Apr 23, 2021
48b2acf
simplify IXXAT interface test
felixdivo Apr 23, 2021
ba1b9e9
rename to CanInterfaceNotImplementedError
felixdivo Apr 25, 2021
6f65d77
Merge branch 'develop' into unified-exceptions
mergify[bot] Apr 25, 2021
d193bf2
cleanup rename to CanInterfaceNotImplementedError
felixdivo Apr 26, 2021
50e1507
fix IXXAT test
felixdivo Apr 26, 2021
cbe3fb2
defer adjusting test_vector_error_pickle
felixdivo Apr 26, 2021
47057a1
Merge branch 'develop' into unified-exceptions
felixdivo Apr 26, 2021
f685678
update IXXAT to new exceptions
felixdivo Apr 27, 2021
7c2228d
fix linter issue
felixdivo Apr 27, 2021
91824da
Merge branch 'develop' into unified-exceptions
mergify[bot] May 9, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions can/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"""

import logging

from typing import Dict, Any

__version__ = "4.0.0-dev"
Expand All @@ -15,13 +14,16 @@

rc: Dict[str, Any] = dict()


class CanError(IOError):
"""Indicates an error with the CAN network."""


from .listener import Listener, BufferedReader, RedirectReader, AsyncBufferedReader

from .exceptions import (
CanError,
CanInterfaceNotImplementedError,
CanInitializationError,
CanOperationError,
CanTimeoutError,
)

from .io import Logger, SizedRotatingLogger, Printer, LogReader, MessageSync
from .io import ASCWriter, ASCReader
from .io import BLFReader, BLFWriter
Expand Down
16 changes: 15 additions & 1 deletion can/broadcastmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ def __init__(
:param messages:
The messages to be sent periodically.
:param period: The rate in seconds at which to send the messages.

:raises ValueError: If the given messages are invalid
"""
messages = self._check_and_convert_messages(messages)

Expand All @@ -74,7 +76,9 @@ def _check_and_convert_messages(
Performs error checking to ensure that all Messages have the same
arbitration ID and channel.

Should be called when the cyclic task is initialized
Should be called when the cyclic task is initialized.

:raises ValueError: If the given messages are invalid
"""
if not isinstance(messages, (list, tuple)):
if isinstance(messages, Message):
Expand Down Expand Up @@ -115,6 +119,8 @@ def __init__(
:param duration:
Approximate duration in seconds to continue sending messages. If
no duration is provided, the task will continue indefinitely.

:raises ValueError: If the given messages are invalid
"""
super().__init__(messages, period)
self.duration = duration
Expand All @@ -139,6 +145,8 @@ def _check_modified_messages(self, messages: Tuple[Message, ...]) -> None:
cyclic messages hasn't changed.

Should be called when modify_data is called in the cyclic task.

:raises ValueError: If the given messages are invalid
"""
if len(self.messages) != len(messages):
raise ValueError(
Expand All @@ -163,6 +171,8 @@ def modify_data(self, messages: Union[Sequence[Message], Message]) -> None:
Note: The number of new cyclic messages to be sent must be equal
to the original number of messages originally specified for this
task.

:raises ValueError: If the given messages are invalid
"""
messages = self._check_and_convert_messages(messages)
self._check_modified_messages(messages)
Expand Down Expand Up @@ -190,6 +200,8 @@ def __init__(
:param count:
:param initial_period:
:param subsequent_period:

:raises ValueError: If the given messages are invalid
"""
super().__init__(messages, subsequent_period)
self._channel = channel
Expand Down Expand Up @@ -221,6 +233,8 @@ def __init__(
error happened on a `bus` while sending `messages`,
it shall return either ``True`` or ``False`` depending
on desired behaviour of `ThreadBasedCyclicSendTask`.

:raises ValueError: If the given messages are invalid
"""
super().__init__(messages, period, duration)
self.bus = bus
Expand Down
25 changes: 15 additions & 10 deletions can/bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ class BusABC(metaclass=ABCMeta):
"""The CAN Bus Abstract Base Class that serves as the basis
for all concrete interfaces.

This class may be used as an iterator over the received messages.
This class may be used as an iterator over the received messages
and as a context manager for auto-closing the bus when done using it.

Please refer to :ref:`errors` for possible exceptions that may be
thrown by certain operations on this bus.
"""

#: a string describing the underlying bus and/or channel
Expand Down Expand Up @@ -60,6 +64,10 @@ def __init__(

:param dict kwargs:
Any backend dependent configurations are passed in this dictionary

:raises ValueError: If parameters are out of range
:raises can.CanInterfaceNotImplementedError: If the driver cannot be accessed
:raises can.CanInitializationError: If the bus cannot be initialized
"""
self._periodic_tasks: List[_SelfRemovingCyclicTask] = []
self.set_filters(can_filters)
Expand All @@ -73,10 +81,9 @@ def recv(self, timeout: Optional[float] = None) -> Optional[Message]:
:param timeout:
seconds to wait for a message or None to wait indefinitely

:return:
None on timeout or a :class:`Message` object.
:raises can.CanError:
if an error occurred while reading
:return: ``None`` on timeout or a :class:`Message` object.

:raises can.CanOperationError: If an error occurred while reading
"""
start = time()
time_left = timeout
Expand Down Expand Up @@ -141,8 +148,7 @@ def _recv_internal(
2. a bool that is True if message filtering has already
been done and else False

:raises can.CanError:
if an error occurred while reading
:raises can.CanOperationError: If an error occurred while reading
:raises NotImplementedError:
if the bus provides it's own :meth:`~can.BusABC.recv`
implementation (legacy implementation)
Expand All @@ -165,8 +171,7 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None:
Might not be supported by all interfaces.
None blocks indefinitely.

:raises can.CanError:
if the message could not be sent
:raises can.CanOperationError: If an error occurred while sending
"""
raise NotImplementedError("Trying to write to a readonly bus?")

Expand Down Expand Up @@ -336,7 +341,7 @@ def set_filters(
messages are matched.

Calling without passing any filters will reset the applied
filters to `None`.
filters to ``None``.

:param filters:
A iterable of dictionaries each containing a "can_id",
Expand Down
97 changes: 97 additions & 0 deletions can/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""
There are several specific :class:`Exception` classes to allow user
code to react to specific scenarios related to CAN busses::

Exception (Python standard library)
+-- ...
+-- CanError (python-can)
+-- CanInterfaceNotImplementedError
+-- CanInterfaceNotImplementedError
+-- CanOperationError
+-- CanTimeoutError

Keep in mind that some functions and methods may raise different exceptions.
For example, validating typical arguments and parameters might result in a
:class:`ValueError`. This should always be documented for the function at hand.
"""

from typing import Optional


class CanError(Exception):
"""Base class for all CAN related exceptions.

If specified, the error code is automatically prepended to the message:

>>> # With an error code (it also works with a specific error):
>>> error = CanOperationError(message="Failed to do the thing", error_code=42)
>>> str(error)
'Failed to do the thing [Error Code 42]'
>>>
>>> # Missing the error code:
>>> plain_error = CanError(message="Something went wrong ...")
>>> str(plain_error)
'Something went wrong ...'

:param error_code:
An optional error code to narrow down the cause of the fault

:arg error_code:
An optional error code to narrow down the cause of the fault
"""

def __init__(
self,
message: str = "",
error_code: Optional[int] = None,
) -> None:
self.error_code = error_code
super().__init__(
message if error_code is None else f"{message} [Error Code {error_code}]"
)


class CanInterfaceNotImplementedError(CanError, NotImplementedError):
"""Indicates that the interface is not supported on the current platform.

Example scenarios:
- No interface with that name exists
- The interface is unsupported on the current operating system or interpreter
- The driver could not be found or has the wrong version
"""


class CanInitializationError(CanError):
"""Indicates an error the occurred while initializing a :class:`can.BusABC`.

If initialization fails due to a driver or platform missing/being unsupported,
a :class:`can.CanInterfaceNotImplementedError` is raised instead.
If initialization fails due to a value being out of range, a :class:`ValueError`
is raised.

Example scenarios:
- Try to open a non-existent device and/or channel
- Try to use an invalid setting, which is ok by value, but not ok for the interface
- The device or other resources are already used
"""


class CanOperationError(CanError):
"""Indicates an error while in operation.

Example scenarios:
- A call to a library function results in an unexpected return value
- An invalid message was received
- The driver rejected a message that was meant to be sent
- Cyclic redundancy check (CRC) failed
- A message remained unacknowledged
"""


class CanTimeoutError(CanError, TimeoutError):
"""Indicates the timeout of an operation.

Example scenarios:
- Some message could not be sent after the timeout elapsed
- No message was read within the given time
"""
21 changes: 12 additions & 9 deletions can/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .bus import BusABC
from .util import load_config
from .interfaces import BACKENDS
from .exceptions import CanInterfaceNotImplementedError
from .typechecking import AutoDetectedConfig, Channel

log = logging.getLogger("can.interface")
Expand All @@ -23,9 +24,8 @@ def _get_class_for_interface(interface: str) -> Type[BusABC]:

:raises:
NotImplementedError if the interface is not known
:raises:
ImportError if there was a problem while importing the
interface or the bus class within that
:raises CanInterfaceNotImplementedError:
if there was a problem while importing the interface or the bus class within that
"""
# Find the correct backend
try:
Expand All @@ -39,7 +39,7 @@ def _get_class_for_interface(interface: str) -> Type[BusABC]:
try:
module = importlib.import_module(module_name)
except Exception as e:
raise ImportError(
raise CanInterfaceNotImplementedError(
"Cannot import module {} for CAN interface '{}': {}".format(
module_name, interface, e
)
Expand All @@ -49,7 +49,7 @@ def _get_class_for_interface(interface: str) -> Type[BusABC]:
try:
bus_class = getattr(module, class_name)
except Exception as e:
raise ImportError(
raise CanInterfaceNotImplementedError(
"Cannot import class {} from module {} for CAN interface '{}': {}".format(
class_name, module_name, interface, e
)
Expand Down Expand Up @@ -83,8 +83,11 @@ def __new__( # type: ignore # pylint: disable=keyword-arg-before-vararg
Should contain an ``interface`` key with a valid interface name. If not,
it is completed using :meth:`can.util.load_config`.

:raises: NotImplementedError
if the ``interface`` isn't recognized
:raises: can.CanInterfaceNotImplementedError
if the ``interface`` isn't recognized or cannot be loaded

:raises: can.CanInitializationError
if the bus cannot be instantiated

:raises: ValueError
if the ``channel`` could not be determined
Expand Down Expand Up @@ -156,9 +159,9 @@ def detect_available_configs(

try:
bus_class = _get_class_for_interface(interface)
except ImportError:
except CanInterfaceNotImplementedError:
log_autodetect.debug(
'interface "%s" can not be loaded for detection of available configurations',
'interface "%s" cannot be loaded for detection of available configurations',
interface,
)
continue
Expand Down
Loading