runtimepy 5.6.3__py3-none-any.whl → 5.7.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.
Files changed (57) 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 +13 -3
  5. runtimepy/data/css/main.css +4 -1
  6. runtimepy/data/dummy_load.yaml +34 -1
  7. runtimepy/data/js/classes/WindowHashManager.js +29 -7
  8. runtimepy/data/md/Connection.md +3 -0
  9. runtimepy/data/md/PeriodicTask.md +3 -0
  10. runtimepy/data/md/RuntimeStruct.md +3 -0
  11. runtimepy/data/md/RuntimepyPeer.md +3 -0
  12. runtimepy/data/md/SinusoidTask.md +20 -0
  13. runtimepy/data/schemas/ClientConnectionConfig.yaml +1 -0
  14. runtimepy/data/schemas/PeerProcessConfig.yaml +1 -0
  15. runtimepy/data/schemas/TaskConfig.yaml +1 -0
  16. runtimepy/data/schemas/has_markdown.yaml +4 -0
  17. runtimepy/mapping.py +1 -1
  18. runtimepy/message/interface.py +2 -2
  19. runtimepy/mixins/environment.py +10 -4
  20. runtimepy/mixins/logging.py +54 -1
  21. runtimepy/net/arbiter/base.py +13 -3
  22. runtimepy/net/arbiter/config/__init__.py +6 -2
  23. runtimepy/net/arbiter/imports/__init__.py +5 -5
  24. runtimepy/net/arbiter/imports/util.py +3 -1
  25. runtimepy/net/connection.py +7 -1
  26. runtimepy/net/http/common.py +5 -0
  27. runtimepy/net/http/header.py +5 -1
  28. runtimepy/net/http/response.py +1 -1
  29. runtimepy/net/server/app/__init__.py +3 -1
  30. runtimepy/net/server/app/bootstrap/elements.py +38 -1
  31. runtimepy/net/server/app/bootstrap/tabs.py +12 -3
  32. runtimepy/net/server/app/env/__init__.py +74 -20
  33. runtimepy/net/server/app/env/modal.py +1 -1
  34. runtimepy/net/server/app/env/settings.py +2 -2
  35. runtimepy/net/server/app/env/tab/base.py +5 -1
  36. runtimepy/net/server/app/env/tab/html.py +8 -8
  37. runtimepy/net/server/app/placeholder.py +1 -1
  38. runtimepy/net/server/app/sound.py +1 -1
  39. runtimepy/net/server/struct/__init__.py +1 -2
  40. runtimepy/net/server/websocket/state.py +3 -1
  41. runtimepy/net/tcp/connection.py +14 -4
  42. runtimepy/net/tcp/http/__init__.py +9 -7
  43. runtimepy/net/udp/connection.py +11 -4
  44. runtimepy/net/udp/tftp/__init__.py +1 -2
  45. runtimepy/net/websocket/connection.py +10 -5
  46. runtimepy/requirements.txt +2 -1
  47. runtimepy/struct/__init__.py +12 -2
  48. runtimepy/subprocess/interface.py +17 -4
  49. runtimepy/subprocess/peer.py +20 -6
  50. runtimepy/task/basic/periodic.py +7 -1
  51. runtimepy/util.py +0 -81
  52. {runtimepy-5.6.3.dist-info → runtimepy-5.7.0.dist-info}/METADATA +8 -7
  53. {runtimepy-5.6.3.dist-info → runtimepy-5.7.0.dist-info}/RECORD +57 -51
  54. {runtimepy-5.6.3.dist-info → runtimepy-5.7.0.dist-info}/WHEEL +1 -1
  55. {runtimepy-5.6.3.dist-info → runtimepy-5.7.0.dist-info}/LICENSE +0 -0
  56. {runtimepy-5.6.3.dist-info → runtimepy-5.7.0.dist-info}/entry_points.txt +0 -0
  57. {runtimepy-5.6.3.dist-info → runtimepy-5.7.0.dist-info}/top_level.txt +0 -0
