runtimepy 5.5.0__py3-none-any.whl → 5.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # =====================================
2
2
  # generator=datazen
3
3
  # version=3.1.4
4
- # hash=e8348ec684fac751916510d1d8c7dafb
4
+ # hash=6c21c41096ed57b6c6469232c97787cd
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.5.0"
13
+ VERSION = "5.6.0"
14
14
 
15
15
  # runtimepy-specific content.
16
16
  METRICS_NAME = "metrics"
@@ -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": {"port": f"${port_name(args)}", "host": args.host},
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(f"ws://localhost:{port}")
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": {"local_addr": ["0.0.0.0", f"${port_name(args)}"]},
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/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("runtimepy_websocket")) {
22
+ if (port["name"].includes(port_name)) {
14
23
  if (port["name"].includes("data")) {
15
- worker_cfg["data"] = "ws://" + hostname + ":" + port["port"];
24
+ worker_cfg["data"] = `${uri_prefix}://${hostname}:` + port["port"];
16
25
  } else {
17
- worker_cfg["json"] = "ws://" + hostname + ":" + port["port"];
26
+ worker_cfg["json"] = `${uri_prefix}://${hostname}:` + port["port"];
18
27
  }
19
28
  }
20
29
  }
@@ -42,6 +42,7 @@ class TcpConnectionFactory(_ConnectionFactory, _Generic[T]):
42
42
  """Create a task that will run a connection server."""
43
43
 
44
44
  assert not [*args], "Only keyword arguments are used!"
