runtimepy 5.6.3__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.
- 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 +1 -0
- runtimepy/data/css/main.css +4 -0
- runtimepy/data/dummy_load.yaml +34 -1
- 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/env/__init__.py +69 -19
- runtimepy/net/server/app/env/tab/base.py +5 -1
- runtimepy/net/server/app/env/tab/html.py +6 -6
- 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.6.4.dist-info}/METADATA +8 -7
- {runtimepy-5.6.3.dist-info → runtimepy-5.6.4.dist-info}/RECORD +51 -45
- {runtimepy-5.6.3.dist-info → runtimepy-5.6.4.dist-info}/LICENSE +0 -0
- {runtimepy-5.6.3.dist-info → runtimepy-5.6.4.dist-info}/WHEEL +0 -0
- {runtimepy-5.6.3.dist-info → runtimepy-5.6.4.dist-info}/entry_points.txt +0 -0
- {runtimepy-5.6.3.dist-info → runtimepy-5.6.4.dist-info}/top_level.txt +0 -0
runtimepy/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# =====================================
|
|
2
2
|
# generator=datazen
|
|
3
3
|
# version=3.1.4
|
|
4
|
-
# hash=
|
|
4
|
+
# hash=beefe82269955725f177c01474f7cea1
|
|
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.6.
|
|
13
|
+
VERSION = "5.6.4"
|
|
14
14
|
|
|
15
15
|
# runtimepy-specific content.
|
|
16
16
|
METRICS_NAME = "metrics"
|
|
@@ -15,6 +15,7 @@ from vcorelib import DEFAULT_ENCODING
|
|
|
15
15
|
from vcorelib.io import ARBITER, JsonObject
|
|
16
16
|
from vcorelib.logging import DEFAULT_TIME_FORMAT, LoggerMixin
|
|
17
17
|
from vcorelib.math import default_time_ns, nano_str
|
|
18
|
+
from vcorelib.names import name_search
|
|
18
19
|
|
|
19
20
|
# internal
|
|
20
21
|
from runtimepy.channel.environment import ChannelEnvironment
|
|
@@ -26,7 +27,6 @@ from runtimepy.channel.environment.command.processor import (
|
|
|
26
27
|
)
|
|
27
28
|
from runtimepy.channel.registry import ParsedEvent
|
|
28
29
|
from runtimepy.mapping import DEFAULT_PATTERN
|
|
29
|
-
from runtimepy.util import name_search
|
|
30
30
|
|
|
31
31
|
# Declared so we re-export FieldOrChannel after moving where it's declared.
|
|
32
32
|
__all__ = [
|
|
@@ -6,6 +6,9 @@ A module implementing channel-environment telemetry registration.
|
|
|
6
6
|
from contextlib import ExitStack, contextmanager
|
|
7
7
|
from typing import BinaryIO, Iterator, Optional, cast
|
|
8
8
|
|
|
9
|
+
# third-party
|
|
10
|
+
from vcorelib.names import name_search
|
|
11
|
+
|
|
9
12
|
# internal
|
|
10
13
|
from runtimepy.channel.environment.base import (
|
|
11
14
|
BaseChannelEnvironment as _BaseChannelEnvironment,
|
|
@@ -14,7 +17,6 @@ from runtimepy.channel.event import PrimitiveEvent
|
|
|
14
17
|
from runtimepy.channel.registry import ParsedEvent
|
|
15
18
|
from runtimepy.mapping import DEFAULT_PATTERN
|
|
16
19
|
from runtimepy.metrics.channel import ChannelMetrics
|
|
17
|
-
from runtimepy.util import name_search
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
class TelemetryChannelEnvironment(_BaseChannelEnvironment):
|
runtimepy/data/css/main.css
CHANGED
runtimepy/data/dummy_load.yaml
CHANGED
|
@@ -9,7 +9,19 @@ tasks:
|
|
|
9
9
|
# Sinusoids.
|
|
10
10
|
- {name: wave1, factory: sinusoid, period_s: 0.01}
|
|
11
11
|
- {name: wave2, factory: sinusoid, period_s: 0.02}
|
|
12
|
-
-
|
|
12
|
+
- name: wave3
|
|
13
|
+
factory: sinusoid
|
|
14
|
+
period_s: 0.03
|
|
15
|
+
markdown: |
|
|
16
|
+
# Markdown for wave3
|
|
17
|
+
|
|
18
|
+
* list element 1
|
|
19
|
+
* list element 2
|
|
20
|
+
* list element 3
|
|
21
|
+
|
|
22
|
+
To be continued.
|
|
23
|
+
|
|
24
|
+
`peace`
|
|
13
25
|
|
|
14
26
|
# Drive interactions with runtime entities that won't otherwise be polled.
|
|
15
27
|
- {name: app, factory: SampleApp, period_s: 0.25}
|
|
@@ -21,6 +33,11 @@ clients:
|
|
|
21
33
|
defer: true
|
|
22
34
|
kwargs:
|
|
23
35
|
remote_addr: [localhost, "$udp_json"]
|
|
36
|
+
markdown: |
|
|
37
|
+
# `udp_json_client`
|
|
38
|
+
|
|
39
|
+
Connects to `udp_json_server`.
|
|
40
|
+
|
|
24
41
|
- factory: udp_json
|
|
25
42
|
name: udp_json_server
|
|
26
43
|
kwargs:
|
|
@@ -36,8 +53,19 @@ structs:
|
|
|
36
53
|
b: 2
|
|
37
54
|
c: 3
|
|
38
55
|
|
|
56
|
+
markdown: |
|
|
57
|
+
# Docs for `example.struct1`
|
|
58
|
+
|
|
59
|
+
Should be shown for peer process as well?
|
|
60
|
+
|
|
39
61
|
- name: struct2
|
|
40
62
|
factory: sample_struct
|
|
63
|
+
config:
|
|
64
|
+
markdown: |
|
|
65
|
+
# `struct2`
|
|
66
|
+
|
|
67
|
+
One of the structs of all time.
|
|
68
|
+
|
|
41
69
|
- name: struct3
|
|
42
70
|
factory: sample_struct
|
|
43
71
|
|
|
@@ -46,6 +74,11 @@ processes:
|
|
|
46
74
|
- name: proc1
|
|
47
75
|
factory: sample_peer
|
|
48
76
|
|
|
77
|
+
markdown: |
|
|
78
|
+
# Markdown for `proc1`
|
|
79
|
+
|
|
80
|
+
A process that
|
|
81
|
+
|
|
49
82
|
# The peer itself runs an arbiter process.
|
|
50
83
|
config:
|
|
51
84
|
includes:
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Sinusoid Tasks
|
|
2
|
+
|
|
3
|
+
## Intent
|
|
4
|
+
|
|
5
|
+
These tasks compute
|
|
6
|
+
[`math.sin`](https://docs.python.org/3/library/math.html#math.sin) and
|
|
7
|
+
[`math.cos`](https://docs.python.org/3/library/math.html#math.cos) on every
|
|
8
|
+
iteration, which advances one "step" per dispatch where the "step angle"
|
|
9
|
+
advanced depends on the `steps` control.
|
|
10
|
+
|
|
11
|
+
Once the current "step angle" is computed, controls `amplitude` and phase
|
|
12
|
+
angles are considered for final `sin` and `cos` channel values.
|
|
13
|
+
|
|
14
|
+
## Discussion
|
|
15
|
+
|
|
16
|
+
* Is it worth adding a `tan` channel that computes
|
|
17
|
+
[`math.tan`](https://docs.python.org/3/library/math.html#math.tan) and/or other
|
|
18
|
+
trigonometric functions?
|
|
19
|
+
* Is it worth adding a "number of steps" control, such that one task iteration
|
|
20
|
+
could advance multiple waveform steps?
|
runtimepy/mapping.py
CHANGED
|
@@ -13,10 +13,10 @@ from typing import cast as _cast
|
|
|
13
13
|
|
|
14
14
|
# third-party
|
|
15
15
|
from vcorelib.logging import LoggerMixin
|
|
16
|
+
from vcorelib.names import name_search
|
|
16
17
|
|
|
17
18
|
# internal
|
|
18
19
|
from runtimepy.mixins.regex import RegexMixin as _RegexMixin
|
|
19
|
-
from runtimepy.util import name_search
|
|
20
20
|
|
|
21
21
|
# This determines types that are valid as keys.
|
|
22
22
|
T = _TypeVar("T", int, bool)
|
runtimepy/message/interface.py
CHANGED
|
@@ -11,7 +11,7 @@ from typing import Any, Optional, Union
|
|
|
11
11
|
|
|
12
12
|
# third-party
|
|
13
13
|
from vcorelib.dict.codec import JsonCodec
|
|
14
|
-
from vcorelib.logging import LoggerType
|
|
14
|
+
from vcorelib.logging import ListLogger, LoggerType
|
|
15
15
|
from vcorelib.target.resolver import TargetResolver
|
|
16
16
|
|
|
17
17
|
# internal
|
|
@@ -39,7 +39,7 @@ from runtimepy.message.types import (
|
|
|
39
39
|
T,
|
|
40
40
|
TypedHandler,
|
|
41
41
|
)
|
|
42
|
-
from runtimepy.util import Identifier
|
|
42
|
+
from runtimepy.util import Identifier
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
class JsonMessageInterface:
|
runtimepy/mixins/environment.py
CHANGED
|
@@ -30,11 +30,14 @@ class ChannelEnvironmentMixin:
|
|
|
30
30
|
return id(self.env)
|
|
31
31
|
|
|
32
32
|
def register_task_metrics(
|
|
33
|
-
self,
|
|
33
|
+
self,
|
|
34
|
+
metrics: PeriodicTaskMetrics,
|
|
35
|
+
*names: str,
|
|
36
|
+
namespace: str = METRICS_NAME,
|
|
34
37
|
) -> None:
|
|
35
38
|
"""Register periodic task metrics."""
|
|
36
39
|
|
|
37
|
-
with self.env.names_pushed(namespace):
|
|
40
|
+
with self.env.names_pushed(namespace, *names):
|
|
38
41
|
self.env.channel(
|
|
39
42
|
"dispatches",
|
|
40
43
|
metrics.dispatches,
|
|
@@ -104,11 +107,14 @@ class ChannelEnvironmentMixin:
|
|
|
104
107
|
)
|
|
105
108
|
|
|
106
109
|
def register_connection_metrics(
|
|
107
|
-
self,
|
|
110
|
+
self,
|
|
111
|
+
metrics: ConnectionMetrics,
|
|
112
|
+
*names: str,
|
|
113
|
+
namespace: str = METRICS_NAME,
|
|
108
114
|
) -> None:
|
|
109
115
|
"""Register connection metrics."""
|
|
110
116
|
|
|
111
|
-
with self.env.names_pushed(namespace):
|
|
117
|
+
with self.env.names_pushed(namespace, *names):
|
|
112
118
|
for name, direction, verb in [
|
|
113
119
|
("tx", metrics.tx, "transmitted"),
|
|
114
120
|
("rx", metrics.rx, "received"),
|
runtimepy/mixins/logging.py
CHANGED
|
@@ -3,10 +3,15 @@ A module implementing a logger-mixin extension.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
# built-in
|
|
6
|
+
from contextlib import AsyncExitStack
|
|
7
|
+
import io
|
|
6
8
|
import logging
|
|
9
|
+
from typing import Any, Iterable
|
|
7
10
|
|
|
8
11
|
# third-party
|
|
9
|
-
|
|
12
|
+
import aiofiles
|
|
13
|
+
from vcorelib.logging import LoggerMixin, LoggerType
|
|
14
|
+
from vcorelib.paths import Pathlike, normalize
|
|
10
15
|
|
|
11
16
|
# internal
|
|
12
17
|
from runtimepy.channel.environment import ChannelEnvironment
|
|
@@ -23,6 +28,9 @@ class LogLevel(RuntimeIntEnum):
|
|
|
23
28
|
CRITICAL = logging.CRITICAL
|
|
24
29
|
|
|
25
30
|
|
|
31
|
+
LogLevellike = LogLevel | int | str
|
|
32
|
+
|
|
33
|
+
|
|
26
34
|
class LoggerMixinLevelControl(LoggerMixin):
|
|
27
35
|
"""A logger mixin that exposes a runtime-controllable level."""
|
|
28
36
|
|
|
@@ -55,3 +63,48 @@ class LoggerMixinLevelControl(LoggerMixin):
|
|
|
55
63
|
env.set(name, initial)
|
|
56
64
|
|
|
57
65
|
del chan
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
LogPaths = Iterable[tuple[LogLevellike, Pathlike]]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class LogCaptureMixin:
|
|
72
|
+
"""A simple async file-reading interface."""
|
|
73
|
+
|
|
74
|
+
logger: LoggerType
|
|
75
|
+
|
|
76
|
+
# Open aiofiles handles.
|
|
77
|
+
streams: list[tuple[int, Any]]
|
|
78
|
+
|
|
79
|
+
ext_log_extra = {"external": True}
|
|
80
|
+
|
|
81
|
+
async def init_log_capture(
|
|
82
|
+
self, stack: AsyncExitStack, log_paths: LogPaths
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Initialize this task with application information."""
|
|
85
|
+
|
|
86
|
+
self.streams = [
|
|
87
|
+
(
|
|
88
|
+
LogLevel.normalize(level),
|
|
89
|
+
await stack.enter_async_context(aiofiles.open(path, mode="r")),
|
|
90
|
+
)
|
|
91
|
+
for level, path in log_paths
|
|
92
|
+
if normalize(path).is_file()
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
# Don't handle any kind of backhaul.
|
|
96
|
+
for stream in self.streams:
|
|
97
|
+
await stream[1].seek(0, io.SEEK_END)
|
|
98
|
+
|
|
99
|
+
def log_line(self, level: int, data: str) -> None:
|
|
100
|
+
"""Log a line for output."""
|
|
101
|
+
self.logger.log(level, data, extra=self.ext_log_extra)
|
|
102
|
+
|
|
103
|
+
async def dispatch_log_capture(self) -> None:
|
|
104
|
+
"""Get the next line from this log stream."""
|
|
105
|
+
|
|
106
|
+
for level, stream in self.streams:
|
|
107
|
+
line = (await stream.readline()).rstrip()
|
|
108
|
+
while line:
|
|
109
|
+
self.log_line(level, line)
|
|
110
|
+
line = (await stream.readline()).rstrip()
|
runtimepy/net/arbiter/base.py
CHANGED
|
@@ -48,7 +48,9 @@ from runtimepy.subprocess.peer import RuntimepyPeer as _RuntimepyPeer
|
|
|
48
48
|
from runtimepy.tui.mixin import CursesWindow, TuiMixin
|
|
49
49
|
|
|
50
50
|
ServerTask = _Awaitable[None]
|
|
51
|
-
RuntimeProcessTask = tuple[
|
|
51
|
+
RuntimeProcessTask = tuple[
|
|
52
|
+
type[_RuntimepyPeer], str, _JsonObject, str, Optional[str]
|
|
53
|
+
]
|
|
52
54
|
|
|
53
55
|
|
|
54
56
|
async def init_only(app: AppInfo) -> int:
|
|
@@ -226,9 +228,17 @@ class BaseConnectionArbiter(_NamespaceMixin, _LoggerMixin, TuiMixin):
|
|
|
226
228
|
async def _start_processes(self, stack: _AsyncExitStack) -> None:
|
|
227
229
|
"""Start processes."""
|
|
228
230
|
|
|
229
|
-
for name, (
|
|
231
|
+
for name, (
|
|
232
|
+
peer,
|
|
233
|
+
name,
|
|
234
|
+
config,
|
|
235
|
+
import_str,
|
|
236
|
+
markdown,
|
|
237
|
+
) in self._peers.items():
|
|
230
238
|
self._runtime_peers[name] = await stack.enter_async_context(
|
|
231
|
-
peer.running_program(
|
|
239
|
+
peer.running_program(
|
|
240
|
+
name, config, import_str, markdown=markdown
|
|
241
|
+
)
|
|
232
242
|
)
|
|
233
243
|
self.logger.info("Started process '%s'.", name)
|
|
234
244
|
|
|
@@ -17,6 +17,7 @@ from vcorelib.dict.env import dict_resolve_env_vars, list_resolve_env_vars
|
|
|
17
17
|
from vcorelib.io import ARBITER as _ARBITER
|
|
18
18
|
from vcorelib.io.types import JsonObject as _JsonObject
|
|
19
19
|
from vcorelib.logging import LoggerMixin as _LoggerMixin
|
|
20
|
+
from vcorelib.names import import_str_and_item
|
|
20
21
|
from vcorelib.paths import Pathlike as _Pathlike
|
|
21
22
|
from vcorelib.paths import find_file
|
|
22
23
|
from vcorelib.paths import normalize as _normalize
|
|
@@ -29,7 +30,6 @@ from runtimepy.net.arbiter.imports import (
|
|
|
29
30
|
ImportConnectionArbiter as _ImportConnectionArbiter,
|
|
30
31
|
)
|
|
31
32
|
from runtimepy.net.arbiter.imports.util import get_apps
|
|
32
|
-
from runtimepy.util import import_str_and_item
|
|
33
33
|
|
|
34
34
|
ConfigObject = dict[str, _Any]
|
|
35
35
|
ConfigBuilder = _Callable[[ConfigObject], None]
|
|
@@ -168,6 +168,7 @@ class ConfigConnectionArbiter(_ImportConnectionArbiter):
|
|
|
168
168
|
kwargs = dict_resolve_env_vars(
|
|
169
169
|
client.get("kwargs", {}), env=config.ports # type: ignore
|
|
170
170
|
)
|
|
171
|
+
kwargs.setdefault("markdown", client.get("markdown"))
|
|
171
172
|
|
|
172
173
|
assert await self.factory_client(
|
|
173
174
|
factory,
|
|
@@ -201,6 +202,7 @@ class ConfigConnectionArbiter(_ImportConnectionArbiter):
|
|
|
201
202
|
name,
|
|
202
203
|
period_s=task["period_s"],
|
|
203
204
|
average_depth=task["average_depth"],
|
|
205
|
+
markdown=task.get("markdown"),
|
|
204
206
|
), f"Couldn't register task '{name}' ({factory})!"
|
|
205
207
|
|
|
206
208
|
# Register structs.
|
|
@@ -216,7 +218,9 @@ class ConfigConnectionArbiter(_ImportConnectionArbiter):
|
|
|
216
218
|
name = process["name"]
|
|
217
219
|
factory = process["factory"]
|
|
218
220
|
assert self.factory_process(
|
|
219
|
-
factory,
|
|
221
|
+
factory,
|
|
222
|
+
name,
|
|
223
|
+
process,
|
|
220
224
|
), f"Couldn't register process '{name}' ({factory})!"
|
|
221
225
|
|
|
222
226
|
# Load initialization methods.
|
|
@@ -8,7 +8,7 @@ from importlib import import_module as _import_module
|
|
|
8
8
|
|
|
9
9
|
# third-party
|
|
10
10
|
from vcorelib.io.types import JsonObject as _JsonObject
|
|
11
|
-
from vcorelib.names import to_snake
|
|
11
|
+
from vcorelib.names import import_str_and_item, to_snake
|
|
12
12
|
|
|
13
13
|
# internal
|
|
14
14
|
from runtimepy.net.arbiter.factory import (
|
|
@@ -23,7 +23,6 @@ from runtimepy.net.arbiter.factory.task import (
|
|
|
23
23
|
from runtimepy.net.arbiter.info import RuntimeStruct as _RuntimeStruct
|
|
24
24
|
from runtimepy.net.arbiter.task import TaskFactory as _TaskFactory
|
|
25
25
|
from runtimepy.subprocess.peer import RuntimepyPeer as _RuntimepyPeer
|
|
26
|
-
from runtimepy.util import import_str_and_item
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
class ImportConnectionArbiter(
|
|
@@ -111,7 +110,7 @@ class ImportConnectionArbiter(
|
|
|
111
110
|
return result
|
|
112
111
|
|
|
113
112
|
def factory_process(
|
|
114
|
-
self, factory: str, name: str,
|
|
113
|
+
self, factory: str, name: str, top_level: _JsonObject
|
|
115
114
|
) -> bool:
|
|
116
115
|
"""Register a runtime process."""
|
|
117
116
|
|
|
@@ -121,8 +120,9 @@ class ImportConnectionArbiter(
|
|
|
121
120
|
self._peers[name] = (
|
|
122
121
|
self._peer_factories[factory],
|
|
123
122
|
name,
|
|
124
|
-
config,
|
|
125
|
-
program,
|
|
123
|
+
top_level.get("config", {}), # type: ignore
|
|
124
|
+
str(top_level["program"]),
|
|
125
|
+
top_level.get("markdown"),
|
|
126
126
|
)
|
|
127
127
|
result = True
|
|
128
128
|
|
|
@@ -5,10 +5,12 @@ Utility interfaces for arbiter runtime-import mechanisms.
|
|
|
5
5
|
# built-in
|
|
6
6
|
from importlib import import_module as _import_module
|
|
7
7
|
|
|
8
|
+
# third-party
|
|
9
|
+
from vcorelib.names import import_str_and_item
|
|
10
|
+
|
|
8
11
|
# internal
|
|
9
12
|
from runtimepy.net.arbiter.config.codec import ConfigApps
|
|
10
13
|
from runtimepy.net.arbiter.info import ArbiterApps
|
|
11
|
-
from runtimepy.util import import_str_and_item
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
def get_apps(
|
runtimepy/net/connection.py
CHANGED
|
@@ -13,9 +13,11 @@ from typing import Union as _Union
|
|
|
13
13
|
|
|
14
14
|
# third-party
|
|
15
15
|
from vcorelib.asyncio import log_exceptions as _log_exceptions
|
|
16
|
+
from vcorelib.io import MarkdownMixin
|
|
16
17
|
from vcorelib.logging import LoggerType as _LoggerType
|
|
17
18
|
|
|
18
19
|
# internal
|
|
20
|
+
from runtimepy import PKG_NAME
|
|
19
21
|
from runtimepy.channel.environment import ChannelEnvironment
|
|
20
22
|
from runtimepy.channel.environment.command.processor import (
|
|
21
23
|
ChannelCommandProcessor,
|
|
@@ -30,7 +32,9 @@ from runtimepy.primitives.byte_order import DEFAULT_BYTE_ORDER, ByteOrder
|
|
|
30
32
|
BinaryMessage = _Union[bytes, bytearray, memoryview]
|
|
31
33
|
|
|
32
34
|
|
|
33
|
-
class Connection(
|
|
35
|
+
class Connection(
|
|
36
|
+
LoggerMixinLevelControl, ChannelEnvironmentMixin, MarkdownMixin, _ABC
|
|
37
|
+
):
|
|
34
38
|
"""A connection interface."""
|
|
35
39
|
|
|
36
40
|
uses_text_tx_queue = True
|
|
@@ -46,9 +50,11 @@ class Connection(LoggerMixinLevelControl, ChannelEnvironmentMixin, _ABC):
|
|
|
46
50
|
logger: _LoggerType,
|
|
47
51
|
env: ChannelEnvironment = None,
|
|
48
52
|
add_metrics: bool = True,
|
|
53
|
+
markdown: str = None,
|
|
49
54
|
) -> None:
|
|
50
55
|
"""Initialize this connection."""
|
|
51
56
|
|
|
57
|
+
self.set_markdown(markdown=markdown, package=PKG_NAME)
|
|
52
58
|
LoggerMixinLevelControl.__init__(self, logger=logger)
|
|
53
59
|
|
|
54
60
|
# A queue for out-going text messages. Connections that don't use
|
runtimepy/net/http/common.py
CHANGED
|
@@ -9,6 +9,7 @@ from typing import Optional, TextIO, Union
|
|
|
9
9
|
|
|
10
10
|
# third-party
|
|
11
11
|
from vcorelib import DEFAULT_ENCODING
|
|
12
|
+
from vcorelib.logging import LoggerType
|
|
12
13
|
|
|
13
14
|
HTTPMethodlike = Union[str, http.HTTPMethod]
|
|
14
15
|
HEADER_LINESEP = "\r\n"
|
|
@@ -62,6 +63,10 @@ class HeadersMixin(ABC):
|
|
|
62
63
|
def from_lines(self, lines: list[str]) -> None:
|
|
63
64
|
"""Update this request from line data."""
|
|
64
65
|
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def log(self, logger: LoggerType, out: bool, **kwargs) -> None:
|
|
68
|
+
"""Log information about this response header."""
|
|
69
|
+
|
|
65
70
|
@abstractmethod
|
|
66
71
|
def __str__(self) -> str:
|
|
67
72
|
"""Get this response as a string."""
|
runtimepy/net/http/header.py
CHANGED
|
@@ -55,7 +55,11 @@ class RequestHeader(HeadersMixin):
|
|
|
55
55
|
HeadersMixin.__init__(self, lines[1:])
|
|
56
56
|
|
|
57
57
|
def log(
|
|
58
|
-
self,
|
|
58
|
+
self,
|
|
59
|
+
logger: LoggerType,
|
|
60
|
+
out: bool,
|
|
61
|
+
level: int = logging.DEBUG,
|
|
62
|
+
**_,
|
|
59
63
|
) -> None:
|
|
60
64
|
"""Log information about this request header."""
|
|
61
65
|
|
runtimepy/net/http/response.py
CHANGED
|
@@ -72,7 +72,7 @@ class ResponseHeader(HeadersMixin):
|
|
|
72
72
|
|
|
73
73
|
return stream.getvalue()
|
|
74
74
|
|
|
75
|
-
def log(self, logger: LoggerType, out: bool) -> None:
|
|
75
|
+
def log(self, logger: LoggerType, out: bool, **_) -> None:
|
|
76
76
|
"""Log information about this response header."""
|
|
77
77
|
|
|
78
78
|
level = logging.INFO if (200 <= self.status <= 299) else logging.ERROR
|
|
@@ -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-light p-3 pb-0",
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
div(parent=horiz_container)
|
|
199
|
+
|
|
200
|
+
div(parent=container)
|