Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eliminates implicit Optional #1817

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions pymodbus/client/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,7 +22,7 @@ class ModbusBaseClient(ModbusClientMixin, ModbusProtocol):

**Parameters common to all clients**:

:param framer: (optional) Modbus Framer class.
:param framer: Modbus Framer class.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but framer is an optional parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you read my comment 3? If framer is optional and defaults to None then it should not be called unprotected in __init__:

self.framer = framer(ClientDecoder(), self)

I don't think None can be called.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I do not read comments, I review code. All optional parameters are described as optional, and framer is and should be optional.

None is a legal value for framer at API level.

: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.
Expand Down Expand Up @@ -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],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the API, that is not something we do lightly !

I do not see the benefit of this change, please revert or explain why it is an advantage for the users.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, there is a type error in __init__ if this stays optional which should then be resolved in another way. How to initialize self.framer if the argument to framer not given and thus framer defaults to None?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is another problem, which I think is handled at lower levels in the code.

Please do not change API, because this will cause every app to be changed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from that the type error is quite easy to solve without changing the parameter itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I did not see how to resolve that easily. Here I need your support.

timeout: float = 3,
retries: int = 3,
retry_on_empty: bool = False,
Expand Down Expand Up @@ -122,10 +122,10 @@ def __init__( # pylint: disable=too-many-arguments
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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both delay_current and reconnect_delay are (or should be float) so why the cast.

cast is an ugly beast, that preferable never should be used, it typically hides a fundamental problem, likely maybe in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.params.reconnect_delay defaults to None. Maybe it shouldn't.

Copy link
Collaborator

@janiversen janiversen Oct 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it should, that is the way to tell the lower system that reconnect is not used. That could of course be done differently, but please make 1 pull request for 1 problem, in this case "typing":

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, but if self.params.reconnect_delay can be None it should not be assigned to self.reconnect_delay_current which is declared to be a float in

self.reconnect_delay_current = 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

# ----------------------------------------------------------------------- #
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -210,15 +210,15 @@ 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
"""
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:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can that really be None ? where is it called with None ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment 2

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again I review your code...All call I can see have an exception.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In transport/transport.py:transport_close there is a value.callback_disconnected(None) as I wrote in the PR comment.

"""Handle lost connection"""
for tid in list(self.transaction):
self.raise_future(
Expand Down
2 changes: 1 addition & 1 deletion pymodbus/client/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Union ?? I think we should either use "Union" or "|" not both.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use from __future__ import annotations to allow | to be used.

I will mark the other redundant comments Resolved.

) -> ModbusResponse:
"""Read FIFO queue (code 0x2B sub 0x0E).

Expand Down
1 change: 1 addition & 0 deletions pymodbus/client/serial.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ def __init__(

self.last_frame_end = None

assert isinstance(self.comm_params.baudrate, int)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No assert in production code please. Apart from that this is a strange place to assert on an internal struct...I could understand (but not accept) if you did assert on a parameter.

Copy link
Collaborator

@alexrudd2 alexrudd2 Oct 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention here was to narrow the type to an int.

However, the better solution is to find where mypy does not understand the default value (which is an int) will be used. There is probably a small bug with parameters in subclasses.

The change in transport.py ( baudrate: int | None = None) is sufficient. The assert() is totally unnecessary.

self._t0 = float(1 + bytesize + stopbits) / self.comm_params.baudrate

# Check every 4 bytes / 2 registers if the reading is ready
Expand Down
6 changes: 3 additions & 3 deletions pymodbus/client/tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
alexrudd2 marked this conversation as resolved.
Show resolved Hide resolved
**kwargs: Any,
) -> None:
"""Initialize Asyncio Modbus TCP Client."""
Expand Down Expand Up @@ -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,
alexrudd2 marked this conversation as resolved.
Show resolved Hide resolved
**kwargs: Any,
) -> None:
"""Initialize Modbus TCP Client."""
Expand Down
22 changes: 11 additions & 11 deletions pymodbus/client/tls.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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,
alexrudd2 marked this conversation as resolved.
Show resolved Hide resolved
certfile: Optional[str] = None,
keyfile: Optional[str] = None,
password: Optional[str] = None,
server_hostname: Optional[str] = None,
**kwargs: Any,
):
"""Initialize Asyncio Modbus TLS Client."""
Expand Down Expand Up @@ -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."""
Expand Down
6 changes: 3 additions & 3 deletions pymodbus/client/udp.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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."""
Expand Down
4 changes: 2 additions & 2 deletions pymodbus/datastore/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion pymodbus/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion pymodbus/repl/client/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
10 changes: 6 additions & 4 deletions pymodbus/server/async_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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."""
Expand Down
8 changes: 4 additions & 4 deletions pymodbus/server/reactive/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 4 additions & 4 deletions pymodbus/server/simulator/http_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
alexrudd2 marked this conversation as resolved.
Show resolved Hide resolved

import contextlib

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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")]
Expand Down
Loading