smpclient 4.3.0__tar.gz → 4.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. {smpclient-4.3.0 → smpclient-4.4.2}/PKG-INFO +4 -3
  2. {smpclient-4.3.0 → smpclient-4.4.2}/README.md +1 -1
  3. {smpclient-4.3.0 → smpclient-4.4.2}/pyproject.toml +3 -1
  4. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/__init__.py +24 -21
  5. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/generics.py +10 -10
  6. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/mcuboot.py +8 -2
  7. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/transport/udp.py +6 -1
  8. {smpclient-4.3.0 → smpclient-4.4.2}/LICENSE +0 -0
  9. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/exceptions.py +0 -0
  10. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/extensions/__init__.py +0 -0
  11. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/extensions/intercreate.py +0 -0
  12. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/py.typed +0 -0
  13. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/requests/__init__.py +0 -0
  14. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/requests/file_management.py +0 -0
  15. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/requests/image_management.py +0 -0
  16. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/requests/os_management.py +0 -0
  17. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/requests/settings_management.py +0 -0
  18. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/requests/shell_management.py +0 -0
  19. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/requests/statistics_management.py +0 -0
  20. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/requests/user/__init__.py +0 -0
  21. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/requests/user/intercreate.py +0 -0
  22. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/requests/zephyr_management.py +0 -0
  23. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/transport/__init__.py +0 -0
  24. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/transport/_udp_client.py +0 -0
  25. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/transport/ble.py +0 -0
  26. {smpclient-4.3.0 → smpclient-4.4.2}/smpclient/transport/serial.py +0 -0
@@ -1,18 +1,19 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: smpclient
3
- Version: 4.3.0
3
+ Version: 4.4.2
4
4
  Summary: Simple Management Protocol (SMP) Client for remotely managing MCU firmware
5
5
  Home-page: https://github.com/intercreate/smpclient
6
6
  License: Apache-2.0
7
7
  Author: J.P. Hutchins
8
8
  Author-email: jphutchins@gmail.com
9
- Requires-Python: >=3.8.1,<3.13
9
+ Requires-Python: >=3.8.1,<3.14
10
10
  Classifier: License :: OSI Approved :: Apache Software License
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.9
13
13
  Classifier: Programming Language :: Python :: 3.10
14
14
  Classifier: Programming Language :: Python :: 3.11
15
15
  Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
16
17
  Requires-Dist: async-timeout (>=4.0.3,<5.0.0) ; python_version < "3.11"
17
18
  Requires-Dist: bleak (>=0.22.1,<0.23.0)
18
19
  Requires-Dist: intelhex (>=2.3.0,<3.0.0)
