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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # =====================================
2
2
  # generator=datazen
3
3
  # version=3.1.4
4
- # hash=acff0a8ea3e1379494862c3188de7a76
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.1"
13
+ VERSION = "5.4.2"
14
14
 
15
15
  # runtimepy-specific content.
16
16
  METRICS_NAME = "metrics"
@@ -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
- addr = (args.host, args.port)
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", type=str, help="path to their file")
89
+ parser.add_argument("their_file", nargs="?", help="path to their file")
85
90
 
86
91
  return tftp_cmd
@@ -9,4 +9,4 @@ clients:
9
9
  - factory: tftp
10
10
  name: tftp_server
11
11
  kwargs:
12
- local_addr: [localhost, "$tftp_server"]
12
+ local_addr: [127.0.0.1, "$tftp_server"]
runtimepy/entry.py CHANGED
@@ -1,7 +1,7 @@
1
1
  # =====================================
2
2
  # generator=datazen
3
3
  # version=3.1.4
4
- # hash=c2bc26deadfa7cc275e815f499693863
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
- result = entry(args)
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):
@@ -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
- await stack.enter_async_context(endpoint.lock)
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
- # Repeat last ack in the background.
116
- if end_of_data:
117
- self._conn_tasks.append(
118
- asyncio.create_task(
119
- repeat_until( # type: ignore
120
- ack_sender,
121
- asyncio.Event(),
122
- endpoint.period.value,
123
- endpoint.timeout.value,
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
- # Make a to-string or log method for vcorelib FileInfo?
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
- event = asyncio.Event()
158
- endpoint.awaiting_acks[0] = event
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(
@@ -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[str, TftpEndpoint] = {}
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
- key = f"{addr[0]}:{addr[1]}"
172
+ addr = normalize_host(*addr)
139
173
 
140
- if key not in self._endpoints:
141
- self._endpoints[key] = TftpEndpoint(
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[key]
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
- self.endpoint(addr).handle_data(block, stream.read())
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).handle_ack(self._read_block_number(stream))
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: Union[IpHost, tuple[str, int]],
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 f"{self.addr[0]}:{self.addr[1]}"
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
- self.chain = chain
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.1
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=039855eb758d9eb1ea70df0654e31b61
47
+ hash=4f8a71a6066638ed1a90f375188f0578
48
48
  =====================================
49
49
  -->
50
50
 
51
- # runtimepy ([5.4.1](https://pypi.org/project/runtimepy/))
51
+ # runtimepy ([5.4.2](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)
@@ -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=FgvS9n7LEWBELW18_MMqLiwrprj83oIUYr7THnPUelU,390
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=j-IQzh-ZIQz4KgTBHcUwrVghfh9AZqCnPo2LpNd0rOw,1855
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=_zDxcOfMwywfC8_aaNcLPCe9wHxmASWddNStkYYRzFU,2129
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=taAf7S2-2oAc-I4h62FRSihhiTIe0N-e2COl9apAYc0,204
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=lbTalWqBYLP8_OzC0t88fY6y-GUPabJtGWBB7cOBnQE,3545
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=t49kWqSxjBnt5n1V-A5uBsYtWKor3B8YhI0E2ifKjOI,7763
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=c874sAV7NoJN9HErqgdSKyFoRVpoATziB4N-qiaUSU0,8592
198
- runtimepy/net/udp/tftp/base.py,sha256=lviBEoDAqu57J2HwIYggH6z7GxeynhwYrCVsTUPFY_o,8958
199
- runtimepy/net/udp/tftp/endpoint.py,sha256=837gzWjwUXFIuZjY44lEFDmK95piWyLbSkHLxKC61wA,10661
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=ci-C5jCCaqtkUu-ofGEJlsJp_zF8taTHomEnvY18k08,4413
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.1.dist-info/LICENSE,sha256=okYCYhGsx_BlzvFdoNVBVpw_Cfb4SOqHA_VAARml4Hc,1071
259
- runtimepy-5.4.1.dist-info/METADATA,sha256=ms0x0qBEiKTcw6kLUQWbH2L7hQYejWMnKF0xlYh6d_Y,8150
260
- runtimepy-5.4.1.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
261
- runtimepy-5.4.1.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
262
- runtimepy-5.4.1.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
263
- runtimepy-5.4.1.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.3.0)
2
+ Generator: setuptools (71.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5