runtimepy 5.5.0__py3-none-any.whl → 5.6.1__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/commands/server.py +35 -3
- runtimepy/data/js/classes/App.js +33 -0
- runtimepy/data/js/classes/PlotModalManager.js +3 -1
- runtimepy/data/js/classes/WindowHashManager.js +17 -2
- runtimepy/data/js/init.js +12 -0
- runtimepy/data/js/util.js +12 -3
- runtimepy/data/js/worker.js +18 -3
- runtimepy/net/arbiter/tcp/__init__.py +1 -0
- runtimepy/net/server/app/__init__.py +21 -20
- runtimepy/net/server/app/env/__init__.py +2 -2
- runtimepy/net/server/app/env/settings.py +59 -0
- runtimepy/net/ssl.py +33 -0
- runtimepy/net/tcp/connection.py +37 -10
- runtimepy/net/tcp/create.py +2 -1
- runtimepy/net/tcp/http/__init__.py +6 -1
- runtimepy/net/util.py +11 -2
- runtimepy/net/websocket/connection.py +28 -6
- {runtimepy-5.5.0.dist-info → runtimepy-5.6.1.dist-info}/METADATA +14 -7
- {runtimepy-5.5.0.dist-info → runtimepy-5.6.1.dist-info}/RECORD +24 -22
- {runtimepy-5.5.0.dist-info → runtimepy-5.6.1.dist-info}/WHEEL +1 -1
- {runtimepy-5.5.0.dist-info → runtimepy-5.6.1.dist-info}/LICENSE +0 -0
- {runtimepy-5.5.0.dist-info → runtimepy-5.6.1.dist-info}/entry_points.txt +0 -0
- {runtimepy-5.5.0.dist-info → runtimepy-5.6.1.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=d200504a923864c56b7eb211fd10233a
|
|
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.
|
|
13
|
+
VERSION = "5.6.1"
|
|
14
14
|
|
|
15
15
|
# runtimepy-specific content.
|
|
16
16
|
METRICS_NAME = "metrics"
|
runtimepy/commands/server.py
CHANGED
|
@@ -14,6 +14,9 @@ from vcorelib.args import CommandFunction as _CommandFunction
|
|
|
14
14
|
from runtimepy.commands.arbiter import arbiter_cmd
|
|
15
15
|
from runtimepy.commands.common import FACTORIES, arbiter_args, cmd_with_jit
|
|
16
16
|
|
|
17
|
+
SSL_PASSTHROUGH = ["cafile", "capath", "cadata", "certfile"]
|
|
18
|
+
PASSTHROUGH = SSL_PASSTHROUGH + ["keyfile"]
|
|
19
|
+
|
|
17
20
|
|
|
18
21
|
def port_name(args: _Namespace, port: str = "port") -> str:
|
|
19
22
|
"""Get the name for a connection factory's port."""
|
|
@@ -25,7 +28,7 @@ def server_data(args: _Namespace) -> dict[str, Any]:
|
|
|
25
28
|
|
|
26
29
|
return {
|
|
27
30
|
"factory": args.factory,
|
|
28
|
-
"kwargs":
|
|
31
|
+
"kwargs": get_kwargs(args, port=f"${port_name(args)}", host=args.host),
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
|
|
@@ -34,6 +37,26 @@ def is_websocket(args: _Namespace) -> bool:
|
|
|
34
37
|
return "websocket" in args.factory.lower()
|
|
35
38
|
|
|
36
39
|
|
|
40
|
+
def is_ssl(kwargs: dict[str, Any]) -> bool:
|
|
41
|
+
"""Determine if server arugments indicate SSL use."""
|
|
42
|
+
return any(x in kwargs for x in SSL_PASSTHROUGH)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_kwargs(args: _Namespace, **kwargs) -> dict[str, Any]:
|
|
46
|
+
"""Get boilerplate kwargs."""
|
|
47
|
+
|
|
48
|
+
new_kwargs: dict[str, Any] = {**kwargs}
|
|
49
|
+
|
|
50
|
+
# Pass additional arguments through.
|
|
51
|
+
print(args)
|
|
52
|
+
for opt in PASSTHROUGH:
|
|
53
|
+
value = getattr(args, opt, None)
|
|
54
|
+
if value is not None:
|
|
55
|
+
new_kwargs[opt] = value
|
|
56
|
+
|
|
57
|
+
return new_kwargs
|
|
58
|
+
|
|
59
|
+
|
|
37
60
|
def client_data(args: _Namespace) -> dict[str, Any]:
|
|
38
61
|
"""Get client data based on command-line arguments."""
|
|
39
62
|
|
|
@@ -43,7 +66,9 @@ def client_data(args: _Namespace) -> dict[str, Any]:
|
|
|
43
66
|
kwargs: dict[str, Any] = {}
|
|
44
67
|
|
|
45
68
|
if is_websocket(args):
|
|
46
|
-
arg_list.append(
|
|
69
|
+
arg_list.append(
|
|
70
|
+
f"ws{'s' if is_ssl(get_kwargs(args)) else ''}://localhost:{port}"
|
|
71
|
+
)
|
|
47
72
|
elif not args.udp:
|
|
48
73
|
kwargs["host"] = "localhost"
|
|
49
74
|
kwargs["port"] = port
|
|
@@ -76,7 +101,9 @@ def config_data(args: _Namespace) -> dict[str, Any]:
|
|
|
76
101
|
{
|
|
77
102
|
"name": port_name(args, port="server"),
|
|
78
103
|
"factory": args.factory,
|
|
79
|
-
"kwargs":
|
|
104
|
+
"kwargs": get_kwargs(
|
|
105
|
+
args, local_addr=["0.0.0.0", f"${port_name(args)}"]
|
|
106
|
+
),
|
|
80
107
|
}
|
|
81
108
|
)
|
|
82
109
|
|
|
@@ -108,6 +135,11 @@ def add_server_cmd(parser: _ArgumentParser) -> _CommandFunction:
|
|
|
108
135
|
"""Add server-command arguments to its parser."""
|
|
109
136
|
|
|
110
137
|
with arbiter_args(parser, nargs="*"):
|
|
138
|
+
for optional in PASSTHROUGH:
|
|
139
|
+
parser.add_argument(
|
|
140
|
+
f"--{optional}", help="passed directly to instantiation"
|
|
141
|
+
)
|
|
142
|
+
|
|
111
143
|
parser.add_argument(
|
|
112
144
|
"--host",
|
|
113
145
|
default="0.0.0.0",
|
runtimepy/data/js/classes/App.js
CHANGED
|
@@ -53,6 +53,12 @@ class App {
|
|
|
53
53
|
this.switchTab(hash.tab);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/* Handle settings controls. */
|
|
57
|
+
loadSettings();
|
|
58
|
+
|
|
59
|
+
/* Handle individual settings. */
|
|
60
|
+
this.handleInitialMinTxPeriod();
|
|
61
|
+
|
|
56
62
|
startMainLoop();
|
|
57
63
|
}
|
|
58
64
|
}, {once : true});
|
|
@@ -62,6 +68,33 @@ class App {
|
|
|
62
68
|
|
|
63
69
|
bootstrap_init();
|
|
64
70
|
}
|
|
71
|
+
|
|
72
|
+
handleInitialMinTxPeriod() {
|
|
73
|
+
if ("min-tx-period-ms" in settings) {
|
|
74
|
+
/* Set up event handle. */
|
|
75
|
+
setupCursorContext(settings["min-tx-period-ms"], (elem, down, move,
|
|
76
|
+
up) => {
|
|
77
|
+
setupCursorMove(
|
|
78
|
+
elem, down, move, up,
|
|
79
|
+
(event) => { this.updateMinTxPeriod(Number(event.target.value)); });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
/* Set slider to correct value. */
|
|
83
|
+
settings["min-tx-period-ms"].value = hash.minTxPeriod;
|
|
84
|
+
settings["min-tx-period-ms"].title = hash.minTxPeriod;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.updateMinTxPeriod();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
updateMinTxPeriod(value) {
|
|
91
|
+
if (value || value === 0) {
|
|
92
|
+
hash.setMinTxPeriod(value);
|
|
93
|
+
settings["min-tx-period-ms"].title = value;
|
|
94
|
+
}
|
|
95
|
+
this.worker.postMessage(
|
|
96
|
+
{"event" : {"worker" : {"min-tx-period-ms" : hash.minTxPeriod}}});
|
|
97
|
+
}
|
|
65
98
|
}
|
|
66
99
|
|
|
67
100
|
function startMainLoop() {
|
|
@@ -6,6 +6,8 @@ class PlotModalManager {
|
|
|
6
6
|
this.body = this.container.querySelector(".modal-body");
|
|
7
7
|
this.footer = this.container.querySelector(".modal-footer");
|
|
8
8
|
|
|
9
|
+
this.plotStatus = this.container.querySelector("#plot-status-inner");
|
|
10
|
+
|
|
9
11
|
this.byEnv = {};
|
|
10
12
|
}
|
|
11
13
|
|
|
@@ -34,7 +36,7 @@ class PlotModalManager {
|
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
this.
|
|
39
|
+
this.plotStatus.innerHTML = content;
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
|
|
@@ -9,6 +9,7 @@ class WindowHashManager {
|
|
|
9
9
|
this.channelsShown = true;
|
|
10
10
|
this.plotChannels = {};
|
|
11
11
|
this.filters = {};
|
|
12
|
+
this.minTxPeriod = 0.0;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
tabClick(event) {
|
|
@@ -160,8 +161,13 @@ class WindowHashManager {
|
|
|
160
161
|
for (let item of split) {
|
|
161
162
|
if (item.includes("=")) {
|
|
162
163
|
let keyVal = item.split("=");
|
|
163
|
-
if (keyVal.length == 2
|
|
164
|
-
|
|
164
|
+
if (keyVal.length == 2) {
|
|
165
|
+
if (keyVal[0] == "filter" && keyVal[1]) {
|
|
166
|
+
this.updateTabFilter(keyVal[1]);
|
|
167
|
+
}
|
|
168
|
+
if (keyVal[0] == "min-tx-period-ms") {
|
|
169
|
+
this.minTxPeriod = Number(keyVal[1]);
|
|
170
|
+
}
|
|
165
171
|
}
|
|
166
172
|
}
|
|
167
173
|
}
|
|
@@ -176,6 +182,11 @@ class WindowHashManager {
|
|
|
176
182
|
return result;
|
|
177
183
|
}
|
|
178
184
|
|
|
185
|
+
setMinTxPeriod(value) {
|
|
186
|
+
this.minTxPeriod = value;
|
|
187
|
+
this.update();
|
|
188
|
+
}
|
|
189
|
+
|
|
179
190
|
buildHash() {
|
|
180
191
|
let hash = this.tab;
|
|
181
192
|
|
|
@@ -189,6 +200,10 @@ class WindowHashManager {
|
|
|
189
200
|
hash += ",hide-channels"
|
|
190
201
|
}
|
|
191
202
|
|
|
203
|
+
if (this.minTxPeriod != 0.0) {
|
|
204
|
+
hash += `,min-tx-period-ms=${this.minTxPeriod}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
192
207
|
for (let tab in tabs) {
|
|
193
208
|
let firstChan = true;
|
|
194
209
|
|
runtimepy/data/js/init.js
CHANGED
|
@@ -46,3 +46,15 @@ function setupCursorMove(elem, down, move, up, handleMove) {
|
|
|
46
46
|
function randomHexColor() {
|
|
47
47
|
return "#" + (Math.random() * 0xFFFFFF << 0).toString(16).padStart(6, "0");
|
|
48
48
|
}
|
|
49
|
+
|
|
50
|
+
/* Load settings control mappings. */
|
|
51
|
+
let settings = {};
|
|
52
|
+
|
|
53
|
+
function loadSettings() {
|
|
54
|
+
for (const key of ["min-tx-period-ms"]) {
|
|
55
|
+
let elem = document.getElementById(`setting-${key}`);
|
|
56
|
+
if (elem) {
|
|
57
|
+
settings[key] = elem;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
runtimepy/data/js/util.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
function worker_config(config) {
|
|
2
2
|
let worker_cfg = {};
|
|
3
3
|
|
|
4
|
+
let port_name = "runtimepy_websocket";
|
|
5
|
+
let uri_prefix = "ws";
|
|
6
|
+
|
|
7
|
+
/* Ensured TLS is handled properly. */
|
|
8
|
+
if (location.protocol.includes("https")) {
|
|
9
|
+
port_name = "runtimepy_secure_websocket";
|
|
10
|
+
uri_prefix += "s";
|
|
11
|
+
}
|
|
12
|
+
|
|
4
13
|
/* Look for connections to establish. */
|
|
5
14
|
let ports = config["config"]["ports"];
|
|
6
15
|
for (let port_idx in ports) {
|
|
@@ -10,11 +19,11 @@ function worker_config(config) {
|
|
|
10
19
|
let hostname = window.location.hostname;
|
|
11
20
|
|
|
12
21
|
/* This business logic could use some work. */
|
|
13
|
-
if (port["name"].includes(
|
|
22
|
+
if (port["name"].includes(port_name)) {
|
|
14
23
|
if (port["name"].includes("data")) {
|
|
15
|
-
worker_cfg["data"] =
|
|
24
|
+
worker_cfg["data"] = `${uri_prefix}://${hostname}:` + port["port"];
|
|
16
25
|
} else {
|
|
17
|
-
worker_cfg["json"] =
|
|
26
|
+
worker_cfg["json"] = `${uri_prefix}://${hostname}:` + port["port"];
|
|
18
27
|
}
|
|
19
28
|
}
|
|
20
29
|
}
|
runtimepy/data/js/worker.js
CHANGED
|
@@ -13,11 +13,16 @@ function create_connections(config) {
|
|
|
13
13
|
|
|
14
14
|
const plots = new PlotManager();
|
|
15
15
|
|
|
16
|
+
/* Used to control messaging rate with server. */
|
|
17
|
+
let minTxPeriod = 0.0;
|
|
18
|
+
|
|
16
19
|
async function message(data) {
|
|
17
20
|
if ("plot" in data) {
|
|
18
21
|
/* Forward the 'name' field. */
|
|
19
22
|
data["plot"]["name"] = data["name"];
|
|
20
23
|
await plots.handleMessage(data["plot"]);
|
|
24
|
+
} else if ("min-tx-period-ms" in data) {
|
|
25
|
+
minTxPeriod = data["min-tx-period-ms"];
|
|
21
26
|
} else {
|
|
22
27
|
console.log(`Message for worker:`);
|
|
23
28
|
console.log(data);
|
|
@@ -38,9 +43,14 @@ async function start(config) {
|
|
|
38
43
|
onmessage = async (event) => {
|
|
39
44
|
/* Handle messages meant for this thread. */
|
|
40
45
|
if ("event" in event.data && "worker" in event.data["event"]) {
|
|
46
|
+
let data = event.data["event"]["worker"];
|
|
47
|
+
|
|
41
48
|
/* Forward the 'name' field. */
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
if ("name" in event.data) {
|
|
50
|
+
data["name"] = event.data["name"];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await message(data);
|
|
44
54
|
}
|
|
45
55
|
/* Forward all other messages to the server. */
|
|
46
56
|
else {
|
|
@@ -63,13 +73,18 @@ async function start(config) {
|
|
|
63
73
|
/* Tell main thread we're ready to go. */
|
|
64
74
|
postMessage(0);
|
|
65
75
|
|
|
76
|
+
let messageTxTime = 0.0;
|
|
77
|
+
|
|
66
78
|
/* Set up the main request-animation-frame loop. */
|
|
67
79
|
function render(time) {
|
|
68
80
|
/* Render plot. */
|
|
69
81
|
plots.render(time);
|
|
70
82
|
|
|
71
83
|
/* Keep the server synchronized with frames. */
|
|
72
|
-
|
|
84
|
+
if (messageTxTime + minTxPeriod <= time) {
|
|
85
|
+
conns["json"].send_json({"ui" : {"time" : time}});
|
|
86
|
+
messageTxTime = time;
|
|
87
|
+
}
|
|
73
88
|
|
|
74
89
|
requestAnimationFrame(render);
|
|
75
90
|
}
|
|
@@ -27,26 +27,27 @@ async def launch_browser(app: AppInfo) -> None:
|
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
29
|
# Launch browser based on config option.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
30
|
+
for prefix in ["http", "https"]:
|
|
31
|
+
if config_param(app, f"xdg_open_{prefix}", False):
|
|
32
|
+
|
|
33
|
+
port: Any
|
|
34
|
+
for port in app.config["root"]["ports"]: # type: ignore
|
|
35
|
+
if f"{prefix}_server" in port["name"]:
|
|
36
|
+
# URI parameters.
|
|
37
|
+
hostname = config_param(app, "xdg_host", "localhost")
|
|
38
|
+
|
|
39
|
+
# Assemble URI.
|
|
40
|
+
uri = f"{prefix}://{hostname}:{port['port']}/"
|
|
41
|
+
|
|
42
|
+
# Add a fragment if one was specified.
|
|
43
|
+
fragment = config_param(app, "xdg_fragment", "")
|
|
44
|
+
if fragment:
|
|
45
|
+
uri += "#" + fragment
|
|
46
|
+
|
|
47
|
+
with suppress(FileNotFoundError):
|
|
48
|
+
await app.stack.enter_async_context(
|
|
49
|
+
spawn_exec("xdg-open", uri)
|
|
50
|
+
)
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
# Could add an interface for adding multiple applications.
|
|
@@ -11,6 +11,7 @@ from runtimepy.net.arbiter.info import AppInfo
|
|
|
11
11
|
from runtimepy.net.server.app.bootstrap.elements import input_box
|
|
12
12
|
from runtimepy.net.server.app.bootstrap.tabs import TabbedContent
|
|
13
13
|
from runtimepy.net.server.app.env.modal import Modal
|
|
14
|
+
from runtimepy.net.server.app.env.settings import plot_settings
|
|
14
15
|
from runtimepy.net.server.app.env.tab import ChannelEnvironmentTab
|
|
15
16
|
from runtimepy.net.server.app.placeholder import dummy_tabs, under_construction
|
|
16
17
|
from runtimepy.net.server.app.sound import SoundTab
|
|
@@ -85,8 +86,7 @@ def channel_environments(app: AppInfo, tabs: TabbedContent) -> None:
|
|
|
85
86
|
)
|
|
86
87
|
|
|
87
88
|
# Plot settings modal.
|
|
88
|
-
plot_settings
|
|
89
|
-
under_construction(plot_settings.footer)
|
|
89
|
+
plot_settings(tabs)
|
|
90
90
|
|
|
91
91
|
# Experimental features.
|
|
92
92
|
if app.config_param("experimental", False):
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A module implementing an application-settings modal.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# third-party
|
|
6
|
+
from svgen.element.html import div
|
|
7
|
+
|
|
8
|
+
# internal
|
|
9
|
+
from runtimepy.net.server.app.bootstrap.elements import flex, slider
|
|
10
|
+
from runtimepy.net.server.app.bootstrap.tabs import TabbedContent
|
|
11
|
+
from runtimepy.net.server.app.env.modal import Modal
|
|
12
|
+
from runtimepy.net.server.app.placeholder import under_construction
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def plot_settings(tabs: TabbedContent) -> None:
|
|
16
|
+
"""Create the plot settings modal."""
|
|
17
|
+
|
|
18
|
+
modal = Modal(tabs, name="plot", icon="graph-up")
|
|
19
|
+
under_construction(modal.footer)
|
|
20
|
+
|
|
21
|
+
div(tag="h1", text="settings", parent=modal.body)
|
|
22
|
+
div(tag="hr", parent=modal.body)
|
|
23
|
+
|
|
24
|
+
div(tag="h2", text="plot status", parent=modal.body)
|
|
25
|
+
div(id="plot-status-inner", parent=modal.body)
|
|
26
|
+
div(tag="hr", parent=modal.body)
|
|
27
|
+
|
|
28
|
+
div(tag="h2", text="minimum transmit period (ms)", parent=modal.body)
|
|
29
|
+
|
|
30
|
+
div(
|
|
31
|
+
tag="p",
|
|
32
|
+
text=(
|
|
33
|
+
"Can be used to throttle the rate of "
|
|
34
|
+
"client <-> server communication. Use the 'ui' tab's metrics to "
|
|
35
|
+
"determine performance impact. Note that only this browser tab's "
|
|
36
|
+
"messaging rate can be controlled (not other connected clients')."
|
|
37
|
+
),
|
|
38
|
+
parent=modal.body,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
container = flex(parent=modal.body)
|
|
42
|
+
|
|
43
|
+
div(
|
|
44
|
+
text="0 ms ('high', run at native refresh rate)",
|
|
45
|
+
parent=container,
|
|
46
|
+
class_str="text-nowrap text-primary",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
slider(
|
|
50
|
+
0, 100, 100, parent=container, value=0, id="setting-min-tx-period-ms"
|
|
51
|
+
).add_class("ms-3 me-3")
|
|
52
|
+
|
|
53
|
+
div(
|
|
54
|
+
text="100 ms ('low', 10 Hz)",
|
|
55
|
+
parent=container,
|
|
56
|
+
class_str="text-nowrap text-primary",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
div(tag="hr", parent=modal.body)
|
runtimepy/net/ssl.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A module implementing SSL-related interfaces.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# built-in
|
|
6
|
+
import ssl
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def handle_possible_ssl(client: bool = True, **kwargs) -> dict[str, Any]:
|
|
11
|
+
"""Handle creating an SSL context based on keyword arguments."""
|
|
12
|
+
|
|
13
|
+
args = ["cafile", "capath", "cadata"]
|
|
14
|
+
if (
|
|
15
|
+
kwargs.pop("use_ssl", False)
|
|
16
|
+
or any(x in kwargs for x in args)
|
|
17
|
+
or "certfile" in kwargs
|
|
18
|
+
):
|
|
19
|
+
context = ssl.create_default_context(
|
|
20
|
+
purpose=(
|
|
21
|
+
ssl.Purpose.SERVER_AUTH if client else ssl.Purpose.CLIENT_AUTH
|
|
22
|
+
),
|
|
23
|
+
**{x: kwargs.pop(x, None) for x in args},
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if "certfile" in kwargs:
|
|
27
|
+
context.load_cert_chain(
|
|
28
|
+
kwargs.pop("certfile"), keyfile=kwargs.pop("keyfile", None)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
kwargs["ssl"] = context
|
|
32
|
+
|
|
33
|
+
return kwargs
|
runtimepy/net/tcp/connection.py
CHANGED
|
@@ -27,6 +27,7 @@ from runtimepy.net.connection import EchoConnection as _EchoConnection
|
|
|
27
27
|
from runtimepy.net.connection import NullConnection as _NullConnection
|
|
28
28
|
from runtimepy.net.manager import ConnectionManager as _ConnectionManager
|
|
29
29
|
from runtimepy.net.mixin import TransportMixin as _TransportMixin
|
|
30
|
+
from runtimepy.net.ssl import handle_possible_ssl
|
|
30
31
|
from runtimepy.net.tcp.create import (
|
|
31
32
|
TcpTransportProtocol,
|
|
32
33
|
tcp_transport_protocol_backoff,
|
|
@@ -70,6 +71,20 @@ class TcpConnection(_Connection, _TransportMixin):
|
|
|
70
71
|
self._protocol = protocol
|
|
71
72
|
self._protocol.conn = self
|
|
72
73
|
|
|
74
|
+
@classmethod
|
|
75
|
+
def get_log_prefix(cls, is_ssl: bool = False) -> str:
|
|
76
|
+
"""Get a logging prefix for this instance."""
|
|
77
|
+
|
|
78
|
+
# Default implementation doesn't handle this.
|
|
79
|
+
del is_ssl
|
|
80
|
+
|
|
81
|
+
return cls.log_prefix
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def is_ssl(self) -> bool:
|
|
85
|
+
"""Determine if this connection uses SSL."""
|
|
86
|
+
return self._transport.get_extra_info("sslcontext") is not None
|
|
87
|
+
|
|
73
88
|
async def _await_message(self) -> _Optional[_Union[_BinaryMessage, str]]:
|
|
74
89
|
"""Await the next message. Return None on error or failure."""
|
|
75
90
|
|
|
@@ -142,20 +157,22 @@ class TcpConnection(_Connection, _TransportMixin):
|
|
|
142
157
|
callback(self.conn)
|
|
143
158
|
|
|
144
159
|
eloop = _get_event_loop()
|
|
160
|
+
|
|
161
|
+
server_kwargs = handle_possible_ssl(client=False, **kwargs)
|
|
162
|
+
is_ssl = "ssl" in server_kwargs
|
|
145
163
|
server = await eloop.create_server(
|
|
146
|
-
CallbackProtocol,
|
|
164
|
+
CallbackProtocol,
|
|
165
|
+
family=_socket.AF_INET,
|
|
166
|
+
**server_kwargs,
|
|
147
167
|
)
|
|
148
168
|
async with server:
|
|
149
169
|
for socket in server.sockets:
|
|
150
|
-
sockname = socket.getsockname()
|
|
151
170
|
LOG.info(
|
|
152
|
-
"Started %s server listening on '%s%s'
|
|
171
|
+
"Started %s%s server listening on '%s%s'.",
|
|
172
|
+
"secure " if is_ssl else "",
|
|
153
173
|
cls.log_alias,
|
|
154
|
-
cls.
|
|
174
|
+
cls.get_log_prefix(is_ssl=is_ssl),
|
|
155
175
|
_sockname(socket),
|
|
156
|
-
cls.log_prefix,
|
|
157
|
-
sockname[0] if sockname[0] != "0.0.0.0" else "localhost",
|
|
158
|
-
sockname[1],
|
|
159
176
|
)
|
|
160
177
|
yield server
|
|
161
178
|
|
|
@@ -193,7 +210,10 @@ class TcpConnection(_Connection, _TransportMixin):
|
|
|
193
210
|
@classmethod
|
|
194
211
|
@_asynccontextmanager
|
|
195
212
|
async def create_pair(
|
|
196
|
-
cls: type[T],
|
|
213
|
+
cls: type[T],
|
|
214
|
+
peer: type[V] = None,
|
|
215
|
+
serve_kwargs: dict[str, _Any] = None,
|
|
216
|
+
connect_kwargs: dict[str, _Any] = None,
|
|
197
217
|
) -> _AsyncIterator[tuple[V, T]]:
|
|
198
218
|
"""Create a connection pair."""
|
|
199
219
|
|
|
@@ -212,13 +232,20 @@ class TcpConnection(_Connection, _TransportMixin):
|
|
|
212
232
|
peer = cls # type: ignore
|
|
213
233
|
assert peer is not None
|
|
214
234
|
|
|
235
|
+
if serve_kwargs is None:
|
|
236
|
+
serve_kwargs = {}
|
|
237
|
+
|
|
215
238
|
server = await stack.enter_async_context(
|
|
216
|
-
peer.serve(callback, port=0, backlog=1)
|
|
239
|
+
peer.serve(callback, port=0, backlog=1, **serve_kwargs)
|
|
217
240
|
)
|
|
218
241
|
|
|
219
242
|
host = server.sockets[0].getsockname()
|
|
243
|
+
|
|
244
|
+
if connect_kwargs is None:
|
|
245
|
+
connect_kwargs = {}
|
|
246
|
+
|
|
220
247
|
client = await cls.create_connection(
|
|
221
|
-
host="localhost", port=host[1]
|
|
248
|
+
host="localhost", port=host[1], **connect_kwargs
|
|
222
249
|
)
|
|
223
250
|
await cond.acquire()
|
|
224
251
|
|
runtimepy/net/tcp/create.py
CHANGED
|
@@ -11,6 +11,7 @@ from typing import Callable, Optional
|
|
|
11
11
|
|
|
12
12
|
# internal
|
|
13
13
|
from runtimepy.net.backoff import ExponentialBackoff
|
|
14
|
+
from runtimepy.net.ssl import handle_possible_ssl
|
|
14
15
|
from runtimepy.net.tcp.protocol import QueueProtocol
|
|
15
16
|
from runtimepy.net.util import try_log_connection_error
|
|
16
17
|
|
|
@@ -26,7 +27,7 @@ async def tcp_transport_protocol(**kwargs) -> TcpTransportProtocol:
|
|
|
26
27
|
|
|
27
28
|
transport: _Transport
|
|
28
29
|
transport, protocol = await _asyncio.get_event_loop().create_connection(
|
|
29
|
-
QueueProtocol, **kwargs
|
|
30
|
+
QueueProtocol, **handle_possible_ssl(**kwargs)
|
|
30
31
|
)
|
|
31
32
|
return transport, protocol
|
|
32
33
|
|
|
@@ -58,7 +58,6 @@ class HttpConnection(_TcpConnection):
|
|
|
58
58
|
expecting_response: bool
|
|
59
59
|
|
|
60
60
|
log_alias = "HTTP"
|
|
61
|
-
log_prefix = "http://"
|
|
62
61
|
|
|
63
62
|
# Handlers registered at the class level so that instances created at
|
|
64
63
|
# runtime don't need additional initialization.
|
|
@@ -79,6 +78,12 @@ class HttpConnection(_TcpConnection):
|
|
|
79
78
|
self.handlers[http.HTTPMethod.GET] = self.get_handler
|
|
80
79
|
self.handlers[http.HTTPMethod.POST] = self.post_handler
|
|
81
80
|
|
|
81
|
+
@classmethod
|
|
82
|
+
def get_log_prefix(cls, is_ssl: bool = False) -> str:
|
|
83
|
+
"""Get a logging prefix for this instance."""
|
|
84
|
+
|
|
85
|
+
return f"http{'s' if is_ssl else ''}://"
|
|
86
|
+
|
|
82
87
|
async def get_handler(
|
|
83
88
|
self,
|
|
84
89
|
response: ResponseHeader,
|
runtimepy/net/util.py
CHANGED
|
@@ -134,15 +134,24 @@ def normalize_host(
|
|
|
134
134
|
return IPv6Host(*args) # type: ignore
|
|
135
135
|
|
|
136
136
|
|
|
137
|
+
USE_FQDN = {"::", "0.0.0.0"}
|
|
138
|
+
|
|
139
|
+
|
|
137
140
|
@cache
|
|
138
141
|
def hostname(ip_address: str) -> str:
|
|
139
142
|
"""
|
|
140
143
|
Attempt to get a string hostname for a string IP address argument that
|
|
141
144
|
'gethostbyaddr' accepts. Otherwise return the original string
|
|
142
145
|
"""
|
|
146
|
+
|
|
143
147
|
result = ip_address
|
|
144
|
-
|
|
145
|
-
|
|
148
|
+
|
|
149
|
+
if ip_address in USE_FQDN:
|
|
150
|
+
result = _socket.getfqdn()
|
|
151
|
+
else:
|
|
152
|
+
with _suppress(_socket.herror, OSError):
|
|
153
|
+
result = _socket.gethostbyaddr(ip_address)[0]
|
|
154
|
+
|
|
146
155
|
return result
|
|
147
156
|
|
|
148
157
|
|
|
@@ -10,6 +10,7 @@ from contextlib import AsyncExitStack as _AsyncExitStack
|
|
|
10
10
|
from contextlib import asynccontextmanager as _asynccontextmanager
|
|
11
11
|
from contextlib import suppress as _suppress
|
|
12
12
|
from logging import getLogger as _getLogger
|
|
13
|
+
from typing import Any as _Any
|
|
13
14
|
from typing import AsyncIterator as _AsyncIterator
|
|
14
15
|
from typing import Awaitable as _Awaitable
|
|
15
16
|
from typing import Callable as _Callable
|
|
@@ -36,6 +37,7 @@ from runtimepy.net.connection import BinaryMessage, Connection
|
|
|
36
37
|
from runtimepy.net.connection import EchoConnection as _EchoConnection
|
|
37
38
|
from runtimepy.net.connection import NullConnection as _NullConnection
|
|
38
39
|
from runtimepy.net.manager import ConnectionManager as _ConnectionManager
|
|
40
|
+
from runtimepy.net.ssl import handle_possible_ssl
|
|
39
41
|
|
|
40
42
|
T = _TypeVar("T", bound="WebsocketConnection")
|
|
41
43
|
ConnectionInit = _Callable[[T], _Awaitable[bool]]
|
|
@@ -93,7 +95,11 @@ class WebsocketConnection(Connection):
|
|
|
93
95
|
async def create_connection(cls: type[T], uri: str, **kwargs) -> T:
|
|
94
96
|
"""Connect a client to an endpoint."""
|
|
95
97
|
|
|
96
|
-
|
|
98
|
+
kwargs.setdefault("use_ssl", uri.startswith("wss"))
|
|
99
|
+
|
|
100
|
+
protocol = await getattr(websockets, "connect")(
|
|
101
|
+
uri, **handle_possible_ssl(**kwargs)
|
|
102
|
+
)
|
|
97
103
|
return cls(protocol)
|
|
98
104
|
|
|
99
105
|
@classmethod
|
|
@@ -101,7 +107,11 @@ class WebsocketConnection(Connection):
|
|
|
101
107
|
async def client(cls: type[T], uri: str, **kwargs) -> _AsyncIterator[T]:
|
|
102
108
|
"""A wrapper for connecting a client."""
|
|
103
109
|
|
|
104
|
-
|
|
110
|
+
kwargs.setdefault("use_ssl", uri.startswith("wss"))
|
|
111
|
+
|
|
112
|
+
async with getattr(websockets, "connect")(
|
|
113
|
+
uri, **handle_possible_ssl(**kwargs)
|
|
114
|
+
) as protocol:
|
|
105
115
|
yield cls(protocol)
|
|
106
116
|
|
|
107
117
|
@classmethod
|
|
@@ -154,7 +164,9 @@ class WebsocketConnection(Connection):
|
|
|
154
164
|
|
|
155
165
|
@classmethod
|
|
156
166
|
@_asynccontextmanager
|
|
157
|
-
async def create_pair(
|
|
167
|
+
async def create_pair(
|
|
168
|
+
cls: type[T], serve_kwargs: dict[str, _Any] = None
|
|
169
|
+
) -> _AsyncIterator[tuple[T, T]]:
|
|
158
170
|
"""Obtain a connected pair of WebsocketConnection objects."""
|
|
159
171
|
|
|
160
172
|
server_conn: _Optional[T] = None
|
|
@@ -167,15 +179,21 @@ class WebsocketConnection(Connection):
|
|
|
167
179
|
return True
|
|
168
180
|
|
|
169
181
|
async with _AsyncExitStack() as stack:
|
|
182
|
+
if serve_kwargs is None:
|
|
183
|
+
serve_kwargs = {}
|
|
184
|
+
|
|
185
|
+
serve_kwargs = handle_possible_ssl(client=False, **serve_kwargs)
|
|
186
|
+
is_ssl = "ssl" in serve_kwargs
|
|
187
|
+
|
|
170
188
|
# Start a server.
|
|
171
189
|
server = await stack.enter_async_context(
|
|
172
|
-
_serve(server_init, host="0.0.0.0", port=0)
|
|
190
|
+
_serve(server_init, host="0.0.0.0", port=0, **serve_kwargs)
|
|
173
191
|
)
|
|
174
192
|
|
|
175
193
|
host = list(server.sockets)[0].getsockname()
|
|
176
194
|
|
|
177
195
|
client_conn = await stack.enter_async_context(
|
|
178
|
-
cls.client(f"ws://localhost:{host[1]}")
|
|
196
|
+
cls.client(f"ws{'s' if is_ssl else ''}://localhost:{host[1]}")
|
|
179
197
|
)
|
|
180
198
|
|
|
181
199
|
# Connect a client and yield both sides of the connection.
|
|
@@ -193,13 +211,17 @@ class WebsocketConnection(Connection):
|
|
|
193
211
|
) -> _AsyncIterator[_WebSocketServer]:
|
|
194
212
|
"""Serve a WebSocket server."""
|
|
195
213
|
|
|
214
|
+
kwargs = handle_possible_ssl(client=False, **kwargs)
|
|
215
|
+
is_ssl = "ssl" in kwargs
|
|
216
|
+
|
|
196
217
|
async with _serve(
|
|
197
218
|
cls.server_handler(init=init, stop_sig=stop_sig, manager=manager),
|
|
198
219
|
**kwargs,
|
|
199
220
|
) as server:
|
|
200
221
|
for socket in server.sockets:
|
|
201
222
|
LOG.info(
|
|
202
|
-
"Started WebSocket server listening on '%s'.",
|
|
223
|
+
"Started WebSocket server listening on 'ws%s://%s'.",
|
|
224
|
+
"s" if is_ssl else "",
|
|
203
225
|
_sockname(socket),
|
|
204
226
|
)
|
|
205
227
|
yield server
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: runtimepy
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.6.1
|
|
4
4
|
Summary: A framework for implementing Python services.
|
|
5
5
|
Home-page: https://github.com/vkottler/runtimepy
|
|
6
6
|
Author: Vaughn Kottler
|
|
@@ -18,9 +18,9 @@ Requires-Python: >=3.11
|
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
License-File: LICENSE
|
|
20
20
|
Requires-Dist: psutil
|
|
21
|
-
Requires-Dist: svgen >=0.6.8
|
|
22
|
-
Requires-Dist: websockets
|
|
23
21
|
Requires-Dist: vcorelib >=3.3.1
|
|
22
|
+
Requires-Dist: websockets
|
|
23
|
+
Requires-Dist: svgen >=0.6.8
|
|
24
24
|
Provides-Extra: test
|
|
25
25
|
Requires-Dist: pylint ; extra == 'test'
|
|
26
26
|
Requires-Dist: flake8 ; extra == 'test'
|
|
@@ -44,11 +44,11 @@ Requires-Dist: uvloop ; (sys_platform != "win32" and sys_platform != "cygwin") a
|
|
|
44
44
|
=====================================
|
|
45
45
|
generator=datazen
|
|
46
46
|
version=3.1.4
|
|
47
|
-
hash=
|
|
47
|
+
hash=a82bbd8413b0036b345251da16b462c3
|
|
48
48
|
=====================================
|
|
49
49
|
-->
|
|
50
50
|
|
|
51
|
-
# runtimepy ([5.
|
|
51
|
+
# runtimepy ([5.6.1](https://pypi.org/project/runtimepy/))
|
|
52
52
|
|
|
53
53
|
[](https://pypi.org/project/runtimepy/)
|
|
54
54
|

|
|
@@ -165,8 +165,10 @@ options:
|
|
|
165
165
|
```
|
|
166
166
|
$ ./venv3.12/bin/runtimepy server -h
|
|
167
167
|
|
|
168
|
-
usage: runtimepy server [-h] [-i] [-w] [--no-poller] [--
|
|
169
|
-
[
|
|
168
|
+
usage: runtimepy server [-h] [-i] [-w] [--no-poller] [--cafile CAFILE]
|
|
169
|
+
[--capath CAPATH] [--cadata CADATA]
|
|
170
|
+
[--certfile CERTFILE] [--keyfile KEYFILE]
|
|
171
|
+
[--host HOST] [-p PORT] [-u] [-l]
|
|
170
172
|
factory [configs ...]
|
|
171
173
|
|
|
172
174
|
positional arguments:
|
|
@@ -181,6 +183,11 @@ options:
|
|
|
181
183
|
ensure that a 'wait_for_stop' application method is
|
|
182
184
|
run last
|
|
183
185
|
--no-poller don't run a connection-metrics poller task
|
|
186
|
+
--cafile CAFILE passed directly to instantiation
|
|
187
|
+
--capath CAPATH passed directly to instantiation
|
|
188
|
+
--cadata CADATA passed directly to instantiation
|
|
189
|
+
--certfile CERTFILE passed directly to instantiation
|
|
190
|
+
--keyfile KEYFILE passed directly to instantiation
|
|
184
191
|
--host HOST host address to listen on (default: '0.0.0.0')
|
|
185
192
|
-p PORT, --port PORT port to listen on (default: 0)
|
|
186
193
|
-u, --udp whether or not this is a UDP-based server (otherwise
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
runtimepy/__init__.py,sha256=
|
|
1
|
+
runtimepy/__init__.py,sha256=sAv_oOiBI0nH6iHhMNxClwYnWlOwwJSeNZcfXxevMiU,390
|
|
2
2
|
runtimepy/__main__.py,sha256=OPAed6hggoQdw-6QAR62mqLC-rCkdDhOq0wyeS2vDRI,332
|
|
3
3
|
runtimepy/app.py,sha256=sTvatbsGZ2Hdel36Si_WUbNMtg9CzsJyExr5xjIcxDE,970
|
|
4
4
|
runtimepy/dev_requirements.txt,sha256=j0dh11ztJAzfaUL0iFheGjaZj9ppDzmTkclTT8YKO8c,230
|
|
@@ -33,7 +33,7 @@ runtimepy/commands/all.py,sha256=jH2dsmkqyBFe_2ZlPFpko0UCMW3fFfJsuGIbeJFbDoQ,161
|
|
|
33
33
|
runtimepy/commands/arbiter.py,sha256=CtTMRYpqCAN3vWHkkr9jqWpoF7JGNXafKIBFmkarAfc,1567
|
|
34
34
|
runtimepy/commands/common.py,sha256=NvZdeIFBHAF52c1n7vqD59DW6ywc-rG5iC5MpuhGf-c,2449
|
|
35
35
|
runtimepy/commands/mtu.py,sha256=LFFjTU4SsuV3j7Mhx_WuKa5lfdfMm70zJvDWToVrP7E,1357
|
|
36
|
-
runtimepy/commands/server.py,sha256=
|
|
36
|
+
runtimepy/commands/server.py,sha256=G1q6bgq0HpsKvDFcPyAC--f3-OuZqtnR4FK7b36Rqv8,4671
|
|
37
37
|
runtimepy/commands/task.py,sha256=6xRVlRwpEZVhrcY18sQcfdWEOxeQZLeOF-6UrUURtO4,1435
|
|
38
38
|
runtimepy/commands/tftp.py,sha256=djFQzYDxy2jvUseHJ4fDR3CowPxQ-Tu0IJz1SwapXX0,2361
|
|
39
39
|
runtimepy/commands/tui.py,sha256=9hWA3_YATibUUDTVQr7UnKzPTDVJ7WxWKTYYQpLoyrE,1869
|
|
@@ -56,11 +56,11 @@ runtimepy/data/js/DataConnection.js,sha256=DnX8FMehjJXqmI62UMYXSvl_XdfQMzq3XUDFb
|
|
|
56
56
|
runtimepy/data/js/JsonConnection.js,sha256=rclZrbmWc_zSs6I_JhOgxnVPFIyPMo5WdjAe8alyZ3o,2729
|
|
57
57
|
runtimepy/data/js/audio.js,sha256=bLkBqbeHMiGGidfL3iXjmVoF9seK-ZeZ3kwgOrcpgk4,1092
|
|
58
58
|
runtimepy/data/js/events.js,sha256=rgz3Q_8J6sfU_7Sa7fG1mZD0pQ4S3vwN2mqcvQfePkM,554
|
|
59
|
-
runtimepy/data/js/init.js,sha256=
|
|
59
|
+
runtimepy/data/js/init.js,sha256=IeFqfab7CM2-Z4fIbyGaUD4M2orUT8uLwcVlleQqXzg,1522
|
|
60
60
|
runtimepy/data/js/main.js,sha256=r0P_0xx5Czd1jfTjsB-tLfwhp4iPNoajlYC858u0ltc,211
|
|
61
|
-
runtimepy/data/js/util.js,sha256=
|
|
62
|
-
runtimepy/data/js/worker.js,sha256=
|
|
63
|
-
runtimepy/data/js/classes/App.js,sha256=
|
|
61
|
+
runtimepy/data/js/util.js,sha256=ymYV3xenF3LZ5fw6ACXFnqHiNhFzf9uS7UUal_KsXr0,1376
|
|
62
|
+
runtimepy/data/js/worker.js,sha256=V9deGAynjvUr1D-WGi3wUW8rxoaNLvBvayMoLFZk3w0,2444
|
|
63
|
+
runtimepy/data/js/classes/App.js,sha256=nnY42Q3tlNzf8JZtuGKyxJZLLNMfResdww8svOQMC3U,3402
|
|
64
64
|
runtimepy/data/js/classes/ChannelTable.js,sha256=V9g4_6N1i7ci7FkhP9eBd9ENbkSBusO5AvWuIEHUKk8,2634
|
|
65
65
|
runtimepy/data/js/classes/DataConnection.js,sha256=DnX8FMehjJXqmI62UMYXSvl_XdfQMzq3XUDFbLu2GgI,98
|
|
66
66
|
runtimepy/data/js/classes/JsonConnection.js,sha256=vgQ3bGvVU5dNXn_uwvH7HjOQm0PwUSx0239Vfi7h1vE,2858
|
|
@@ -68,13 +68,13 @@ runtimepy/data/js/classes/OverlayManager.js,sha256=9JCo1_O7Gqjfrim4StWW_6JsRIC8r
|
|
|
68
68
|
runtimepy/data/js/classes/Plot.js,sha256=44DUxvmOe2cqKRjrk91Abo-myxWWFAFUDsZb05F4rrA,1979
|
|
69
69
|
runtimepy/data/js/classes/PlotDrawer.js,sha256=DIRCwFt_lXSMB7GFrl6Y6y5Z74A291YUmmrXSNPuVhw,4830
|
|
70
70
|
runtimepy/data/js/classes/PlotManager.js,sha256=tGnqFP_d5Z9Hn20OQfgu-h0DKM5zhD91whG0pF7_VFk,4973
|
|
71
|
-
runtimepy/data/js/classes/PlotModalManager.js,sha256=
|
|
71
|
+
runtimepy/data/js/classes/PlotModalManager.js,sha256=lEbACLC5VzV-aSAb7G-WacmLLf_IRx7-pNJs9lL8bvY,928
|
|
72
72
|
runtimepy/data/js/classes/PointBuffer.js,sha256=iVtq_q5gBaV9IVX55pHVjQdMJ4phJ6QTdAiM7EZrLRA,5061
|
|
73
73
|
runtimepy/data/js/classes/PointManager.js,sha256=0lr2AReLdDNrY47UuOjjCRDPMiSRsOVggFHDPf8V-6Y,460
|
|
74
74
|
runtimepy/data/js/classes/TabFilter.js,sha256=FygFFmWa1IRQ0DZlrOscdrkz6W3VkG87nHSud56kCzI,1185
|
|
75
75
|
runtimepy/data/js/classes/TabInterface.js,sha256=i-dM_FpRsp_hJEh6CdtSY8hiTS6-KZx5dkOYXD_crh0,12084
|
|
76
76
|
runtimepy/data/js/classes/UnitSystem.js,sha256=ys4OMabq47k_VvJpRItm82U0IequDvx3ysRJOQzDf94,906
|
|
77
|
-
runtimepy/data/js/classes/WindowHashManager.js,sha256=
|
|
77
|
+
runtimepy/data/js/classes/WindowHashManager.js,sha256=aQcdUwWw-Ksp-ffywOlv64TdG6R-UrP4DVGLrGIi8QQ,6253
|
|
78
78
|
runtimepy/data/js/classes/WorkerInterface.js,sha256=qARPW1CUDnHnVFVE8UjqKq74QfommCLwd6nisy-ayOw,346
|
|
79
79
|
runtimepy/data/js/tab/env.js,sha256=MB79l3XyXKELWRqHcTnwWHiwdiceLHl1N_s-mS33pyU,22
|
|
80
80
|
runtimepy/data/js/tab/sound.js,sha256=RSKp0AXM_zGOCsUvIT-BUjIzOE7Dp5NHiQG4fy7gBgY,1388
|
|
@@ -124,7 +124,8 @@ runtimepy/net/connection.py,sha256=lg_cAGCAdOqlh3SURJRKDQ5TraiG7sV0kA_U2FGGNVU,1
|
|
|
124
124
|
runtimepy/net/manager.py,sha256=-M-ZSB9izay6HK1ytTayAYnSHYAz34dcwxaiNhC4lWg,4264
|
|
125
125
|
runtimepy/net/mixin.py,sha256=5UlFK4lRrJ2O0nEUuScGbkYd4-El-RruFt_UcQR0aic,3039
|
|
126
126
|
runtimepy/net/mtu.py,sha256=XnLXAFMsDxK1Lj5v_zgWaBrC3lNqf81DkbDc6hpMdmI,3495
|
|
127
|
-
runtimepy/net/
|
|
127
|
+
runtimepy/net/ssl.py,sha256=dj9uECPKDT5k-5vlR5I3Z7Go3WWZhbaJ9nb0rC3kJvg,854
|
|
128
|
+
runtimepy/net/util.py,sha256=P6WnH4n8JJkEfKwepk1eP4lGPxWjqcFv0yL3N0mvtrw,5897
|
|
128
129
|
runtimepy/net/apps/__init__.py,sha256=vjo7e19QXtJwe6V6B-QGvYiJveYobnYIfpkKZrnS17w,710
|
|
129
130
|
runtimepy/net/arbiter/__init__.py,sha256=ptKF995rYKvkm4Mya92vA5QEDqcFq5NRD0IYGqZ6_do,740
|
|
130
131
|
runtimepy/net/arbiter/base.py,sha256=WRbgavarmOx6caQJmfI03udZvNC7o298uOhOsN-lp2E,14658
|
|
@@ -143,7 +144,7 @@ runtimepy/net/arbiter/housekeeping/__init__.py,sha256=80vzksjCq1r9Kx25YeOKTJu2Ek
|
|
|
143
144
|
runtimepy/net/arbiter/imports/__init__.py,sha256=bjBks4kdwtkzG8VjsNJewaxT4_QFhVGoZf3g6R3lrEs,4980
|
|
144
145
|
runtimepy/net/arbiter/imports/util.py,sha256=Ltp5hHUkahiUfIWeeK9fTtGQb9UMJZPqfKquuibCV9M,1071
|
|
145
146
|
runtimepy/net/arbiter/struct/__init__.py,sha256=Vr38dp2X0PZOrAbjKsZ9xZdQ1j3z92s4QuvRtYYVuNI,5990
|
|
146
|
-
runtimepy/net/arbiter/tcp/__init__.py,sha256=
|
|
147
|
+
runtimepy/net/arbiter/tcp/__init__.py,sha256=djNm8il_9aLNpGsYResJlFmyIqx9XNLqVay-mYnn8vc,1530
|
|
147
148
|
runtimepy/net/arbiter/tcp/json.py,sha256=W9a_OwBPmIoB2XZf4iuAIWQhMg2qA9xejBhGBdNCPnI,742
|
|
148
149
|
runtimepy/net/factories/__init__.py,sha256=rPdBVpgzzQYF61w6efQrEre71yMPHd6kanBpMdOX-3c,4672
|
|
149
150
|
runtimepy/net/http/__init__.py,sha256=4TjFp_ajAVcOEvwtjlF6mG-9EbEePqFZht-QpWIKVBo,1802
|
|
@@ -156,7 +157,7 @@ runtimepy/net/http/version.py,sha256=mp6rgIM7-VUVKLCA0Uw96CmBkL0ET860lDVVEewpZ7w
|
|
|
156
157
|
runtimepy/net/server/__init__.py,sha256=J8gl91YltD8Wo2y_AXxaL6liLu3vomfzUz_nULa3e2Y,6707
|
|
157
158
|
runtimepy/net/server/html.py,sha256=xaTGelH4zrwndQjU24kbCj9Yqu-D17nK5682P6xa-cU,1153
|
|
158
159
|
runtimepy/net/server/json.py,sha256=RfNt7Gr4-X5DMinV1UeiWneTIJr0LO6BXo2GzE0C1PQ,2475
|
|
159
|
-
runtimepy/net/server/app/__init__.py,sha256
|
|
160
|
+
runtimepy/net/server/app/__init__.py,sha256=1qAC9E0mqD73sGXb3CBI7ync2WFfZkMNcYlfLSs5wt8,2775
|
|
160
161
|
runtimepy/net/server/app/base.py,sha256=1KMEWCwDf3cducIbt9geTmMwugAMKl1Io2sBr6qajo4,2082
|
|
161
162
|
runtimepy/net/server/app/create.py,sha256=N-g3kClBsG4pKOd9tx947rOq4sfgrH_FAMVfZacjhFA,2666
|
|
162
163
|
runtimepy/net/server/app/elements.py,sha256=KJt9vWqkfvniJMiLOJN467JjPPrEqJYZXmDuY1JoY1g,455
|
|
@@ -169,8 +170,9 @@ runtimepy/net/server/app/tab.py,sha256=4jhw71LfA23wT9m9e5ofw-tBXYW6xky4tGwBOlGkO
|
|
|
169
170
|
runtimepy/net/server/app/bootstrap/__init__.py,sha256=ONhwx68piWjsrf88FMpda84TWSPqgi-RZCBuWCci_ak,1444
|
|
170
171
|
runtimepy/net/server/app/bootstrap/elements.py,sha256=uWcaVBdrZvL3_lNZ13jQ8AfPN5f4ye2feZN7ve9RDnM,3868
|
|
171
172
|
runtimepy/net/server/app/bootstrap/tabs.py,sha256=0azUtpZTO80kjhX65vU3K5QfW4cwK7ULixqM8O0FgKQ,4179
|
|
172
|
-
runtimepy/net/server/app/env/__init__.py,sha256=
|
|
173
|
+
runtimepy/net/server/app/env/__init__.py,sha256=SbIgHg2KQtYSLnHPQzHpK1--f_xl4ASFVTvx-TIDJUw,3202
|
|
173
174
|
runtimepy/net/server/app/env/modal.py,sha256=d7OdKVJfKXnOkDhvsF2nQmTfZHAdx70wbK2pGlNIcEQ,1753
|
|
175
|
+
runtimepy/net/server/app/env/settings.py,sha256=M0DFibrzF5-nxZ8udKMi503HiTZWIQNbzj_t9TWi34Q,1720
|
|
174
176
|
runtimepy/net/server/app/env/widgets.py,sha256=_kNvPl7MXnZOiwTjoZiU2hfuSjkLnRUrORTVDi3w7Ls,5312
|
|
175
177
|
runtimepy/net/server/app/env/tab/__init__.py,sha256=stTVKyHljLQWnnhxkWPwa7bLdZtjhiMFbiVFgbiYaFI,647
|
|
176
178
|
runtimepy/net/server/app/env/tab/base.py,sha256=uBPpOeqI23341IezIQcGvJciPfIL2P5qQgbZ74aQRKA,991
|
|
@@ -185,10 +187,10 @@ runtimepy/net/stream/base.py,sha256=Dg4vcR0n9y2122AyJ-9W-jkEhNla_EHO-DqJJPfGD4k,
|
|
|
185
187
|
runtimepy/net/stream/string.py,sha256=61mgserU3p6j5gAcK0oe0aKqL6vDh7NtgJvbPoiAUPM,784
|
|
186
188
|
runtimepy/net/stream/json/__init__.py,sha256=h--C_9moW92TC_e097FRRXcg8GJ6VVbMLXl1cICknys,2508
|
|
187
189
|
runtimepy/net/tcp/__init__.py,sha256=OOWohegpoioSTf8M7uDf-4EV1IDungz7-U19L_2yW4I,250
|
|
188
|
-
runtimepy/net/tcp/connection.py,sha256=
|
|
189
|
-
runtimepy/net/tcp/create.py,sha256=
|
|
190
|
+
runtimepy/net/tcp/connection.py,sha256=izRVSZdrhkk21javEdJB2BJTPmiz87_DoUE1oKuQq58,8596
|
|
191
|
+
runtimepy/net/tcp/create.py,sha256=zZsRs5KYpO3bNGh-DwEOEzjUDE4ixj-UBHYgZ0GvC7c,2013
|
|
190
192
|
runtimepy/net/tcp/protocol.py,sha256=vEnIX3gUX2nrw9ofT_e4KYU4VY2k4WP0WuOi4eE_OOQ,1444
|
|
191
|
-
runtimepy/net/tcp/http/__init__.py,sha256=
|
|
193
|
+
runtimepy/net/tcp/http/__init__.py,sha256=4ZSM-Fma_IpmDNCu5E3VBPRrVxOjPgKpKAS3uag1V1I,5657
|
|
192
194
|
runtimepy/net/tcp/scpi/__init__.py,sha256=aWCWQfdeyfoU9bpOnOtyIQbT1swl4ergXLFn5kXAH28,2105
|
|
193
195
|
runtimepy/net/tcp/telnet/__init__.py,sha256=96eJFb301I3H2ivDtGMQtDDw09Xm5NRvM9VEC-wjt8c,4768
|
|
194
196
|
runtimepy/net/tcp/telnet/codes.py,sha256=1-yyRe-Kz_W7d6B0P3iT1AaSNR3_Twmn-MUjKCJJknY,3518
|
|
@@ -204,7 +206,7 @@ runtimepy/net/udp/tftp/endpoint.py,sha256=so60LdPTG66N5tdhHhiX7j_TBHvNOTi4JIgLcg
|
|
|
204
206
|
runtimepy/net/udp/tftp/enums.py,sha256=06juMd__pJZsyL8zO8p3hRucnOratt1qtz9zcxzMg4s,1579
|
|
205
207
|
runtimepy/net/udp/tftp/io.py,sha256=w6cnUt-T-Ma6Vg8BWoRbsNnIWUv0HTY4am6bcLWxNJs,803
|
|
206
208
|
runtimepy/net/websocket/__init__.py,sha256=YjSmoxiigmsI_hcQw6nueX7bxhrRGerEERnPvgLVEVA,313
|
|
207
|
-
runtimepy/net/websocket/connection.py,sha256=
|
|
209
|
+
runtimepy/net/websocket/connection.py,sha256=r0HUB5PFhWGmmarwEKp10CJ449Iwj6WOcMyhjzgCMpU,8996
|
|
208
210
|
runtimepy/noise/__init__.py,sha256=EJM7h3t_z74wwrn6FAFQwYE2yUcOZQ1K1IQqOb8Z0AI,384
|
|
209
211
|
runtimepy/primitives/__init__.py,sha256=nwWJH1e0KN2NsVwQ3wvRtUpl9s9Ap8Q32NNZLGol0wU,2323
|
|
210
212
|
runtimepy/primitives/base.py,sha256=BaGPUTeVMnLnTPcpjqnS2lzPN74Pe5C0XaQdgrTfW7A,9185
|
|
@@ -261,9 +263,9 @@ runtimepy/tui/task.py,sha256=nUZo9fuOC-k1Wpqdzkv9v1tQirCI28fZVgcC13Ijvus,1093
|
|
|
261
263
|
runtimepy/tui/channels/__init__.py,sha256=evDaiIn-YS9uGhdo8ZGtP9VK1ek6sr_P1nJ9JuSET0o,4536
|
|
262
264
|
runtimepy/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
263
265
|
runtimepy/ui/controls.py,sha256=yvT7h3thbYaitsakcIAJ90EwKzJ4b-jnc6p3UuVf_XE,1241
|
|
264
|
-
runtimepy-5.
|
|
265
|
-
runtimepy-5.
|
|
266
|
-
runtimepy-5.
|
|
267
|
-
runtimepy-5.
|
|
268
|
-
runtimepy-5.
|
|
269
|
-
runtimepy-5.
|
|
266
|
+
runtimepy-5.6.1.dist-info/LICENSE,sha256=okYCYhGsx_BlzvFdoNVBVpw_Cfb4SOqHA_VAARml4Hc,1071
|
|
267
|
+
runtimepy-5.6.1.dist-info/METADATA,sha256=ttK3mwA-y9-Jg8ARP2LWXia7GVDB7K1OhIZM3fd3imo,9280
|
|
268
|
+
runtimepy-5.6.1.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
269
|
+
runtimepy-5.6.1.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
|
|
270
|
+
runtimepy-5.6.1.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
|
|
271
|
+
runtimepy-5.6.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|