runtimepy 5.9.2__py3-none-any.whl → 5.10.0__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.1.4
4
- # hash=ba2f544d63fad56dd7605cf937fe52d8
4
+ # hash=a9a53fbeb974140d3611fecadbce5da4
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.9.2"
13
+ VERSION = "5.10.0"
14
14
 
15
15
  # runtimepy-specific content.
16
16
  METRICS_NAME = "metrics"
@@ -98,10 +98,9 @@ processes:
98
98
 
99
99
  # The peer itself runs an arbiter process.
100
100
  config:
101
+ config: *cfg
101
102
  includes:
102
103
  - package://runtimepy/server.yaml
103
-
104
104
  app: runtimepy.sample.program.run
105
- config: *cfg
106
105
 
107
106
  program: runtimepy.sample.program.SampleProgram
@@ -45,6 +45,7 @@ factories:
45
45
 
46
46
  # Useful subprocess peer interfaces.
47
47
  - {name: runtimepy.sample.peer.SamplePeer}
48
+ - {name: runtimepy.subprocess.peer.RuntimepyPeer}
48
49
 
49
50
  ports:
50
51
  # Reserve ports for JSON listeners.
@@ -5,8 +5,7 @@ includes:
5
5
  - has_config.yaml
6
6
  - has_markdown.yaml
7
7
 
8
- required: [program]
9
-
10
8
  properties:
11
9
  program:
12
10
  type: string
11
+ default: runtimepy.subprocess.program.PeerProgram
@@ -4,7 +4,7 @@ A module implementing a message-stream processing interface.
4
4
 
5
5
  # built-in
6
6
  from io import BytesIO as _BytesIO
7
- from json import dumps, loads
7
+ from json import JSONEncoder, dumps, loads
8
8
  from typing import Any
9
9
  from typing import Iterator as _Iterator
10
10
 
@@ -18,6 +18,18 @@ from runtimepy.primitives.byte_order import DEFAULT_BYTE_ORDER, ByteOrder
18
18
  JsonMessage = dict[str, Any]
19
19
 
20
20
 
21
+ class StrFallbackJSONEncoder(JSONEncoder):
22
+ """Custom JSON encoder."""
23
+
24
+ def default(self, o):
25
+ """Use a string conversion if necessary."""
26
+
27
+ try:
28
+ return super().default(o)
29
+ except TypeError:
30
+ return str(o)
31
+
32
+
21
33
  class MessageProcessor:
22
34
  """A class for parsing size-delimited messages."""
23
35
 
@@ -48,7 +60,11 @@ class MessageProcessor:
48
60
 
49
61
  def encode_json(self, stream: _BytesIO, data: JsonMessage) -> None:
50
62
  """Encode a message as JSON."""
51
- self.encode(stream, dumps(data, separators=(",", ":")))
63
+
64
+ self.encode(
65
+ stream,
66
+ dumps(data, cls=StrFallbackJSONEncoder, separators=(",", ":")),
67
+ )
52
68
 
53
69
  def messages(self, data: bytes) -> _Iterator[JsonMessage]:
54
70
  """Iterate over incoming messages."""
@@ -9,13 +9,15 @@ import asyncio
9
9
  from vcorelib.math import RateLimiter
10
10
 
11
11
  # internal
12
- from runtimepy.net.arbiter.info import AppInfo
12
+ from runtimepy.net.arbiter.info import AppInfo, SampleStruct
13
13
  from runtimepy.subprocess.program import PeerProgram
14
14
 
15
15
 
16
16
  class SampleProgram(PeerProgram):
17
17
  """A sample peer program."""
18
18
 
19
+ struct_type = SampleStruct
20
+
19
21
  stderr_task: asyncio.Task[None]
20
22
 
