runtimepy 5.6.2__py3-none-any.whl → 5.6.4__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.
Files changed (55) hide show
  1. runtimepy/__init__.py +2 -2
  2. runtimepy/channel/environment/command/__init__.py +1 -1
  3. runtimepy/channel/environment/telemetry.py +3 -1
  4. runtimepy/data/css/bootstrap_extra.css +1 -0
  5. runtimepy/data/css/main.css +4 -0
  6. runtimepy/data/dummy_load.yaml +34 -1
  7. runtimepy/data/js/classes/OverlayManager.js +1 -1
  8. runtimepy/data/js/util.js +3 -2
  9. runtimepy/data/md/Connection.md +3 -0
  10. runtimepy/data/md/PeriodicTask.md +3 -0
  11. runtimepy/data/md/RuntimeStruct.md +3 -0
  12. runtimepy/data/md/RuntimepyPeer.md +3 -0
  13. runtimepy/data/md/SinusoidTask.md +20 -0
  14. runtimepy/data/schemas/ClientConnectionConfig.yaml +1 -0
  15. runtimepy/data/schemas/ConnectionArbiterConfig.yaml +3 -0
  16. runtimepy/data/schemas/PeerProcessConfig.yaml +1 -0
  17. runtimepy/data/schemas/TaskConfig.yaml +1 -0
  18. runtimepy/data/schemas/has_markdown.yaml +4 -0
  19. runtimepy/mapping.py +1 -1
  20. runtimepy/message/interface.py +2 -2
  21. runtimepy/mixins/environment.py +10 -4
  22. runtimepy/mixins/logging.py +54 -1
  23. runtimepy/net/arbiter/base.py +13 -3
  24. runtimepy/net/arbiter/config/__init__.py +6 -2
  25. runtimepy/net/arbiter/imports/__init__.py +5 -5
  26. runtimepy/net/arbiter/imports/util.py +3 -1
  27. runtimepy/net/connection.py +7 -1
  28. runtimepy/net/http/common.py +5 -0
  29. runtimepy/net/http/header.py +5 -1
  30. runtimepy/net/http/response.py +1 -1
  31. runtimepy/net/server/app/__init__.py +3 -1
  32. runtimepy/net/server/app/bootstrap/elements.py +38 -1
  33. runtimepy/net/server/app/env/__init__.py +69 -19
  34. runtimepy/net/server/app/env/tab/base.py +5 -1
  35. runtimepy/net/server/app/env/tab/html.py +6 -6
  36. runtimepy/net/server/app/files.py +10 -6
  37. runtimepy/net/server/struct/__init__.py +1 -2
  38. runtimepy/net/server/websocket/state.py +3 -1
  39. runtimepy/net/tcp/connection.py +14 -4
  40. runtimepy/net/tcp/http/__init__.py +9 -7
  41. runtimepy/net/udp/connection.py +11 -4
  42. runtimepy/net/udp/tftp/__init__.py +1 -2
  43. runtimepy/net/websocket/connection.py +10 -5
  44. runtimepy/requirements.txt +2 -1
  45. runtimepy/struct/__init__.py +12 -2
  46. runtimepy/subprocess/interface.py +17 -4
  47. runtimepy/subprocess/peer.py +20 -6
  48. runtimepy/task/basic/periodic.py +7 -1
  49. runtimepy/util.py +0 -81
  50. {runtimepy-5.6.2.dist-info → runtimepy-5.6.4.dist-info}/METADATA +9 -8
  51. {runtimepy-5.6.2.dist-info → runtimepy-5.6.4.dist-info}/RECORD +55 -49
  52. {runtimepy-5.6.2.dist-info → runtimepy-5.6.4.dist-info}/LICENSE +0 -0
  53. {runtimepy-5.6.2.dist-info → runtimepy-5.6.4.dist-info}/WHEEL +0 -0
  54. {runtimepy-5.6.2.dist-info → runtimepy-5.6.4.dist-info}/entry_points.txt +0 -0
  55. {runtimepy-5.6.2.dist-info → runtimepy-5.6.4.dist-info}/top_level.txt +0 -0
@@ -8,53 +8,75 @@ from svgen.element.html import div
8
8
  # internal
