-
Notifications
You must be signed in to change notification settings - Fork 631
/
Copy pathexceptions.py
121 lines (93 loc) · 3.91 KB
/
exceptions.py
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