21
23
  async def log_message_sender(
@@ -91,4 +93,6 @@ async def run(app: AppInfo) -> int:
91
93
 
92
94
  await prog.wait_json({"a": 1, "b": 2, "c": 3})
93
95
 
96
+ await app.stop.wait()
97
+
94
98
  return 0
@@ -20,7 +20,7 @@ from vcorelib.paths.context import tempfile
20
20
  from runtimepy.metrics.channel import ChannelMetrics
21
21
  from runtimepy.mixins.psutil import PsutilMixin
22
22
  from runtimepy.net.arbiter import ConnectionArbiter
23
- from runtimepy.net.arbiter.info import RuntimeStruct, SampleStruct
23
+ from runtimepy.net.arbiter.info import RuntimeStruct
24
24
  from runtimepy.subprocess.interface import RuntimepyPeerInterface
25
25
 
26
26
  T = TypeVar("T", bound="PeerProgram")
@@ -34,7 +34,7 @@ class PeerProgram(RuntimepyPeerInterface, PsutilMixin):
34
34
  stream_output: BinaryIO
35
35
  stream_metrics: ChannelMetrics
36
36
 
37
- struct_type: Type[RuntimeStruct] = SampleStruct
37
+ struct_type: Type[RuntimeStruct] = RuntimeStruct
38
38
 
39
39
  got_eof: asyncio.Event
40
40
 
@@ -71,7 +71,7 @@ class PeerProgram(RuntimepyPeerInterface, PsutilMixin):
71
71
  prev_poll_time = loop.time()
72
72
 
73
73
  with suppress(asyncio.CancelledError):
74
- while True:
74
+ while not self.got_eof.is_set():
75
75
  # Perform heartbeat.
76
76
  if self._peer_env_event.is_set():
77
77
  self.stderr_metrics.update(self.stream_metrics)
@@ -90,6 +90,8 @@ class PeerProgram(RuntimepyPeerInterface, PsutilMixin):
90
90
  async def io_task(self, buffer: BinaryIO) -> None:
91
91
  """Run this peer program's main loop."""
92
92
 
93
+ self.got_eof.clear()
94
+
93
95
  # Allow polling stdin.
94
96
  if hasattr(os, "set_blocking"):
95
97
  getattr(os, "set_blocking")(buffer.fileno(), False)
@@ -97,7 +99,7 @@ class PeerProgram(RuntimepyPeerInterface, PsutilMixin):
97
99
  accumulator = 0
98
100
 
99
101
  with suppress(asyncio.CancelledError):
100
- while True:
102
+ while not self.got_eof.is_set():
101
103
  data: bytes = buffer.read(1)
102
104
  if data is None:
103
105
  await asyncio.sleep(self.poll_period_s)
@@ -148,7 +150,7 @@ class PeerProgram(RuntimepyPeerInterface, PsutilMixin):
148
150
  with tempfile(suffix=".json") as config_path:
149
151
  arbiter = ConnectionArbiter(stop_sig=self.got_eof)
150
152
  ARBITER.encode(config_path, self.struct.config)
151
- await arbiter.load_configs([config_path], wait_for_stop=True)
153
+ await arbiter.load_configs([config_path])
152
154
 
153
155
  await arbiter.app()
154
156
 
@@ -210,9 +212,9 @@ class PeerProgram(RuntimepyPeerInterface, PsutilMixin):
210
212
  # Don't respond to keyboard interrupt.
211
213
  signal.signal(signal.SIGINT, signal.SIG_IGN)
212
214
 
213
- async with cls.running(name, config, argv) as (io_task, main_task, _):
214
- await main_task
215
- await io_task
215
+ async with cls.running(name, config, argv) as (_, main_task, peer):
216
+ with peer.log_time("Main task context", reminder=True):
217
+ await main_task
216
218
 
217
219
  async def poll_handler(self) -> None:
218
220
  """Handle a 'poll' message."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: runtimepy
3
- Version: 5.9.2
3
+ Version: 5.10.0
4
4
  Summary: A framework for implementing Python services.
5
5
  Home-page: https://github.com/vkottler/runtimepy
6
6
  Author: Vaughn Kottler
@@ -17,10 +17,10 @@ Classifier: License :: OSI Approved :: MIT License
17
17
  Requires-Python: >=3.12
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
- Requires-Dist: aiofiles
21
- Requires-Dist: websockets
22
- Requires-Dist: vcorelib>=3.5.1
23
20
  Requires-Dist: svgen>=0.7.4
21
+ Requires-Dist: vcorelib>=3.5.1
22
+ Requires-Dist: websockets
23
+ Requires-Dist: aiofiles
24
24
  Requires-Dist: psutil
25
25
  Provides-Extra: test
26
26
  Requires-Dist: pylint; extra == "test"
@@ -49,11 +49,11 @@ Dynamic: requires-python
49
49
  =====================================
50
50
  generator=datazen
51
51
  version=3.1.4
52
- hash=663b9a39a640cae079d35c0621ae20f9
52
+ hash=cbacac02049e2f568e5ebad3cc428033
53
53
  =====================================
54
54
  -->
55
55
 
56
- # runtimepy ([5.9.2](https://pypi.org/project/runtimepy/))
56
+ # runtimepy ([5.10.0](https://pypi.org/project/runtimepy/))
57
57
 
58
58
  [![python](https://img.shields.io/pypi/pyversions/runtimepy.svg)](https://pypi.org/project/runtimepy/)
59
59
  ![Build Status](https://github.com/vkottler/runtimepy/workflows/Python%20Package/badge.svg)
@@ -1,4 +1,4 @@
1
- runtimepy/__init__.py,sha256=8xWVxuWwxsXJGjNAD_wAhQl8kTAend0Cz5sjesrRoKs,390
1
+ runtimepy/__init__.py,sha256=HxCNJsJjN2uUsJWlGzOeJVd9NXxPu4uXdaB7WmpLQ3Y,391
2
2
  runtimepy/__main__.py,sha256=OPAed6hggoQdw-6QAR62mqLC-rCkdDhOq0wyeS2vDRI,332
3
3
  runtimepy/app.py,sha256=sTvatbsGZ2Hdel36Si_WUbNMtg9CzsJyExr5xjIcxDE,970
4
4
  runtimepy/dev_requirements.txt,sha256=j0dh11ztJAzfaUL0iFheGjaZj9ppDzmTkclTT8YKO8c,230
@@ -43,8 +43,8 @@ runtimepy/control/step.py,sha256=2LdZTpMHLwHLdpPVinpC2qByTs5I5LTDt-xONn_6Fc8,549
43
43
  runtimepy/control/env/__init__.py,sha256=RHJqysY7Pv4VDs2SGk0X-qc5xp_SrQ_oxb5Deug8HEM,1339
44
44
  runtimepy/data/404.html,sha256=sn0Mcntzb1-AekukYZAQqz43xfmZtTWa2n2HD2ItqDM,4697
45
45
  runtimepy/data/browser.yaml,sha256=oc5KEV1C1uAJ4MkhNo4hyVVfJtZvHelRNqzNvD313Ow,79
46
- runtimepy/data/dummy_load.yaml,sha256=eCsWVfEJJ7pUl_JFAD3iDy-nFkw6cf03y9fBqZnaPUM,1986
47
- runtimepy/data/factories.yaml,sha256=Jm0QPwW2BQgnj1fIFQwygsWXzT78QJuf7auVRtRzWKQ,1847
46
+ runtimepy/data/dummy_load.yaml,sha256=PfKRXXgZnENRMSd68eznSMTV8xanVH5JY4FmoZRPFGY,1985
47
+ runtimepy/data/factories.yaml,sha256=esuoouMre8w7siZfBoZKqC-5myghJ_WwOOCDIq135zg,1899
48
48
  runtimepy/data/favicon.ico,sha256=iPOtqnqrFVr5tNBM6kTsw7nh0KullEz4TpS6CEQ2HVM,362870
49
49
  runtimepy/data/sample_telemetry.yaml,sha256=OpdFurkvtWJGaNl9LMlU2rKo15AaVVr-U_hoZfsbp-Y,695
50
50
  runtimepy/data/server.yaml,sha256=wS_Ceiu2TpkfPurpqoYoPlgzc9DAWtUd24MW7t-S5rU,97
@@ -96,7 +96,7 @@ runtimepy/data/schemas/ClientConnectionConfig.yaml,sha256=XUWkOiCOw175harOsDRV3T
96
96
  runtimepy/data/schemas/ConnectionArbiterConfig.yaml,sha256=WorY7ttxvQA78TYuFoNHw41hc9johxvgO72dusfLSqE,2133
97
97
  runtimepy/data/schemas/EnumRegistry.yaml,sha256=BfLzEEUsHj6Fg_0JyGmgbNJ7F-auuLs5tAhJjSc86uU,145
98
98
  runtimepy/data/schemas/FindFile.yaml,sha256=dSPCDQy4FQZLMgOjtbR0-o2sZECvN_tvGa3LXNd1-wc,934
99
- runtimepy/data/schemas/PeerProcessConfig.yaml,sha256=_YhfZ2pHCSKqcZqz8-sQAVrwPzFwfb5toWN893yNqPA,157
99
+ runtimepy/data/schemas/PeerProcessConfig.yaml,sha256=RNVGG1NV9-Goa7aLE0ah7QNwKOTEoJj6EZDXTW-TALM,190
100
100
  runtimepy/data/schemas/RuntimeEnum.yaml,sha256=dN5bl-lOB3JON02aP1fdwAEP7BhadG1wo_oX6rn5d80,430
101
101
  runtimepy/data/schemas/ServerConnectionConfig.yaml,sha256=IqQUQB09xte4zGmM6Qi0-LTm5McDkoi2tehYspoNUXM,100
102
102
  runtimepy/data/schemas/StructConfig.yaml,sha256=3ifuvRIVzS4AzYu0c9k6TdmLs0PG5y8u25aRphTrxg0,73
@@ -120,7 +120,7 @@ runtimepy/data/static/woff2/README.md,sha256=flHwSRmDxd6OnWhzxmnXzwio1Mong5tB4d8
120
120
  runtimepy/enum/__init__.py,sha256=WIBMOaogauR1u7mK-I4Z5BZUoWANU7alIlrz82QdCmY,5820
121
121
  runtimepy/enum/registry.py,sha256=EU7oUejI60kzrhdtzgWpnFYP8h4CFLy_fxvGTwq1Uvc,2631
122
122
  runtimepy/enum/types.py,sha256=uQYGvaAJVm5tUdwxn_SLHkTZHJpEf7364rTSL27necA,1027
123
- runtimepy/message/__init__.py,sha256=nF9yU9SV0PxZyHt12oh6o_ENR374x1Yn72UambecGlY,2668
123
+ runtimepy/message/__init__.py,sha256=X5PZqSPGhNT4MruaiQjN4MwJHhy8gV9W32QXyxgS9-E,3004
124
124
  runtimepy/message/handlers.py,sha256=He9NC7MCkaV2clKc8dTSZ_T8N2l3ATjaTFzBr9n1T34,3775
125
125
  runtimepy/message/interface.py,sha256=vg25Uhp7wrDFH6v2iRKUBwnCBpjc3JAFjwdu3KArrLo,11285
126
126
  runtimepy/message/types.py,sha256=vajDWKW32LjzsfH-eVCCxZTWVctj4_-tjanhnmCqUis,821
@@ -260,12 +260,12 @@ runtimepy/registry/item.py,sha256=HPqxmzuK8aOc1siHzYScgxYxSMZ4RVTmnsRW2oNcW34,85
260
260
  runtimepy/registry/name.py,sha256=uhxmijwUT7x59NUYV4hJSe7VcnAbLDUSAPaKhR6K-N0,1749
261
261
  runtimepy/sample/__init__.py,sha256=N7P6hnCLF9NwnkZA1h3LzS2C334SAO48Fu8adHxWGLU,56
262
262
  runtimepy/sample/peer.py,sha256=uNlIPAv1-wjGUWdR0AM3L9NUMjnIMJBqQ9ZsHdTLx3Q,1688
263
- runtimepy/sample/program.py,sha256=I8_JGjEAh-d3eEw4M9l9CxEw4KG4s8f4i8STwRei5IY,2355
263
+ runtimepy/sample/program.py,sha256=AFqinsnsjzatgJXePAO4yXYerxmzN7ajtDmJPwWigYc,2428
264
264
  runtimepy/struct/__init__.py,sha256=Q7t1qKpuYGjtRGXdC_x8IwJyyCQ1Xy2wYriud89XJnk,2229
265
265
  runtimepy/subprocess/__init__.py,sha256=VAiFrYFCU5ETDVoWLOVlrrSsx2d1_atYXUfXYc_OQ9g,4059
266
266
  runtimepy/subprocess/interface.py,sha256=4nGVUfDThaZrfqgn2nZHMt1hfUwHezSvFjykBXGT36g,8041
267
267
  runtimepy/subprocess/peer.py,sha256=oYw9a0yKAPR18Z6Qt24wYWrcX6EizeQE64htL11WVjM,7593
268
- runtimepy/subprocess/program.py,sha256=UCKBvmIc4JBitWju396TA8Kxdtmko50jQ3ZPKNkBjFk,6792
268
+ runtimepy/subprocess/program.py,sha256=exnmG6S8IZyHRr4RyET0cloBc3Wnip1KRWeegKwJZPM,6874
269
269
  runtimepy/subprocess/protocol.py,sha256=LYApfKZ0bXSj6gGqLdNfwsIjxWyrdVUK0Jz_qc33ik4,3074
270
270
  runtimepy/task/__init__.py,sha256=euLIxCl1wve-4Nr2DUms8XrWT3liUV-ihQiQlZ21nqI,348
271
271
  runtimepy/task/asynchronous.py,sha256=w4oEUCyj-X-zDVFmlsAtRL1gv66ahQ78QKE0GmLGT1w,5947
@@ -284,9 +284,9 @@ runtimepy/tui/task.py,sha256=nUZo9fuOC-k1Wpqdzkv9v1tQirCI28fZVgcC13Ijvus,1093
284
284
  runtimepy/tui/channels/__init__.py,sha256=evDaiIn-YS9uGhdo8ZGtP9VK1ek6sr_P1nJ9JuSET0o,4536
285
285
  runtimepy/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
286
286
  runtimepy/ui/controls.py,sha256=yvT7h3thbYaitsakcIAJ90EwKzJ4b-jnc6p3UuVf_XE,1241
287
- runtimepy-5.9.2.dist-info/LICENSE,sha256=yKBRwbO-cOPBrlpsZmJkkSa33DfY31aE8t7lZ0DwlUo,1071
288
- runtimepy-5.9.2.dist-info/METADATA,sha256=Gg-EmUL90ZpybmT4WzcVlaYKlqbo5VrBboNt2_sbq6g,9371
289
- runtimepy-5.9.2.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
290
- runtimepy-5.9.2.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
291
- runtimepy-5.9.2.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
292
- runtimepy-5.9.2.dist-info/RECORD,,
287
+ runtimepy-5.10.0.dist-info/LICENSE,sha256=yKBRwbO-cOPBrlpsZmJkkSa33DfY31aE8t7lZ0DwlUo,1071
288
+ runtimepy-5.10.0.dist-info/METADATA,sha256=-jhaz7qH__FgJIRWZWrDpmVbb6TzfqN5ElYMgxzyi1I,9373
289
+ runtimepy-5.10.0.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
290
+ runtimepy-5.10.0.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
291
+ runtimepy-5.10.0.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
292
+ runtimepy-5.10.0.dist-info/RECORD,,