45
+
45
46
  return self.kind.app(
46
47
  stop_sig,
47
48
  manager=manager,
@@ -27,26 +27,27 @@ async def launch_browser(app: AppInfo) -> None:
27
27
  """
28
28
 
29
29
  # Launch browser based on config option.
30
- if config_param(app, "xdg_open_http", False):
31
-
32
- port: Any
33
- for port in app.config["root"]["ports"]: # type: ignore
34
- if "http_server" in port["name"]:
35
- # URI parameters.
36
- hostname = config_param(app, "xdg_host", "localhost")
37
-
38
- # Assemble URI.
39
- uri = f"http://{hostname}:{port['port']}/"
40
-
41
- # Add a fragment if one was specified.
42
- fragment = config_param(app, "xdg_fragment", "")
43
- if fragment:
44
- uri += "#" + fragment
45
-
46
- with suppress(FileNotFoundError):
47
- await app.stack.enter_async_context(
48
- spawn_exec("xdg-open", uri)
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.
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
@@ -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,18 +157,24 @@ 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, family=_socket.AF_INET, **kwargs
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
170
  sockname = socket.getsockname()
151
171
  LOG.info(
152
- "Started %s server listening on '%s%s' (%s%s:%d).",
172
+ "Started %s%s server listening on '%s%s' (%s%s:%d).",
173
+ "secure " if is_ssl else "",
153
174
  cls.log_alias,
154
- cls.log_prefix,
175
+ cls.get_log_prefix(is_ssl=is_ssl),
155
176
  _sockname(socket),
156
- cls.log_prefix,
177
+ cls.get_log_prefix(is_ssl=is_ssl),
157
178
  sockname[0] if sockname[0] != "0.0.0.0" else "localhost",
158
179
  sockname[1],
159
180
  )
@@ -193,7 +214,10 @@ class TcpConnection(_Connection, _TransportMixin):
193
214
  @classmethod
194
215
  @_asynccontextmanager
195
216
  async def create_pair(
196
- cls: type[T], peer: type[V] = None
217
+ cls: type[T],
218
+ peer: type[V] = None,
219
+ serve_kwargs: dict[str, _Any] = None,
220
+ connect_kwargs: dict[str, _Any] = None,
197
221
  ) -> _AsyncIterator[tuple[V, T]]:
198
222
  """Create a connection pair."""
199
223
 
@@ -212,13 +236,20 @@ class TcpConnection(_Connection, _TransportMixin):
212
236
  peer = cls # type: ignore
213
237
  assert peer is not None
214
238
 
239
+ if serve_kwargs is None:
240
+ serve_kwargs = {}
241
+
215
242
  server = await stack.enter_async_context(
216
- peer.serve(callback, port=0, backlog=1)
243
+ peer.serve(callback, port=0, backlog=1, **serve_kwargs)
217
244
  )
218
245
 
219
246
  host = server.sockets[0].getsockname()
247
+
248
+ if connect_kwargs is None:
249
+ connect_kwargs = {}
250
+
220
251
  client = await cls.create_connection(
221
- host="localhost", port=host[1]
252
+ host="localhost", port=host[1], **connect_kwargs
222
253
  )
223
254
  await cond.acquire()
224
255
 
@@ -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,
@@ -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
- protocol = await getattr(websockets, "connect")(uri, **kwargs)
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
- async with getattr(websockets, "connect")(uri, **kwargs) as protocol:
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(cls: type[T]) -> _AsyncIterator[tuple[T, T]]:
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.5.0
3
+ Version: 5.6.0
4
4
  Summary: A framework for implementing Python services.
5
5
  Home-page: https://github.com/vkottler/runtimepy
6
6
  Author: Vaughn Kottler
@@ -17,10 +17,10 @@ Classifier: License :: OSI Approved :: MIT License
17
17
  Requires-Python: >=3.11
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
- Requires-Dist: psutil
21
- Requires-Dist: svgen >=0.6.8
22
20
  Requires-Dist: websockets
23
21
  Requires-Dist: vcorelib >=3.3.1
22
+ Requires-Dist: psutil
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=5c5edaf1332d842e9ca04a018455beba
47
+ hash=cd04f0ce079f8c60bc93c7f1d098316b
48
48
  =====================================
49
49
  -->
50
50
 
51
- # runtimepy ([5.5.0](https://pypi.org/project/runtimepy/))
51
+ # runtimepy ([5.6.0](https://pypi.org/project/runtimepy/))
52
52
 
53
53
  [![python](https://img.shields.io/pypi/pyversions/runtimepy.svg)](https://pypi.org/project/runtimepy/)
54
54
  ![Build Status](https://github.com/vkottler/runtimepy/workflows/Python%20Package/badge.svg)
@@ -1,4 +1,4 @@
1
- runtimepy/__init__.py,sha256=ShrPdHuQw62Pp-9QV8h8xgc2KyWFgJwfnR6O97tC7Ec,390
1
+ runtimepy/__init__.py,sha256=I77rsBqYyHVXsW8K5PAegQGJjSX43TwuCmBNnqki_Hk,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=T5IwBeqwJPpg35Ms_Vmz6xS1T-8U3fcgiRU6mAFlkEU,3767
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
@@ -58,7 +58,7 @@ runtimepy/data/js/audio.js,sha256=bLkBqbeHMiGGidfL3iXjmVoF9seK-ZeZ3kwgOrcpgk4,10
58
58
  runtimepy/data/js/events.js,sha256=rgz3Q_8J6sfU_7Sa7fG1mZD0pQ4S3vwN2mqcvQfePkM,554
59
59
  runtimepy/data/js/init.js,sha256=q1SfcbC0RKr-a3z48PLC1usdAj9dh1_EMw5sJvvQKGs,1279
60
60
  runtimepy/data/js/main.js,sha256=r0P_0xx5Czd1jfTjsB-tLfwhp4iPNoajlYC858u0ltc,211
61
- runtimepy/data/js/util.js,sha256=jGs9iMo9yLaZzHcefCTnritotH-ak2Vn1n3BoEhW0MU,1149
61
+ runtimepy/data/js/util.js,sha256=ymYV3xenF3LZ5fw6ACXFnqHiNhFzf9uS7UUal_KsXr0,1376
62
62
  runtimepy/data/js/worker.js,sha256=Yz9VEbE1I1gMrw-Zt7VB3RoII_xHWrS6_nhESEf5c6Q,2128
63
63
  runtimepy/data/js/classes/App.js,sha256=5dQ2_M_23WRfO3MtQ_3imy_oBNnGcS2uWY3g9PS6fJI,2392
64
64
  runtimepy/data/js/classes/ChannelTable.js,sha256=V9g4_6N1i7ci7FkhP9eBd9ENbkSBusO5AvWuIEHUKk8,2634
@@ -124,6 +124,7 @@ 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/ssl.py,sha256=dj9uECPKDT5k-5vlR5I3Z7Go3WWZhbaJ9nb0rC3kJvg,854
127
128
  runtimepy/net/util.py,sha256=foXz7ZZDFpc7_vazzKT304Edgf7sOXj3NrDOY87kNsU,5779
128
129
  runtimepy/net/apps/__init__.py,sha256=vjo7e19QXtJwe6V6B-QGvYiJveYobnYIfpkKZrnS17w,710
129
130
  runtimepy/net/arbiter/__init__.py,sha256=ptKF995rYKvkm4Mya92vA5QEDqcFq5NRD0IYGqZ6_do,740
@@ -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=wNJzoD_aN3sbWZXnAu3-fvb-MbbAtcl5cIG_bpXRmGc,1529
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=-nVkFEZyPcEYsElV5a70qSwsAm-eSkyj3r0c4zE0f3g,2660
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
@@ -185,10 +186,10 @@ runtimepy/net/stream/base.py,sha256=Dg4vcR0n9y2122AyJ-9W-jkEhNla_EHO-DqJJPfGD4k,
185
186
  runtimepy/net/stream/string.py,sha256=61mgserU3p6j5gAcK0oe0aKqL6vDh7NtgJvbPoiAUPM,784
186
187
  runtimepy/net/stream/json/__init__.py,sha256=h--C_9moW92TC_e097FRRXcg8GJ6VVbMLXl1cICknys,2508
187
188
  runtimepy/net/tcp/__init__.py,sha256=OOWohegpoioSTf8M7uDf-4EV1IDungz7-U19L_2yW4I,250
188
- runtimepy/net/tcp/connection.py,sha256=8PbsdWQ32NTFAorEBVcJ3pPa9SpM0Y1FXm-FoSZ0JGs,7849
189
- runtimepy/net/tcp/create.py,sha256=yYlMuvV9N5i5PgwXrK8brpKdrQjmLNPtQsdEtnIMfH8,1940
189
+ runtimepy/net/tcp/connection.py,sha256=od9hFj-zZWPfyQ4lQ47nTUE8tA1ZnhIAF0ps9s6BBHk,8820
190
+ runtimepy/net/tcp/create.py,sha256=zZsRs5KYpO3bNGh-DwEOEzjUDE4ixj-UBHYgZ0GvC7c,2013
190
191
  runtimepy/net/tcp/protocol.py,sha256=vEnIX3gUX2nrw9ofT_e4KYU4VY2k4WP0WuOi4eE_OOQ,1444
191
- runtimepy/net/tcp/http/__init__.py,sha256=NeJi6Utmpc2m3D18DFMIlXfv3xymxX7OIzImTFTz4zI,5504
192
+ runtimepy/net/tcp/http/__init__.py,sha256=4ZSM-Fma_IpmDNCu5E3VBPRrVxOjPgKpKAS3uag1V1I,5657
192
193
  runtimepy/net/tcp/scpi/__init__.py,sha256=aWCWQfdeyfoU9bpOnOtyIQbT1swl4ergXLFn5kXAH28,2105
193
194
  runtimepy/net/tcp/telnet/__init__.py,sha256=96eJFb301I3H2ivDtGMQtDDw09Xm5NRvM9VEC-wjt8c,4768
194
195
  runtimepy/net/tcp/telnet/codes.py,sha256=1-yyRe-Kz_W7d6B0P3iT1AaSNR3_Twmn-MUjKCJJknY,3518
@@ -204,7 +205,7 @@ runtimepy/net/udp/tftp/endpoint.py,sha256=so60LdPTG66N5tdhHhiX7j_TBHvNOTi4JIgLcg
204
205
  runtimepy/net/udp/tftp/enums.py,sha256=06juMd__pJZsyL8zO8p3hRucnOratt1qtz9zcxzMg4s,1579
205
206
  runtimepy/net/udp/tftp/io.py,sha256=w6cnUt-T-Ma6Vg8BWoRbsNnIWUv0HTY4am6bcLWxNJs,803
206
207
  runtimepy/net/websocket/__init__.py,sha256=YjSmoxiigmsI_hcQw6nueX7bxhrRGerEERnPvgLVEVA,313
207
- runtimepy/net/websocket/connection.py,sha256=Q4TDXcEXrUv1IfNg1veMxag9AyvvYmuYsnzufYaWnD8,8274
208
+ runtimepy/net/websocket/connection.py,sha256=r0HUB5PFhWGmmarwEKp10CJ449Iwj6WOcMyhjzgCMpU,8996
208
209
  runtimepy/noise/__init__.py,sha256=EJM7h3t_z74wwrn6FAFQwYE2yUcOZQ1K1IQqOb8Z0AI,384
209
210
  runtimepy/primitives/__init__.py,sha256=nwWJH1e0KN2NsVwQ3wvRtUpl9s9Ap8Q32NNZLGol0wU,2323
210
211
  runtimepy/primitives/base.py,sha256=BaGPUTeVMnLnTPcpjqnS2lzPN74Pe5C0XaQdgrTfW7A,9185
@@ -261,9 +262,9 @@ runtimepy/tui/task.py,sha256=nUZo9fuOC-k1Wpqdzkv9v1tQirCI28fZVgcC13Ijvus,1093
261
262
  runtimepy/tui/channels/__init__.py,sha256=evDaiIn-YS9uGhdo8ZGtP9VK1ek6sr_P1nJ9JuSET0o,4536
262
263
  runtimepy/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
263
264
  runtimepy/ui/controls.py,sha256=yvT7h3thbYaitsakcIAJ90EwKzJ4b-jnc6p3UuVf_XE,1241
264
- runtimepy-5.5.0.dist-info/LICENSE,sha256=okYCYhGsx_BlzvFdoNVBVpw_Cfb4SOqHA_VAARml4Hc,1071
265
- runtimepy-5.5.0.dist-info/METADATA,sha256=2wXUkVGv7ALo-oNtbi-51eMTqiVpXklvc0_4eQP4ECY,8851
266
- runtimepy-5.5.0.dist-info/WHEEL,sha256=nCVcAvsfA9TDtwGwhYaRrlPhTLV9m-Ga6mdyDtuwK18,91
267
- runtimepy-5.5.0.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
268
- runtimepy-5.5.0.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
269
- runtimepy-5.5.0.dist-info/RECORD,,
265
+ runtimepy-5.6.0.dist-info/LICENSE,sha256=okYCYhGsx_BlzvFdoNVBVpw_Cfb4SOqHA_VAARml4Hc,1071
266
+ runtimepy-5.6.0.dist-info/METADATA,sha256=7HqQbErEKuufEXCpMUpmGACLaX_sMahCQ4qpPGTFZlY,8851
267
+ runtimepy-5.6.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
268
+ runtimepy-5.6.0.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
269
+ runtimepy-5.6.0.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
270
+ runtimepy-5.6.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (73.0.0)
2
+ Generator: setuptools (75.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5