runtimepy 5.14.0__py3-none-any.whl → 5.14.2__py3-none-any.whl

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.
runtimepy/__init__.py CHANGED
@@ -1,7 +1,7 @@
1
1
  # =====================================
2
2
  # generator=datazen
3
3
  # version=3.2.3
4
- # hash=c6b2bd8ab0196f119c02bcdf6807ecab
4
+ # hash=f2469e97ed5a3a48c5347357f12c3554
5
5
  # =====================================
6
6
 
7
7
  """
@@ -10,7 +10,7 @@ Useful defaults and other package metadata.
10
10
 
11
11
  DESCRIPTION = "A framework for implementing Python services."
12
12
  PKG_NAME = "runtimepy"
13
- VERSION = "5.14.0"
13
+ VERSION = "5.14.2"
14
14
 
15
15
  # runtimepy-specific content.
16
16
  METRICS_NAME = "metrics"
@@ -5,7 +5,7 @@ A module implementing an interface for receiving struct messages.
5
5
  # built-in
6
6
  from io import BytesIO
7
7
  import os
8
- from typing import Callable
8
+ from typing import Callable, Optional
9
9
 
10
10
  # third-party
11
11
  from vcorelib.logging import LoggerMixin
@@ -16,11 +16,15 @@ from runtimepy.primitives.byte_order import ByteOrder
16
16
  from runtimepy.primitives.int import UnsignedInt
17
17
 
18
18
  StructHandler = Callable[[Protocol], None]
19
+ NonStructHandler = Callable[[BytesIO], bool]
20
+
21
+ NON_STRUCT_ID = 0
19
22
 
20
23
 
21
24
  class StructReceiver(LoggerMixin):
22
25
  """A class for sending and receiving struct messages."""
23
26
 
27
+ non_struct_message_prefix: bytes
24
28
  id_primitive: UnsignedInt
25
29
  byte_order: ByteOrder
26
30
 
@@ -29,15 +33,22 @@ class StructReceiver(LoggerMixin):
29
33
 
30
34
  super().__init__()
31
35
 
36
+ self.non_struct_handler: Optional[NonStructHandler] = None
32
37
  self.handlers: dict[int, StructHandler] = {}
33
38
  self.instances: dict[int, Protocol] = {}
34
39
  for factory in factories:
35
40
  self.register(factory)
36
41
 
42
+ def add_non_struct_handler(self, handler: NonStructHandler) -> None:
43
+ """Set the non-struct handler for this instance."""
44
+ assert self.non_struct_handler is None
45
+ self.non_struct_handler = handler
46
+
37
47
  def add_handler(self, identifier: int, handler: StructHandler) -> None:
38
48
  """Add a struct message handler."""
39
49
 
40
50
  assert identifier not in self.handlers
51
+ assert identifier != NON_STRUCT_ID
41
52
  self.handlers[identifier] = handler
42
53
 
43
54
  def register(self, factory: type[ProtocolFactory]) -> None:
@@ -45,9 +56,14 @@ class StructReceiver(LoggerMixin):
45
56
 
46
57
  inst = factory.singleton()
47
58
 
59
+ assert inst.id != NON_STRUCT_ID
60
+
48
61
  if not hasattr(self, "id_primitive"):
49
62
  self.id_primitive = inst.id_primitive.copy() # type: ignore
50
63
  self.byte_order = inst.byte_order
64
+ self.non_struct_message_prefix = self.id_primitive.kind.encode(
65
+ NON_STRUCT_ID, byte_order=self.byte_order
66
+ )
51
67
  else:
52
68
  assert self.id_primitive.kind == inst.id_primitive.kind
53
69
  assert self.byte_order == inst.byte_order
@@ -67,7 +83,23 @@ class StructReceiver(LoggerMixin):
67
83
  ident = self.id_primitive.from_stream(
68
84
  stream, byte_order=self.byte_order
69
85
  )
70
- if ident in self.instances:
86
+
87
+ # Handle non-struct messages.
88
+ if ident == NON_STRUCT_ID:
89
+ if self.non_struct_handler is not None:
90
+ if not self.non_struct_handler(stream):
91
+ self.logger.error(
92
+ "Parsing non-struct message failed."
93
+ )
94
+ stream.seek(0, os.SEEK_END)
95
+ else:
96
+ self.logger.error(
97
+ "No handler for non-struct messages."
98
+ )
99
+ stream.seek(0, os.SEEK_END)
100
+
101
+ # Handle struct messages.
102
+ elif ident in self.instances:
71
103
  inst = self.instances[ident]
72
104
  inst.from_stream(stream)
73
105
  if ident in self.handlers:
@@ -81,11 +113,5 @@ class StructReceiver(LoggerMixin):
81
113
 
82
114
  # Can't continue reading if we don't know this identifier.
83
115
  else:
84
- self.logger.error(
85
- "Unknown struct identifier '%d' "
86
- "@%d/%d of stream (aborting).",
87
- ident,
88
- stream.tell(),
89
- end_pos,
90
- )
116
+ self.logger.error("Unknown struct identifier '%d'.", ident)
91
117
  stream.seek(0, os.SEEK_END)
@@ -41,11 +41,12 @@ def app(args: _Namespace) -> int:
41
41
  if args.init_only:
42
42
  stop_sig.set()
43
43
 
44
- return _run_handle_stop(
44
+ result = _run_handle_stop(
45
45
  stop_sig,
46
46
  entry(stop_sig, args, window=args.window),
47
47
  enable_uvloop=not getattr(args, "no_uvloop", False),
48
48
  )
49
+ return result if result is not None else 1
49
50
 
50
51
 
51
52
  def arbiter_cmd(args: _Namespace) -> int:
@@ -42,9 +42,10 @@ def tftp_cmd(args: argparse.Namespace) -> int:
42
42
  else:
43
43
  task = tftp_write(addr, args.our_file, args.their_file, **kwargs)
44
44
 
45
- return run_handle_stop(
45
+ result = run_handle_stop(
46
46
  stop_sig, task, enable_uvloop=not getattr(args, "no_uvloop", False)
47
47
  )
48
+ return result if result is not None else 1
48
49
 
49
50
 
50
51
  def add_tftp_cmd(parser: argparse.ArgumentParser) -> CommandFunction:
@@ -470,7 +470,7 @@ class BaseConnectionArbiter(_NamespaceMixin, _LoggerMixin, TuiMixin):
470
470
  ) -> int:
471
471
  """Run the application until the stop signal is set."""
472
472
 
473
- return run_handle_stop(
473
+ result = run_handle_stop(
474
474
  self.stop_sig,
475
475
  self.app(
476
476
  app=app, check_connections=check_connections, config=config
@@ -479,3 +479,4 @@ class BaseConnectionArbiter(_NamespaceMixin, _LoggerMixin, TuiMixin):
479
479
  signals=signals,
480
480
  enable_uvloop=enable_uvloop,
481
481
  )
482
+ return result if result is not None else 1
@@ -93,7 +93,12 @@ class Primitive(_Generic[T]):
93
93
 
94
94
  def __copy__(self) -> "Primitive[T]":
95
95
  """Make a copy of this primitive."""
96
- return type(self)(value=self.value)
96
+
97
+ return type(self)(
98
+ value=self.value,
99
+ scaling=self.scaling,
100
+ time_source=self.time_source,
101
+ )
97
102
 
98
103
  def copy(self) -> "Primitive[T]":
99
104
  """A simple wrapper for copy."""
@@ -1,5 +1,5 @@
1
1
  aiofiles
2
- vcorelib>=3.5.8
2
+ vcorelib>=3.6.0
3
3
  svgen>=0.7.12
4
4
  websockets
5
5
  psutil
@@ -5,6 +5,7 @@ A module implementing a periodic-task manager.
5
5
  # built-in
6
6
  import asyncio as _asyncio
7
7
  from contextlib import asynccontextmanager as _asynccontextmanager
8
+ from contextlib import suppress as _suppress
8
9
  from typing import AsyncIterator as _AsyncIterator
9
10
  from typing import Generic as _Generic
10
11
  from typing import Iterator as _Iterator
@@ -57,8 +58,24 @@ class PeriodicTaskManager(_Generic[T]):
57
58
  ) -> _AsyncIterator[None]:
58
59
  """Run tasks as an async context."""
59
60
 
61
+ task = None
62
+ if stop_sig is not None:
63
+
64
+ async def stopper() -> None:
65
+ """Stop tasks when stop signal is set."""
66
+
67
+ with _suppress(_asyncio.CancelledError):
68
+ await stop_sig.wait()
69
+ await self.stop()
70
+
71
+ task = _asyncio.get_running_loop().create_task(stopper())
72
+
60
73
  await self.start(stop_sig=stop_sig)
61
74
  try:
62
75
  yield
63
76
  finally:
64
77
  await self.stop()
78
+
79
+ if task is not None:
80
+ task.cancel()
81
+ await task
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: runtimepy
3
- Version: 5.14.0
3
+ Version: 5.14.2
4
4
  Summary: A framework for implementing Python services.
5
5
  Home-page: https://github.com/libre-embedded/runtimepy
6
6
  Author: Libre Embedded
@@ -17,11 +17,11 @@ Classifier: Development Status :: 5 - Production/Stable
17
17
  Requires-Python: >=3.12
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
- Requires-Dist: vcorelib>=3.5.8
21
- Requires-Dist: psutil
22
- Requires-Dist: websockets
23
20
  Requires-Dist: aiofiles
24
21
  Requires-Dist: svgen>=0.7.12
22
+ Requires-Dist: vcorelib>=3.6.0
23
+ Requires-Dist: websockets
24
+ Requires-Dist: psutil
25
25
  Provides-Extra: test
26
26
  Requires-Dist: pylint; extra == "test"
27
27
  Requires-Dist: flake8; extra == "test"
@@ -51,11 +51,11 @@ Dynamic: requires-python
51
51
  =====================================
52
52
  generator=datazen
53
53
  version=3.2.3
54
- hash=f46a9eb2c8979cc9ff0a3444077956ec
54
+ hash=d3dcde1ba35cb14decfc6881b6a2d4e6
55
55
  =====================================
56
56
  -->
57
57
 
58
- # runtimepy ([5.14.0](https://pypi.org/project/runtimepy/))
58
+ # runtimepy ([5.14.2](https://pypi.org/project/runtimepy/))
59
59
 
60
60
  [![python](https://img.shields.io/pypi/pyversions/runtimepy.svg)](https://pypi.org/project/runtimepy/)
61
61
  ![Build Status](https://github.com/libre-embedded/runtimepy/workflows/Python%20Package/badge.svg)
@@ -1,11 +1,11 @@
1
- runtimepy/__init__.py,sha256=QTcIVQhNPp8R5ZdjWYD99HzoCmAlfUyTO-OLPwSonys,391
1
+ runtimepy/__init__.py,sha256=mXM-PK0XW-WTNeNncfVfDdlF2hhVzUyeG01xGugXoAw,391
2
2
  runtimepy/__main__.py,sha256=IKioH2xOtsXwrwb9zABDQEJvuAX--Lnh84TeSz0XSs0,332
3
3
  runtimepy/app.py,sha256=Er1ZKKrG9U0FV0gQg_GYF9xDb89HgYnVzS5SjxGa2Tg,970
4
4
  runtimepy/dev_requirements.txt,sha256=VZhW6bJ5YbwaoN4d_XxZFuN5BbDLaG7ngKrGnugVPRw,245
5
5
  runtimepy/entry.py,sha256=fy9irGEgyFAsu868bOymHX8I3CkkrMNThUWAm-4chiM,1930
6
6
  runtimepy/mapping.py,sha256=VQK1vzmQVvYYKI85_II37-hIEbvgL3PzNy-WI6TTo80,5091
7
7
  runtimepy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- runtimepy/requirements.txt,sha256=uc8eW9HzoBsDmspEvyDKIs-xGrWs_UUltwAQCk072PU,125
8
+ runtimepy/requirements.txt,sha256=F4rTeIfG1iYsWFG6szk162sOlN-71HpKtXuqBjIGFFs,125
9
9
  runtimepy/schemas.py,sha256=zTgxPm9DHZ0R_bmmOjNQMTXdtM_Hb1bE-Fog40jDCgg,839
10
10
  runtimepy/util.py,sha256=ZHSucNi-gbrcajoCv2dNjQs48dJPC3mTM_wZHx7AW1U,1719
11
11
  runtimepy/channel/__init__.py,sha256=pf0RJ5g37_FVV8xoUNgzFGuIfbZEYSBA_cQlJSDTPDo,4774
@@ -27,16 +27,16 @@ runtimepy/codec/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  runtimepy/codec/protocol/__init__.py,sha256=0mdPIb5mpkqofClqW23kslsgc3V_aSKJ1RmjEHmKfeI,1673
28
28
  runtimepy/codec/protocol/base.py,sha256=8pt8va16zHcHIgFTyhfx6tgDcsQxbqL93CfSgq0ZTBY,11796
29
29
  runtimepy/codec/protocol/json.py,sha256=pDEpHYFNYjiU2whvQemdZmH9Igx7aLEYCoS_Xpnqdcs,4483
30
- runtimepy/codec/protocol/receiver.py,sha256=67fH90YY3bgMzF-aqsyUvpIF77qxaMX3NJVwCC4BgKc,2976
30
+ runtimepy/codec/protocol/receiver.py,sha256=RGfpTNiFB_ZtDXtnMYSQ6oO3HiIJKcwCSwoIgvtMsbY,4085
31
31
  runtimepy/codec/system/__init__.py,sha256=fIOUo7QhwI81YAIz9myeSo1oo94De41sK5HKJ-sAdfY,7959
32
32
  runtimepy/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  runtimepy/commands/all.py,sha256=eAn52vuAgOh0jr93LROfbTjSPuJILGFRCPB9UeS3Q1c,1537
34
- runtimepy/commands/arbiter.py,sha256=CtTMRYpqCAN3vWHkkr9jqWpoF7JGNXafKIBFmkarAfc,1567
34
+ runtimepy/commands/arbiter.py,sha256=9xcKnI1Sogmmv8J6FnjuY3zIEgSacmD7pZgAStuWTcc,1616
35
35
  runtimepy/commands/common.py,sha256=NvZdeIFBHAF52c1n7vqD59DW6ywc-rG5iC5MpuhGf-c,2449
36
36
  runtimepy/commands/mtu.py,sha256=LFFjTU4SsuV3j7Mhx_WuKa5lfdfMm70zJvDWToVrP7E,1357
37
37
  runtimepy/commands/server.py,sha256=q49WlQQWYBcHrKQo7hCc3Y6hNbG2DNLMXIlLg8Mc8lA,4655
38
38
  runtimepy/commands/task.py,sha256=6xRVlRwpEZVhrcY18sQcfdWEOxeQZLeOF-6UrUURtO4,1435
39
- runtimepy/commands/tftp.py,sha256=djFQzYDxy2jvUseHJ4fDR3CowPxQ-Tu0IJz1SwapXX0,2361
39
+ runtimepy/commands/tftp.py,sha256=gxkPryKB0ZEZeOPosHM-GSoWIItpWZBu5S6tW42bfK8,2410
40
40
  runtimepy/commands/tui.py,sha256=9hWA3_YATibUUDTVQr7UnKzPTDVJ7WxWKTYYQpLoyrE,1869
41
41
  runtimepy/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
42
  runtimepy/control/source.py,sha256=nW3Q2D-LcekII7K5XKbxXCcR-9jYQyvv0UypeNy1Dnw,1695
@@ -164,7 +164,7 @@ runtimepy/net/ssl.py,sha256=dj9uECPKDT5k-5vlR5I3Z7Go3WWZhbaJ9nb0rC3kJvg,854
164
164
  runtimepy/net/util.py,sha256=XTQQ-Ql_ImhVd1_O8nSeDX9MY8xJwRBggvliSLCrsc8,5913
165
165
  runtimepy/net/apps/__init__.py,sha256=vjo7e19QXtJwe6V6B-QGvYiJveYobnYIfpkKZrnS17w,710
166
166
  runtimepy/net/arbiter/__init__.py,sha256=ptKF995rYKvkm4Mya92vA5QEDqcFq5NRD0IYGqZ6_do,740
167
- runtimepy/net/arbiter/base.py,sha256=hggbHBWkuJaEkBTenlI2eCtTywhicvUpAIeI-PPYUkY,14958
167
+ runtimepy/net/arbiter/base.py,sha256=q9mQ3vjVjlzjGrDcTq8gengsEV2AnCUx8IK_40h6uxg,15011
168
168
  runtimepy/net/arbiter/info.py,sha256=aMo4LvRAO7_4x1ff13yZCiI5qOUdzAk1yZZAbrQIOAc,9734
169
169
  runtimepy/net/arbiter/result.py,sha256=PHZo5qj4SI08ZeWPFk_8OZ1umI6L0dp5nJpjjS8QUpM,2871
170
170
  runtimepy/net/arbiter/task.py,sha256=APcc5QioAG8uueIzxJU-vktIn8Ys3yJd_CFlRWb6Ieo,795
@@ -248,7 +248,7 @@ runtimepy/net/websocket/__init__.py,sha256=YjSmoxiigmsI_hcQw6nueX7bxhrRGerEERnPv
248
248
  runtimepy/net/websocket/connection.py,sha256=BMR58bLpHuulCdbLGnmMdFJOF53wVxYcUe52WbdzKEM,9025
249
249
  runtimepy/noise/__init__.py,sha256=EJM7h3t_z74wwrn6FAFQwYE2yUcOZQ1K1IQqOb8Z0AI,384
250
250
  runtimepy/primitives/__init__.py,sha256=g5IK6e_UZuGhj9QNYoCn5TbhNatWX0nEYUs-JzsOizQ,2618
251
- runtimepy/primitives/base.py,sha256=BaGPUTeVMnLnTPcpjqnS2lzPN74Pe5C0XaQdgrTfW7A,9185
251
+ runtimepy/primitives/base.py,sha256=5bMNHA9LA10qg8Qpg1YDjkfRZK0XWyaQsTNkNzaR9Es,9285
252
252
  runtimepy/primitives/bool.py,sha256=lATPgb1e62rjLn5XlJX8lP3tVYR3DlxV8RT9HpOMdT0,1640
253
253
  runtimepy/primitives/byte_order.py,sha256=J2Pg3gXg8Lmu_uU2mduNJt3mnP_enwDI4Y-X8kWAUP0,1456
254
254
  runtimepy/primitives/evaluation.py,sha256=0N7mT8uoiJaY-coF2PeEXU2WO-FmbyN2Io9_EaghO9Q,4657
@@ -289,7 +289,7 @@ runtimepy/task/__init__.py,sha256=euLIxCl1wve-4Nr2DUms8XrWT3liUV-ihQiQlZ21nqI,34
289
289
  runtimepy/task/asynchronous.py,sha256=w4oEUCyj-X-zDVFmlsAtRL1gv66ahQ78QKE0GmLGT1w,5947
290
290
  runtimepy/task/sample.py,sha256=_nbLj5Julwa1NJC_-WQsotI0890G-TlOWftVHEKiclY,2735
291
291
  runtimepy/task/basic/__init__.py,sha256=NryfG-JmSyqYh-TNG4NLO6-9RJFVq5vn1Z-oV3qTVvg,243
292
- runtimepy/task/basic/manager.py,sha256=2P_swKZlFfPHCmX6sMVGSYePWEZOqXp9B4YPyRYSGCo,1858
292
+ runtimepy/task/basic/manager.py,sha256=4dd2k-azYfyKS6jiCK9MCL-yHomuq32maicpjZvrOjA,2357
293
293
  runtimepy/task/basic/periodic.py,sha256=81ACFNzttJh2PQryHUA8La9MJvDUOSTxfiXkqtaOK4M,7595
294
294
  runtimepy/task/trig/__init__.py,sha256=GuGNb9eLuFEnl3nAI7Mi_eS5mqJ4MOhEy2Wfv48RFic,785
295
295
  runtimepy/telemetry/__init__.py,sha256=G_JLZsp0EZMGaxSQ12fYgbG2kN4QkvVG4lExV_TzEhM,268
@@ -302,9 +302,9 @@ runtimepy/tui/task.py,sha256=nUZo9fuOC-k1Wpqdzkv9v1tQirCI28fZVgcC13Ijvus,1093
302
302
  runtimepy/tui/channels/__init__.py,sha256=evDaiIn-YS9uGhdo8ZGtP9VK1ek6sr_P1nJ9JuSET0o,4536
303
303
  runtimepy/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
304
304
  runtimepy/ui/controls.py,sha256=yvT7h3thbYaitsakcIAJ90EwKzJ4b-jnc6p3UuVf_XE,1241
305
- runtimepy-5.14.0.dist-info/licenses/LICENSE,sha256=yKBRwbO-cOPBrlpsZmJkkSa33DfY31aE8t7lZ0DwlUo,1071
306
- runtimepy-5.14.0.dist-info/METADATA,sha256=7g1Ho2eto4w_nLS1GXjqyuHOlAZYXvFHXeYoOq8s194,9269
307
- runtimepy-5.14.0.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
308
- runtimepy-5.14.0.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
309
- runtimepy-5.14.0.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
310
- runtimepy-5.14.0.dist-info/RECORD,,
305
+ runtimepy-5.14.2.dist-info/licenses/LICENSE,sha256=yKBRwbO-cOPBrlpsZmJkkSa33DfY31aE8t7lZ0DwlUo,1071
306
+ runtimepy-5.14.2.dist-info/METADATA,sha256=X_vqvOJe5dbwhTksgXV9TRN9hJLZkLQ9DXUiJ2MO7po,9269
307
+ runtimepy-5.14.2.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
308
+ runtimepy-5.14.2.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
309
+ runtimepy-5.14.2.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
310
+ runtimepy-5.14.2.dist-info/RECORD,,