9
9
  from runtimepy import PKG_NAME
10
10
  from runtimepy.net.arbiter.info import AppInfo
11
- from runtimepy.net.server.app.bootstrap.elements import input_box
11
+ from runtimepy.net.server.app.bootstrap.elements import (
12
+ centered_markdown,
13
+ input_box,
14
+ )
12
15
  from runtimepy.net.server.app.bootstrap.tabs import TabbedContent
13
16
  from runtimepy.net.server.app.env.modal import Modal
14
17
  from runtimepy.net.server.app.env.settings import plot_settings
15
18
  from runtimepy.net.server.app.env.tab import ChannelEnvironmentTab
16
- from runtimepy.net.server.app.placeholder import dummy_tabs, under_construction
19
+ from runtimepy.net.server.app.placeholder import dummy_tabs
17
20
  from runtimepy.net.server.app.sound import SoundTab
18
21
 
19
22
 
20
- def channel_environments(app: AppInfo, tabs: TabbedContent) -> None:
21
- """Populate application elements."""
22
-
23
- # Remove tab-content scrolling.
24
- tabs.set_scroll(False)
25
-
26
- # Tab name filter.
27
- input_box(tabs.tabs, label="tab", description="Tab name filter.")
23
+ def populate_tabs(app: AppInfo, tabs: TabbedContent) -> None:
24
+ """Populate tab contents."""
28
25
 
29
26
  # Connection tabs.
30
27
  for name, conn in app.connections.items():
31
28
  ChannelEnvironmentTab(
32
- name, conn.command, app, tabs, icon="ethernet"
29
+ name,
30
+ conn.command,
31
+ app,
32
+ tabs,
33
+ icon="ethernet",
34
+ markdown=conn.markdown,
33
35
  ).entry()
34
36
 
35
37
  # Task tabs.
36
38
  for name, task in app.tasks.items():
37
39
  ChannelEnvironmentTab(
38
- name, task.command, app, tabs, icon="arrow-repeat"
40
+ name,
41
+ task.command,
42
+ app,
43
+ tabs,
44
+ icon="arrow-repeat",
45
+ markdown=task.markdown,
39
46
  ).entry()
40
47
 
41
48
  # Struct tabs.
42
49
  for struct in app.structs.values():
43
50
  ChannelEnvironmentTab(
44
- struct.name, struct.command, app, tabs, icon="bucket"
51
+ struct.name,
52
+ struct.command,
53
+ app,
54
+ tabs,
55
+ icon="bucket",
56
+ markdown=struct.markdown,
45
57
  ).entry()
46
58
 
47
59
  # Subprocess tabs.
48
60
  for peer in app.peers.values():
49
61
  # Host side.
50
62
  ChannelEnvironmentTab(
51
- peer.struct.name, peer.struct.command, app, tabs, icon="cpu-fill"
63
+ peer.struct.name,
64
+ peer.struct.command,
65
+ app,
66
+ tabs,
67
+ icon="cpu-fill",
68
+ markdown=peer.markdown,
52
69
  ).entry()
53
70
 
54
71
  # Remote side.
55
72
  assert peer.peer is not None
56
73
  ChannelEnvironmentTab(
57
- peer.peer_name, peer.peer, app, tabs, icon="cpu"
74
+ peer.peer_name,
75
+ peer.peer,
76
+ app,
77
+ tabs,
78
+ icon="cpu",
79
+ markdown=peer.struct.markdown,
58
80
  ).entry()
59
81
 
60
82
  # If we are a peer program, load environments.
@@ -69,14 +91,40 @@ def channel_environments(app: AppInfo, tabs: TabbedContent) -> None:
69
91
  app,
70
92
  tabs,
71
93
  icon="cpu-fill",
94
+ markdown=PROGRAM.struct.markdown,
72
95
  ).entry()
73
96
 
74
97
  # Remote side.
75
98
  assert PROGRAM.peer is not None
76
99
  ChannelEnvironmentTab(
77
- PROGRAM.peer_name, PROGRAM.peer, app, tabs, icon="cpu"
100
+ PROGRAM.peer_name,
101
+ PROGRAM.peer,
102
+ app,
103
+ tabs,
104
+ icon="cpu",
105
+ markdown=PROGRAM.markdown,
78
106
  ).entry()
