From 4a71cd837939aacfacd1ab66d147d9befdf0fffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Tue, 5 Aug 2025 13:46:44 +0200 Subject: [PATCH 1/4] Bump minimum Python version to 3.9 and update license metadata. Avoid deprecation warnings with modern setuptools, which will stop supporting a table as the project.license option in pyproject.toml soon. Require a modern version >= 77 to make use of the alternative options. --- README.rst | 2 +- pyproject.toml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 320ebfac..e01ac668 100644 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ The aim of the project is to support the most common parts of the CiA 301 standard in a simple Pythonic interface. It is mainly targeted for testing and automation tasks rather than a standard compliant master implementation. -The library supports Python 3.8 or newer. +The library supports Python 3.9 or newer. Features diff --git a/pyproject.toml b/pyproject.toml index 98529bc6..e24f6132 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=69", "wheel", "setuptools_scm>=8"] +requires = ["setuptools>=77", "wheel", "setuptools_scm>=8"] build-backend = "setuptools.build_meta" [project] @@ -11,11 +11,11 @@ authors = [ ] description = "CANopen stack implementation" readme = "README.rst" -requires-python = ">=3.8" -license = {file = "LICENSE.txt"} +requires-python = ">=3.9" +license = "MIT" +license-files = ["LICENSE.txt"] classifiers = [ "Development Status :: 5 - Production/Stable", - "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3 :: Only", "Intended Audience :: Developers", @@ -50,7 +50,7 @@ filterwarnings = [ ] [tool.mypy] -python_version = "3.8" +python_version = "3.9" exclude = [ "^examples*", "^test*", From 76c749eb8f6fdab597bfcf6ef043d89dc23d97a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Tue, 5 Aug 2025 14:23:54 +0200 Subject: [PATCH 2/4] Modernize Dict and List type annotations. Since Python 3.9 we can use the built-in types directly. --- canopen/emcy.py | 12 +++++++----- canopen/network.py | 10 +++++----- canopen/nmt.py | 10 +++++----- canopen/node/local.py | 4 ++-- canopen/objectdictionary/__init__.py | 6 +++--- canopen/pdo/base.py | 6 +++--- canopen/profiles/p402.py | 5 ++--- 7 files changed, 27 insertions(+), 26 deletions(-) diff --git a/canopen/emcy.py b/canopen/emcy.py index ec2c489f..22d1eba8 100644 --- a/canopen/emcy.py +++ b/canopen/emcy.py @@ -1,8 +1,10 @@ +from __future__ import annotations + import logging import struct import threading import time -from typing import Callable, List, Optional +from typing import Callable, Optional import canopen.network @@ -17,9 +19,9 @@ class EmcyConsumer: def __init__(self): #: Log of all received EMCYs for this node - self.log: List["EmcyError"] = [] + self.log: list[EmcyError] = [] #: Only active EMCYs. Will be cleared on Error Reset - self.active: List["EmcyError"] = [] + self.active: list[EmcyError] = [] self.callbacks = [] self.emcy_received = threading.Condition() @@ -39,7 +41,7 @@ def on_emcy(self, can_id, data, timestamp): for callback in self.callbacks: callback(entry) - def add_callback(self, callback: Callable[["EmcyError"], None]): + def add_callback(self, callback: Callable[[EmcyError], None]): """Get notified on EMCY messages from this node. :param callback: @@ -55,7 +57,7 @@ def reset(self): def wait( self, emcy_code: Optional[int] = None, timeout: float = 10 - ) -> "EmcyError": + ) -> EmcyError: """Wait for a new EMCY to arrive. :param emcy_code: EMCY code to wait for diff --git a/canopen/network.py b/canopen/network.py index 4e85e5b5..88077370 100644 --- a/canopen/network.py +++ b/canopen/network.py @@ -3,7 +3,7 @@ import logging import threading from collections.abc import MutableMapping -from typing import Callable, Dict, Final, Iterator, List, Optional, Union +from typing import Callable, Final, Iterator, Optional, Union import can from can import Listener @@ -40,10 +40,10 @@ def __init__(self, bus: Optional[can.BusABC] = None): self.scanner = NodeScanner(self) #: List of :class:`can.Listener` objects. #: Includes at least MessageListener. - self.listeners = [MessageListener(self)] + self.listeners: list[can.Listener] = [MessageListener(self)] self.notifier: Optional[can.Notifier] = None - self.nodes: Dict[int, Union[RemoteNode, LocalNode]] = {} - self.subscribers: Dict[int, List[Callback]] = {} + self.nodes: dict[int, Union[RemoteNode, LocalNode]] = {} + self.subscribers: dict[int, list[Callback]] = {} self.send_lock = threading.Lock() self.sync = SyncProducer(self) self.time = TimeProducer(self) @@ -396,7 +396,7 @@ def __init__(self, network: Optional[Network] = None): network = _UNINITIALIZED_NETWORK self.network: Network = network #: A :class:`list` of nodes discovered - self.nodes: List[int] = [] + self.nodes: list[int] = [] def on_message_received(self, can_id: int): service = can_id & 0x780 diff --git a/canopen/nmt.py b/canopen/nmt.py index c13d0779..1c583330 100644 --- a/canopen/nmt.py +++ b/canopen/nmt.py @@ -2,7 +2,7 @@ import struct import threading import time -from typing import Callable, Dict, Final, List, Optional, TYPE_CHECKING +from typing import Callable, Final, Optional, TYPE_CHECKING import canopen.network @@ -12,7 +12,7 @@ logger = logging.getLogger(__name__) -NMT_STATES: Final[Dict[int, str]] = { +NMT_STATES: Final[dict[int, str]] = { 0: 'INITIALISING', 4: 'STOPPED', 5: 'OPERATIONAL', @@ -21,7 +21,7 @@ 127: 'PRE-OPERATIONAL' } -NMT_COMMANDS: Final[Dict[str, int]] = { +NMT_COMMANDS: Final[dict[str, int]] = { 'OPERATIONAL': 1, 'STOPPED': 2, 'SLEEP': 80, @@ -32,7 +32,7 @@ 'RESET COMMUNICATION': 130 } -COMMAND_TO_STATE: Final[Dict[int, int]] = { +COMMAND_TO_STATE: Final[dict[int, int]] = { 1: 5, 2: 4, 80: 80, @@ -117,7 +117,7 @@ def __init__(self, node_id: int): #: Timestamp of last heartbeat message self.timestamp: Optional[float] = None self.state_update = threading.Condition() - self._callbacks: List[Callable[[int], None]] = [] + self._callbacks: list[Callable[[int], None]] = [] def on_heartbeat(self, can_id, data, timestamp): with self.state_update: diff --git a/canopen/node/local.py b/canopen/node/local.py index 34bba899..886d8ac8 100644 --- a/canopen/node/local.py +++ b/canopen/node/local.py @@ -1,7 +1,7 @@ from __future__ import annotations import logging -from typing import Dict, Union +from typing import Union import canopen.network from canopen import objectdictionary @@ -25,7 +25,7 @@ def __init__( ): super(LocalNode, self).__init__(node_id, object_dictionary) - self.data_store: Dict[int, Dict[int, bytes]] = {} + self.data_store: dict[int, dict[int, bytes]] = {} self._read_callbacks = [] self._write_callbacks = [] diff --git a/canopen/objectdictionary/__init__.py b/canopen/objectdictionary/__init__.py index f394da23..a836b740 100644 --- a/canopen/objectdictionary/__init__.py +++ b/canopen/objectdictionary/__init__.py @@ -7,7 +7,7 @@ import logging import struct from collections.abc import Mapping, MutableMapping -from typing import Dict, Iterator, List, Optional, TextIO, Union +from typing import Iterator, Optional, TextIO, Union from canopen.objectdictionary.datatypes import * from canopen.objectdictionary.datatypes import IntegerN, UnsignedN @@ -367,9 +367,9 @@ def __init__(self, name: str, index: int, subindex: int = 0): #: Description of variable self.description: str = "" #: Dictionary of value descriptions - self.value_descriptions: Dict[int, str] = {} + self.value_descriptions: dict[int, str] = {} #: Dictionary of bitfield definitions - self.bit_definitions: Dict[str, List[int]] = {} + self.bit_definitions: dict[str, list[int]] = {} #: Storage location of index self.storage_location = None #: Can this variable be mapped to a PDO diff --git a/canopen/pdo/base.py b/canopen/pdo/base.py index 7bef6e62..6b25daf2 100644 --- a/canopen/pdo/base.py +++ b/canopen/pdo/base.py @@ -5,7 +5,7 @@ import math import threading from collections.abc import Mapping -from typing import Callable, Dict, Iterator, List, Optional, TYPE_CHECKING, Union +from typing import Callable, Iterator, Optional, TYPE_CHECKING, Union import canopen.network from canopen import objectdictionary @@ -150,7 +150,7 @@ def __init__(self, com_offset, map_offset, pdo_node: PdoBase, cob_base=None): :param pdo_node: :param cob_base: """ - self.maps: Dict[int, PdoMap] = {} + self.maps: dict[int, PdoMap] = {} for map_no in range(512): if com_offset + map_no in pdo_node.node.object_dictionary: new_map = PdoMap( @@ -196,7 +196,7 @@ def __init__(self, pdo_node, com_record, map_array): #: Ignores SYNC objects up to this SYNC counter value (optional) self.sync_start_value: Optional[int] = None #: List of variables mapped to this PDO - self.map: List[PdoVariable] = [] + self.map: list[PdoVariable] = [] self.length: int = 0 #: Current message data self.data = bytearray() diff --git a/canopen/profiles/p402.py b/canopen/profiles/p402.py index 88d86aad..fd4059fe 100644 --- a/canopen/profiles/p402.py +++ b/canopen/profiles/p402.py @@ -1,7 +1,6 @@ # inspired by the NmtMaster code import logging import time -from typing import Dict from canopen.node import RemoteNode from canopen.pdo import PdoMap @@ -215,8 +214,8 @@ class BaseNode402(RemoteNode): def __init__(self, node_id, object_dictionary): super(BaseNode402, self).__init__(node_id, object_dictionary) self.tpdo_values = {} # { index: value from last received TPDO } - self.tpdo_pointers: Dict[int, PdoMap] = {} - self.rpdo_pointers: Dict[int, PdoMap] = {} + self.tpdo_pointers: dict[int, PdoMap] = {} + self.rpdo_pointers: dict[int, PdoMap] = {} def setup_402_state_machine(self, read_pdos=True): """Configure the state machine by searching for a TPDO that has the StatusWord mapped. From 612bbe66cfb73b8963b4a30d25f109d2ccf1a181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Tue, 5 Aug 2025 14:27:55 +0200 Subject: [PATCH 3/4] Simplify type import. --- canopen/network.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/canopen/network.py b/canopen/network.py index 88077370..163668a9 100644 --- a/canopen/network.py +++ b/canopen/network.py @@ -6,7 +6,6 @@ from typing import Callable, Final, Iterator, Optional, Union import can -from can import Listener from canopen.lss import LssMaster from canopen.nmt import NmtMaster @@ -352,7 +351,7 @@ def update(self, data: bytes) -> None: self._start() -class MessageListener(Listener): +class MessageListener(can.Listener): """Listens for messages on CAN bus and feeds them to a Network instance. :param network: From 0fc1c300508fad51f9738e1091c176a48d4b2d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Tue, 5 Aug 2025 14:31:02 +0200 Subject: [PATCH 4/4] Modernize Iterator type annotations. Since Python 3.9 the collections.abc module can be used directly for typing. --- canopen/network.py | 4 ++-- canopen/objectdictionary/__init__.py | 4 ++-- canopen/pdo/base.py | 4 ++-- canopen/sdo/base.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/canopen/network.py b/canopen/network.py index 163668a9..6a8d95f6 100644 --- a/canopen/network.py +++ b/canopen/network.py @@ -2,8 +2,8 @@ import logging import threading -from collections.abc import MutableMapping -from typing import Callable, Final, Iterator, Optional, Union +from collections.abc import Iterator, MutableMapping +from typing import Callable, Final, Optional, Union import can diff --git a/canopen/objectdictionary/__init__.py b/canopen/objectdictionary/__init__.py index a836b740..fa694c56 100644 --- a/canopen/objectdictionary/__init__.py +++ b/canopen/objectdictionary/__init__.py @@ -6,8 +6,8 @@ import logging import struct -from collections.abc import Mapping, MutableMapping -from typing import Iterator, Optional, TextIO, Union +from collections.abc import Iterator, Mapping, MutableMapping +from typing import Optional, TextIO, Union from canopen.objectdictionary.datatypes import * from canopen.objectdictionary.datatypes import IntegerN, UnsignedN diff --git a/canopen/pdo/base.py b/canopen/pdo/base.py index 6b25daf2..2e335e54 100644 --- a/canopen/pdo/base.py +++ b/canopen/pdo/base.py @@ -4,8 +4,8 @@ import logging import math import threading -from collections.abc import Mapping -from typing import Callable, Iterator, Optional, TYPE_CHECKING, Union +from collections.abc import Iterator, Mapping +from typing import Callable, Optional, TYPE_CHECKING, Union import canopen.network from canopen import objectdictionary diff --git a/canopen/sdo/base.py b/canopen/sdo/base.py index ddc75ed9..4496e715 100644 --- a/canopen/sdo/base.py +++ b/canopen/sdo/base.py @@ -1,8 +1,8 @@ from __future__ import annotations import binascii -from collections.abc import Mapping -from typing import Iterator, Optional, Union +from collections.abc import Iterator, Mapping +from typing import Optional, Union import canopen.network from canopen import objectdictionary