diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml deleted file mode 100644 index 30f95c103..000000000 --- a/.github/workflows/format-code.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Format Code - -on: - push: - paths: - - '**.py' - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -e .[lint] - - name: Code Format Check with Black - run: | - black --verbose . - - name: Commit Formated Code - uses: EndBug/add-and-commit@v9 - with: - message: "Format code with black" - # Ref https://git-scm.com/docs/git-add#_examples - add: './*.py' diff --git a/can/bus.py b/can/bus.py index 555389b0f..c3a906757 100644 --- a/can/bus.py +++ b/can/bus.py @@ -281,7 +281,7 @@ def wrapped_stop_method(remove_task: bool = True) -> None: pass # allow the task to be already removed original_stop_method() - task.stop = wrapped_stop_method # type: ignore + task.stop = wrapped_stop_method # type: ignore[method-assign] if store_task: self._periodic_tasks.append(task) @@ -401,7 +401,8 @@ def set_filters( messages based only on the arbitration ID and mask. """ self._filters = filters or None - self._apply_filters(self._filters) + with contextlib.suppress(NotImplementedError): + self._apply_filters(self._filters) def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None: """ @@ -411,6 +412,7 @@ def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None :param filters: See :meth:`~can.BusABC.set_filters` for details. """ + raise NotImplementedError def _matches_filters(self, msg: Message) -> bool: """Checks whether the given message matches at least one of the @@ -450,6 +452,7 @@ def _matches_filters(self, msg: Message) -> bool: def flush_tx_buffer(self) -> None: """Discard every message that may be queued in the output buffer(s).""" + raise NotImplementedError def shutdown(self) -> None: """ diff --git a/can/ctypesutil.py b/can/ctypesutil.py index e624db6d2..fa59255d1 100644 --- a/can/ctypesutil.py +++ b/can/ctypesutil.py @@ -1,4 +1,3 @@ -# type: ignore """ This module contains common `ctypes` utils. """ @@ -11,11 +10,10 @@ __all__ = ["CLibrary", "HANDLE", "PHANDLE", "HRESULT"] - -try: +if sys.platform == "win32": _LibBase = ctypes.WinDLL _FUNCTION_TYPE = ctypes.WINFUNCTYPE -except AttributeError: +else: _LibBase = ctypes.CDLL _FUNCTION_TYPE = ctypes.CFUNCTYPE @@ -60,7 +58,7 @@ def map_symbol( f'Could not map function "{func_name}" from library {self._name}' ) from None - func._name = func_name # pylint: disable=protected-access + func._name = func_name # type: ignore[attr-defined] # pylint: disable=protected-access log.debug( 'Wrapped function "%s", result type: %s, error_check %s', func_name, diff --git a/can/interface.py b/can/interface.py index 9c828a608..2b7e27f8d 100644 --- a/can/interface.py +++ b/can/interface.py @@ -61,7 +61,7 @@ def _get_class_for_interface(interface: str) -> Type[BusABC]: bustype="interface", context="config_context", ) -def Bus( +def Bus( # noqa: N802 channel: Optional[Channel] = None, interface: Optional[str] = None, config_context: Optional[str] = None, diff --git a/can/interfaces/etas/__init__.py b/can/interfaces/etas/__init__.py index b40f10b77..2bfcbf427 100644 --- a/can/interfaces/etas/__init__.py +++ b/can/interfaces/etas/__init__.py @@ -229,10 +229,10 @@ def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None self._oci_filters = (ctypes.POINTER(OCI_CANRxFilterEx) * len(filters))() - for i, filter in enumerate(filters): + for i, filter_ in enumerate(filters): f = OCI_CANRxFilterEx() - f.frameIDValue = filter["can_id"] - f.frameIDMask = filter["can_mask"] + f.frameIDValue = filter_["can_id"] + f.frameIDMask = filter_["can_mask"] f.tag = 0 f.flagsValue = 0 if self.receive_own_messages: @@ -241,7 +241,7 @@ def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None else: # enable the SR bit in the mask. since the bit is 0 in flagsValue -> do not self-receive f.flagsMask = OCI_CAN_MSG_FLAG_SELFRECEPTION - if filter.get("extended"): + if filter_.get("extended"): f.flagsValue |= OCI_CAN_MSG_FLAG_EXTENDED f.flagsMask |= OCI_CAN_MSG_FLAG_EXTENDED self._oci_filters[i].contents = f diff --git a/can/interfaces/gs_usb.py b/can/interfaces/gs_usb.py index 38f9fe41a..21e199a30 100644 --- a/can/interfaces/gs_usb.py +++ b/can/interfaces/gs_usb.py @@ -95,8 +95,8 @@ def send(self, msg: can.Message, timeout: Optional[float] = None): try: self.gs_usb.send(frame) - except usb.core.USBError: - raise CanOperationError("The message could not be sent") + except usb.core.USBError as exc: + raise CanOperationError("The message could not be sent") from exc def _recv_internal( self, timeout: Optional[float] diff --git a/can/interfaces/ics_neovi/neovi_bus.py b/can/interfaces/ics_neovi/neovi_bus.py index 9270bfc90..66e109c97 100644 --- a/can/interfaces/ics_neovi/neovi_bus.py +++ b/can/interfaces/ics_neovi/neovi_bus.py @@ -109,8 +109,10 @@ def __reduce__(self): def error_number(self) -> int: """Deprecated. Renamed to :attr:`can.CanError.error_code`.""" warn( - "ICSApiError::error_number has been renamed to error_code defined by CanError", + "ICSApiError::error_number has been replaced by ICSApiError.error_code in python-can 4.0" + "and will be remove in version 5.0.", DeprecationWarning, + stacklevel=2, ) return self.error_code @@ -223,10 +225,8 @@ def __init__(self, channel, can_filters=None, **kwargs): self._use_system_timestamp = bool(kwargs.get("use_system_timestamp", False)) self._receive_own_messages = kwargs.get("receive_own_messages", True) - self.channel_info = "{} {} CH:{}".format( - self.dev.Name, - self.get_serial_number(self.dev), - self.channels, + self.channel_info = ( + f"{self.dev.Name} {self.get_serial_number(self.dev)} CH:{self.channels}" ) logger.info(f"Using device: {self.channel_info}") diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index 334adee11..709780ceb 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -79,8 +79,8 @@ def __vciFormatErrorExtended( Formatted string """ # TODO: make sure we don't generate another exception - return "{} - arguments were {}".format( - __vciFormatError(library_instance, function, vret), args + return ( + f"{__vciFormatError(library_instance, function, vret)} - arguments were {args}" ) @@ -512,13 +512,11 @@ def __init__( if unique_hardware_id is None: raise VCIDeviceNotFoundError( "No IXXAT device(s) connected or device(s) in use by other process(es)." - ) + ) from None else: raise VCIDeviceNotFoundError( - "Unique HW ID {} not connected or not available.".format( - unique_hardware_id - ) - ) + f"Unique HW ID {unique_hardware_id} not connected or not available." + ) from None else: if (unique_hardware_id is None) or ( self._device_info.UniqueHardwareId.AsChar @@ -815,7 +813,8 @@ def _send_periodic_internal( # fallback to thread based cyclic task warnings.warn( f"{self.__class__.__name__} falls back to a thread-based cyclic task, " - "when the `modifier_callback` argument is given." + "when the `modifier_callback` argument is given.", + stacklevel=3, ) return BusABC._send_periodic_internal( self, diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index aaefa1bf9..18b3a1e57 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -77,8 +77,8 @@ def __vciFormatErrorExtended( Formatted string """ # TODO: make sure we don't generate another exception - return "{} - arguments were {}".format( - __vciFormatError(library_instance, function, vret), args + return ( + f"{__vciFormatError(library_instance, function, vret)} - arguments were {args}" ) @@ -553,13 +553,11 @@ def __init__( if unique_hardware_id is None: raise VCIDeviceNotFoundError( "No IXXAT device(s) connected or device(s) in use by other process(es)." - ) + ) from None else: raise VCIDeviceNotFoundError( - "Unique HW ID {} not connected or not available.".format( - unique_hardware_id - ) - ) + f"Unique HW ID {unique_hardware_id} not connected or not available." + ) from None else: if (unique_hardware_id is None) or ( self._device_info.UniqueHardwareId.AsChar @@ -579,7 +577,9 @@ def __init__( ctypes.byref(self._device_handle), ) except Exception as exception: - raise CanInitializationError(f"Could not open device: {exception}") + raise CanInitializationError( + f"Could not open device: {exception}" + ) from exception log.info("Using unique HW ID %s", self._device_info.UniqueHardwareId.AsChar) @@ -600,7 +600,7 @@ def __init__( except Exception as exception: raise CanInitializationError( f"Could not open and initialize channel: {exception}" - ) + ) from exception # Signal TX/RX events when at least one frame has been handled _canlib.canChannelInitialize( @@ -959,7 +959,8 @@ def _send_periodic_internal( # fallback to thread based cyclic task warnings.warn( f"{self.__class__.__name__} falls back to a thread-based cyclic task, " - "when the `modifier_callback` argument is given." + "when the `modifier_callback` argument is given.", + stacklevel=3, ) return BusABC._send_periodic_internal( self, diff --git a/can/interfaces/kvaser/canlib.py b/can/interfaces/kvaser/canlib.py index 0983e28dc..4c621ecf4 100644 --- a/can/interfaces/kvaser/canlib.py +++ b/can/interfaces/kvaser/canlib.py @@ -458,7 +458,7 @@ def __init__( try: channel = int(channel) except ValueError: - raise ValueError("channel must be an integer") + raise ValueError("channel must be an integer") from None self.channel = channel self.single_handle = single_handle diff --git a/can/interfaces/nixnet.py b/can/interfaces/nixnet.py index ba665442e..2b3cbd69a 100644 --- a/can/interfaces/nixnet.py +++ b/can/interfaces/nixnet.py @@ -192,10 +192,12 @@ def __init__( @property def fd(self) -> bool: + class_name = self.__class__.__name__ warnings.warn( - "The NiXNETcanBus.fd property is deprecated and superseded by " - "BusABC.protocol. It is scheduled for removal in version 5.0.", + f"The {class_name}.fd property is deprecated and superseded by " + f"{class_name}.protocol. It is scheduled for removal in python-can version 5.0.", DeprecationWarning, + stacklevel=2, ) return self._can_protocol is CanProtocol.CAN_FD diff --git a/can/interfaces/pcan/basic.py b/can/interfaces/pcan/basic.py index e003e83f9..b704ca9bd 100644 --- a/can/interfaces/pcan/basic.py +++ b/can/interfaces/pcan/basic.py @@ -694,9 +694,9 @@ def Initialize( self, Channel, Btr0Btr1, - HwType=TPCANType(0), - IOPort=c_uint(0), - Interrupt=c_ushort(0), + HwType=TPCANType(0), # noqa: B008 + IOPort=c_uint(0), # noqa: B008 + Interrupt=c_ushort(0), # noqa: B008 ): """Initializes a PCAN Channel diff --git a/can/interfaces/pcan/pcan.py b/can/interfaces/pcan/pcan.py index 884c1680b..0fdca51bb 100644 --- a/can/interfaces/pcan/pcan.py +++ b/can/interfaces/pcan/pcan.py @@ -657,10 +657,12 @@ def shutdown(self): @property def fd(self) -> bool: + class_name = self.__class__.__name__ warnings.warn( - "The PcanBus.fd property is deprecated and superseded by BusABC.protocol. " - "It is scheduled for removal in version 5.0.", + f"The {class_name}.fd property is deprecated and superseded by {class_name}.protocol. " + "It is scheduled for removal in python-can version 5.0.", DeprecationWarning, + stacklevel=2, ) return self._can_protocol is CanProtocol.CAN_FD diff --git a/can/interfaces/robotell.py b/can/interfaces/robotell.py index bfe8f5774..16668bdda 100644 --- a/can/interfaces/robotell.py +++ b/can/interfaces/robotell.py @@ -376,7 +376,7 @@ def fileno(self): except io.UnsupportedOperation: raise NotImplementedError( "fileno is not implemented using current CAN bus on this platform" - ) + ) from None except Exception as exception: raise CanOperationError("Cannot fetch fileno") from exception diff --git a/can/interfaces/seeedstudio/seeedstudio.py b/can/interfaces/seeedstudio/seeedstudio.py index 8e0dca8c7..a0817e932 100644 --- a/can/interfaces/seeedstudio/seeedstudio.py +++ b/can/interfaces/seeedstudio/seeedstudio.py @@ -63,7 +63,6 @@ def __init__( frame_type="STD", operation_mode="normal", bitrate=500000, - *args, **kwargs, ): """ @@ -115,7 +114,7 @@ def __init__( "could not create the serial device" ) from error - super().__init__(channel=channel, *args, **kwargs) + super().__init__(channel=channel, **kwargs) self.init_frame() def shutdown(self): diff --git a/can/interfaces/serial/serial_can.py b/can/interfaces/serial/serial_can.py index 9de2da99c..476cbd624 100644 --- a/can/interfaces/serial/serial_can.py +++ b/can/interfaces/serial/serial_can.py @@ -131,13 +131,15 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: try: timestamp = struct.pack(" int: except io.UnsupportedOperation: raise NotImplementedError( "fileno is not implemented using current CAN bus on this platform" - ) + ) from None except Exception as exception: raise CanOperationError("Cannot fetch fileno") from exception diff --git a/can/interfaces/slcan.py b/can/interfaces/slcan.py index 7ff12ce44..7851a0322 100644 --- a/can/interfaces/slcan.py +++ b/can/interfaces/slcan.py @@ -268,7 +268,7 @@ def fileno(self) -> int: except io.UnsupportedOperation: raise NotImplementedError( "fileno is not implemented using current CAN bus on this platform" - ) + ) from None except Exception as exception: raise CanOperationError("Cannot fetch fileno") from exception diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 377fe6478..cdf4afac6 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -536,7 +536,9 @@ def capture_message( else: channel = None except OSError as error: - raise can.CanOperationError(f"Error receiving: {error.strerror}", error.errno) + raise can.CanOperationError( + f"Error receiving: {error.strerror}", error.errno + ) from error can_id, can_dlc, flags, data = dissect_can_frame(cf) @@ -601,7 +603,7 @@ def capture_message( RECEIVED_ANCILLARY_BUFFER_SIZE = CMSG_SPACE(RECEIVED_TIMESTAMP_STRUCT.size) -class SocketcanBus(BusABC): +class SocketcanBus(BusABC): # pylint: disable=abstract-method """A SocketCAN interface to CAN. It implements :meth:`can.BusABC._detect_available_configs` to search for @@ -737,7 +739,7 @@ def _recv_internal( # something bad happened (e.g. the interface went down) raise can.CanOperationError( f"Failed to receive: {error.strerror}", error.errno - ) + ) from error if ready_receive_sockets: # not empty get_channel = self.channel == "" @@ -798,7 +800,7 @@ def _send_once(self, data: bytes, channel: Optional[str] = None) -> int: except OSError as error: raise can.CanOperationError( f"Failed to transmit: {error.strerror}", error.errno - ) + ) from error return sent def _send_periodic_internal( @@ -853,7 +855,8 @@ def _send_periodic_internal( # fallback to thread based cyclic task warnings.warn( f"{self.__class__.__name__} falls back to a thread-based cyclic task, " - "when the `modifier_callback` argument is given." + "when the `modifier_callback` argument is given.", + stacklevel=3, ) return BusABC._send_periodic_internal( self, @@ -897,35 +900,3 @@ def _detect_available_configs() -> List[can.typechecking.AutoDetectedConfig]: {"interface": "socketcan", "channel": channel} for channel in find_available_interfaces() ] - - -if __name__ == "__main__": - # This example demonstrates how to use the internal methods of this module. - # It creates two sockets on vcan0 to test sending and receiving. - # - # If you want to try it out you can do the following (possibly using sudo): - # - # modprobe vcan - # ip link add dev vcan0 type vcan - # ip link set vcan0 up - # - log.setLevel(logging.DEBUG) - - def receiver(event: threading.Event) -> None: - receiver_socket = create_socket() - bind_socket(receiver_socket, "vcan0") - print("Receiver is waiting for a message...") - event.set() - print(f"Receiver got: {capture_message(receiver_socket)}") - - def sender(event: threading.Event) -> None: - event.wait() - sender_socket = create_socket() - bind_socket(sender_socket, "vcan0") - msg = Message(arbitration_id=0x01, data=b"\x01\x02\x03") - sender_socket.send(build_can_frame(msg)) - print("Sender sent a message.") - - e = threading.Event() - threading.Thread(target=receiver, args=(e,)).start() - threading.Thread(target=sender, args=(e,)).start() diff --git a/can/interfaces/socketcand/socketcand.py b/can/interfaces/socketcand/socketcand.py index 045882388..e852c5e14 100644 --- a/can/interfaces/socketcand/socketcand.py +++ b/can/interfaces/socketcand/socketcand.py @@ -118,7 +118,7 @@ def detect_beacon(timeout_ms: int = 3100) -> List[can.typechecking.AutoDetectedC log.error(f"Failed to detect beacon: {exc} {traceback.format_exc()}") raise OSError( f"Failed to detect beacon: {exc} {traceback.format_exc()}" - ) + ) from exc return [] @@ -248,7 +248,7 @@ def _recv_internal(self, timeout): except OSError as exc: # something bad happened (e.g. the interface went down) log.error(f"Failed to receive: {exc}") - raise can.CanError(f"Failed to receive: {exc}") + raise can.CanError(f"Failed to receive: {exc}") from exc try: if not ready_receive_sockets: @@ -307,7 +307,9 @@ def _recv_internal(self, timeout): except Exception as exc: log.error(f"Failed to receive: {exc} {traceback.format_exc()}") - raise can.CanError(f"Failed to receive: {exc} {traceback.format_exc()}") + raise can.CanError( + f"Failed to receive: {exc} {traceback.format_exc()}" + ) from exc def _tcp_send(self, msg: str): log.debug(f"Sending TCP Message: '{msg}'") diff --git a/can/interfaces/systec/structures.py b/can/interfaces/systec/structures.py index 699763989..9acc34c2c 100644 --- a/can/interfaces/systec/structures.py +++ b/can/interfaces/systec/structures.py @@ -52,9 +52,9 @@ class CanMsg(Structure): ), # Receive time stamp in ms (for transmit messages no meaning) ] - def __init__(self, id=0, frame_format=MsgFrameFormat.MSG_FF_STD, data=None): + def __init__(self, id_=0, frame_format=MsgFrameFormat.MSG_FF_STD, data=None): data = [] if data is None else data - super().__init__(id, frame_format, len(data), (BYTE * 8)(*data), 0) + super().__init__(id_, frame_format, len(data), (BYTE * 8)(*data), 0) def __eq__(self, other): if not isinstance(other, CanMsg): @@ -71,8 +71,8 @@ def id(self): return self.m_dwID @id.setter - def id(self, id): - self.m_dwID = id + def id(self, value): + self.m_dwID = value @property def frame_format(self): diff --git a/can/interfaces/udp_multicast/bus.py b/can/interfaces/udp_multicast/bus.py index 089b8182f..8ca2d516b 100644 --- a/can/interfaces/udp_multicast/bus.py +++ b/can/interfaces/udp_multicast/bus.py @@ -114,10 +114,12 @@ def __init__( @property def is_fd(self) -> bool: + class_name = self.__class__.__name__ warnings.warn( - "The UdpMulticastBus.is_fd property is deprecated and superseded by " - "BusABC.protocol. It is scheduled for removal in version 5.0.", + f"The {class_name}.is_fd property is deprecated and superseded by " + f"{class_name}.protocol. It is scheduled for removal in python-can version 5.0.", DeprecationWarning, + stacklevel=2, ) return self._can_protocol is CanProtocol.CAN_FD diff --git a/can/interfaces/usb2can/usb2canabstractionlayer.py b/can/interfaces/usb2can/usb2canabstractionlayer.py index a894c3953..9fbf5c15c 100644 --- a/can/interfaces/usb2can/usb2canabstractionlayer.py +++ b/can/interfaces/usb2can/usb2canabstractionlayer.py @@ -150,7 +150,7 @@ def open(self, configuration: str, flags: int): # catch any errors thrown by this call and re-raise raise can.CanInitializationError( f'CanalOpen() failed, configuration: "{configuration}", error: {ex}' - ) + ) from ex else: # any greater-than-zero return value indicates a success # (see https://grodansparadis.gitbooks.io/the-vscp-daemon/canal_interface_specification.html) diff --git a/can/interfaces/vector/canlib.py b/can/interfaces/vector/canlib.py index 576fa26cf..797539c88 100644 --- a/can/interfaces/vector/canlib.py +++ b/can/interfaces/vector/canlib.py @@ -58,7 +58,10 @@ INFINITE: Optional[int] try: # Try builtin Python 3 Windows API - from _winapi import INFINITE, WaitForSingleObject # type: ignore + from _winapi import ( # type: ignore[attr-defined,no-redef,unused-ignore] + INFINITE, + WaitForSingleObject, + ) HAS_EVENTS = True except ImportError: @@ -333,10 +336,12 @@ def __init__( @property def fd(self) -> bool: + class_name = self.__class__.__name__ warnings.warn( - "The VectorBus.fd property is deprecated and superseded by " - "BusABC.protocol. It is scheduled for removal in version 5.0.", + f"The {class_name}.fd property is deprecated and superseded by " + f"{class_name}.protocol. It is scheduled for removal in python-can version 5.0.", DeprecationWarning, + stacklevel=2, ) return self._can_protocol is CanProtocol.CAN_FD diff --git a/can/io/logger.py b/can/io/logger.py index c3e83c883..0da0e25c6 100644 --- a/can/io/logger.py +++ b/can/io/logger.py @@ -8,7 +8,18 @@ from abc import ABC, abstractmethod from datetime import datetime from types import TracebackType -from typing import Any, Callable, Dict, Literal, Optional, Set, Tuple, Type, cast +from typing import ( + Any, + Callable, + ClassVar, + Dict, + Literal, + Optional, + Set, + Tuple, + Type, + cast, +) from typing_extensions import Self @@ -60,7 +71,7 @@ class Logger(MessageWriter): """ fetched_plugins = False - message_writers: Dict[str, Type[MessageWriter]] = { + message_writers: ClassVar[Dict[str, Type[MessageWriter]]] = { ".asc": ASCWriter, ".blf": BLFWriter, ".csv": CSVWriter, @@ -72,7 +83,7 @@ class Logger(MessageWriter): } @staticmethod - def __new__( # type: ignore + def __new__( # type: ignore[misc] cls: Any, filename: Optional[StringPathLike], **kwargs: Any ) -> MessageWriter: """ @@ -160,7 +171,7 @@ class BaseRotatingLogger(Listener, BaseIOHandler, ABC): Subclasses must set the `_writer` attribute upon initialization. """ - _supported_formats: Set[str] = set() + _supported_formats: ClassVar[Set[str]] = set() #: If this attribute is set to a callable, the :meth:`~BaseRotatingLogger.rotation_filename` #: method delegates to this callable. The parameters passed to the callable are @@ -182,12 +193,14 @@ def __init__(self, **kwargs: Any) -> None: self.writer_kwargs = kwargs # Expected to be set by the subclass - self._writer: FileIOMessageWriter = None # type: ignore + self._writer: Optional[FileIOMessageWriter] = None @property def writer(self) -> FileIOMessageWriter: """This attribute holds an instance of a writer class which manages the actual file IO.""" - return self._writer + if self._writer is not None: + return self._writer + raise ValueError(f"{self.__class__.__name__}.writer is None.") def rotation_filename(self, default_name: StringPathLike) -> StringPathLike: """Modify the filename of a log file when rotating. @@ -284,7 +297,7 @@ def __exit__( exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> Literal[False]: - return self._writer.__exit__(exc_type, exc_val, exc_tb) + return self.writer.__exit__(exc_type, exc_val, exc_tb) @abstractmethod def should_rollover(self, msg: Message) -> bool: @@ -337,7 +350,7 @@ class SizedRotatingLogger(BaseRotatingLogger): :meth:`~can.Listener.stop` is called. """ - _supported_formats = {".asc", ".blf", ".csv", ".log", ".txt"} + _supported_formats: ClassVar[Set[str]] = {".asc", ".blf", ".csv", ".log", ".txt"} def __init__( self, diff --git a/can/io/mf4.py b/can/io/mf4.py index c7e71e816..7ab6ba8a7 100644 --- a/can/io/mf4.py +++ b/can/io/mf4.py @@ -68,7 +68,7 @@ ] ) except ImportError: - asammdf = None # type: ignore + asammdf = None CAN_MSG_EXT = 0x80000000 diff --git a/can/io/player.py b/can/io/player.py index 9fd9d9ed3..73ef3c356 100644 --- a/can/io/player.py +++ b/can/io/player.py @@ -6,7 +6,18 @@ import gzip import pathlib import time -from typing import Any, Dict, Generator, Iterable, Optional, Tuple, Type, Union, cast +from typing import ( + Any, + ClassVar, + Dict, + Generator, + Iterable, + Optional, + Tuple, + Type, + Union, + cast, +) from .._entry_points import read_entry_points from ..message import Message @@ -53,7 +64,7 @@ class LogReader(MessageReader): """ fetched_plugins = False - message_readers: Dict[str, Optional[Type[MessageReader]]] = { + message_readers: ClassVar[Dict[str, Optional[Type[MessageReader]]]] = { ".asc": ASCReader, ".blf": BLFReader, ".csv": CSVReader, @@ -64,7 +75,7 @@ class LogReader(MessageReader): } @staticmethod - def __new__( # type: ignore + def __new__( # type: ignore[misc] cls: Any, filename: StringPathLike, **kwargs: Any, diff --git a/can/io/printer.py b/can/io/printer.py index 40b42862d..00e4545df 100644 --- a/can/io/printer.py +++ b/can/io/printer.py @@ -46,7 +46,7 @@ def on_message_received(self, msg: Message) -> None: if self.write_to_file: cast(TextIO, self.file).write(str(msg) + "\n") else: - print(msg) + print(msg) # noqa: T201 def file_size(self) -> int: """Return an estimate of the current file size in bytes.""" diff --git a/can/io/trc.py b/can/io/trc.py index 889d55196..ce7bf6789 100644 --- a/can/io/trc.py +++ b/can/io/trc.py @@ -116,19 +116,19 @@ def _extract_header(self): logger.info( "TRCReader: No file version was found, so version 1.0 is assumed" ) - self._parse_cols = self._parse_msg_V1_0 + self._parse_cols = self._parse_msg_v1_0 elif self.file_version == TRCFileVersion.V1_0: - self._parse_cols = self._parse_msg_V1_0 + self._parse_cols = self._parse_msg_v1_0 elif self.file_version == TRCFileVersion.V1_1: - self._parse_cols = self._parse_cols_V1_1 + self._parse_cols = self._parse_cols_v1_1 elif self.file_version in [TRCFileVersion.V2_0, TRCFileVersion.V2_1]: - self._parse_cols = self._parse_cols_V2_x + self._parse_cols = self._parse_cols_v2_x else: raise NotImplementedError("File version not fully implemented for reading") return line - def _parse_msg_V1_0(self, cols: List[str]) -> Optional[Message]: + def _parse_msg_v1_0(self, cols: List[str]) -> Optional[Message]: arbit_id = cols[2] if arbit_id == "FFFFFFFF": logger.info("TRCReader: Dropping bus info line") @@ -143,7 +143,7 @@ def _parse_msg_V1_0(self, cols: List[str]) -> Optional[Message]: msg.data = bytearray([int(cols[i + 4], 16) for i in range(msg.dlc)]) return msg - def _parse_msg_V1_1(self, cols: List[str]) -> Optional[Message]: + def _parse_msg_v1_1(self, cols: List[str]) -> Optional[Message]: arbit_id = cols[3] msg = Message() @@ -161,7 +161,7 @@ def _parse_msg_V1_1(self, cols: List[str]) -> Optional[Message]: msg.is_rx = cols[2] == "Rx" return msg - def _parse_msg_V2_x(self, cols: List[str]) -> Optional[Message]: + def _parse_msg_v2_x(self, cols: List[str]) -> Optional[Message]: type_ = cols[self.columns["T"]] bus = self.columns.get("B", None) @@ -195,18 +195,18 @@ def _parse_msg_V2_x(self, cols: List[str]) -> Optional[Message]: return msg - def _parse_cols_V1_1(self, cols: List[str]) -> Optional[Message]: + def _parse_cols_v1_1(self, cols: List[str]) -> Optional[Message]: dtype = cols[2] if dtype in ("Tx", "Rx"): - return self._parse_msg_V1_1(cols) + return self._parse_msg_v1_1(cols) else: logger.info("TRCReader: Unsupported type '%s'", dtype) return None - def _parse_cols_V2_x(self, cols: List[str]) -> Optional[Message]: + def _parse_cols_v2_x(self, cols: List[str]) -> Optional[Message]: dtype = cols[self.columns["T"]] if dtype in ["DT", "FD", "FB"]: - return self._parse_msg_V2_x(cols) + return self._parse_msg_v2_x(cols) else: logger.info("TRCReader: Unsupported type '%s'", dtype) return None @@ -291,7 +291,7 @@ def __init__( self._msg_fmt_string = self.FORMAT_MESSAGE_V1_0 self._format_message = self._format_message_init - def _write_header_V1_0(self, start_time: datetime) -> None: + def _write_header_v1_0(self, start_time: datetime) -> None: lines = [ ";##########################################################################", f"; {self.filepath}", @@ -312,7 +312,7 @@ def _write_header_V1_0(self, start_time: datetime) -> None: ] self.file.writelines(line + "\n" for line in lines) - def _write_header_V2_1(self, start_time: datetime) -> None: + def _write_header_v2_1(self, start_time: datetime) -> None: header_time = start_time - datetime(year=1899, month=12, day=30) lines = [ ";$FILEVERSION=2.1", @@ -372,9 +372,9 @@ def write_header(self, timestamp: float) -> None: start_time = datetime.utcfromtimestamp(timestamp) if self.file_version == TRCFileVersion.V1_0: - self._write_header_V1_0(start_time) + self._write_header_v1_0(start_time) elif self.file_version == TRCFileVersion.V2_1: - self._write_header_V2_1(start_time) + self._write_header_v2_1(start_time) else: raise NotImplementedError("File format is not supported") self.header_written = True diff --git a/can/listener.py b/can/listener.py index d6f252d17..ff3672913 100644 --- a/can/listener.py +++ b/can/listener.py @@ -29,9 +29,6 @@ class Listener(metaclass=ABCMeta): listener.stop() """ - def __init__(self, *args: Any, **kwargs: Any) -> None: - pass - @abstractmethod def on_message_received(self, msg: Message) -> None: """This method is called to handle the given message. @@ -49,6 +46,7 @@ def on_error(self, exc: Exception) -> None: """ raise NotImplementedError() + @abstractmethod def stop(self) -> None: """ Stop handling new messages, carry out any final tasks to ensure @@ -85,9 +83,7 @@ class BufferedReader(Listener): # pylint: disable=abstract-method :attr is_stopped: ``True`` if the reader has been stopped """ - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - + def __init__(self) -> None: # set to "infinite" size self.buffer: SimpleQueue[Message] = SimpleQueue() self.is_stopped: bool = False @@ -139,9 +135,7 @@ class AsyncBufferedReader( print(msg) """ - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - + def __init__(self, **kwargs: Any) -> None: self.buffer: "asyncio.Queue[Message]" if "loop" in kwargs: @@ -149,19 +143,22 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: "The 'loop' argument is deprecated since python-can 4.0.0 " "and has no effect starting with Python 3.10", DeprecationWarning, + stacklevel=2, ) if sys.version_info < (3, 10): self.buffer = asyncio.Queue(loop=kwargs["loop"]) return self.buffer = asyncio.Queue() + self._is_stopped: bool = False def on_message_received(self, msg: Message) -> None: """Append a message to the buffer. Must only be called inside an event loop! """ - self.buffer.put_nowait(msg) + if not self._is_stopped: + self.buffer.put_nowait(msg) async def get_message(self) -> Message: """ @@ -178,3 +175,6 @@ def __aiter__(self) -> AsyncIterator[Message]: async def __anext__(self) -> Message: return await self.buffer.get() + + def stop(self) -> None: + self._is_stopped = True diff --git a/can/logger.py b/can/logger.py index f20965b04..7d1ae6f66 100644 --- a/can/logger.py +++ b/can/logger.py @@ -134,7 +134,7 @@ def _parse_additional_config( def _split_arg(_arg: str) -> Tuple[str, str]: left, right = _arg.split("=", 1) - return left.lstrip("--").replace("-", "_"), right + return left.lstrip("-").replace("-", "_"), right args: Dict[str, Union[str, int, float, bool]] = {} for key, string_val in map(_split_arg, unknown_args): diff --git a/can/util.py b/can/util.py index 42b1f49ac..c1eeca8b1 100644 --- a/can/util.py +++ b/can/util.py @@ -68,7 +68,7 @@ def load_file_config( config = ConfigParser() # make sure to not transform the entries such that capitalization is preserved - config.optionxform = lambda entry: entry # type: ignore + config.optionxform = lambda optionstr: optionstr # type: ignore[method-assign] if path is None: config.read([os.path.expanduser(path) for path in CONFIG_FILES]) @@ -411,7 +411,7 @@ def _rename_kwargs( ) kwargs[new] = value - warnings.warn(deprecation_notice, DeprecationWarning) + warnings.warn(deprecation_notice, DeprecationWarning, stacklevel=3) T2 = TypeVar("T2", BitTiming, BitTimingFd) @@ -444,7 +444,8 @@ def check_or_adjust_timing_clock(timing: T2, valid_clocks: Iterable[int]) -> T2: adjusted_timing = timing.recreate_with_f_clock(clock) warnings.warn( f"Adjusted f_clock in {timing.__class__.__name__} from " - f"{timing.f_clock} to {adjusted_timing.f_clock}" + f"{timing.f_clock} to {adjusted_timing.f_clock}", + stacklevel=2, ) return adjusted_timing except ValueError: @@ -506,11 +507,3 @@ def cast_from_string(string_val: str) -> Union[str, int, float, bool]: # value is string return string_val - - -if __name__ == "__main__": - print("Searching for configuration named:") - print("\n".join(CONFIG_FILES)) - print() - print("Settings:") - print(load_config()) diff --git a/can/viewer.py b/can/viewer.py index db19fd1f6..07752327d 100644 --- a/can/viewer.py +++ b/can/viewer.py @@ -43,14 +43,14 @@ try: import curses - from curses.ascii import ESC as KEY_ESC - from curses.ascii import SP as KEY_SPACE + from curses.ascii import ESC as KEY_ESC # type: ignore[attr-defined,unused-ignore] + from curses.ascii import SP as KEY_SPACE # type: ignore[attr-defined,unused-ignore] except ImportError: # Probably on Windows while windows-curses is not installed (e.g. in PyPy) logger.warning( "You won't be able to use the viewer program without curses installed!" ) - curses = None # type: ignore + curses = None # type: ignore[assignment] class CanViewer: # pylint: disable=too-many-instance-attributes @@ -554,7 +554,7 @@ def main() -> None: additional_config.update({"can_filters": can_filters}) bus = _create_bus(parsed_args, **additional_config) - curses.wrapper(CanViewer, bus, data_structs) + curses.wrapper(CanViewer, bus, data_structs) # type: ignore[attr-defined,unused-ignore] if __name__ == "__main__": diff --git a/pyproject.toml b/pyproject.toml index a34508ee8..209305fba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,9 +59,9 @@ changelog = "https://github.com/hardbyte/python-can/blob/develop/CHANGELOG.md" [project.optional-dependencies] lint = [ "pylint==2.17.*", - "ruff==0.0.292", - "black==23.9.*", - "mypy==1.5.*", + "ruff==0.1.13", + "black==23.12.*", + "mypy==1.8.*", ] seeedstudio = ["pyserial>=3.0"] serial = ["pyserial~=3.0"] @@ -100,13 +100,12 @@ ignore_missing_imports = true no_implicit_optional = true disallow_incomplete_defs = true warn_redundant_casts = true -warn_unused_ignores = false +warn_unused_ignores = true exclude = [ "venv", "^doc/conf.py$", "^build", "^test", - "^setup.py$", "^can/interfaces/__init__.py", "^can/interfaces/etas", "^can/interfaces/gs_usb", @@ -128,23 +127,39 @@ exclude = [ [tool.ruff] select = [ + "A", # flake8-builtins + "B", # flake8-bugbear + "C4", # flake8-comprehensions "F", # pyflakes - "UP", # pyupgrade - "I", # isort "E", # pycodestyle errors - "W", # pycodestyle warnings + "I", # isort + "N", # pep8-naming + "PGH", # pygrep-hooks "PL", # pylint "RUF", # ruff-specific rules - "C4", # flake8-comprehensions + "T20", # flake8-print "TCH", # flake8-type-checking + "UP", # pyupgrade + "W", # pycodestyle warnings + "YTT", # flake8-2020 ] ignore = [ + "B026", # star-arg-unpacking-after-keyword-arg + "PLR", # pylint refactor +] +line-length = 100 + +[tool.ruff.per-file-ignores] +"can/interfaces/*" = [ "E501", # Line too long "F403", # undefined-local-with-import-star "F405", # undefined-local-with-import-star-usage - "PLR", # pylint refactor + "N", # pep8-naming + "PGH003", # blanket-type-ignore "RUF012", # mutable-class-default ] +"can/logger.py" = ["T20"] # flake8-print +"can/player.py" = ["T20"] # flake8-print [tool.ruff.isort] known-first-party = ["can"] diff --git a/test/back2back_test.py b/test/back2back_test.py index d9896c19f..8269a73a5 100644 --- a/test/back2back_test.py +++ b/test/back2back_test.py @@ -273,6 +273,7 @@ def test_sub_second_timestamp_resolution(self): self.bus2.recv(0) self.bus2.recv(0) + @unittest.skipIf(IS_PYPY, "fails randomly when run on CI server") def test_send_periodic_duration(self): """ Verify that send_periodic only transmits for the specified duration. diff --git a/test/network_test.py b/test/network_test.py index 250976fb2..b0fcba37f 100644 --- a/test/network_test.py +++ b/test/network_test.py @@ -1,6 +1,5 @@ #!/usr/bin/env python - - +import contextlib import logging import random import threading @@ -117,7 +116,8 @@ def testProducerConsumer(self): i += 1 t.join() - self.server_bus.flush_tx_buffer() + with contextlib.suppress(NotImplementedError): + self.server_bus.flush_tx_buffer() self.server_bus.shutdown()