runtimepy 5.4.1__py3-none-any.whl → 5.4.2__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/tftp.py +7 -2
- runtimepy/data/tftp_server.yaml +1 -1
- runtimepy/entry.py +5 -3
- runtimepy/net/udp/connection.py +8 -5
- runtimepy/net/udp/tftp/__init__.py +27 -26
- runtimepy/net/udp/tftp/base.py +64 -7
- runtimepy/net/udp/tftp/endpoint.py +9 -2
- runtimepy/net/util.py +18 -0
- runtimepy/primitives/serializable/base.py +5 -1
- {runtimepy-5.4.1.dist-info → runtimepy-5.4.2.dist-info}/METADATA +6 -6
- {runtimepy-5.4.1.dist-info → runtimepy-5.4.2.dist-info}/RECORD +16 -16
- {runtimepy-5.4.1.dist-info → runtimepy-5.4.2.dist-info}/WHEEL +1 -1
- {runtimepy-5.4.1.dist-info → runtimepy-5.4.2.dist-info}/LICENSE +0 -0
- {runtimepy-5.4.1.dist-info → runtimepy-5.4.2.dist-info}/entry_points.txt +0 -0
- {runtimepy-5.4.1.dist-info → runtimepy-5.4.2.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=e6970089f5f2935c496cb3e9bb06b774
|
|
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.4.
|
|
13
|
+
VERSION = "5.4.2"
|
|
14
14
|
|
|
15
15
|
# runtimepy-specific content.
|
|
16
16
|
METRICS_NAME = "metrics"
|
runtimepy/commands/tftp.py
CHANGED
|
@@ -6,6 +6,7 @@ An entry-point for the 'tftp' command.
|
|
|
6
6
|
import argparse
|
|
7
7
|
import asyncio
|
|
8
8
|
from pathlib import Path
|
|
9
|
+
from socket import getaddrinfo
|
|
9
10
|
|
|
10
11
|
from vcorelib.args import CommandFunction
|
|
11
12
|
|
|
@@ -21,7 +22,8 @@ from runtimepy.net.udp.tftp.enums import DEFAULT_MODE
|
|
|
21
22
|
def tftp_cmd(args: argparse.Namespace) -> int:
|
|
22
23
|
"""Execute the tftp command."""
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
# Resolve hostname as early as possible.
|
|
26
|
+
addr = (getaddrinfo(args.host, None)[0][4][0], args.port)
|
|
25
27
|
|
|
26
28
|
stop_sig = asyncio.Event()
|
|
27
29
|
kwargs = {
|
|
@@ -31,6 +33,9 @@ def tftp_cmd(args: argparse.Namespace) -> int:
|
|
|
31
33
|
"process_kwargs": {"stop_sig": stop_sig},
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
if not args.their_file:
|
|
37
|
+
args.their_file = str(args.our_file)
|
|
38
|
+
|
|
34
39
|
if args.operation == "read":
|
|
35
40
|
task = tftp_read(addr, args.our_file, args.their_file, **kwargs)
|
|
36
41
|
else:
|
|
@@ -81,6 +86,6 @@ def add_tftp_cmd(parser: argparse.ArgumentParser) -> CommandFunction:
|
|
|
81
86
|
parser.add_argument("host", help="host to message")
|
|
82
87
|
|
|
83
88
|
parser.add_argument("our_file", type=Path, help="path to our file")
|
|
84
|
-
parser.add_argument("their_file",
|
|
89
|
+
parser.add_argument("their_file", nargs="?", help="path to their file")
|
|
85
90
|
|
|
86
91
|
return tftp_cmd
|
runtimepy/data/tftp_server.yaml
CHANGED
runtimepy/entry.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# =====================================
|
|
2
2
|
# generator=datazen
|
|
3
3
|
# version=3.1.4
|
|
4
|
-
# hash=
|
|
4
|
+
# hash=79c31d1280a6e97b5d326aecb758c597
|
|
5
5
|
# =====================================
|
|
6
6
|
|
|
7
7
|
"""
|
|
@@ -10,13 +10,14 @@ This package's command-line entry-point (boilerplate).
|
|
|
10
10
|
|
|
11
11
|
# built-in
|
|
12
12
|
import argparse
|
|
13
|
+
from logging import getLogger
|
|
13
14
|
import os
|
|
14
15
|
from pathlib import Path
|
|
15
16
|
import sys
|
|
16
17
|
from typing import List
|
|
17
18
|
|
|
18
19
|
# third-party
|
|
19
|
-
from vcorelib.logging import init_logging, logging_args
|
|
20
|
+
from vcorelib.logging import init_logging, log_time, logging_args
|
|
20
21
|
|
|
21
22
|
# internal
|
|
22
23
|
from runtimepy import DESCRIPTION, VERSION
|
|
@@ -68,7 +69,8 @@ def main(argv: List[str] = None) -> int:
|
|
|
68
69
|
os.chdir(args.dir)
|
|
69
70
|
|
|
70
71
|
# run the application
|
|
71
|
-
|
|
72
|
+
with log_time(getLogger(__name__), "Command"):
|
|
73
|
+
result = entry(args)
|
|
72
74
|
except SystemExit as exc:
|
|
73
75
|
result = 1
|
|
74
76
|
if exc.code is not None and isinstance(exc.code, int):
|
runtimepy/net/udp/connection.py
CHANGED
|
@@ -126,14 +126,17 @@ class UdpConnection(_Connection, _TransportMixin):
|
|
|
126
126
|
)
|
|
127
127
|
return result is not None
|
|
128
128
|
|
|
129
|
+
should_connect: bool = True
|
|
130
|
+
|
|
129
131
|
@classmethod
|
|
130
|
-
async def create_connection(
|
|
131
|
-
cls: type[T], connect: bool = True, **kwargs
|
|
132
|
-
) -> T:
|
|
132
|
+
async def create_connection(cls: type[T], **kwargs) -> T:
|
|
133
133
|
"""Create a UDP connection."""
|
|
134
134
|
|
|
135
135
|
LOG.debug("kwargs: %s", kwargs)
|
|
136
136
|
|
|
137
|
+
# Allows certain connections to have more sane defaults.
|
|
138
|
+
connect = kwargs.pop("connect", cls.should_connect)
|
|
139
|
+
|
|
137
140
|
# If the caller specifies a remote address but doesn't want a connected
|
|
138
141
|
# socket, handle this after initial creation.
|
|
139
142
|
remote_addr = None
|
|
@@ -173,8 +176,8 @@ class UdpConnection(_Connection, _TransportMixin):
|
|
|
173
176
|
sock1.connect(("localhost", sock2.getsockname()[1]))
|
|
174
177
|
sock2.connect(("localhost", sock1.getsockname()[1]))
|
|
175
178
|
|
|
176
|
-
conn1 = await cls.create_connection(sock=sock1)
|
|
177
|
-
conn2 = await cls.create_connection(sock=sock2)
|
|
179
|
+
conn1 = await cls.create_connection(sock=sock1, connect=True)
|
|
180
|
+
conn2 = await cls.create_connection(sock=sock2, connect=True)
|
|
178
181
|
assert conn1.remote_address is not None
|
|
179
182
|
assert conn2.remote_address is not None
|
|
180
183
|
|
|
@@ -38,19 +38,19 @@ class TftpConnection(BaseTftpConnection):
|
|
|
38
38
|
) -> bool:
|
|
39
39
|
"""Request a tftp read operation."""
|
|
40
40
|
|
|
41
|
-
endpoint = self.endpoint(addr)
|
|
42
41
|
end_of_data = False
|
|
43
42
|
idx = 1
|
|
44
43
|
|
|
45
|
-
def ack_sender() -> None:
|
|
46
|
-
"""Send acks."""
|
|
47
|
-
nonlocal idx
|
|
48
|
-
self.send_ack(block=idx - 1, addr=addr)
|
|
49
|
-
|
|
50
44
|
async with AsyncExitStack() as stack:
|
|
51
45
|
# Claim read lock and ignore cancellation.
|
|
52
46
|
stack.enter_context(suppress(asyncio.CancelledError))
|
|
53
|
-
|
|
47
|
+
|
|
48
|
+
endpoint, event = await self._await_first_block(stack, addr=addr)
|
|
49
|
+
|
|
50
|
+
def ack_sender() -> None:
|
|
51
|
+
"""Send acks."""
|
|
52
|
+
nonlocal idx
|
|
53
|
+
endpoint.ack_sender(idx - 1, endpoint.addr)
|
|
54
54
|
|
|
55
55
|
def send_rrq() -> None:
|
|
56
56
|
"""Send request"""
|
|
@@ -60,9 +60,6 @@ class TftpConnection(BaseTftpConnection):
|
|
|
60
60
|
"Requesting '%s' (%s) -> %s.", filename, mode, destination
|
|
61
61
|
)
|
|
62
62
|
|
|
63
|
-
event = asyncio.Event()
|
|
64
|
-
endpoint.awaiting_blocks[idx] = event
|
|
65
|
-
|
|
66
63
|
with self.log_time("Awaiting first data block", reminder=True):
|
|
67
64
|
# Wait for first data block.
|
|
68
65
|
if not await repeat_until(
|
|
@@ -112,21 +109,22 @@ class TftpConnection(BaseTftpConnection):
|
|
|
112
109
|
if success:
|
|
113
110
|
write_block()
|
|
114
111
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
112
|
+
# Repeat last ack in the background.
|
|
113
|
+
if end_of_data:
|
|
114
|
+
self._conn_tasks.append(
|
|
115
|
+
asyncio.create_task(
|
|
116
|
+
repeat_until( # type: ignore
|
|
117
|
+
ack_sender,
|
|
118
|
+
asyncio.Event(),
|
|
119
|
+
endpoint.period.value,
|
|
120
|
+
endpoint.timeout.value,
|
|
121
|
+
)
|
|
124
122
|
)
|
|
125
123
|
)
|
|
126
|
-
)
|
|
127
124
|
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
# Ensure at least one ack sends.
|
|
126
|
+
await asyncio.sleep(0.01)
|
|
127
|
+
|
|
130
128
|
self.logger.info(
|
|
131
129
|
"Read %s (%s).",
|
|
132
130
|
FileInfo.from_file(destination),
|
|
@@ -146,16 +144,14 @@ class TftpConnection(BaseTftpConnection):
|
|
|
146
144
|
"""Request a tftp write operation."""
|
|
147
145
|
|
|
148
146
|
result = False
|
|
149
|
-
endpoint = self.endpoint(addr)
|
|
150
147
|
|
|
151
148
|
with as_path(source) as src:
|
|
152
149
|
async with AsyncExitStack() as stack:
|
|
153
150
|
# Claim write lock and ignore cancellation.
|
|
154
151
|
stack.enter_context(suppress(asyncio.CancelledError))
|
|
155
|
-
await stack.enter_async_context(endpoint.lock)
|
|
156
152
|
|
|
157
|
-
|
|
158
|
-
endpoint.
|
|
153
|
+
# Set up first-ack handling.
|
|
154
|
+
endpoint, event = await self._await_first_ack(stack, addr=addr)
|
|
159
155
|
|
|
160
156
|
def send_wrq() -> None:
|
|
161
157
|
"""Send request."""
|
|
@@ -183,6 +179,11 @@ class TftpConnection(BaseTftpConnection):
|
|
|
183
179
|
)
|
|
184
180
|
|
|
185
181
|
# Compare hashes.
|
|
182
|
+
self.logger.info(
|
|
183
|
+
"Reading '%s' %s.",
|
|
184
|
+
filename,
|
|
185
|
+
"succeeded" if result else "failed",
|
|
186
|
+
)
|
|
186
187
|
if result:
|
|
187
188
|
result = file_md5_hex(src) == file_md5_hex(tmp)
|
|
188
189
|
self.logger.info(
|
runtimepy/net/udp/tftp/base.py
CHANGED
|
@@ -3,6 +3,8 @@ A module implementing a base tftp (RFC 1350) connection interface.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
# built-in
|
|
6
|
+
import asyncio
|
|
7
|
+
from contextlib import AsyncExitStack
|
|
6
8
|
from io import BytesIO
|
|
7
9
|
import logging
|
|
8
10
|
from pathlib import Path
|
|
@@ -22,6 +24,7 @@ from runtimepy.net.udp.tftp.enums import (
|
|
|
22
24
|
encode_filename_mode,
|
|
23
25
|
parse_filename_mode,
|
|
24
26
|
)
|
|
27
|
+
from runtimepy.net.util import normalize_host
|
|
25
28
|
from runtimepy.primitives import Double, Uint16
|
|
26
29
|
|
|
27
30
|
REEMIT_PERIOD_S = 0.20
|
|
@@ -36,6 +39,7 @@ class BaseTftpConnection(UdpConnection):
|
|
|
36
39
|
_path: Path
|
|
37
40
|
|
|
38
41
|
default_auto_restart = True
|
|
42
|
+
should_connect = False
|
|
39
43
|
|
|
40
44
|
def set_root(self, path: Path) -> None:
|
|
41
45
|
"""Set a new root path for this instance."""
|
|
@@ -123,9 +127,39 @@ class BaseTftpConnection(UdpConnection):
|
|
|
123
127
|
|
|
124
128
|
self.error_sender = error_sender
|
|
125
129
|
|
|
126
|
-
self._endpoints: dict[
|
|
130
|
+
self._endpoints: dict[IpHost, TftpEndpoint] = {}
|
|
131
|
+
self._awaiting_first_ack: dict[str, TftpEndpoint] = {}
|
|
132
|
+
self._awaiting_first_block: dict[str, TftpEndpoint] = {}
|
|
127
133
|
# self._self = self.endpoint(self.local_address)
|
|
128
134
|
|
|
135
|
+
async def _await_first_ack(
|
|
136
|
+
self,
|
|
137
|
+
stack: AsyncExitStack,
|
|
138
|
+
addr: Union[IpHost, tuple[str, int]] = None,
|
|
139
|
+
) -> tuple[TftpEndpoint, asyncio.Event]:
|
|
140
|
+
"""Set up an endpoint to wait for an initial ack from a server."""
|
|
141
|
+
|
|
142
|
+
endpoint = self.endpoint(addr)
|
|
143
|
+
await stack.enter_async_context(endpoint.lock)
|
|
144
|
+
event = asyncio.Event()
|
|
145
|
+
endpoint.awaiting_acks[0] = event
|
|
146
|
+
self._awaiting_first_ack[endpoint.addr.hostname] = endpoint
|
|
147
|
+
return endpoint, event
|
|
148
|
+
|
|
149
|
+
async def _await_first_block(
|
|
150
|
+
self,
|
|
151
|
+
stack: AsyncExitStack,
|
|
152
|
+
addr: Union[IpHost, tuple[str, int]] = None,
|
|
153
|
+
) -> tuple[TftpEndpoint, asyncio.Event]:
|
|
154
|
+
"""Set up an endpoint to wait for an initial block from a server."""
|
|
155
|
+
|
|
156
|
+
endpoint = self.endpoint(addr)
|
|
157
|
+
await stack.enter_async_context(endpoint.lock)
|
|
158
|
+
event = asyncio.Event()
|
|
159
|
+
endpoint.awaiting_blocks[1] = event
|
|
160
|
+
self._awaiting_first_block[endpoint.addr.hostname] = endpoint
|
|
161
|
+
return endpoint, event
|
|
162
|
+
|
|
129
163
|
def endpoint(
|
|
130
164
|
self, addr: Union[IpHost, tuple[str, int]] = None
|
|
131
165
|
) -> TftpEndpoint:
|
|
@@ -135,10 +169,10 @@ class BaseTftpConnection(UdpConnection):
|
|
|
135
169
|
addr = self.remote_address
|
|
136
170
|
|
|
137
171
|
assert addr is not None
|
|
138
|
-
|
|
172
|
+
addr = normalize_host(*addr)
|
|
139
173
|
|
|
140
|
-
if
|
|
141
|
-
self._endpoints[
|
|
174
|
+
if addr not in self._endpoints:
|
|
175
|
+
self._endpoints[addr] = TftpEndpoint(
|
|
142
176
|
self._path,
|
|
143
177
|
self.logger,
|
|
144
178
|
addr,
|
|
@@ -149,7 +183,7 @@ class BaseTftpConnection(UdpConnection):
|
|
|
149
183
|
self.endpoint_timeout,
|
|
150
184
|
)
|
|
151
185
|
|
|
152
|
-
return self._endpoints[
|
|
186
|
+
return self._endpoints[addr]
|
|
153
187
|
|
|
154
188
|
def send_rrq(
|
|
155
189
|
self,
|
|
@@ -269,15 +303,38 @@ class BaseTftpConnection(UdpConnection):
|
|
|
269
303
|
) -> None:
|
|
270
304
|
"""Handle a data message."""
|
|
271
305
|
|
|
306
|
+
endpoint = self.endpoint(addr)
|
|
272
307
|
block = self._read_block_number(stream)
|
|
273
|
-
|
|
308
|
+
|
|
309
|
+
# Check if we're currently waiting for an initial block.
|
|
310
|
+
hostname = endpoint.addr.hostname
|
|
311
|
+
if block == 1 and hostname in self._awaiting_first_block:
|
|
312
|
+
to_update = self._awaiting_first_block[hostname]
|
|
313
|
+
del self._awaiting_first_block[hostname]
|
|
314
|
+
self._endpoints[endpoint.addr] = to_update
|
|
315
|
+
endpoint = to_update.update_from_other(endpoint)
|
|
316
|
+
|
|
317
|
+
endpoint.handle_data(block, stream.read())
|
|
274
318
|
|
|
275
319
|
async def _handle_ack(
|
|
276
320
|
self, stream: BinaryIO, addr: tuple[str, int]
|
|
277
321
|
) -> None:
|
|
278
322
|
"""Handle an acknowledge message."""
|
|
279
323
|
|
|
280
|
-
self.endpoint(addr)
|
|
324
|
+
endpoint = self.endpoint(addr)
|
|
325
|
+
block = self._read_block_number(stream)
|
|
326
|
+
|
|
327
|
+
# Check if we're currently waiting for an initial acknowledgement. This
|
|
328
|
+
# will come from the same host but a different port, so update
|
|
329
|
+
# references when this is detected.
|
|
330
|
+
hostname = endpoint.addr.hostname
|
|
331
|
+
if block == 0 and hostname in self._awaiting_first_ack:
|
|
332
|
+
to_update = self._awaiting_first_ack[hostname]
|
|
333
|
+
del self._awaiting_first_ack[hostname]
|
|
334
|
+
self._endpoints[endpoint.addr] = to_update
|
|
335
|
+
endpoint = to_update.update_from_other(endpoint)
|
|
336
|
+
|
|
337
|
+
endpoint.handle_ack(block)
|
|
281
338
|
|
|
282
339
|
def _read_block_number(self, stream: BinaryIO) -> int:
|
|
283
340
|
"""Read block number from the stream."""
|
|
@@ -40,7 +40,7 @@ class TftpEndpoint(LoggerMixin):
|
|
|
40
40
|
self,
|
|
41
41
|
root: Path,
|
|
42
42
|
logger: LoggerType,
|
|
43
|
-
addr:
|
|
43
|
+
addr: IpHost,
|
|
44
44
|
data_sender: TftpDataSender,
|
|
45
45
|
ack_sender: TftpAckSender,
|
|
46
46
|
error_sender: TftpErrorSender,
|
|
@@ -75,6 +75,13 @@ class TftpEndpoint(LoggerMixin):
|
|
|
75
75
|
self.timeout = timeout
|
|
76
76
|
self.log_limiter = RateLimiter.from_s(1.0)
|
|
77
77
|
|
|
78
|
+
def update_from_other(self, other: "TftpEndpoint") -> "TftpEndpoint":
|
|
79
|
+
"""Update this endpoint's attributes with attributes of another's."""
|
|
80
|
+
|
|
81
|
+
self.logger.info("Updating address to '%s'.", other.addr)
|
|
82
|
+
self.addr = other.addr
|
|
83
|
+
return self
|
|
84
|
+
|
|
78
85
|
def chunk_sender(self, block: int, data: bytes) -> Callable[[], None]:
|
|
79
86
|
"""Create a method that sends a specific block of data."""
|
|
80
87
|
|
|
@@ -135,7 +142,7 @@ class TftpEndpoint(LoggerMixin):
|
|
|
135
142
|
|
|
136
143
|
def __str__(self) -> str:
|
|
137
144
|
"""Get this instance as a string."""
|
|
138
|
-
return
|
|
145
|
+
return str(self.addr)
|
|
139
146
|
|
|
140
147
|
def handle_error(self, error_code: TftpErrorCode, message: str) -> None:
|
|
141
148
|
"""Handle a tftp error message."""
|
runtimepy/net/util.py
CHANGED
|
@@ -19,6 +19,11 @@ class IPv4Host(NamedTuple):
|
|
|
19
19
|
name: str = ""
|
|
20
20
|
port: int = 0
|
|
21
21
|
|
|
22
|
+
@property
|
|
23
|
+
def hostname(self) -> str:
|
|
24
|
+
"""Get a hostname for this instance."""
|
|
25
|
+
return hostname(self.name)
|
|
26
|
+
|
|
22
27
|
@property
|
|
23
28
|
def address(self) -> ipaddress.IPv4Address:
|
|
24
29
|
"""Get an address object for this hostname."""
|
|
@@ -28,6 +33,10 @@ class IPv4Host(NamedTuple):
|
|
|
28
33
|
"""Get this host as a string."""
|
|
29
34
|
return hostname_port(self.name, self.port)
|
|
30
35
|
|
|
36
|
+
def __hash__(self) -> int:
|
|
37
|
+
"""Get a hash for this instance."""
|
|
38
|
+
return hash(str(self))
|
|
39
|
+
|
|
31
40
|
|
|
32
41
|
class IPv6Host(NamedTuple):
|
|
33
42
|
"""See: https://docs.python.org/3/library/socket.html#socket-families."""
|
|
@@ -37,6 +46,11 @@ class IPv6Host(NamedTuple):
|
|
|
37
46
|
flowinfo: int = 0
|
|
38
47
|
scope_id: int = 0
|
|
39
48
|
|
|
49
|
+
@property
|
|
50
|
+
def hostname(self) -> str:
|
|
51
|
+
"""Get a hostname for this instance."""
|
|
52
|
+
return hostname(self.name)
|
|
53
|
+
|
|
40
54
|
@property
|
|
41
55
|
def address(self) -> ipaddress.IPv6Address:
|
|
42
56
|
"""Get an address object for this hostname."""
|
|
@@ -46,6 +60,10 @@ class IPv6Host(NamedTuple):
|
|
|
46
60
|
"""Get this host as a string."""
|
|
47
61
|
return hostname_port(self.name, self.port)
|
|
48
62
|
|
|
63
|
+
def __hash__(self) -> int:
|
|
64
|
+
"""Get a hash for this instance."""
|
|
65
|
+
return hash(str(self))
|
|
66
|
+
|
|
49
67
|
|
|
50
68
|
IpHost = _Union[IPv4Host, IPv6Host]
|
|
51
69
|
IpHostlike = _Union[str, int, IpHost, None]
|
|
@@ -134,7 +134,11 @@ class Serializable(ABC):
|
|
|
134
134
|
"""Assign a next serializable."""
|
|
135
135
|
|
|
136
136
|
assert self.chain is None, self.chain
|
|
137
|
-
|
|
137
|
+
|
|
138
|
+
# mypy regression?
|
|
139
|
+
self.chain = chain # type: ignore
|
|
140
|
+
assert self.chain is not None
|
|
141
|
+
|
|
138
142
|
return self.chain.size
|
|
139
143
|
|
|
140
144
|
def add_to_end(self, chain: T, array_length: int = None) -> int:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: runtimepy
|
|
3
|
-
Version: 5.4.
|
|
3
|
+
Version: 5.4.2
|
|
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: svgen >=0.6.8
|
|
21
|
-
Requires-Dist: psutil
|
|
22
|
-
Requires-Dist: websockets
|
|
23
21
|
Requires-Dist: vcorelib >=3.3.1
|
|
22
|
+
Requires-Dist: websockets
|
|
23
|
+
Requires-Dist: psutil
|
|
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=4f8a71a6066638ed1a90f375188f0578
|
|
48
48
|
=====================================
|
|
49
49
|
-->
|
|
50
50
|
|
|
51
|
-
# runtimepy ([5.4.
|
|
51
|
+
# runtimepy ([5.4.2](https://pypi.org/project/runtimepy/))
|
|
52
52
|
|
|
53
53
|
[](https://pypi.org/project/runtimepy/)
|
|
54
54
|

|
|
@@ -197,7 +197,7 @@ options:
|
|
|
197
197
|
$ ./venv3.12/bin/runtimepy tftp -h
|
|
198
198
|
|
|
199
199
|
usage: runtimepy tftp [-h] [-p PORT] [-m MODE] [-t TIMEOUT] [-r REEMIT]
|
|
200
|
-
{read,write} host our_file their_file
|
|
200
|
+
{read,write} host our_file [their_file]
|
|
201
201
|
|
|
202
202
|
positional arguments:
|
|
203
203
|
{read,write} action to perform
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
runtimepy/__init__.py,sha256=
|
|
1
|
+
runtimepy/__init__.py,sha256=ojyvRCtIcczCpJWeruS4_U9LFJ6NMGfeWnb-MKrQ3pU,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
|
|
5
|
-
runtimepy/entry.py,sha256=
|
|
5
|
+
runtimepy/entry.py,sha256=3672ccoslf2h8Wg5M_SuW6SoEx0oslRoi0ngZsgjNz8,1954
|
|
6
6
|
runtimepy/mapping.py,sha256=nb3btKUGHhuhkhPg9NnpBdh4QGQVjE0Zsu4Sl3OBIgY,5091
|
|
7
7
|
runtimepy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
runtimepy/requirements.txt,sha256=Wf3CEWVylp7Em7-FMWWhjyAwxNvNmh7vPRqW2KkbbXY,115
|
|
@@ -34,7 +34,7 @@ runtimepy/commands/arbiter.py,sha256=CtTMRYpqCAN3vWHkkr9jqWpoF7JGNXafKIBFmkarAfc
|
|
|
34
34
|
runtimepy/commands/common.py,sha256=NvZdeIFBHAF52c1n7vqD59DW6ywc-rG5iC5MpuhGf-c,2449
|
|
35
35
|
runtimepy/commands/server.py,sha256=T5IwBeqwJPpg35Ms_Vmz6xS1T-8U3fcgiRU6mAFlkEU,3767
|
|
36
36
|
runtimepy/commands/task.py,sha256=6xRVlRwpEZVhrcY18sQcfdWEOxeQZLeOF-6UrUURtO4,1435
|
|
37
|
-
runtimepy/commands/tftp.py,sha256=
|
|
37
|
+
runtimepy/commands/tftp.py,sha256=5dsrYSWbAY-ZAdvfukLOynkrJBtR4J1zEOZey4FerF4,2308
|
|
38
38
|
runtimepy/commands/tui.py,sha256=9hWA3_YATibUUDTVQr7UnKzPTDVJ7WxWKTYYQpLoyrE,1869
|
|
39
39
|
runtimepy/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
40
|
runtimepy/control/source.py,sha256=nW3Q2D-LcekII7K5XKbxXCcR-9jYQyvv0UypeNy1Dnw,1695
|
|
@@ -47,7 +47,7 @@ runtimepy/data/favicon.ico,sha256=jgGIqM4gTrqPs3I6wajqKUH6Je885zy-jI7eU-Nl3AA,36
|
|
|
47
47
|
runtimepy/data/server.yaml,sha256=wS_Ceiu2TpkfPurpqoYoPlgzc9DAWtUd24MW7t-S5rU,97
|
|
48
48
|
runtimepy/data/server_base.yaml,sha256=QkF1xcHyMEViUhOc5rKFbFXjbRDDDHbX5LXvSQJRS58,615
|
|
49
49
|
runtimepy/data/server_dev.yaml,sha256=nQsPh7LuQig3pzHfdg_aD3yOUiCj1sKKfI-WwW3hXmQ,523
|
|
50
|
-
runtimepy/data/tftp_server.yaml,sha256
|
|
50
|
+
runtimepy/data/tftp_server.yaml,sha256=-bFOWJSagI-fEQQcT8k7eDMJVfSPm2XAxLVG3dqUTa4,204
|
|
51
51
|
runtimepy/data/css/bootstrap_extra.css,sha256=UyGXyv7nULrJ0UHsXTSM2IIrJ4X2XIEbPjmmU87yQno,1334
|
|
52
52
|
runtimepy/data/css/main.css,sha256=h1iKkxg6-t7wTwNQvxDiD9mjpWrqq_FmgT6yXP7UANQ,688
|
|
53
53
|
runtimepy/data/js/DataConnection.js,sha256=DnX8FMehjJXqmI62UMYXSvl_XdfQMzq3XUDFbLu2GgI,98
|
|
@@ -121,7 +121,7 @@ runtimepy/net/backoff.py,sha256=IVloxQd-gEK62gFAlU2aOwgTEXhsNUTBfS16eiOqKG8,1080
|
|
|
121
121
|
runtimepy/net/connection.py,sha256=lg_cAGCAdOqlh3SURJRKDQ5TraiG7sV0kA_U2FGGNVU,12678
|
|
122
122
|
runtimepy/net/manager.py,sha256=-M-ZSB9izay6HK1ytTayAYnSHYAz34dcwxaiNhC4lWg,4264
|
|
123
123
|
runtimepy/net/mixin.py,sha256=0ySqjWlU-iqUS0Xgr2rzdbzZtbGoZEDLjF3TzHQpV8U,2271
|
|
124
|
-
runtimepy/net/util.py,sha256=
|
|
124
|
+
runtimepy/net/util.py,sha256=kgOrNKzXdRPhsKU4GBmnJii6-Pcdnl5nDXI8IiSNYBQ,4017
|
|
125
125
|
runtimepy/net/apps/__init__.py,sha256=vjo7e19QXtJwe6V6B-QGvYiJveYobnYIfpkKZrnS17w,710
|
|
126
126
|
runtimepy/net/arbiter/__init__.py,sha256=ptKF995rYKvkm4Mya92vA5QEDqcFq5NRD0IYGqZ6_do,740
|
|
127
127
|
runtimepy/net/arbiter/base.py,sha256=WRbgavarmOx6caQJmfI03udZvNC7o298uOhOsN-lp2E,14658
|
|
@@ -190,13 +190,13 @@ runtimepy/net/tcp/telnet/__init__.py,sha256=96eJFb301I3H2ivDtGMQtDDw09Xm5NRvM9VE
|
|
|
190
190
|
runtimepy/net/tcp/telnet/codes.py,sha256=1-yyRe-Kz_W7d6B0P3iT1AaSNR3_Twmn-MUjKCJJknY,3518
|
|
191
191
|
runtimepy/net/tcp/telnet/np_05b.py,sha256=ikp9txHBvbRps6x7C1FsEf9i6vmUtGvei4Nukc_gypI,7887
|
|
192
192
|
runtimepy/net/udp/__init__.py,sha256=VSgle6cO2RXfwaei66wMIJ4zSB4gzpoadSSbmLfCzBw,357
|
|
193
|
-
runtimepy/net/udp/connection.py,sha256=
|
|
193
|
+
runtimepy/net/udp/connection.py,sha256=YtLty6iveVH6PQRODKriwR5Q6lFi0Ed4k0o5r0bgA3A,7914
|
|
194
194
|
runtimepy/net/udp/create.py,sha256=84YDfJbNBlN0ZwbNpvh6Dl7ZPecbZfmpjMNRRWcvJDk,2005
|
|
195
195
|
runtimepy/net/udp/protocol.py,sha256=A4SRHf0CgcL2zDs1nAsGDqz0RxKBy1soS8wtNdS5S0I,1492
|
|
196
196
|
runtimepy/net/udp/queue.py,sha256=DF-YscxQcGbGCYQLz_l_BMaSRfraZOhRwieTEdXLMds,637
|
|
197
|
-
runtimepy/net/udp/tftp/__init__.py,sha256=
|
|
198
|
-
runtimepy/net/udp/tftp/base.py,sha256=
|
|
199
|
-
runtimepy/net/udp/tftp/endpoint.py,sha256=
|
|
197
|
+
runtimepy/net/udp/tftp/__init__.py,sha256=SJGkizBqV73XfQ9HIRAcFE8Xy9JXJHptr_WtbFswZgc,8724
|
|
198
|
+
runtimepy/net/udp/tftp/base.py,sha256=FCrtpLdUFkQU4nkn5vJc6Gn5EsZauCf8eLhxx1r_vG8,11276
|
|
199
|
+
runtimepy/net/udp/tftp/endpoint.py,sha256=so60LdPTG66N5tdhHhiX7j_TBHvNOTi4JIgLcg2MAm0,10890
|
|
200
200
|
runtimepy/net/udp/tftp/enums.py,sha256=06juMd__pJZsyL8zO8p3hRucnOratt1qtz9zcxzMg4s,1579
|
|
201
201
|
runtimepy/net/udp/tftp/io.py,sha256=w6cnUt-T-Ma6Vg8BWoRbsNnIWUv0HTY4am6bcLWxNJs,803
|
|
202
202
|
runtimepy/net/websocket/__init__.py,sha256=YjSmoxiigmsI_hcQw6nueX7bxhrRGerEERnPvgLVEVA,313
|
|
@@ -217,7 +217,7 @@ runtimepy/primitives/field/fields.py,sha256=jDNi1tl2Xc3GBmt6QJuqxbhP8MtxgertGbPF
|
|
|
217
217
|
runtimepy/primitives/field/manager/__init__.py,sha256=BCRi6-_5OOJ8kz78JHkiLp8cZ71KA1uiF2zq5FFe9js,2586
|
|
218
218
|
runtimepy/primitives/field/manager/base.py,sha256=EyWs5D9_reKOTLkh8PuW45ySjCh31fY_qrtFIcmIOV4,6914
|
|
219
219
|
runtimepy/primitives/serializable/__init__.py,sha256=mZ8KZe2aQswGL6GTjQ8-IaUQwg-6aUUBP5RXyzR6crc,393
|
|
220
|
-
runtimepy/primitives/serializable/base.py,sha256=
|
|
220
|
+
runtimepy/primitives/serializable/base.py,sha256=piviwBXWV6dvbppaVnH-k1fmgVbE8rHvHvZogb2jsLw,4496
|
|
221
221
|
runtimepy/primitives/serializable/fixed.py,sha256=rhr6uVbo0Lvazk4fLI7iei-vVNEwP1J8-LoUjW1NaMI,1077
|
|
222
222
|
runtimepy/primitives/serializable/prefixed.py,sha256=oQXW0pGRovKolheL5ZL2m9aNVMCtKTAi5OlC9KW0iKI,2855
|
|
223
223
|
runtimepy/primitives/types/__init__.py,sha256=JUJpDFIjDUYo-Jnx5sZnkmbGLVjIHVsRcvBjVlJ7fsA,1588
|
|
@@ -255,9 +255,9 @@ runtimepy/tui/task.py,sha256=nUZo9fuOC-k1Wpqdzkv9v1tQirCI28fZVgcC13Ijvus,1093
|
|
|
255
255
|
runtimepy/tui/channels/__init__.py,sha256=evDaiIn-YS9uGhdo8ZGtP9VK1ek6sr_P1nJ9JuSET0o,4536
|
|
256
256
|
runtimepy/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
257
257
|
runtimepy/ui/controls.py,sha256=yvT7h3thbYaitsakcIAJ90EwKzJ4b-jnc6p3UuVf_XE,1241
|
|
258
|
-
runtimepy-5.4.
|
|
259
|
-
runtimepy-5.4.
|
|
260
|
-
runtimepy-5.4.
|
|
261
|
-
runtimepy-5.4.
|
|
262
|
-
runtimepy-5.4.
|
|
263
|
-
runtimepy-5.4.
|
|
258
|
+
runtimepy-5.4.2.dist-info/LICENSE,sha256=okYCYhGsx_BlzvFdoNVBVpw_Cfb4SOqHA_VAARml4Hc,1071
|
|
259
|
+
runtimepy-5.4.2.dist-info/METADATA,sha256=HuPnRnjp8IDWbQUxSwohDtt4GvNFQJYvZ8rgY_NfyoQ,8152
|
|
260
|
+
runtimepy-5.4.2.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
|
|
261
|
+
runtimepy-5.4.2.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
|
|
262
|
+
runtimepy-5.4.2.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
|
|
263
|
+
runtimepy-5.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|