@@ -7,6 +7,9 @@ from contextlib import suppress
7
7
  from importlib import import_module as _import_module
8
8
  from typing import Any
9
9
 
10
+ # third-party
11
+ from vcorelib.names import import_str_and_item
12
+
10
13
  # internal
11
14
  from runtimepy.net.arbiter.info import AppInfo
12
15
  from runtimepy.net.server import RuntimepyServerConnection
@@ -17,7 +20,6 @@ from runtimepy.net.server.app.create import (
17
20
  )
18
21
  from runtimepy.net.server.app.landing_page import landing_page
19
22
  from runtimepy.subprocess import spawn_exec
20
- from runtimepy.util import import_str_and_item
21
23
 
22
24
 
23
25
  async def launch_browser(app: AppInfo) -> None:
@@ -3,17 +3,19 @@ A module for creating various bootstrap-related elements.
3
3
  """
4
4
 
5
5
  # built-in
6
+ from io import StringIO
6
7
  from typing import Optional
7
8
 
8
9
  # third-party
9
10
  from svgen.element import Element
10
11
  from svgen.element.html import div
12
+ from vcorelib.io.file_writer import IndentedFileWriter
11
13
 
12
14
  # internal
13
15
  from runtimepy.net.server.app.bootstrap import icon_str
14
16
 
15
17
  TEXT = "font-monospace"
16
- BOOTSTRAP_BUTTON = f"rounded-0 {TEXT} button-bodge"
18
+ BOOTSTRAP_BUTTON = f"rounded-0 {TEXT} button-bodge text-nowrap"
17
19
 
18
20
 
19
21
  def flex(kind: str = "row", **kwargs) -> Element:
@@ -161,3 +163,38 @@ def slider(
161
163
  # div(tag="option", value=start + (idx * step), parent=markers)
162
164
 
163
165
  return elem
166
+
167
+
168
+ def centered_markdown(
169
+ parent: Element, markdown: str, *container_classes: str
170
+ ) -> None:
171
+ """Add centered markdown."""
172
+
173
+ container = div(parent=parent)
174
+ container.add_class(
175
+ "flex-grow-1",
176
+ "d-flex",
177
+ "flex-column",
178
+ "justify-content-between",
179
+ *container_classes,
180
+ )
181
+
182
+ div(parent=container)
183
+
184
+ horiz_container = div(parent=container)
185
+ horiz_container.add_class("d-flex", "flex-row", "justify-content-between")
186
+
187
+ div(parent=horiz_container)
188
+
189
+ with StringIO() as stream:
190
+ writer = IndentedFileWriter(stream)
191
+ writer.write_markdown(markdown)
192
+ div(
193
+ text=stream.getvalue(),
194
+ parent=horiz_container,
195
+ class_str="text-body p-3 pb-0",
196
+ )
197
+
198
+ div(parent=horiz_container)
199
+
200
+ div(parent=container)
@@ -8,8 +8,10 @@ from svgen.element.html import div
8
8
 
9
9
  # internal
10
10
  from runtimepy import PKG_NAME
11
+ from runtimepy.net.server.app.bootstrap import icon_str
11
12
  from runtimepy.net.server.app.bootstrap.elements import (
12
13
  BOOTSTRAP_BUTTON,
14
+ bootstrap_button,
13
15
  collapse_button,
14
16
  flex,
15
17
  toggle_button,
@@ -85,16 +87,23 @@ class TabbedContent:
85
87
 
86
88
  # Create application container.
87
89
  self.container = div(parent=parent, id=name)
88
- self.container.add_class("d-flex", "align-items-start")
90
+ self.container.add_class("d-flex", "align-items-start", "bg-body")
89
91
 
90
92
  # Dark theme.
91
93
  self.container["data-bs-theme"] = "dark"
92
- parent.add_class("bg-dark")
93
94
 
94
95
  # Buttons.
95
96
  self.button_column = div(parent=self.container)
96
97
  self.button_column.add_class(
97
- "d-flex", "flex-column", "h-100", "bg-secondary-subtle"
98
+ "d-flex", "flex-column", "h-100", "bg-dark-subtle"
99
+ )
100
+
101
+ # Dark/light theme switch button.
102
+ bootstrap_button(
103
+ icon_str("lightbulb"),
104
+ tooltip=" Toggle light/dark.",
105
+ id="theme-button",
106
+ parent=self.button_column,
98
107
  )
99
108
 
100
109
  # Toggle tabs button.
@@ -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,10 +147,16 @@ 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.
108
- div(id=f"{PKG_NAME}-splash", parent=tabs.container)
158
+ div(
159
+ id=f"{PKG_NAME}-splash",
160
+ parent=tabs.container,
161
+ class_str="bg-success-subtle bg-gradient",
162
+ )
@@ -32,7 +32,7 @@ class Modal:
32
32
 
33
33
  content = div(
34
34
  parent=div(
35
- parent=modal, class_str="modal-dialog text-light " + TEXT
35
+ parent=modal, class_str="modal-dialog text-body " + TEXT
36
36
  ),
37
37
  class_str="modal-content rounded-0",
38
38
  )
@@ -43,7 +43,7 @@ def plot_settings(tabs: TabbedContent) -> None:
43
43
  div(
44
44
  text="0 ms ('high', run at native refresh rate)",
45
45
  parent=container,
46
- class_str="text-nowrap text-primary",
46
+ class_str="text-nowrap text-body-emphasis",
47
47
  )
48
48
 
49
49
  slider(
@@ -53,7 +53,7 @@ def plot_settings(tabs: TabbedContent) -> None:
53
53
  div(
54
54
  text="100 ms ('low', 10 Hz)",
55
55
  parent=container,
56
- class_str="text-nowrap text-primary",
56
+ class_str="text-nowrap text-body-emphasis",
57
57
  )
58
58
 
59
59
  div(tag="hr", parent=modal.body)
@@ -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:
@@ -238,7 +238,7 @@ class ChannelEnvironmentTabHtml(ChannelEnvironmentTabControls):
238
238
  logs = div(
239
239
  tag="textarea",
240
240
  parent=div(parent=vert_container, class_str="form-floating"),
241
- class_str=f"form-control rounded-0 {TEXT} text-logs",
241
+ class_str=(f"form-control rounded-0 {TEXT} text-logs"),
242
242
  id=self.get_id("logs"),
243
243
  title=f"Text logs for {self.name}.",
244
244
  )
@@ -246,19 +246,19 @@ 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.
258
258
  div(
259
259
  id=self.get_id("divider"),
260
260
  parent=container,
261
- class_str="vertical-divider border-start",
261
+ class_str="vertical-divider border-start bg-dark-subtle",
262
262
  )
263
263
 
264
264
  self._compose_plot(container)
@@ -19,7 +19,7 @@ class DummyTab(Tab):
19
19
  def compose(self, parent: Element) -> None:
20
20
  """Compose the tab's HTML elements."""
21
21
 
22
- parent.add_class("text-light")
22
+ parent.add_class("text-body")
23
23
 
24
24
  for idx in range(10):
25
25
  div(parent=parent, text="Hello, world! " + str(idx))
@@ -17,7 +17,7 @@ class SoundTab(Tab):
17
17
  def compose(self, parent: Element) -> None:
18
18
  """Compose the tab's HTML elements."""
19
19
 
20
- container = div(parent=parent, class_str="text-light")
20
+ container = div(parent=parent, class_str="text-body")
21
21
 
22
22
  div(text="Hello, world! 1", parent=container)
23
23
  div(text="Hello, world! 2", parent=container)
@@ -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.2
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: