From 96e6fc99b0a2caa010a767a1806851fc96a2a987 Mon Sep 17 00:00:00 2001 From: MAKOMO Date: Sat, 14 Oct 2023 16:38:47 +0200 Subject: [PATCH 1/6] eliminates implicit Optional --- pymodbus/client/base.py | 32 +++--- pymodbus/client/mixin.py | 2 +- pymodbus/client/serial.py | 1 + pymodbus/client/tcp.py | 6 +- pymodbus/client/tls.py | 22 ++--- pymodbus/client/udp.py | 6 +- pymodbus/datastore/simulator.py | 4 +- pymodbus/logging.py | 2 +- pymodbus/repl/client/helper.py | 2 +- pymodbus/server/async_io.py | 10 +- pymodbus/server/reactive/main.py | 8 +- pymodbus/server/simulator/http_server.py | 8 +- pymodbus/transport/transport.py | 119 ++++++++++++++--------- pymodbus/transport/transport_serial.py | 12 ++- pyproject.toml | 2 +- 15 files changed, 132 insertions(+), 104 deletions(-) diff --git a/pymodbus/client/base.py b/pymodbus/client/base.py index 8f9ff853b..96878782c 100644 --- a/pymodbus/client/base.py +++ b/pymodbus/client/base.py @@ -4,7 +4,7 @@ import asyncio import socket from dataclasses import dataclass -from typing import Any, Callable +from typing import Any, Callable, cast from pymodbus.client.mixin import ModbusClientMixin from pymodbus.exceptions import ConnectionException, ModbusIOException @@ -53,20 +53,20 @@ class ModbusBaseClient(ModbusClientMixin, ModbusProtocol): class _params: """Parameter class.""" - retries: int = None - retry_on_empty: bool = None - close_comm_on_error: bool = None - strict: bool = None - broadcast_enable: bool = None - reconnect_delay: int = None + retries: int | None = None + retry_on_empty: bool | None = None + close_comm_on_error: bool | None = None + strict: bool | None = None + broadcast_enable: bool | None = None + reconnect_delay: int | None = None - source_address: tuple[str, int] = None + source_address: tuple[str, int] | None = None - server_hostname: str = None + server_hostname: str | None = None def __init__( # pylint: disable=too-many-arguments self, - framer: type[ModbusFramer] = None, + framer: type[ModbusFramer] | None = None, timeout: float = 3, retries: int = 3, retry_on_empty: bool = False, @@ -118,14 +118,14 @@ def __init__( # pylint: disable=too-many-arguments self.slaves: list[int] = [] # Common variables. - self.framer = framer(ClientDecoder(), self) + self.framer = cast(type[ModbusFramer], framer)(ClientDecoder(), self) self.transaction = DictTransactionManager( self, retries=retries, retry_on_empty=retry_on_empty, **kwargs ) - self.reconnect_delay_current = self.params.reconnect_delay + self.reconnect_delay_current = cast(float, self.params.reconnect_delay) self.use_udp = False self.state = ModbusTransactionState.IDLE - self.last_frame_end: float = 0 + self.last_frame_end: float | None = 0 self.silent_interval: float = 0 # ----------------------------------------------------------------------- # @@ -164,7 +164,7 @@ def idle_time(self) -> float: return 0 return self.last_frame_end + self.silent_interval - def execute(self, request: ModbusRequest = None) -> ModbusResponse: + def execute(self, request: ModbusRequest | None = None) -> ModbusResponse: """Execute request and get response (call **sync/async**). :param request: The request to process @@ -210,7 +210,7 @@ async def async_execute(self, request=None): return resp - def callback_data(self, data: bytes, addr: tuple = None) -> int: + def callback_data(self, data: bytes, addr: tuple | None = None) -> int: """Handle received data returns number of bytes consumed @@ -218,7 +218,7 @@ def callback_data(self, data: bytes, addr: tuple = None) -> int: self.framer.processIncomingPacket(data, self._handle_response, slave=0) return len(data) - def callback_disconnected(self, _reason: Exception) -> None: + def callback_disconnected(self, _reason: Exception | None) -> None: """Handle lost connection""" for tid in list(self.transaction): self.raise_future( diff --git a/pymodbus/client/mixin.py b/pymodbus/client/mixin.py index 64b41b6bb..64d011995 100644 --- a/pymodbus/client/mixin.py +++ b/pymodbus/client/mixin.py @@ -502,7 +502,7 @@ def read_fifo_queue(self, address: int = 0x0000, **kwargs: Any) -> ModbusRespons # code 0x2B sub 0x0D: CANopen General Reference Request and Response, NOT IMPLEMENTED def read_device_information( - self, read_code: int = None, object_id: int = 0x00, **kwargs: Any + self, read_code: Union[int, None] = None, object_id: int = 0x00, **kwargs: Any ) -> ModbusResponse: """Read FIFO queue (code 0x2B sub 0x0E). diff --git a/pymodbus/client/serial.py b/pymodbus/client/serial.py index 94bdb31ac..5a811bc18 100644 --- a/pymodbus/client/serial.py +++ b/pymodbus/client/serial.py @@ -141,6 +141,7 @@ def __init__( self.last_frame_end = None + assert isinstance(self.comm_params.baudrate, int) self._t0 = float(1 + bytesize + stopbits) / self.comm_params.baudrate # Check every 4 bytes / 2 registers if the reading is ready diff --git a/pymodbus/client/tcp.py b/pymodbus/client/tcp.py index f73769539..ae411eca2 100644 --- a/pymodbus/client/tcp.py +++ b/pymodbus/client/tcp.py @@ -3,7 +3,7 @@ import select import socket import time -from typing import Any, Tuple, Type +from typing import Any, Optional, Tuple, Type from pymodbus.client.base import ModbusBaseClient from pymodbus.exceptions import ConnectionException @@ -40,7 +40,7 @@ def __init__( host: str, port: int = 502, framer: Type[ModbusFramer] = ModbusSocketFramer, - source_address: Tuple[str, int] = None, + source_address: Optional[Tuple[str, int]] = None, **kwargs: Any, ) -> None: """Initialize Asyncio Modbus TCP Client.""" @@ -102,7 +102,7 @@ def __init__( host: str, port: int = 502, framer: Type[ModbusFramer] = ModbusSocketFramer, - source_address: Tuple[str, int] = None, + source_address: Optional[Tuple[str, int]] = None, **kwargs: Any, ) -> None: """Initialize Modbus TCP Client.""" diff --git a/pymodbus/client/tls.py b/pymodbus/client/tls.py index b823745ea..b6aab5199 100644 --- a/pymodbus/client/tls.py +++ b/pymodbus/client/tls.py @@ -1,7 +1,7 @@ """Modbus client async TLS communication.""" import socket import ssl -from typing import Any, Type +from typing import Any, Optional, Type from pymodbus.client.tcp import AsyncModbusTcpClient, ModbusTcpClient from pymodbus.framer import ModbusFramer @@ -44,11 +44,11 @@ def __init__( host: str, port: int = 802, framer: Type[ModbusFramer] = ModbusTlsFramer, - sslctx: ssl.SSLContext = None, - certfile: str = None, - keyfile: str = None, - password: str = None, - server_hostname: str = None, + sslctx: Optional[ssl.SSLContext] = None, + certfile: Optional[str] = None, + keyfile: Optional[str] = None, + password: Optional[str] = None, + server_hostname: Optional[str] = None, **kwargs: Any, ): """Initialize Asyncio Modbus TLS Client.""" @@ -113,11 +113,11 @@ def __init__( host: str, port: int = 802, framer: Type[ModbusFramer] = ModbusTlsFramer, - sslctx: ssl.SSLContext = None, - certfile: str = None, - keyfile: str = None, - password: str = None, - server_hostname: str = None, + sslctx: Optional[ssl.SSLContext] = None, + certfile: Optional[str] = None, + keyfile: Optional[str] = None, + password: Optional[str] = None, + server_hostname: Optional[str] = None, **kwargs: Any, ): """Initialize Modbus TLS Client.""" diff --git a/pymodbus/client/udp.py b/pymodbus/client/udp.py index d0fa84da5..711b53b6a 100644 --- a/pymodbus/client/udp.py +++ b/pymodbus/client/udp.py @@ -1,7 +1,7 @@ """Modbus client async UDP communication.""" import asyncio import socket -from typing import Any, Tuple, Type +from typing import Any, Optional, Tuple, Type from pymodbus.client.base import ModbusBaseClient from pymodbus.exceptions import ConnectionException @@ -45,7 +45,7 @@ def __init__( host: str, port: int = 502, framer: Type[ModbusFramer] = ModbusSocketFramer, - source_address: Tuple[str, int] = None, + source_address: Optional[Tuple[str, int]] = None, **kwargs: Any, ) -> None: """Initialize Asyncio Modbus UDP Client.""" @@ -106,7 +106,7 @@ def __init__( host: str, port: int = 502, framer: Type[ModbusFramer] = ModbusSocketFramer, - source_address: Tuple[str, int] = None, + source_address: Optional[Tuple[str, int]] = None, **kwargs: Any, ) -> None: """Initialize Modbus UDP Client.""" diff --git a/pymodbus/datastore/simulator.py b/pymodbus/datastore/simulator.py index eff5a09e6..c005e15cd 100644 --- a/pymodbus/datastore/simulator.py +++ b/pymodbus/datastore/simulator.py @@ -3,7 +3,7 @@ import random import struct from datetime import datetime -from typing import Any, Callable, Dict, List +from typing import Any, Callable, Dict, List, Optional WORD_SIZE = 16 @@ -30,7 +30,7 @@ class Cell: access: bool = False value: int = 0 action: int = 0 - action_kwargs: Dict[str, Any] = None + action_kwargs: Optional[Dict[str, Any]] = None count_read: int = 0 count_write: int = 0 diff --git a/pymodbus/logging.py b/pymodbus/logging.py index 7fa04887b..0d486cb36 100644 --- a/pymodbus/logging.py +++ b/pymodbus/logging.py @@ -17,7 +17,7 @@ def pymodbus_apply_logging_config( - level: Union[str, int] = logging.DEBUG, log_file_name: str = None + level: Union[str, int] = logging.DEBUG, log_file_name: Union[str, None] = None ): """Apply basic logging configuration used by default by Pymodbus maintainers. diff --git a/pymodbus/repl/client/helper.py b/pymodbus/repl/client/helper.py index 09676f153..ea5e908be 100644 --- a/pymodbus/repl/client/helper.py +++ b/pymodbus/repl/client/helper.py @@ -229,7 +229,7 @@ def get_commands(client): class Result: """Represent result command.""" - function_code: int = None + function_code: Union[int, None] = None data: Union[Dict[int, Any], Any] = None def __init__(self, result): diff --git a/pymodbus/server/async_io.py b/pymodbus/server/async_io.py index 29e7f3902..085a65049 100644 --- a/pymodbus/server/async_io.py +++ b/pymodbus/server/async_io.py @@ -5,7 +5,7 @@ import time import traceback from contextlib import suppress -from typing import Union +from typing import Optional, Union from pymodbus.datastore import ModbusServerContext from pymodbus.device import ModbusControlBlock, ModbusDeviceIdentification @@ -84,7 +84,7 @@ def callback_connected(self) -> None: traceback.format_exc(), ) - def callback_disconnected(self, call_exc: Exception) -> None: + def callback_disconnected(self, call_exc: Optional[Exception]) -> None: """Call when connection is lost.""" try: if self.handler_task: @@ -239,7 +239,7 @@ async def _recv_(self): # pragma: no cover result = None return result - def callback_data(self, data: bytes, addr: tuple = None) -> int: + def callback_data(self, data: bytes, addr: Union[tuple, None] = None) -> int: """Handle received data.""" if addr: self.receive_queue.put_nowait((data, addr)) @@ -563,7 +563,9 @@ class _serverList: :meta private: """ - active_server: Union[ModbusTcpServer, ModbusUdpServer, ModbusSerialServer] = None + active_server: Union[ + ModbusTcpServer, ModbusUdpServer, ModbusSerialServer, None + ] = None def __init__(self, server): """Register new server.""" diff --git a/pymodbus/server/reactive/main.py b/pymodbus/server/reactive/main.py index 409cb8ae9..918ad03b7 100644 --- a/pymodbus/server/reactive/main.py +++ b/pymodbus/server/reactive/main.py @@ -97,10 +97,10 @@ class ReactiveModbusSlaveContext(ModbusSlaveContext): def __init__( self, - discrete_inputs: BaseModbusDataBlock = None, - coils: BaseModbusDataBlock = None, - input_registers: BaseModbusDataBlock = None, - holding_registers: BaseModbusDataBlock = None, + discrete_inputs: BaseModbusDataBlock | None = None, + coils: BaseModbusDataBlock | None = None, + input_registers: BaseModbusDataBlock | None = None, + holding_registers: BaseModbusDataBlock | None = None, zero_mode: bool = False, randomize: int = 0, change_rate: int = 0, diff --git a/pymodbus/server/simulator/http_server.py b/pymodbus/server/simulator/http_server.py index a9f529c8d..19ef065e8 100644 --- a/pymodbus/server/simulator/http_server.py +++ b/pymodbus/server/simulator/http_server.py @@ -5,13 +5,13 @@ import json import os from time import time -from typing import List +from typing import Any, List, Optional, cast try: from aiohttp import web except ImportError: - web = None + web = cast(Any, None) import contextlib @@ -124,7 +124,7 @@ def __init__( http_port: int = 8080, log_file: str = "server.log", json_file: str = "setup.json", - custom_actions_module: str = None, + custom_actions_module: Optional[str] = None, ): """Initialize http interface.""" if not web: @@ -157,7 +157,7 @@ def __init__( del server["port"] device = setup["device_list"][modbus_device] self.datastore_context = ModbusSimulatorContext( - device, custom_actions_dict or None + device, custom_actions_dict or {} ) datastore = ModbusServerContext(slaves=self.datastore_context, single=True) comm = comm_class[server.pop("comm")] diff --git a/pymodbus/transport/transport.py b/pymodbus/transport/transport.py index 297272b96..2ac4ba98f 100644 --- a/pymodbus/transport/transport.py +++ b/pymodbus/transport/transport.py @@ -54,7 +54,7 @@ import sys from contextlib import suppress from enum import Enum -from typing import Any, Callable, Coroutine +from typing import Any, Callable, Coroutine, cast from pymodbus.logging import Log from pymodbus.transport.transport_serial import create_serial_connection @@ -85,33 +85,33 @@ class CommParams: """Parameter class.""" # generic - comm_name: str = None - comm_type: CommType = None - reconnect_delay: float = None - reconnect_delay_max: float = None - timeout_connect: float = None + comm_name: str | None = None + comm_type: CommType | None = None + reconnect_delay: float | None = None + reconnect_delay_max: float | None = None + timeout_connect: float | None = None host: str = "127.0.0.1" port: int = 0 source_address: tuple[str, int] = ("0.0.0.0", 0) handle_local_echo: bool = False # tls - sslctx: ssl.SSLContext = None + sslctx: ssl.SSLContext | None = None # serial - baudrate: int = None - bytesize: int = None - parity: str = None - stopbits: int = None + baudrate: int | None = None + bytesize: int | None = None + parity: str | None = None + stopbits: int | None = None @classmethod def generate_ssl( cls, is_server: bool, - certfile: str = None, - keyfile: str = None, - password: str = None, - sslctx: ssl.SSLContext = None, + certfile: str | None = None, + keyfile: str | None = None, + password: str | None = None, + sslctx: ssl.SSLContext | None = None, ) -> ssl.SSLContext: """Generate sslctx from cert/key/passwor @@ -134,7 +134,7 @@ def generate_ssl( ) return new_sslctx - def copy(self): + def copy(self) -> CommParams: """Create a copy.""" return dataclasses.replace(self) @@ -156,16 +156,16 @@ def __init__( self.is_server = is_server self.is_closing = False - self.transport: asyncio.BaseTransport = None - self.loop: asyncio.AbstractEventLoop = None + self.transport: asyncio.BaseTransport | None = None + self.loop: asyncio.AbstractEventLoop | None = None self.recv_buffer: bytes = b"" - self.call_create: Callable[[], Coroutine[Any, Any, Any]] = lambda: None + self.call_create: Callable[[], Coroutine[Any, Any, Any]] | None = None if self.is_server: self.active_connections: dict[str, ModbusProtocol] = {} else: - self.listener: ModbusProtocol = None + self.listener: ModbusProtocol | None = None self.unique_id: str = str(id(self)) - self.reconnect_task: asyncio.Task = None + self.reconnect_task: asyncio.Task | None = None self.reconnect_delay_current: float = 0.0 self.sent_buffer: bytes = b"" @@ -177,16 +177,19 @@ def __init__( host = self.comm_params.host port = int(self.comm_params.port) if self.comm_params.comm_type == CommType.SERIAL: - host, port = self.init_setup_serial(host, port) - if not host and not port: + host_serial, port_serial = self.init_setup_serial(host, port) + if host_serial is None or port_serial is None: return + host, port = host_serial, port_serial if host == NULLMODEM_HOST: self.call_create = lambda: self.create_nullmodem(port) return # TCP/TLS/UDP self.init_setup_connect_listen(host, port) - def init_setup_serial(self, host: str, _port: int) -> tuple[str, int]: + def init_setup_serial( + self, host: str, _port: int + ) -> tuple[str, int] | tuple[None, None]: """Split host for serial if needed.""" if NULLMODEM_HOST in host: return NULLMODEM_HOST, int(host[9:].split(":")[1]) @@ -211,19 +214,25 @@ def init_setup_connect_listen(self, host: str, port: int) -> None: """Handle connect/listen handler.""" if self.comm_params.comm_type == CommType.UDP: if self.is_server: - self.call_create = lambda: self.loop.create_datagram_endpoint( + self.call_create = lambda: cast( + asyncio.AbstractEventLoop, self.loop + ).create_datagram_endpoint( self.handle_new_connection, local_addr=(host, port), ) else: - self.call_create = lambda: self.loop.create_datagram_endpoint( + self.call_create = lambda: cast( + asyncio.AbstractEventLoop, self.loop + ).create_datagram_endpoint( self.handle_new_connection, remote_addr=(host, port), ) return # TLS and TCP if self.is_server: - self.call_create = lambda: self.loop.create_server( + self.call_create = lambda: cast( + asyncio.AbstractEventLoop, self.loop + ).create_server( self.handle_new_connection, host, port, @@ -232,7 +241,9 @@ def init_setup_connect_listen(self, host: str, port: int) -> None: start_serving=True, ) else: - self.call_create = lambda: self.loop.create_connection( + self.call_create = lambda: cast( + asyncio.AbstractEventLoop, self.loop + ).create_connection( self.handle_new_connection, host, port, @@ -247,6 +258,7 @@ async def transport_connect(self) -> bool: self.loop = asyncio.get_running_loop() self.is_closing = False try: + assert self.call_create is not None self.transport, _protocol = await asyncio.wait_for( self.call_create(), timeout=self.comm_params.timeout_connect, @@ -267,9 +279,12 @@ async def transport_listen(self) -> bool: self.loop = asyncio.get_running_loop() self.is_closing = False try: - self.transport = await self.call_create() - if isinstance(self.transport, tuple): - self.transport = self.transport[0] + assert self.call_create is not None + transport = await self.call_create() + if isinstance(transport, tuple): + self.transport = transport[0] + else: + self.transport = transport except OSError as exc: Log.warning("Failed to start server {}", exc) # self.transport_close(intern=True) @@ -279,7 +294,7 @@ async def transport_listen(self) -> bool: # ---------------------------------- # # ModbusProtocol asyncio standard methods # # ---------------------------------- # - def connection_made(self, transport: asyncio.BaseTransport): + def connection_made(self, transport: asyncio.BaseTransport) -> None: """Call from asyncio, when a connection is made. :param transport: socket etc. representing the connection. @@ -289,7 +304,7 @@ def connection_made(self, transport: asyncio.BaseTransport): self.reset_delay() self.callback_connected() - def connection_lost(self, reason: Exception): + def connection_lost(self, reason: Exception | None) -> None: """Call from asyncio, when the connection is lost or closed. :param reason: None or an exception object @@ -306,14 +321,14 @@ def connection_lost(self, reason: Exception): self.reconnect_task = asyncio.create_task(self.do_reconnect()) self.callback_disconnected(reason) - def data_received(self, data: bytes): + def data_received(self, data: bytes) -> None: """Call when some data is received. :param data: non-empty bytes object with incoming data. """ self.datagram_received(data, None) - def datagram_received(self, data: bytes, addr: tuple): + def datagram_received(self, data: bytes, addr: tuple | None) -> None: """Receive datagram (UDP connections).""" if self.comm_params.handle_local_echo and self.sent_buffer: if data.startswith(self.sent_buffer): @@ -354,7 +369,7 @@ def datagram_received(self, data: bytes, addr: tuple): ":hex", ) - def eof_received(self): + def eof_received(self) -> None: """Accept other end terminates connection.""" Log.debug("-> transport: received eof") @@ -374,11 +389,11 @@ def callback_connected(self) -> None: """Call when connection is succcesfull.""" Log.debug("callback_connected called") - def callback_disconnected(self, exc: Exception) -> None: + def callback_disconnected(self, exc: Exception | None) -> None: """Call when connection is lost.""" Log.debug("callback_disconnected called: {}", exc) - def callback_data(self, data: bytes, addr: tuple = None) -> int: + def callback_data(self, data: bytes, addr: tuple | None = None) -> int: """Handle received data.""" Log.debug("callback_data called: {} addr={}", data, ":hex", addr) return 0 @@ -386,7 +401,7 @@ def callback_data(self, data: bytes, addr: tuple = None) -> int: # ----------------------------------- # # Helper methods for external classes # # ----------------------------------- # - def transport_send(self, data: bytes, addr: tuple = None) -> None: + def transport_send(self, data: bytes, addr: tuple | None = None) -> None: """Send request. :param data: non-empty bytes object with data to send. @@ -395,6 +410,7 @@ def transport_send(self, data: bytes, addr: tuple = None) -> None: Log.debug("send: {}", data, ":hex") if self.comm_params.handle_local_echo: self.sent_buffer += data + assert self.transport is not None if self.comm_params.comm_type == CommType.UDP: if addr: self.transport.sendto(data, addr=addr) # type: ignore[attr-defined] @@ -435,7 +451,7 @@ def transport_close(self, intern: bool = False, reconnect: bool = False) -> None def reset_delay(self) -> None: """Reset wait time before next reconnect to minimal period.""" - self.reconnect_delay_current = self.comm_params.reconnect_delay + self.reconnect_delay_current = cast(float, self.comm_params.reconnect_delay) def is_active(self) -> bool: """Return true if connected/listening.""" @@ -444,7 +460,9 @@ def is_active(self) -> bool: # ---------------- # # Internal methods # # ---------------- # - async def create_nullmodem(self, port): + async def create_nullmodem( + self, port + ) -> tuple[asyncio.Transport, asyncio.BaseProtocol]: """Bypass create_ and use null modem""" if self.is_server: # Listener object @@ -454,7 +472,7 @@ async def create_nullmodem(self, port): # connect object return NullModem.set_connection(port, self) - def handle_new_connection(self): + def handle_new_connection(self) -> ModbusProtocol: """Handle incoming connect.""" if not self.is_server: # Clients reuse the same object. @@ -465,9 +483,10 @@ def handle_new_connection(self): new_protocol.listener = self return new_protocol - async def do_reconnect(self): + async def do_reconnect(self) -> None: """Handle reconnect as a task.""" try: + assert isinstance(self.comm_params.reconnect_delay, float) self.reconnect_delay_current = self.comm_params.reconnect_delay while True: Log.debug( @@ -478,6 +497,7 @@ async def do_reconnect(self): await asyncio.sleep(self.reconnect_delay_current) if await self.transport_connect(): break + assert isinstance(self.comm_params.reconnect_delay_max, float) self.reconnect_delay_current = min( 2 * self.reconnect_delay_current, self.comm_params.reconnect_delay_max, @@ -489,7 +509,7 @@ async def do_reconnect(self): # ----------------- # # The magic methods # # ----------------- # - async def __aenter__(self): + async def __aenter__(self) -> ModbusProtocol: """Implement the client with async enter block.""" return self @@ -512,14 +532,14 @@ class NullModem(asyncio.DatagramTransport, asyncio.Transport): listeners: dict[int, ModbusProtocol] = {} connections: dict[NullModem, int] = {} - def __init__(self, protocol: ModbusProtocol, listen: int = None) -> None: + def __init__(self, protocol: ModbusProtocol, listen: int | None = None) -> None: """Create half part of null modem""" asyncio.DatagramTransport.__init__(self) asyncio.Transport.__init__(self) self.protocol: ModbusProtocol = protocol - self.other_modem: NullModem = None + self.other_modem: NullModem | None = None self.listen = listen - self.manipulator: Callable[[bytes], list[bytes]] = None + self.manipulator: Callable[[bytes], list[bytes]] | None = None self._is_closing = False # -------------------------- # @@ -603,6 +623,7 @@ def sendto(self, data: bytes, _addr: Any = None) -> None: def write(self, data: bytes) -> None: """Send data""" + assert isinstance(self.other_modem, NullModem) if not self.manipulator: self.other_modem.protocol.data_received(data) return @@ -629,7 +650,9 @@ def get_write_buffer_limits(self) -> tuple[int, int]: """Set flush limits""" return (1, 1024) - def set_write_buffer_limits(self, high: int = None, low: int = None) -> None: + def set_write_buffer_limits( + self, high: int | None = None, low: int | None = None + ) -> None: """Set flush limits""" def write_eof(self) -> None: diff --git a/pymodbus/transport/transport_serial.py b/pymodbus/transport/transport_serial.py index f57f12df4..2f8ee2e52 100644 --- a/pymodbus/transport/transport_serial.py +++ b/pymodbus/transport/transport_serial.py @@ -14,13 +14,13 @@ class SerialTransport(asyncio.Transport): force_poll: bool = False - def __init__(self, loop, protocol, *args, **kwargs): + def __init__(self, loop, protocol, *args, **kwargs) -> None: """Initialize.""" super().__init__() self.async_loop = loop self._protocol: asyncio.BaseProtocol = protocol self.sync_serial = serial.serial_for_url(*args, **kwargs) - self._write_buffer = [] + self._write_buffer: list[bytes] = [] self.poll_task = None self._poll_wait_time = 0.0005 self.sync_serial.timeout = 0 @@ -53,13 +53,13 @@ def close(self, exc=None): with contextlib.suppress(Exception): self._protocol.connection_lost(exc) - def write(self, data): + def write(self, data) -> None: """Write some data to the transport.""" self._write_buffer.append(data) if not self.poll_task: self.async_loop.add_writer(self.sync_serial.fileno(), self._write_ready) - def flush(self): + def flush(self) -> None: """Clear output buffer and stops any more data being written""" if not self.poll_task: self.async_loop.remove_writer(self.sync_serial.fileno()) @@ -159,7 +159,9 @@ async def _polling_task(self): pass -async def create_serial_connection(loop, protocol_factory, *args, **kwargs): +async def create_serial_connection( + loop, protocol_factory, *args, **kwargs +) -> tuple[asyncio.Transport, asyncio.BaseProtocol]: """Create a connection to a new serial port instance.""" protocol = protocol_factory() transport = SerialTransport(loop, protocol, *args, **kwargs) diff --git a/pyproject.toml b/pyproject.toml index 490255f4c..8becabe4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -223,7 +223,7 @@ overgeneral-exceptions = "builtins.Exception" bad-functions = "map,input" [tool.mypy] -strict_optional = false +strict_optional = true exclude = "pymodbus/client/base.py" show_error_codes = true local_partial_types = true From 1563190abdef75550c03a8912b29e0f749ccfcec Mon Sep 17 00:00:00 2001 From: MAKOMO Date: Sat, 14 Oct 2023 17:09:35 +0200 Subject: [PATCH 2/6] make the framer argument to ModbusBaseClient mandatory to make pylint on Python 3.8 happy --- pymodbus/client/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pymodbus/client/base.py b/pymodbus/client/base.py index 96878782c..de71e11f2 100644 --- a/pymodbus/client/base.py +++ b/pymodbus/client/base.py @@ -22,7 +22,7 @@ class ModbusBaseClient(ModbusClientMixin, ModbusProtocol): **Parameters common to all clients**: - :param framer: (optional) Modbus Framer class. + :param framer: Modbus Framer class. :param timeout: (optional) Timeout for a request, in seconds. :param retries: (optional) Max number of retries per request. :param retry_on_empty: (optional) Retry on empty response. @@ -66,7 +66,7 @@ class _params: def __init__( # pylint: disable=too-many-arguments self, - framer: type[ModbusFramer] | None = None, + framer: type[ModbusFramer], timeout: float = 3, retries: int = 3, retry_on_empty: bool = False, @@ -118,7 +118,7 @@ def __init__( # pylint: disable=too-many-arguments self.slaves: list[int] = [] # Common variables. - self.framer = cast(type[ModbusFramer], framer)(ClientDecoder(), self) + self.framer = framer(ClientDecoder(), self) self.transaction = DictTransactionManager( self, retries=retries, retry_on_empty=retry_on_empty, **kwargs ) From 9128fad13d1eeac2aec878bb79b465853136a03a Mon Sep 17 00:00:00 2001 From: MAKOMO Date: Sat, 14 Oct 2023 17:26:30 +0200 Subject: [PATCH 3/6] replace list and tuple by typing.List and typing.Tuple for Python3.8 --- pymodbus/transport/transport_serial.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pymodbus/transport/transport_serial.py b/pymodbus/transport/transport_serial.py index 2f8ee2e52..1b53d319c 100644 --- a/pymodbus/transport/transport_serial.py +++ b/pymodbus/transport/transport_serial.py @@ -2,7 +2,7 @@ import asyncio import contextlib import os -from typing import Tuple +from typing import Tuple, List with contextlib.suppress(ImportError): @@ -20,7 +20,7 @@ def __init__(self, loop, protocol, *args, **kwargs) -> None: self.async_loop = loop self._protocol: asyncio.BaseProtocol = protocol self.sync_serial = serial.serial_for_url(*args, **kwargs) - self._write_buffer: list[bytes] = [] + self._write_buffer: List[bytes] = [] self.poll_task = None self._poll_wait_time = 0.0005 self.sync_serial.timeout = 0 @@ -161,7 +161,7 @@ async def _polling_task(self): async def create_serial_connection( loop, protocol_factory, *args, **kwargs -) -> tuple[asyncio.Transport, asyncio.BaseProtocol]: +) -> Tuple[asyncio.Transport, asyncio.BaseProtocol]: """Create a connection to a new serial port instance.""" protocol = protocol_factory() transport = SerialTransport(loop, protocol, *args, **kwargs) From fa7584317ee42f6ce33eb9facdec67421571e7a3 Mon Sep 17 00:00:00 2001 From: MAKOMO Date: Sat, 14 Oct 2023 17:48:14 +0200 Subject: [PATCH 4/6] ruff formatting --- pymodbus/transport/transport_serial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymodbus/transport/transport_serial.py b/pymodbus/transport/transport_serial.py index 1b53d319c..9a42fe372 100644 --- a/pymodbus/transport/transport_serial.py +++ b/pymodbus/transport/transport_serial.py @@ -2,7 +2,7 @@ import asyncio import contextlib import os -from typing import Tuple, List +from typing import List, Tuple with contextlib.suppress(ImportError): From 12882937a1691eeac31cea869c6092ca53e34130 Mon Sep 17 00:00:00 2001 From: MAKOMO Date: Sat, 14 Oct 2023 18:26:05 +0200 Subject: [PATCH 5/6] dont't exclude client/based from mypy --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8becabe4d..7c95190a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -224,7 +224,7 @@ bad-functions = "map,input" [tool.mypy] strict_optional = true -exclude = "pymodbus/client/base.py" +#exclude = "pymodbus/client/base.py" show_error_codes = true local_partial_types = true strict_equality = true From 64ac7e0c541f2b141da49ee4f232459ce0552342 Mon Sep 17 00:00:00 2001 From: MAKOMO Date: Sat, 14 Oct 2023 19:00:11 +0200 Subject: [PATCH 6/6] pymodbus/transport/serial_asyncio is gone --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7c95190a9..20a434fe4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -147,7 +147,6 @@ version = {attr = "pymodbus.__version__"} [tool.pylint.main] init-hook='import sys; sys.path.append("examples")' ignore-paths = [ - "pymodbus/transport/serial_asyncio", "doc" ] ignore-patterns = '^\.#'