File: exceptions.py

package info (click to toggle)
python-can 4.6.1-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 3,428 kB
  • sloc: python: 27,154; makefile: 31; sh: 16
file content (121 lines) | stat: -rw-r--r-- 4,004 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
"""
There are several specific :class:`Exception` classes to allow user
code to react to specific scenarios related to CAN buses::

    Exception (Python standard library)
     +-- ...
     +-- CanError (python-can)
         +-- CanInterfaceNotImplementedError
         +-- CanInitializationError
         +-- 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 collections.abc import Generator
from contextlib import contextmanager
from typing import Optional


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

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

    .. testsetup:: canerror

        from can import CanError, CanOperationError

    .. doctest:: canerror

        >>> # 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 :exc:`~can.exceptions.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
      - A buffer is full
    """


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
    """


@contextmanager
def error_check(
    error_message: Optional[str] = None,
    exception_type: type[CanError] = CanOperationError,
) -> Generator[None, None, None]:
    """Catches any exceptions and turns them into the new type while preserving the stack trace."""
    try:
        yield
    except Exception as error:  # pylint: disable=broad-except
        if error_message is None:
            raise exception_type(str(error)) from error
        else:
            raise exception_type(error_message) from error