79
107
 
108
+
109
+ def channel_environments(app: AppInfo, tabs: TabbedContent) -> None:
110
+ """Populate application elements."""
111
+
112
+ # Remove tab-content scrolling.
113
+ tabs.set_scroll(False)
114
+
115
+ # Tab name filter.
116
+ input_box(tabs.tabs, label="tab", description="Tab name filter.")
117
+
118
+ centered_markdown(
119
+ tabs.tabs,
120
+ app.config_param("top_markdown", "configure `top_markdown`"),
121
+ "border-start",
122
+ "border-bottom",
123
+ "border-end",
124
+ )
125
+
126
+ populate_tabs(app, tabs)
127
+
80
128
  # Toggle channel-table button.
81
129
  tabs.add_button(
82
130
  "Toggle channel table",
@@ -99,9 +147,11 @@ def channel_environments(app: AppInfo, tabs: TabbedContent) -> None:
99
147
  Modal(tabs)
100
148
  Modal(tabs, name="diagnostics", icon="activity")
101
149
 
102
- # Placeholder for using space at the bottom of the tab list.
103
- under_construction(
104
- tabs.tabs, note="unused space", class_str="border-start border-end"
150
+ centered_markdown(
151
+ tabs.tabs,
152
+ app.config_param("bottom_markdown", "configure `bottom_markdown`"),
153
+ "border-start",
154
+ "border-end",
105
155
  )
106
156
 
107
157
  # Add splash screen element.
@@ -3,10 +3,12 @@ A module implementing a channel-environment tab HTML interface.
3
3
  """
4
4
 
5
5
  # third-party
6
+ from vcorelib.io import MarkdownMixin
6
7
  from vcorelib.logging import LoggerMixin
7
8
  from vcorelib.math import RateLimiter
8
9
 
9
10
  # internal
11
+ from runtimepy import PKG_NAME
10
12
  from runtimepy.channel.environment.command.processor import (
11
13
  ChannelCommandProcessor,
12
14
  )
@@ -15,7 +17,7 @@ from runtimepy.net.server.app.bootstrap.tabs import TabbedContent
15
17
  from runtimepy.net.server.app.tab import Tab
16
18
 
17
19
 
18
- class ChannelEnvironmentTabBase(Tab, LoggerMixin):
20
+ class ChannelEnvironmentTabBase(Tab, LoggerMixin, MarkdownMixin):
19
21
  """A channel-environment tab interface."""
20
22
 
21
23
  def __init__(
@@ -25,10 +27,12 @@ class ChannelEnvironmentTabBase(Tab, LoggerMixin):
25
27
  app: AppInfo,
26
28
  tabs: TabbedContent,
27
29
  icon: str = "alarm",
30
+ markdown: str = None,
28
31
  ) -> None:
29
32
  """Initialize this instance."""
30
33
 
31
34
  self.command = command
35
+ self.set_markdown(markdown=markdown, package=PKG_NAME)
32
36
  super().__init__(name, app, tabs, source="env", icon=icon)
33
37
 
34
38
  # Logging.
@@ -14,6 +14,7 @@ from runtimepy.channel import AnyChannel
14
14
  from runtimepy.enum import RuntimeEnum
15
15
  from runtimepy.net.server.app.bootstrap.elements import (
16
16
  TEXT,
17
+ centered_markdown,
17
18
  flex,
18
19
  input_box,
19
20
  set_tooltip,
@@ -26,7 +27,6 @@ from runtimepy.net.server.app.env.widgets import (
26
27
  channel_table_header,
27
28
  plot_checkbox,
28
29
  )
29
- from runtimepy.net.server.app.placeholder import under_construction
30
30
 
31
31
 
32
32
  def channel_color_button(parent: Element, name: str) -> Element:
@@ -246,12 +246,12 @@ class ChannelEnvironmentTabHtml(ChannelEnvironmentTabControls):
246
246
 
247
247
  self.channel_table(vert_container)
248
248
 
249
- # Possible empty space that could eventually be used (scenario: channel
250
- # table doesn't take up full vertical space, few channels).
251
- under_construction(
249
+ centered_markdown(
252
250
  vert_container,
253
- class_str="border-start border-top border-end",
254
- note="unused space",
251
+ self.markdown,
252
+ "border-start",
253
+ "border-top",
254
+ "border-end",
255
255
  )
256
256
 
257
257
  # Divider.
@@ -77,12 +77,16 @@ def set_text_to_kind(
77
77
  WORKER_TYPE = "text/js-worker"
78
78
 
79
79
 
80
- def handle_worker(writer: IndentedFileWriter) -> bool:
80
+ def handle_worker(writer: IndentedFileWriter) -> int:
81
81
  """Boilerplate contents for worker thread block."""
82
82
 
83
- return write_found_file(
84
- writer, kind_url("js", "webgl-debug", subdir="third-party")
85
- )
83
+ # Not currently used.
84
+ # return write_found_file(
85
+ # writer, kind_url("js", "webgl-debug", subdir="third-party")
86
+ # )
87
+ del writer
88
+
89
+ return 0
86
90
 
87
91
 
88
92
  def append_kind(
@@ -107,8 +111,8 @@ def append_kind(
107
111
  ):
108
112
  found_count += 1
109
113
 
110
- if worker and handle_worker(writer):
111
- found_count += 1
114
+ if worker:
115
+ found_count += handle_worker(writer)
112
116
 
113
117
  if found_count:
114
118
  elem.text = stream.getvalue()
@@ -43,8 +43,7 @@ class UiState(RuntimeStruct, PsutilMixin):
43
43
 
44
44
  # JSON-messaging interface metrics.
45
45
  self.json_metrics = ConnectionMetrics()
46
- with self.env.names_pushed("json"):
47
- self.register_connection_metrics(self.json_metrics)
46
+ self.register_connection_metrics(self.json_metrics, "json")
48
47
 
49
48
  # System metrics.
50
49
  self.use_psutil = self.config.get("psutil", True) # type: ignore
@@ -7,11 +7,13 @@ from collections import defaultdict
7
7
  from dataclasses import dataclass
8
8
  import logging
9
9
 
10
+ # third-party
11
+ from vcorelib.logging import ListLogger
12
+
10
13
  # internal
11
14
  from runtimepy.channel.environment.base import ValueMap
12
15
  from runtimepy.message import JsonMessage
13
16
  from runtimepy.primitives import AnyPrimitive
14
- from runtimepy.util import ListLogger
15
17
 
16
18
  # (value, nanosecond timestamp)
17
19
  Point = tuple[str | int | float | bool, int]
@@ -51,7 +51,12 @@ class TcpConnection(_Connection, _TransportMixin):
51
51
  log_alias = "TCP"
52
52
  log_prefix = ""
53
53
 
54
- def __init__(self, transport: _Transport, protocol: QueueProtocol) -> None:
54
+ def __init__(
55
+ self,
56
+ transport: _Transport,
57
+ protocol: QueueProtocol,
58
+ **kwargs,
59
+ ) -> None:
55
60
  """Initialize this TCP connection."""
56
61
 
57
62
  _TransportMixin.__init__(self, transport)
@@ -60,7 +65,9 @@ class TcpConnection(_Connection, _TransportMixin):
60
65
  self._transport: _Transport = transport
61
66
  self._set_protocol(protocol)
62
67
 
63
- super().__init__(_getLogger(self.logger_name(f"{self.log_alias} ")))
68
+ super().__init__(
69
+ _getLogger(self.logger_name(f"{self.log_alias} ")), **kwargs
70
+ )
64
71
 
65
72
  # Store connection-instantiation arguments.
66
73
  self._conn_kwargs: dict[str, _Any] = {}
@@ -121,14 +128,17 @@ class TcpConnection(_Connection, _TransportMixin):
121
128
 
122
129
  @classmethod
123
130
  async def create_connection(
124
- cls: type[T], backoff: ExponentialBackoff = None, **kwargs
131
+ cls: type[T],
132
+ backoff: ExponentialBackoff = None,
133
+ markdown: str = None,
134
+ **kwargs,
125
135
  ) -> T:
126
136
  """Create a TCP connection."""
127
137
 
128
138
  transport, protocol = await tcp_transport_protocol_backoff(
129
139
  backoff=backoff, **kwargs
130
140
  )
131
- inst = cls(transport, protocol)
141
+ inst = cls(transport, protocol, markdown=markdown)
132
142
 
133
143
  # Is there a better way to do this? We can't restart a server's side
134
144
  # of a connection (seems okay).
@@ -7,7 +7,7 @@ import asyncio
7
7
  from copy import copy
8
8
  import http
9
9
  from json import loads
10
- from typing import Any, Awaitable, Callable, Optional, Tuple, Union
10
+ from typing import Any, Awaitable, Callable, Optional, Tuple, Union, cast
11
11
 
12
12
  # third-party
13
13
  from vcorelib import DEFAULT_ENCODING
@@ -174,11 +174,9 @@ class HttpConnection(_TcpConnection):
174
174
  async def process_binary(self, data: bytes) -> bool:
175
175
  """Process a binary frame."""
176
176
 
177
- kind = RequestHeader if not self.expecting_response else ResponseHeader
178
-
179
- for header, payload in self.processor.ingest( # type: ignore
177
+ for header, payload in self.processor.ingest(
180
178
  data,
181
- kind, # type: ignore
179
+ RequestHeader if not self.expecting_response else ResponseHeader,
182
180
  ):
183
181
  header.log(self.logger, False)
184
182
 
@@ -187,11 +185,15 @@ class HttpConnection(_TcpConnection):
187
185
  response = ResponseHeader()
188
186
  self._send(
189
187
  response,
190
- await self._process_request(response, header, payload),
188
+ await self._process_request(
189
+ response, cast(RequestHeader, header), payload
190
+ ),
191
191
  )
192
192
 
193
193
  # Process the response to a pending request.
194
194
  else:
195
- await self.responses.put((header, payload))
195
+ await self.responses.put(
196
+ (cast(ResponseHeader, header), payload)
197
+ )
196
198
 
197
199
  return True
@@ -44,7 +44,10 @@ class UdpConnection(_Connection, _TransportMixin):
44
44
  log_alias = "UDP"
45
45
 
46
46
  def __init__(
47
- self, transport: _DatagramTransport, protocol: UdpQueueProtocol
47
+ self,
48
+ transport: _DatagramTransport,
49
+ protocol: UdpQueueProtocol,
50
+ **kwargs,
48
51
  ) -> None:
49
52
  """Initialize this UDP connection."""
50
53
 
@@ -55,7 +58,9 @@ class UdpConnection(_Connection, _TransportMixin):
55
58
  # Re-assign with updated type information.
56
59
  self._transport: _DatagramTransport = transport
57
60
 
58
- super().__init__(_getLogger(self.logger_name(f"{self.log_alias} ")))
61
+ super().__init__(
62
+ _getLogger(self.logger_name(f"{self.log_alias} ")), **kwargs
63
+ )
59
64
  self._set_protocol(protocol)
60
65
 
61
66
  # Store connection-instantiation arguments.
@@ -127,7 +132,9 @@ class UdpConnection(_Connection, _TransportMixin):
127
132
  should_connect: bool = True
128
133
 
129
134
  @classmethod
130
- async def create_connection(cls: type[T], **kwargs) -> T:
135
+ async def create_connection(
136
+ cls: type[T], markdown: str = None, **kwargs
137
+ ) -> T:
131
138
  """Create a UDP connection."""
132
139
 
133
140
  LOG.debug("kwargs: %s", kwargs)
@@ -151,7 +158,7 @@ class UdpConnection(_Connection, _TransportMixin):
151
158
 
152
159
  # Create the underlying connection.
153
160
  transport, protocol = await udp_transport_protocol_backoff(**kwargs)
154
- conn = cls(transport, protocol)
161
+ conn = cls(transport, protocol, markdown=markdown)
155
162
  conn._conn_kwargs = {**kwargs}
156
163
 
157
164
  # Set the remote address manually if necessary.
@@ -11,7 +11,7 @@ from typing import Any, AsyncIterator
11
11
 
12
12
  # third-party
13
13
  from vcorelib.asyncio.poll import repeat_until
14
- from vcorelib.paths.context import tempfile
14
+ from vcorelib.paths.context import PossiblePath, as_path, tempfile
15
15
  from vcorelib.paths.hashing import file_md5_hex
16
16
  from vcorelib.paths.info import FileInfo
17
17
 
@@ -25,7 +25,6 @@ from runtimepy.net.udp.tftp.base import (
25
25
  )
26
26
  from runtimepy.net.udp.tftp.enums import DEFAULT_MODE
27
27
  from runtimepy.net.util import IpHostTuplelike
28
- from runtimepy.util import PossiblePath, as_path
29
28
 
30
29
 
31
30
  class TftpConnection(BaseTftpConnection):
@@ -51,11 +51,12 @@ class WebsocketConnection(Connection):
51
51
  def __init__(
52
52
  self,
53
53
  protocol: _Union[_WebSocketClientProtocol, _WebSocketServerProtocol],
54
+ **kwargs,
54
55
  ) -> None:
55
56
  """Initialize this connection."""
56
57
 
57
58
  self.protocol = protocol
58
- super().__init__(self.protocol.logger)
59
+ super().__init__(self.protocol.logger, **kwargs)
59
60
 
60
61
  async def _handle_connection_closed(
61
62
  self, task: _Awaitable[V]
@@ -92,7 +93,9 @@ class WebsocketConnection(Connection):
92
93
  await self.protocol.close()
93
94
 
94
95
  @classmethod
95
- async def create_connection(cls: type[T], uri: str, **kwargs) -> T:
96
+ async def create_connection(
97
+ cls: type[T], uri: str, markdown: str = None, **kwargs
98
+ ) -> T:
96
99
  """Connect a client to an endpoint."""
97
100
 
98
101
  kwargs.setdefault("use_ssl", uri.startswith("wss"))
@@ -100,11 +103,13 @@ class WebsocketConnection(Connection):
100
103
  protocol = await getattr(websockets, "connect")(
101
104
  uri, **handle_possible_ssl(**kwargs)
102
105
  )
103
- return cls(protocol)
106
+ return cls(protocol, markdown=markdown)
104
107
 
105
108
  @classmethod
106
109
  @_asynccontextmanager
107
- async def client(cls: type[T], uri: str, **kwargs) -> _AsyncIterator[T]:
110
+ async def client(
111
+ cls: type[T], uri: str, markdown: str = None, **kwargs
112
+ ) -> _AsyncIterator[T]:
108
113
  """A wrapper for connecting a client."""
109
114
 
110
115
  kwargs.setdefault("use_ssl", uri.startswith("wss"))
@@ -112,7 +117,7 @@ class WebsocketConnection(Connection):
112
117
  async with getattr(websockets, "connect")(
113
118
  uri, **handle_possible_ssl(**kwargs)
114
119
  ) as protocol:
115
- yield cls(protocol)
120
+ yield cls(protocol, markdown=markdown)
116
121
 
117
122
  @classmethod
118
123
  def server_handler(
@@ -1,4 +1,5 @@
1
- vcorelib>=3.3.1
1
+ aiofiles
2
+ vcorelib>=3.4.1
2
3
  svgen>=0.6.8
3
4
  websockets
4
5
  psutil
@@ -6,9 +6,11 @@ A module implementing a runtime structure base.
6
6
  from logging import getLogger as _getLogger
7
7
 
8
8
  # third-party
9
+ from vcorelib.io import MarkdownMixin
9
10
  from vcorelib.io.types import JsonObject as _JsonObject
10
11
 
11
12
  # internal
13
+ from runtimepy import PKG_NAME
12
14
  from runtimepy.channel.environment.command.processor import (
13
15
  ChannelCommandProcessor,
14
16
  )
@@ -16,15 +18,23 @@ from runtimepy.mixins.environment import ChannelEnvironmentMixin
16
18
  from runtimepy.mixins.logging import LoggerMixinLevelControl
17
19
 
18
20
 
19
- class RuntimeStructBase(LoggerMixinLevelControl, ChannelEnvironmentMixin):
21
+ class RuntimeStructBase(
22
+ LoggerMixinLevelControl, ChannelEnvironmentMixin, MarkdownMixin
23
+ ):
20
24
  """A base runtime structure."""
21
25
 
22
26
  log_level_channel: bool = True
23
27
 
24
- def __init__(self, name: str, config: _JsonObject) -> None:
28
+ # Unclear why this is/was necessary (mypy bug?)
29
+ markdown: str
30
+
31
+ def __init__(
32
+ self, name: str, config: _JsonObject, markdown: str = None
33
+ ) -> None:
25
34
  """Initialize this instance."""
26
35
 
27
36
  self.name = name
37
+ self.set_markdown(config=config, markdown=markdown, package=PKG_NAME)
28
38
  LoggerMixinLevelControl.__init__(self, logger=_getLogger(self.name))
29
39
  ChannelEnvironmentMixin.__init__(self)
30
40
  if self.log_level_channel:
@@ -12,11 +12,12 @@ from logging import INFO, getLogger
12
12
  from typing import Optional
13
13
 
14
14
  # third-party
15
+ from vcorelib.io import MarkdownMixin
15
16
  from vcorelib.io.types import JsonObject
16
17
  from vcorelib.math import RateLimiter
17
18
 
18
19
  # internal
19
- from runtimepy import METRICS_NAME
20
+ from runtimepy import METRICS_NAME, PKG_NAME
20
21
  from runtimepy.channel.environment import ChannelEnvironment
21
22
  from runtimepy.channel.environment.base import FieldOrChannel
22
23
  from runtimepy.channel.environment.command import register_env
@@ -34,7 +35,7 @@ PEER_SUFFIX = ".peer"
34
35
 
35
36
 
36
37
  class RuntimepyPeerInterface(
37
- JsonMessageInterface, AsyncCommandProcessingMixin
38
+ JsonMessageInterface, AsyncCommandProcessingMixin, MarkdownMixin
38
39
  ):
39
40
  """A class implementing an interface for messaging peer subprocesses."""
40
41
 
@@ -42,13 +43,25 @@ class RuntimepyPeerInterface(
42
43
 
43
44
  struct_type: type[RuntimeStruct] = SampleStruct
44
45
 
45
- def __init__(self, name: str, config: JsonObject) -> None:
46
+ # Unclear why this is/was necessary (mypy bug?)
47
+ markdown: str
48
+
49
+ def __init__(
50
+ self, name: str, config: JsonObject, markdown: str = None
51
+ ) -> None:
46
52
  """Initialize this instance."""
47
53
 
54
+ self.set_markdown(markdown=markdown, package=PKG_NAME)
55
+
48
56
  self.processor = MessageProcessor()
49
57
 
50
58
  self.basename = name
51
- self.struct = self.struct_type(self.basename + HOST_SUFFIX, config)
59
+
60
+ self.struct = self.struct_type(
61
+ self.basename + HOST_SUFFIX,
62
+ config,
63
+ markdown=config.get("config", {}).get("markdown"), # type: ignore
64
+ )
52
65
 
53
66
  self.peer: Optional[RemoteCommandProcessor] = None
54
67
  self.peer_config: Optional[JsonMessage] = None
@@ -19,13 +19,13 @@ from typing import AsyncIterator, Iterator, Type, TypeVar
19
19
  from vcorelib.io import ARBITER, DEFAULT_INCLUDES_KEY
20
20
  from vcorelib.io.file_writer import IndentedFileWriter
21
21
  from vcorelib.io.types import JsonObject
22
+ from vcorelib.names import import_str_and_item
22
23
  from vcorelib.paths.context import tempfile
23
24
 
24
25
  # internal
25
26
  from runtimepy.subprocess import spawn_exec, spawn_shell
26
27
  from runtimepy.subprocess.interface import RuntimepyPeerInterface
27
28
  from runtimepy.subprocess.protocol import RuntimepySubprocessProtocol
28
- from runtimepy.util import import_str_and_item
29
29
 
30
30
  T = TypeVar("T", bound="RuntimepyPeer")
31
31
 
@@ -38,10 +38,11 @@ class RuntimepyPeer(RuntimepyPeerInterface):
38
38
  protocol: RuntimepySubprocessProtocol,
39
39
  name: str,
40
40
  config: JsonObject,
41
+ markdown: str = None,
41
42
  ) -> None:
42
43
  """Initialize this instance."""
43
44
 
44
- super().__init__(name, config)
45
+ super().__init__(name, config, markdown=markdown)
45
46
  self.protocol = protocol
46
47
 
47
48
  # Offset message identifiers.
@@ -89,14 +90,20 @@ class RuntimepyPeer(RuntimepyPeerInterface):
89
90
  @classmethod
90
91
  @asynccontextmanager
91
92
  async def shell(
92
- cls: Type[T], name: str, config: JsonObject, cmd: str
93
+ cls: Type[T],
94
+ name: str,
95
+ config: JsonObject,
96
+ cmd: str,
97
+ markdown: str = None,
93
98
  ) -> AsyncIterator[T]:
94
99
  """Create an instance from a shell command."""
95
100
 
96
101
  async with spawn_shell(
97
102
  cmd, stdout=asyncio.Queue(), stderr=asyncio.Queue()
98
103
  ) as proto:
99
- async with cls(proto, name, config)._context() as inst:
104
+ async with cls(
105
+ proto, name, config, markdown=markdown
106
+ )._context() as inst:
100
107
  yield inst
101
108
 
102
109
  async def main(self) -> None:
@@ -105,14 +112,21 @@ class RuntimepyPeer(RuntimepyPeerInterface):
105
112
  @classmethod
106
113
  @asynccontextmanager
107
114
  async def exec(
108
- cls: Type[T], name: str, config: JsonObject, *args, **kwargs
115
+ cls: Type[T],
116
+ name: str,
117
+ config: JsonObject,
118
+ *args,
119
+ markdown: str = None,
120
+ **kwargs,
109
121
  ) -> AsyncIterator[T]:
110
122
  """Create an instance from comand-line arguments."""
111
123
 
112
124
  async with spawn_exec(
113
125
  *args, stdout=asyncio.Queue(), stderr=asyncio.Queue(), **kwargs
114
126
  ) as proto:
115
- async with cls(proto, name, config)._context() as inst:
127
+ async with cls(
128
+ proto, name, config, markdown=markdown
129
+ )._context() as inst:
116
130
  yield inst
117
131
 
118
132
  @classmethod
@@ -13,12 +13,14 @@ from logging import getLogger as _getLogger
13
13
  from typing import Optional as _Optional
14
14
 
15
15
  # third-party
16
+ from vcorelib.io import MarkdownMixin
16
17
  from vcorelib.math import DEFAULT_DEPTH as _DEFAULT_DEPTH
17
18
  from vcorelib.math import MovingAverage as _MovingAverage
18
19
  from vcorelib.math import RateTracker as _RateTracker
19
20
  from vcorelib.math import rate_str as _rate_str
20
21
 
21
22
  # internal
23
+ from runtimepy import PKG_NAME
22
24
  from runtimepy.channel.environment import ChannelEnvironment
23
25
  from runtimepy.channel.environment.command.processor import (
24
26
  ChannelCommandProcessor,
@@ -32,7 +34,9 @@ from runtimepy.primitives import Float as _Float
32
34
  from runtimepy.ui.controls import Controlslike
33
35
 
34
36
 
35
- class PeriodicTask(LoggerMixinLevelControl, ChannelEnvironmentMixin, _ABC):
37
+ class PeriodicTask(
38
+ LoggerMixinLevelControl, ChannelEnvironmentMixin, MarkdownMixin, _ABC
39
+ ):
36
40
  """A class implementing a simple periodic-task interface."""
37
41
 
38
42
  auto_finalize = True
@@ -45,10 +49,12 @@ class PeriodicTask(LoggerMixinLevelControl, ChannelEnvironmentMixin, _ABC):
45
49
  period_s: float = 1.0,
46
50
  env: ChannelEnvironment = None,
47
51
  period_controls: Controlslike = "period",
52
+ markdown: str = None,
48
53
  ) -> None:
49
54
  """Initialize this task."""
50
55
 
51
56
  self.name = name
57
+ self.set_markdown(markdown=markdown, package=PKG_NAME)
52
58
  LoggerMixinLevelControl.__init__(self, logger=_getLogger(self.name))
53
59
  self._task: _Optional[_asyncio.Task[None]] = None
54
60