@@ -73,7 +74,7 @@ An online version is generated and available [here](https://intercreate.github.i
73
74
 
74
75
  ### Install Dependencies
75
76
 
76
- - poetry: https://python-poetry.org/docs/#installation
77
+ - poetry==1.8.5: https://python-poetry.org/docs/#installation
77
78
 
78
79
  ### Create the venv
79
80
 
@@ -49,7 +49,7 @@ An online version is generated and available [here](https://intercreate.github.i
49
49
 
50
50
  ### Install Dependencies
51
51
 
52
- - poetry: https://python-poetry.org/docs/#installation
52
+ - poetry==1.8.5: https://python-poetry.org/docs/#installation
53
53
 
54
54
  ### Create the venv
55
55
 
@@ -19,7 +19,7 @@ mcuimg = "smpclient.mcuboot:mcuimg"
19
19
  source = "git-tag"
20
20
 
21
21
  [tool.poetry.dependencies]
22
- python = ">=3.8.1, <3.13"
22
+ python = ">=3.8.1, <3.14"
23
23
  pyserial = "^3.5"
24
24
  smp = "^3.1.1"
25
25
  intelhex = "^2.3.0"
@@ -45,6 +45,7 @@ mike = "^2.1.3"
45
45
  mkdocs-material = "^9.5.38"
46
46
  griffe-inherited-docstrings = "^1.0.1"
47
47
  griffe = "^1.3.1"
48
+ smp = "^3.1.1"
48
49
 
49
50
  [tool.black]
50
51
  line-length = 100
@@ -82,6 +83,7 @@ legacy_tox_ini = """
82
83
  py310
83
84
  py311
84
85
  py312
86
+ py313
85
87
 
86
88
  [testenv]
87
89
  allowlist_externals =
@@ -29,11 +29,12 @@ import asyncio
29
29
  import logging
30
30
  from hashlib import sha256
31
31
  from types import TracebackType
32
- from typing import AsyncIterator, Final, NoReturn, Tuple, Type
32
+ from typing import AsyncIterator, Final, Tuple, Type
33
33
 
34
34
  from pydantic import ValidationError
35
35
  from smp import header as smpheader
36
36
  from smp import message as smpmsg
37
+ from typing_extensions import assert_never
37
38
 
38
39
  from smpclient.exceptions import SMPBadSequence, SMPUploadError
39
40
  from smpclient.generics import SMPRequest, TEr1, TEr2, TRep, error, success
@@ -130,9 +131,11 @@ class SMPClient:
130
131
  print(f"Response: {response=}")
131
132
  elif error(response):
132
133
  print(f"Error: {response=}")
134
+ else:
135
+ assert_never(response)
133
136
  ```
134
137
 
135
- Type Safety with Generic Typing:
138
+ Type Safety and Exhaustiveness with Generic Typing:
136
139
 
137
140
  ```python
138
141
  response = await client.request(EchoWrite(d="Hello, World!"))
@@ -147,9 +150,13 @@ class SMPClient:
147
150
  if error_v1(response):
148
151
  reveal_type(response)
149
152
  # Revealed type is 'EchoWriteErrorV1'
150
- else:
153
+ elif error_v2(response):
151
154
  reveal_type(response)
152
155
  # Revealed type is 'EchoWriteErrorV2'
156
+ else:
157
+ assert_never(response)
158
+ else:
159
+ assert_never(response)
153
160
  ```
154
161
 
155
162
  """
@@ -244,8 +251,8 @@ class SMPClient:
244
251
  if response.off is None:
245
252
  raise SMPUploadError(f"No offset received: {response=}")
246
253
  yield response.off
247
- else: # pragma: no cover
248
- _unreachable()
254
+ else:
255
+ assert_never(response) # pragma: no cover
249
256
 
250
257
  # send chunks until the SMP server reports that the offset is at the end of the image
251
258
  while response.off != len(image):
@@ -268,8 +275,8 @@ class SMPClient:
268
275
  if response.off is None:
269
276
  raise SMPUploadError(f"No offset received: {response=}")
270
277
  yield response.off
271
- else: # pragma: no cover
272
- _unreachable()
278
+ else:
279
+ assert_never(response) # pragma: no cover
273
280
 
274
281
  logger.info("Upload complete")
275
282
 
@@ -313,8 +320,8 @@ class SMPClient:
313
320
  if response.off is None:
314
321
  raise SMPUploadError(f"No offset received: {response=}")
315
322
  yield response.off
316
- else: # pragma: no cover
317
- _unreachable()
323
+ else:
324
+ assert_never(response) # pragma: no cover
318
325
 
319
326
  # send chunks until the SMP server reports that the offset is at the end of the image
320
327
  while response.off != len(file_data):
@@ -328,8 +335,8 @@ class SMPClient:
328
335
  raise SMPUploadError(response)
329
336
  elif success(response):
330
337
  yield response.off
331
- else: # pragma: no cover
332
- _unreachable()
338
+ else:
339
+ assert_never(response) # pragma: no cover
333
340
 
334
341
  logger.info("Upload complete")
335
342
 
@@ -359,8 +366,8 @@ class SMPClient:
359
366
  if response.len is None:
360
367
  raise SMPUploadError(f"No length received: {response=}")
361
368
  file_length = response.len
362
- else: # pragma: no cover
363
- _unreachable()
369
+ else:
370
+ assert_never(response) # pragma: no cover
364
371
 
365
372
  file_data = response.data
366
373
 
@@ -374,8 +381,8 @@ class SMPClient:
374
381
  raise SMPUploadError(response)
375
382
  elif success(response):
376
383
  file_data += response.data
377
- else: # pragma: no cover
378
- _unreachable()
384
+ else:
385
+ assert_never(response) # pragma: no cover
379
386
 
380
387
  logger.info("Download complete")
381
388
  return file_data
@@ -407,7 +414,7 @@ class SMPClient:
407
414
  # If the integer is less than 24, then the size is encoded in the same
408
415
  # byte as the value.
409
416
  # https://datatracker.ietf.org/doc/html/rfc8949#name-core-deterministic-encoding
410
- return 0 if integer < 24 else 1 if integer < 0xFF else 2 if integer < 0xFFFF else 4
417
+ return 0 if integer < 24 else 1 if integer <= 0xFF else 2 if integer <= 0xFFFF else 4
411
418
 
412
419
  def _get_max_cbor_and_data_size(self, request: smpmsg.WriteRequest) -> Tuple[int, int]:
413
420
  """Given an `ImageUploadWrite`, return the maximum CBOR size and data size."""
@@ -494,10 +501,6 @@ class SMPClient:
494
501
  elif error(mcumgr_parameters):
495
502
  logger.warning(f"Error reading MCUMgr parameters: {mcumgr_parameters}")
496
503
  else:
497
- _unreachable()
504
+ assert_never(mcumgr_parameters)
498
505
  except asyncio.TimeoutError:
499
506
  logger.warning("Timeout waiting for MCUMgr parameters")
500
-
501
-
502
- def _unreachable() -> NoReturn:
503
- raise Exception("Unreachable")
@@ -1,4 +1,4 @@
1
- """Generics and TypeGuards for SMP Requests and Responses."""
1
+ """Generics and Type Narrowing for SMP Requests and Responses."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -7,7 +7,7 @@ from typing import Protocol, Type, TypeVar, Union
7
7
  from smp import error as smperror
8
8
  from smp import header as smphdr
9
9
  from smp import message as smpmessage
10
- from typing_extensions import TypeGuard
10
+ from typing_extensions import TypeIs
11
11
 
12
12
  TEr1 = TypeVar("TEr1", bound=smperror.ErrorV1)
13
13
  """Type of SMP Error V1."""
@@ -47,8 +47,8 @@ class SMPRequest(Protocol[TRep, TEr1, TEr2]):
47
47
  ...
48
48
 
49
49
 
50
- def error_v1(response: smperror.ErrorV1 | TEr2 | TRep) -> TypeGuard[smperror.ErrorV1]:
51
- """`TypeGuard` that returns `True` if the `response` is an `ErrorV1`.
50
+ def error_v1(response: smperror.ErrorV1 | TEr2 | TRep) -> TypeIs[smperror.ErrorV1]:
51
+ """`TypeIs` that returns `True` if the `response` is an `ErrorV1`.
52
52
 
53
53
  Args:
54
54
  response: The response to check.
@@ -59,8 +59,8 @@ def error_v1(response: smperror.ErrorV1 | TEr2 | TRep) -> TypeGuard[smperror.Err
59
59
  return response.RESPONSE_TYPE == smpmessage.ResponseType.ERROR_V1
60
60
 
61
61
 
62
- def error_v2(response: smperror.ErrorV1 | TEr2 | TRep) -> TypeGuard[TEr2]:
63
- """`TypeGuard` that returns `True` if the `response` is an `ErrorV2`.
62
+ def error_v2(response: smperror.ErrorV1 | TEr2 | TRep) -> TypeIs[TEr2]:
63
+ """`TypeIs` that returns `True` if the `response` is an `ErrorV2`.
64
64
 
65
65
  Args:
66
66
  response: The response to check.
@@ -71,8 +71,8 @@ def error_v2(response: smperror.ErrorV1 | TEr2 | TRep) -> TypeGuard[TEr2]:
71
71
  return response.RESPONSE_TYPE == smpmessage.ResponseType.ERROR_V2
72
72
 
73
73
 
74
- def error(response: smperror.ErrorV1 | TEr2 | TRep) -> TypeGuard[smperror.ErrorV1 | TEr2]:
75
- """`TypeGuard` that returns `True` if the `response` is an `ErrorV1` or `ErrorV2`.
74
+ def error(response: smperror.ErrorV1 | TEr2 | TRep) -> TypeIs[smperror.ErrorV1 | TEr2]:
75
+ """`TypeIs` that returns `True` if the `response` is an `ErrorV1` or `ErrorV2`.
76
76
 
77
77
  Args:
78
78
  response: The response to check.
@@ -83,8 +83,8 @@ def error(response: smperror.ErrorV1 | TEr2 | TRep) -> TypeGuard[smperror.ErrorV
83
83
  return error_v1(response) or error_v2(response)
84
84
 
85
85
 
86
- def success(response: smperror.ErrorV1 | TEr2 | TRep) -> TypeGuard[TRep]:
87
- """`TypeGuard` that returns `True` if the `response` is a successful `Response`.
86
+ def success(response: smperror.ErrorV1 | TEr2 | TRep) -> TypeIs[TRep]:
87
+ """`TypeIs` that returns `True` if the `response` is a successful `Response`.
88
88
 
89
89
  Args:
90
90
  response: The response to check.
@@ -1,4 +1,4 @@
1
- """Tools for inspecting firmware images.
1
+ """Tools for inspecting MCUBoot compatible firmware images.
2
2
 
3
3
  Specification: https://docs.mcuboot.com/design.html
4
4
  """
@@ -101,6 +101,7 @@ class ImageVersion:
101
101
 
102
102
  @staticmethod
103
103
  def loads(data: bytes) -> 'ImageVersion':
104
+ """Load an `ImageVersion` from `bytes`."""
104
105
  return ImageVersion(*IMAGE_VERSION_STRUCT.unpack(data))
105
106
 
106
107
  def __str__(self) -> str:
@@ -148,10 +149,12 @@ class ImageHeader:
148
149
 
149
150
  @staticmethod
150
151
  def load_from(file: BytesIO | BufferedReader) -> 'ImageHeader':
152
+ """Load an `ImageHeader` from an open file."""
151
153
  return ImageHeader.loads(file.read(IMAGE_HEADER_STRUCT.size))
152
154
 
153
155
  @staticmethod
154
156
  def load_file(path: str) -> 'ImageHeader':
157
+ """Load an `ImageHeader` the file at `path`."""
155
158
  with open(path, 'rb') as f:
156
159
  return ImageHeader.load_from(f)
157
160
 
@@ -178,6 +181,7 @@ class ImageTLVInfo:
178
181
 
179
182
  @staticmethod
180
183
  def load_from(file: BytesIO | BufferedReader) -> 'ImageTLVInfo':
184
+ """Load an `ImageTLVInfo` from a file."""
181
185
  return ImageTLVInfo.loads(file.read(IMAGE_TLV_INFO_STRUCT.size))
182
186
 
183
187
 
@@ -191,6 +195,7 @@ class ImageTLV:
191
195
 
192
196
  @staticmethod
193
197
  def load_from(file: BytesIO | BufferedReader) -> 'ImageTLV':
198
+ """Load an `ImageTLV` from a file."""
194
199
  return ImageTLV(*IMAGE_TLV_STRUCT.unpack_from(file.read(IMAGE_TLV_STRUCT.size)))
195
200
 
196
201
 
@@ -225,6 +230,7 @@ class ImageInfo:
225
230
 
226
231
  @staticmethod
227
232
  def load_file(path: str) -> 'ImageInfo':
233
+ """Load MCUBoot `ImageInfo` from the .bin or .hex file at `path`."""
228
234
  file_path = pathlib.Path(path)
229
235
  if file_path.suffix not in {".bin", ".hex"}:
230
236
  raise MCUBootImageError(
@@ -279,7 +285,7 @@ def mcuimg() -> int:
279
285
  prog="mcuimg",
280
286
  description=(
281
287
  "Inspect an MCUBoot compatible firmware update image."
282
- "\nCopyright (C) 2023 Intercreate, Inc. | github.com/intercreate/smpclient"
288
+ "\nCopyright (C) 2023-2024 Intercreate, Inc. | github.com/intercreate/smpclient"
283
289
  ),
284
290
  formatter_class=argparse.RawDescriptionHelpFormatter,
285
291
  )
@@ -99,4 +99,9 @@ class SMPUDPTransport(SMPTransport):
99
99
  @override
100
100
  @property
101
101
  def mtu(self) -> int:
102
- return self._smp_server_transport_buffer_size or self._mtu
102
+ return self._mtu
103
+
104
+ @override
105
+ @property
106
+ def max_unencoded_size(self) -> int:
107
+ return self._mtu
File without changes
File without changes