Skip to content

Commit d3a619e

Browse files
committed
Bump SLMP Python to 0.1.12
1 parent 7cffb78 commit d3a619e

11 files changed

Lines changed: 71 additions & 59 deletions

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7+
## 0.1.12 - 2026-04-27
8+
9+
### Changed
10+
- Bumped the library revision for the cross-library SLMP parity release. The Python route guards from `0.1.11` are unchanged and remain aligned with the updated shared verification suite.
11+
- Kept the CLI's internal manual-profile client type-checkable under the repository mypy settings.
12+
713
## 0.1.11 - 2026-04-27
814

915
### Changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "slmp-connect-python"
7-
version = "0.1.11"
7+
version = "0.1.12"
88
description = "SLMP Connect Python: client library for Mitsubishi SLMP binary communication"
99
readme = "README.md"
1010
requires-python = ">=3.10"

slmp/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
- ``poll``
1111
"""
1212

13-
__version__ = "0.1.11"
13+
__version__ = "0.1.12"
1414

1515
from .async_client import AsyncSlmpClient
1616
from .client import SlmpClient

slmp/async_client.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,7 @@ def __init__(
133133
"plc_family is required for the standard AsyncSlmpClient route "
134134
"unless you explicitly opt into a low-level frame/profile path."
135135
)
136-
if plc_family is not None and any(
137-
value is not None for value in (plc_series, frame_type, device_family)
138-
):
136+
if plc_family is not None and any(value is not None for value in (plc_series, frame_type, device_family)):
139137
raise ValueError(
140138
"plc_family is the only supported PLC selector for the standard AsyncSlmpClient route."
141139
)

slmp/cli.py

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,40 @@
5050
from .errors import SlmpPracticalPathWarning
5151

5252

53-
def SlmpClient(*args: object, **kwargs: object) -> _StandardSlmpClient:
54-
"""Internal low-level constructor for CLI probe commands."""
55-
kwargs["_allow_manual_profile"] = True
56-
return _StandardSlmpClient(*args, **kwargs)
53+
class SlmpClient(_StandardSlmpClient):
54+
"""Internal low-level client for CLI probe commands."""
55+
56+
def __init__(
57+
self,
58+
host: str,
59+
port: int = 5000,
60+
*,
61+
transport: str = "tcp",
62+
timeout: float = 3.0,
63+
plc_family: object | None = None,
64+
plc_series: PLCSeries | str | None = None,
65+
frame_type: FrameType | str | None = None,
66+
default_target: SlmpTarget | None = None,
67+
monitoring_timer: int = 0x0010,
68+
raise_on_error: bool = True,
69+
trace_hook: Callable[[SlmpTraceFrame], None] | None = None,
70+
device_family: object | None = None,
71+
) -> None:
72+
super().__init__(
73+
host,
74+
port,
75+
transport=transport,
76+
timeout=timeout,
77+
plc_family=plc_family,
78+
plc_series=plc_series,
79+
frame_type=frame_type,
80+
default_target=default_target,
81+
monitoring_timer=monitoring_timer,
82+
raise_on_error=raise_on_error,
83+
trace_hook=trace_hook,
84+
device_family=device_family,
85+
_allow_manual_profile=True,
86+
)
5787

5888

5989
def _int_auto(text: str) -> int:
@@ -1698,7 +1728,7 @@ def _resolve_report_output(
16981728

16991729

17001730
def _probe_device_read_with_series(
1701-
client: SlmpClient,
1731+
client: _StandardSlmpClient,
17021732
*,
17031733
device: str,
17041734
points: int,
@@ -1796,7 +1826,7 @@ def _compatibility_subprobe(
17961826

17971827

17981828
def _compatibility_request_subprobe(
1799-
client: SlmpClient,
1829+
client: _StandardSlmpClient,
18001830
*,
18011831
name: str,
18021832
command: int | Command,
@@ -1812,7 +1842,7 @@ def _compatibility_request_subprobe(
18121842

18131843

18141844
def _compatibility_probe_direct_word_write_restore(
1815-
client: SlmpClient,
1845+
client: _StandardSlmpClient,
18161846
*,
18171847
device: str,
18181848
preferred_write_value: int,
@@ -1831,7 +1861,7 @@ def _compatibility_probe_direct_word_write_restore(
18311861

18321862

18331863
def _compatibility_probe_direct_bit_write_restore(
1834-
client: SlmpClient,
1864+
client: _StandardSlmpClient,
18351865
*,
18361866
device: str,
18371867
series: PLCSeries,
@@ -1846,7 +1876,7 @@ def _compatibility_probe_direct_bit_write_restore(
18461876

18471877

18481878
def _compatibility_probe_random_word_write_restore(
1849-
client: SlmpClient,
1879+
client: _StandardSlmpClient,
18501880
*,
18511881
devices: Sequence[str],
18521882
preferred_write_value: int,
@@ -1868,7 +1898,7 @@ def _compatibility_probe_random_word_write_restore(
18681898

18691899

18701900
def _compatibility_probe_random_bit_write_restore(
1871-
client: SlmpClient,
1901+
client: _StandardSlmpClient,
18721902
*,
18731903
devices: Sequence[str],
18741904
series: PLCSeries,
@@ -1890,7 +1920,7 @@ def _compatibility_probe_random_bit_write_restore(
18901920

18911921

18921922
def _compatibility_probe_block_write_restore(
1893-
client: SlmpClient,
1923+
client: _StandardSlmpClient,
18941924
*,
18951925
word_device: str | None,
18961926
bit_device: str | None,
@@ -1943,7 +1973,7 @@ def _compatibility_probe_block_write_restore(
19431973

19441974

19451975
def _compatibility_probe_memory_write_restore(
1946-
client: SlmpClient,
1976+
client: _StandardSlmpClient,
19471977
*,
19481978
head_address: int,
19491979
preferred_write_value: int,
@@ -1962,7 +1992,7 @@ def _compatibility_probe_memory_write_restore(
19621992

19631993
def _compatibility_run_command(
19641994
spec: CompatibilityCommandSpec,
1965-
client: SlmpClient,
1995+
client: _StandardSlmpClient,
19661996
*,
19671997
series: PLCSeries,
19681998
args: argparse.Namespace,
@@ -2246,7 +2276,7 @@ def _block_read_mixed_detail() -> str:
22462276
raise ValueError(f"unsupported compatibility command code: {spec.code}")
22472277

22482278

2249-
def _probe_sm400_series(client: SlmpClient) -> tuple[PLCSeries, list[bool]]:
2279+
def _probe_sm400_series(client: _StandardSlmpClient) -> tuple[PLCSeries, list[bool]]:
22502280
series, values = _probe_device_read_with_series(
22512281
client,
22522282
device="SM400",
@@ -2435,7 +2465,7 @@ def _format_manual_value(kind: str, value: int | bool) -> str:
24352465
return f"0x{int(value):04X} ({int(value)})"
24362466

24372467

2438-
def _read_manual_row_value(client: SlmpClient, row: DeviceMatrixRow, *, series: str) -> int | bool:
2468+
def _read_manual_row_value(client: _StandardSlmpClient, row: DeviceMatrixRow, *, series: str) -> int | bool:
24392469
code = row.device_code.upper()
24402470
number = parse_device(row.device).number
24412471
if code == "LTC":
@@ -2458,7 +2488,7 @@ def _read_manual_row_value(client: SlmpClient, row: DeviceMatrixRow, *, series:
24582488

24592489

24602490
def _write_manual_row_value(
2461-
client: SlmpClient,
2491+
client: _StandardSlmpClient,
24622492
row: DeviceMatrixRow,
24632493
value: int | bool,
24642494
*,
@@ -2826,7 +2856,7 @@ def _trace_hook(trace: SlmpTraceFrame) -> None:
28262856

28272857

28282858
def _run_extended_device_word_probe(
2829-
client: SlmpClient,
2859+
client: _StandardSlmpClient,
28302860
*,
28312861
item_name: str,
28322862
device: str,
@@ -2872,7 +2902,7 @@ def _run_extended_device_word_probe(
28722902

28732903

28742904
def _run_extended_device_word_span_probe(
2875-
client: SlmpClient,
2905+
client: _StandardSlmpClient,
28762906
*,
28772907
item_name: str,
28782908
device: str,
@@ -2983,7 +3013,7 @@ def _format_counter(counter: dict[str, int] | Sequence[tuple[str, int]]) -> str:
29833013

29843014

29853015
def _raw_device_read(
2986-
client: SlmpClient,
3016+
client: _StandardSlmpClient,
29873017
*,
29883018
device: str,
29893019
points: int,
@@ -3002,7 +3032,7 @@ def _raw_device_read(
30023032

30033033

30043034
def _raw_device_write(
3005-
client: SlmpClient,
3035+
client: _StandardSlmpClient,
30063036
*,
30073037
device: str,
30083038
values: Sequence[int | bool],

slmp/client.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,8 @@ def __init__(
132132
"plc_family is required for the standard SlmpClient route "
133133
"unless you explicitly opt into a low-level frame/profile path."
134134
)
135-
if plc_family is not None and any(
136-
value is not None for value in (plc_series, frame_type, device_family)
137-
):
138-
raise ValueError(
139-
"plc_family is the only supported PLC selector for the standard SlmpClient route."
140-
)
135+
if plc_family is not None and any(value is not None for value in (plc_series, frame_type, device_family)):
136+
raise ValueError("plc_family is the only supported PLC selector for the standard SlmpClient route.")
141137
(
142138
self.plc_family,
143139
self.plc_series,

slmp/core.py

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,8 +1087,7 @@ def _validate_direct_read_device(ref: DeviceRef, *, points: int, bit_unit: bool)
10871087
)
10881088
if not bit_unit and ref.code in _RANDOM_DWORD_ONLY_DIRECT_CODES:
10891089
raise ValueError(
1090-
f"Direct word read is not supported for {ref.code}. "
1091-
"Use read_typed/read_named for 32-bit access."
1090+
f"Direct word read is not supported for {ref.code}. Use read_typed/read_named for 32-bit access."
10921091
)
10931092

10941093

@@ -1100,8 +1099,7 @@ def _validate_direct_write_device(ref: DeviceRef, *, bit_unit: bool) -> None:
11001099
)
11011100
if not bit_unit and (ref.code in _LT_LST_CURRENT_CODES or ref.code in _DWORD_ONLY_DIRECT_CODES):
11021101
raise ValueError(
1103-
f"Direct word write is not supported for {ref.code}. "
1104-
"Use write_typed/write_named for 32-bit access."
1102+
f"Direct word write is not supported for {ref.code}. Use write_typed/write_named for 32-bit access."
11051103
)
11061104

11071105

@@ -1121,8 +1119,7 @@ def _validate_random_read_devices(word_refs: Sequence[DeviceRef], dword_refs: Se
11211119
)
11221120
if any(ref.code in _LC_CONTACT_CODES for ref in (*word_refs, *dword_refs)):
11231121
raise ValueError(
1124-
"Read Random (0x0403) does not support LCS/LCC. "
1125-
"Use read_typed/read_named so direct bit read is selected."
1122+
"Read Random (0x0403) does not support LCS/LCC. Use read_typed/read_named so direct bit read is selected."
11261123
)
11271124
if any(ref.code in _LT_LST_CURRENT_CODES or ref.code in _DWORD_ONLY_DIRECT_CODES for ref in word_refs):
11281125
raise ValueError(
@@ -1145,11 +1142,7 @@ def _validate_block_read_devices(
11451142
) -> None:
11461143
all_refs = tuple(ref for ref, _ in (*word_blocks, *bit_blocks))
11471144
invalid_long_block = next(
1148-
(
1149-
(ref, points)
1150-
for ref, points in word_blocks
1151-
if ref.code in _LT_LST_CURRENT_BLOCK_CODES and points % 4 != 0
1152-
),
1145+
((ref, points) for ref, points in word_blocks if ref.code in _LT_LST_CURRENT_BLOCK_CODES and points % 4 != 0),
11531146
None,
11541147
)
11551148
if invalid_long_block is not None:
@@ -1165,15 +1158,13 @@ def _validate_block_read_devices(
11651158
)
11661159
if any(ref.code in _LC_CONTACT_CODES for ref in all_refs):
11671160
raise ValueError(
1168-
"Read Block (0x0406) does not support LCS/LCC. "
1169-
"Use read_typed/read_named so direct bit read is selected."
1161+
"Read Block (0x0406) does not support LCS/LCC. Use read_typed/read_named so direct bit read is selected."
11701162
)
11711163

11721164

11731165
def _validate_block_write_devices(word_refs: Sequence[DeviceRef], bit_refs: Sequence[DeviceRef]) -> None:
11741166
if any(
1175-
ref.code in _LT_LST_CURRENT_CODES or ref.code in _DWORD_ONLY_DIRECT_CODES
1176-
for ref in (*word_refs, *bit_refs)
1167+
ref.code in _LT_LST_CURRENT_CODES or ref.code in _DWORD_ONLY_DIRECT_CODES for ref in (*word_refs, *bit_refs)
11771168
):
11781169
raise ValueError(
11791170
"Write Block (0x1406) does not support LTN/LSTN/LCN/LZ as word or bit blocks. "

slmp/utils.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,7 +1034,6 @@ async def read_words_chunked(
10341034
semantics are acceptable to the caller.
10351035
"""
10361036

1037-
10381037
effective_max = (max_per_request // 2) * 2
10391038
if effective_max <= 0:
10401039
raise ValueError("max_per_request must be at least 2")
@@ -1082,7 +1081,6 @@ async def write_words_chunked(
10821081
caller.
10831082
"""
10841083

1085-
10861084
effective_max = (max_per_request // 2) * 2
10871085
if effective_max <= 0:
10881086
raise ValueError("max_per_request must be at least 2")
@@ -1108,7 +1106,6 @@ async def write_dwords_chunked(
11081106
intact inside one request.
11091107
"""
11101108

1111-
11121109
if max_dwords_per_request <= 0:
11131110
raise ValueError("max_dwords_per_request must be at least 1")
11141111

@@ -1230,7 +1227,6 @@ def read_words_chunked_sync(
12301227
) -> list[int]:
12311228
"""Synchronously read contiguous 16-bit values across multiple aligned requests."""
12321229

1233-
12341230
effective_max = (max_per_request // 2) * 2
12351231
if effective_max <= 0:
12361232
raise ValueError("max_per_request must be at least 2")
@@ -1270,7 +1266,6 @@ def write_words_chunked_sync(
12701266
) -> None:
12711267
"""Synchronously write contiguous 16-bit values across multiple aligned requests."""
12721268

1273-
12741269
effective_max = (max_per_request // 2) * 2
12751270
if effective_max <= 0:
12761271
raise ValueError("max_per_request must be at least 2")
@@ -1292,7 +1287,6 @@ def write_dwords_chunked_sync(
12921287
) -> None:
12931288
"""Synchronously write contiguous unsigned 32-bit values across multiple aligned requests."""
12941289

1295-
12961290
if max_dwords_per_request <= 0:
12971291
raise ValueError("max_dwords_per_request must be at least 1")
12981292

tests/test_async_client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
try:
88
import pytest
99
except ModuleNotFoundError: # pragma: no cover - lets unittest discovery import this module without pytest
10+
1011
class _RaisesContext(AbstractContextManager):
1112
def __init__(self, expected_exception: type[BaseException], match: str | None = None) -> None:
1213
self._expected_exception = expected_exception

tests/test_device_vectors.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77
from slmp.constants import PLCSeries
88
from slmp.core import encode_device_spec
99

10-
_SHARED_SPEC_DIR = (
11-
Path(__file__).resolve().parents[2] / "plc-comm-slmp-cross-verify" / "specs" / "shared"
12-
)
10+
_SHARED_SPEC_DIR = Path(__file__).resolve().parents[2] / "plc-comm-slmp-cross-verify" / "specs" / "shared"
1311
_VECTORS = json.loads((_SHARED_SPEC_DIR / "device_spec_vectors.json").read_text(encoding="utf-8"))["vectors"]
1412

1513

0 commit comments

Comments
 (0)