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.
- runtimepy/__init__.py +2 -2
- runtimepy/channel/environment/command/__init__.py +1 -1
- runtimepy/channel/environment/telemetry.py +3 -1
- runtimepy/data/css/bootstrap_extra.css +13 -3
- runtimepy/data/css/main.css +4 -1
- runtimepy/data/dummy_load.yaml +34 -1
- runtimepy/data/js/classes/WindowHashManager.js +29 -7
- runtimepy/data/md/Connection.md +3 -0
- runtimepy/data/md/PeriodicTask.md +3 -0
- runtimepy/data/md/RuntimeStruct.md +3 -0
- runtimepy/data/md/RuntimepyPeer.md +3 -0
- runtimepy/data/md/SinusoidTask.md +20 -0
- runtimepy/data/schemas/ClientConnectionConfig.yaml +1 -0
- runtimepy/data/schemas/PeerProcessConfig.yaml +1 -0
- runtimepy/data/schemas/TaskConfig.yaml +1 -0
- runtimepy/data/schemas/has_markdown.yaml +4 -0
- runtimepy/mapping.py +1 -1
- runtimepy/message/interface.py +2 -2
- runtimepy/mixins/environment.py +10 -4
- runtimepy/mixins/logging.py +54 -1
- runtimepy/net/arbiter/base.py +13 -3
- runtimepy/net/arbiter/config/__init__.py +6 -2
- runtimepy/net/arbiter/imports/__init__.py +5 -5
- runtimepy/net/arbiter/imports/util.py +3 -1
- runtimepy/net/connection.py +7 -1
- runtimepy/net/http/common.py +5 -0
- runtimepy/net/http/header.py +5 -1
- runtimepy/net/http/response.py +1 -1
- runtimepy/net/server/app/__init__.py +3 -1
- runtimepy/net/server/app/bootstrap/elements.py +38 -1
- runtimepy/net/server/app/bootstrap/tabs.py +12 -3
- runtimepy/net/server/app/env/__init__.py +74 -20
- runtimepy/net/server/app/env/modal.py +1 -1
- runtimepy/net/server/app/env/settings.py +2 -2
- runtimepy/net/server/app/env/tab/base.py +5 -1
- runtimepy/net/server/app/env/tab/html.py +8 -8
- runtimepy/net/server/app/placeholder.py +1 -1
- runtimepy/net/server/app/sound.py +1 -1
- runtimepy/net/server/struct/__init__.py +1 -2
- runtimepy/net/server/websocket/state.py +3 -1
- runtimepy/net/tcp/connection.py +14 -4
- runtimepy/net/tcp/http/__init__.py +9 -7
- runtimepy/net/udp/connection.py +11 -4
- runtimepy/net/udp/tftp/__init__.py +1 -2
- runtimepy/net/websocket/connection.py +10 -5
- runtimepy/requirements.txt +2 -1
- runtimepy/struct/__init__.py +12 -2
- runtimepy/subprocess/interface.py +17 -4
- runtimepy/subprocess/peer.py +20 -6
- runtimepy/task/basic/periodic.py +7 -1
- runtimepy/util.py +0 -81
- {runtimepy-5.6.3.dist-info → runtimepy-5.7.0.dist-info}/METADATA +8 -7
- {runtimepy-5.6.3.dist-info → runtimepy-5.7.0.dist-info}/RECORD +57 -51
- {runtimepy-5.6.3.dist-info → runtimepy-5.7.0.dist-info}/WHEEL +1 -1
- {runtimepy-5.6.3.dist-info → runtimepy-5.7.0.dist-info}/LICENSE +0 -0
- {runtimepy-5.6.3.dist-info → runtimepy-5.7.0.dist-info}/entry_points.txt +0 -0
- {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-
|
|
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
|
|
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
|
|
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
|
|
21
|
-
"""Populate
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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(
|
|
158
|
+
div(
|
|
159
|
+
id=f"{PKG_NAME}-splash",
|
|
160
|
+
parent=tabs.container,
|
|
161
|
+
class_str="bg-success-subtle bg-gradient",
|
|
162
|
+
)
|
|
@@ -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-
|
|
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-
|
|
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
|
-
|
|
250
|
-
# table doesn't take up full vertical space, few channels).
|
|
251
|
-
under_construction(
|
|
249
|
+
centered_markdown(
|
|
252
250
|
vert_container,
|
|
253
|
-
|
|
254
|
-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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]
|
runtimepy/net/tcp/connection.py
CHANGED
|
@@ -51,7 +51,12 @@ class TcpConnection(_Connection, _TransportMixin):
|
|
|
51
51
|
log_alias = "TCP"
|
|
52
52
|
log_prefix = ""
|
|
53
53
|
|
|
54
|
-
def __init__(
|
|
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__(
|
|
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],
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
for header, payload in self.processor.ingest( # type: ignore
|
|
177
|
+
for header, payload in self.processor.ingest(
|
|
180
178
|
data,
|
|
181
|
-
|
|
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(
|
|
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(
|
|
195
|
+
await self.responses.put(
|
|
196
|
+
(cast(ResponseHeader, header), payload)
|
|
197
|
+
)
|
|
196
198
|
|
|
197
199
|
return True
|
runtimepy/net/udp/connection.py
CHANGED
|
@@ -44,7 +44,10 @@ class UdpConnection(_Connection, _TransportMixin):
|
|
|
44
44
|
log_alias = "UDP"
|
|
45
45
|
|
|
46
46
|
def __init__(
|
|
47
|
-
self,
|
|
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__(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
runtimepy/requirements.txt
CHANGED
runtimepy/struct/__init__.py
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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:
|