runtimepy 5.10.1__py3-none-any.whl → 5.11.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=649474f8a3122bccd6febc4bb2156b11
4
+ # hash=c4e165f8c6309dd6819689f0262cf92d
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.10.1"
13
+ VERSION = "5.11.0"
14
14
 
15
15
  # runtimepy-specific content.
16
16
  METRICS_NAME = "metrics"
@@ -95,10 +95,10 @@ class ArrayChannelEnvironment(_BaseChannelEnvironment):
95
95
 
96
96
  # Begin handling a new bit-fields primitive.
97
97
  if curr_fields is None:
98
- fields = self.fields.get_fields(name)
99
- assert fields is not None, f"Unknown bit-field '{name}'!"
100
- result.array.add(fields.raw)
101
- available_field_names = set(fields.names)
98
+ curr_fields = self.fields.get_fields(name)
99
+ assert curr_fields is not None, f"Unknown bit-field '{name}'!"
100
+ result.array.add(curr_fields.raw)
101
+ available_field_names = set(curr_fields.names)
102
102
 
103
103
  # Keep track of field names processed in the current bit-fields
104
104
  # primitive.
@@ -99,7 +99,7 @@ class TelemetryChannelEnvironment(_BaseChannelEnvironment):
99
99
  cast(int, channels.event_header["timestamp"]),
100
100
  kind.decode(
101
101
  data,
102
- byte_order=channels.event_header.array.byte_order,
102
+ byte_order=channels.event_header.byte_order,
103
103
  ),
104
104
  )
105
105
 
@@ -120,7 +120,7 @@ class TelemetryChannelEnvironment(_BaseChannelEnvironment):
120
120
  cast(int, self.channels.event_header["timestamp"]),
121
121
  kind.decode(
122
122
  data,
123
- byte_order=self.channels.event_header.array.byte_order,
123
+ byte_order=self.channels.event_header.byte_order,
124
124
  ),
125
125
  )
126
126
 
@@ -146,7 +146,7 @@ class TelemetryChannelEnvironment(_BaseChannelEnvironment):
146
146
  read_size = channels.event_header.size
147
147
  data = channels.event_fifo.pop(read_size)
148
148
  if data is not None:
149
- channels.event_header.array.update(data)
149
+ channels.event_header.update(data)
150
150
 
151
151
  # Update local variables.
152
152
  ident = cast(int, channels.event_header["identifier"])
@@ -90,7 +90,7 @@ class PrimitiveEvent:
90
90
  self.header["timestamp"] = curr_ns
91
91
 
92
92
  # Write header then value.
93
- array = self.header.array
93
+ array = self.header
94
94
  written += array.to_stream(stream)
95
95
  written += raw.to_stream(stream, byte_order=array.byte_order)
96
96
  if flush:
@@ -4,7 +4,6 @@ A module implementing an interface to build communication protocols.
4
4
 
5
5
  # built-in
6
6
  from abc import ABC, abstractmethod
7
- from copy import copy
8
7
  from typing import Optional
9
8
 
10
9
  # internal
@@ -40,10 +39,12 @@ class ProtocolFactory(ABC):
40
39
 
41
40
  # We only need to run the routine that populates the protocol once.
42
41
  if not cls.initialized:
42
+ if not cls.protocol.alias:
43
+ cls.protocol.alias = cls.__name__
43
44
  cls.initialize(cls.protocol)
44
45
  cls.initialized = True
45
46
 
46
- return copy(cls.protocol)
47
+ return cls.protocol.copy()
47
48
 
48
49
  @classmethod
49
50
  def singleton(cls) -> Protocol:
@@ -3,8 +3,9 @@ A module implementing an interface to build communication protocols.
3
3
  """
4
4
 
5
5
  # built-in
6
- from contextlib import contextmanager
6
+ from contextlib import contextmanager, suppress
7
7
  from copy import copy as _copy
8
+ from io import StringIO
8
9
  from typing import Iterator as _Iterator
9
10
  from typing import NamedTuple
10
11
  from typing import Optional as _Optional
@@ -57,9 +58,10 @@ class FieldSpec(NamedTuple):
57
58
 
58
59
 
59
60
  T = _TypeVar("T", bound="ProtocolBase")
61
+ ProtocolBuild = list[_Union[int, FieldSpec, tuple[str, int]]]
60
62
 
61
63
 
62
- class ProtocolBase:
64
+ class ProtocolBase(PrimitiveArray):
63
65
  """A class for defining runtime communication protocols."""
64
66
 
65
67
  def __init__(
@@ -67,14 +69,16 @@ class ProtocolBase:
67
69
  enum_registry: _EnumRegistry,
68
70
  names: _NameRegistry = None,
69
71
  fields: BitFieldsManager = None,
70
- build: list[_Union[int, FieldSpec, str]] = None,
72
+ build: ProtocolBuild = None,
71
73
  identifier: int = 1,
72
74
  byte_order: _Union[_ByteOrder, _RegistryKey] = _DEFAULT_BYTE_ORDER,
73
75
  serializables: SerializableMap = None,
76
+ alias: str = None,
74
77
  ) -> None:
75
78
  """Initialize this protocol."""
76
79
 
77
80
  self.id = identifier
81
+ self.alias = alias
78
82
 
79
83
  # Register the byte-order enumeration if it's not present.
80
84
  self._enum_registry = enum_registry
@@ -86,7 +90,8 @@ class ProtocolBase:
86
90
  byte_order = _ByteOrder(
87
91
  self._enum_registry["ByteOrder"].get_int(byte_order)
88
92
  )
89
- self.array = PrimitiveArray(byte_order=byte_order)
93
+
94
+ super().__init__(byte_order=byte_order)
90
95
 
91
96
  if names is None:
92
97
  names = _NameRegistry()
@@ -96,11 +101,11 @@ class ProtocolBase:
96
101
  fields = BitFieldsManager(self.names, self._enum_registry)
97
102
  self._fields = fields
98
103
 
99
- self._regular_fields: dict[str, _AnyPrimitive] = {}
104
+ self._regular_fields: dict[str, list[_AnyPrimitive]] = {}
100
105
  self._enum_fields: dict[str, _RuntimeEnum] = {}
101
106
 
102
107
  # Keep track of the order that the protocol was created.
103
- self._build: list[_Union[int, FieldSpec, str]] = []
108
+ self._build: ProtocolBuild = []
104
109
 
105
110
  # Keep track of named serializables.
106
111
  self.serializables: SerializableMap = {}
@@ -110,24 +115,28 @@ class ProtocolBase:
110
115
  build = []
111
116
  for item in build:
112
117
  if isinstance(item, int):
113
- self._add_bit_fields(self._fields.fields[item], track=False)
114
- elif isinstance(item, str):
115
- assert serializables, (item, serializables)
116
- self.add_field(item, serializable=serializables[item])
117
- del serializables[item]
118
- else:
118
+ self._add_bit_fields(self._fields.fields[item])
119
+ elif isinstance(item, FieldSpec):
119
120
  self.add_field(
120
121
  item.name,
121
122
  item.kind,
122
123
  enum=item.enum,
123
- track=False,
124
124
  array_length=item.array_length,
125
125
  )
126
+ else:
127
+ assert serializables, (item, serializables)
128
+ name = item[0]
129
+ self.add_serializable(
130
+ name,
131
+ serializables[name][0],
132
+ array_length=None if item[1] == 1 else item[1],
133
+ )
134
+ del serializables[name]
126
135
 
127
136
  # Ensure all serializables were handled via build.
128
137
  assert not serializables, serializables
129
138
 
130
- def __copy__(self: T) -> T:
139
+ def _copy_impl(self: T) -> T:
131
140
  """Create another protocol instance from this one."""
132
141
 
133
142
  return self.__class__(
@@ -135,11 +144,12 @@ class ProtocolBase:
135
144
  names=self.names,
136
145
  fields=_copy(self._fields),
137
146
  build=self._build,
138
- byte_order=self.array.byte_order,
147
+ byte_order=self.byte_order,
139
148
  serializables={
140
- key: val.copy_without_chain()
149
+ key: [val[0].copy_without_chain()]
141
150
  for key, val in self.serializables.items()
142
151
  },
152
+ alias=self.alias,
143
153
  )
144
154
 
145
155
  def register_name(self, name: str) -> int:
@@ -151,13 +161,14 @@ class ProtocolBase:
151
161
 
152
162
  def add_serializable(
153
163
  self, name: str, serializable: Serializable, array_length: int = None
154
- ) -> int:
164
+ ) -> None:
155
165
  """Add a serializable instance."""
156
166
 
157
167
  self.register_name(name)
158
- self.serializables[name] = serializable
159
- self._build.append(name)
160
- return self.array.add_to_end(serializable, array_length=array_length)
168
+
169
+ instances = self.add_to_end(serializable, array_length=array_length)
170
+ self._build.append((name, len(instances)))
171
+ self.serializables[name] = instances
161
172
 
162
173
  def add_field(
163
174
  self,
@@ -165,17 +176,17 @@ class ProtocolBase:
165
176
  kind: _Primitivelike = None,
166
177
  enum: _RegistryKey = None,
167
178
  serializable: Serializable = None,
168
- track: bool = True,
169
179
  array_length: int = None,
170
- ) -> int:
180
+ ) -> None:
171
181
  """Add a new field to the protocol."""
172
182
 
173
183
  # Add the serializable to the end of this protocol.
174
184
  if serializable is not None:
175
185
  assert kind is None and enum is None
176
- return self.add_serializable(
186
+ self.add_serializable(
177
187
  name, serializable, array_length=array_length
178
188
  )
189
+ return
179
190
 
180
191
  self.register_name(name)
181
192
 
@@ -189,25 +200,20 @@ class ProtocolBase:
189
200
  kind = runtime_enum.primitive
190
201
 
191
202
  assert kind is not None
192
- new = _create(kind)
193
-
194
- result = self.array.add(new, array_length=array_length)
195
- self._regular_fields[name] = new
196
203
 
197
- if track:
198
- self._build.append(
199
- FieldSpec(name, kind, enum, array_length=array_length)
200
- )
204
+ self._regular_fields[name] = self.add(
205
+ _create(kind), array_length=array_length
206
+ )
201
207
 
202
- return result
208
+ self._build.append(
209
+ FieldSpec(name, kind, enum, array_length=array_length)
210
+ )
203
211
 
204
- def _add_bit_fields(self, fields: _BitFields, track: bool = True) -> None:
212
+ def _add_bit_fields(self, fields: _BitFields) -> None:
205
213
  """Add a bit-fields instance."""
206
214
 
207
- idx = self._fields.add(fields)
208
- self.array.add(fields.raw)
209
- if track:
210
- self._build.append(idx)
215
+ self._build.append(self._fields.add(fields))
216
+ self.add(fields.raw)
211
217
 
212
218
  @contextmanager
213
219
  def add_bit_fields(
@@ -219,55 +225,92 @@ class ProtocolBase:
219
225
  yield new
220
226
  self._add_bit_fields(new)
221
227
 
222
- def value(self, name: str, resolve_enum: bool = True) -> ProtocolPrimitive:
228
+ def value(
229
+ self, name: str, resolve_enum: bool = True, index: int = 0
230
+ ) -> ProtocolPrimitive:
223
231
  """Get the value of a field belonging to the protocol."""
224
232
 
225
233
  val: ProtocolPrimitive = 0
226
234
 
227
235
  if name in self._regular_fields:
228
- val = self._regular_fields[name].value
236
+ val = self._regular_fields[name][index].value
229
237
 
230
238
  # Resolve the enum value.
231
239
  if resolve_enum and name in self._enum_fields:
232
- val = self._enum_fields[name].get_str(val) # type: ignore
240
+ with suppress(KeyError):
241
+ val = self._enum_fields[name].get_str(val) # type: ignore
233
242
 
234
243
  return val
235
244
 
236
245
  return self._fields.get(name, resolve_enum=resolve_enum)
237
246
 
238
- @property
239
- def size(self) -> int:
240
- """Get this protocol's size in bytes."""
241
- return self.array.length()
242
-
243
247
  def trace_size(self, logger: LoggerType) -> None:
244
248
  """Log a size trace."""
245
- logger.info("%s: %s", self, self.array.length_trace())
249
+ logger.info("%s: %s", self, self.length_trace(alias=self.alias))
246
250
 
247
251
  def __str__(self) -> str:
248
252
  """Get this instance as a string."""
249
253
 
250
- return f"({self.size}) " + " ".join(
251
- f"{name}={self[name]}" for name in self.names.registered_order
252
- )
254
+ with StringIO() as stream:
255
+ stream.write(
256
+ "{"
257
+ + f"{self.resolve_alias(alias=self.alias)}({self.length()}): "
258
+ )
259
+
260
+ parts = []
261
+ for name in self.names.registered_order:
262
+ part = name
263
+ count = 1
264
+
265
+ if name in self.serializables:
266
+ count = len(self.serializables[name])
267
+ part += f"<{self.serializables[name][0].resolve_alias()}>"
268
+ elif name in self._regular_fields:
269
+ count = len(self._regular_fields[name])
270
+
271
+ part += f"<{self._regular_fields[name][0].kind}>"
272
+
273
+ if count > 1:
274
+ part += f"[{count}] = ["
275
+ if name in self._regular_fields:
276
+ part += ", ".join(
277
+ str(x.value) for x in self._regular_fields[name]
278
+ )
279
+ else:
280
+ part += ", ".join("..." for _ in range(count))
281
+ part += "]"
282
+ else:
283
+ part += f" = {self[name]}"
284
+
285
+ parts.append(part)
253
286
 
254
- def __getitem__(self, name: str) -> ProtocolPrimitive:
287
+ stream.write(", ".join(parts))
288
+
289
+ stream.write("}")
290
+
291
+ return stream.getvalue()
292
+
293
+ def __getitem__(self, name: str) -> ProtocolPrimitive: # type: ignore
255
294
  """Get the value of a protocol field."""
256
295
 
257
296
  if name in self.serializables:
258
- return str(self.serializables[name])
297
+ return str(self.serializables[name][0])
259
298
 
260
299
  return self.value(name)
261
300
 
262
- def __setitem__(self, name: str, val: ProtocolPrimitive) -> None:
301
+ def set(self, name: str, val: ProtocolPrimitive, index: int = 0) -> None:
263
302
  """Set a value of a field belonging to the protocol."""
264
303
 
265
304
  if name in self._regular_fields:
266
305
  # Resolve an enum value.
267
306
  if isinstance(val, str):
268
307
  val = self._enum_fields[name].get_int(val)
269
- self._regular_fields[name].value = val
308
+ self._regular_fields[name][index].value = val
270
309
  elif name in self.serializables and isinstance(val, str):
271
- self.serializables[name].update_str(val)
310
+ self.serializables[name][index].update_str(val)
272
311
  else:
273
312
  self._fields.set(name, val) # type: ignore
313
+
314
+ def __setitem__(self, name: str, val: ProtocolPrimitive) -> None:
315
+ """Set a value of a field belonging to the protocol."""
316
+ self.set(name, val)
@@ -14,7 +14,11 @@ from vcorelib.io.types import JsonObject as _JsonObject
14
14
  from vcorelib.io.types import JsonValue as _JsonValue
15
15
 
16
16
  # internal
17
- from runtimepy.codec.protocol.base import FieldSpec, ProtocolBase
17
+ from runtimepy.codec.protocol.base import (
18
+ FieldSpec,
19
+ ProtocolBase,
20
+ ProtocolBuild,
21
+ )
18
22
  from runtimepy.primitives.field.manager import (
19
23
  ENUMS_KEY,
20
24
  NAMES_KEY,
@@ -46,9 +50,11 @@ class JsonProtocol(ProtocolBase):
46
50
  """Export this protocol's data to JSON."""
47
51
 
48
52
  data = self._fields.export_json(resolve_enum=resolve_enum)
53
+
49
54
  data[META_KEY] = {
50
55
  "id": self.id,
51
- "byte_order": self.array.byte_order.name.lower(),
56
+ "byte_order": self.byte_order.name.lower(),
57
+ "alias": self.alias,
52
58
  }
53
59
 
54
60
  # Export regular-field names.
@@ -72,12 +78,12 @@ class JsonProtocol(ProtocolBase):
72
78
  )
73
79
 
74
80
  # Export the build specification.
75
- build: list[_Union[int, _JsonObject, str]] = []
81
+ build: list[_Union[int, _JsonObject, str, tuple[str, int]]] = []
76
82
  for item in self._build:
77
- if isinstance(item, (int, str)):
78
- build.append(item)
79
- else:
83
+ if isinstance(item, FieldSpec):
80
84
  build.append(item.asdict())
85
+ else:
86
+ build.append(item)
81
87
  data[BUILD_KEY] = _cast(_JsonObject, build)
82
88
 
83
89
  # Export regular-field values.
@@ -101,18 +107,16 @@ class JsonProtocol(ProtocolBase):
101
107
  fields = BitFieldsManager.import_json(data)
102
108
 
103
109
  # Create the build specification.
104
- build: list[_Union[int, FieldSpec, str]] = []
110
+ build: ProtocolBuild = []
105
111
  for item in data[BUILD_KEY]:
106
- if isinstance(item, int):
107
- build.append(item)
108
- else:
112
+ if isinstance(item, dict):
109
113
  build.append(
110
114
  FieldSpec(
111
- item["name"], # type: ignore
112
- item["kind"], # type: ignore
113
- enum=item.get("enum"), # type: ignore
115
+ item["name"], item["kind"], enum=item.get("enum")
114
116
  )
115
117
  )
118
+ else:
119
+ build.append(item) # type: ignore
116
120
 
117
121
  result = cls(
118
122
  fields.enums,
@@ -121,6 +125,7 @@ class JsonProtocol(ProtocolBase):
121
125
  build=build,
122
126
  identifier=_cast(int, data[META_KEY]["id"]),
123
127
  byte_order=_cast(str, data[META_KEY]["byte_order"]),
128
+ alias=data[META_KEY]["alias"], # type: ignore
124
129
  )
125
130
 
126
131
  # Set values.
@@ -146,7 +146,7 @@ class TypeSystem(LoggerMixin):
146
146
  if field_type_name in self.custom:
147
147
  custom.add_serializable(
148
148
  field_name,
149
- self.custom[field_type_name].array.copy(),
149
+ self.custom[field_type_name].copy(),
150
150
  array_length=array_length,
151
151
  )
152
152
  else:
@@ -227,8 +227,7 @@ class TypeSystem(LoggerMixin):
227
227
  if found in self.primitives:
228
228
  return self.primitives[found].size
229
229
 
230
- result = self.custom[found].size
231
230
  if trace:
232
231
  self.custom[found].trace_size(self.logger)
233
232
 
234
- return result
233
+ return self.custom[found].length()
Binary file
@@ -1,5 +1,5 @@
1
1
  <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
2
- <circle r="50.0" cx="50.0" cy="50.0" style="fill: #ADB5BD" />
2
+ <circle r="50.0" cx="50.0" cy="50.0" style="fill: #6C757D" />
3
3
  <rect x="16.666666666666664" y="33.333333333333336" width="16.666666666666668" height="8.333333333333334" rx="4.166666666666667" ry="4.166666666666667" style="fill: #495057" />
4
4
  <rect x="16.666666666666664" y="45.833333333333336" width="16.666666666666668" height="8.333333333333334" rx="4.166666666666667" ry="4.166666666666667" style="fill: #495057" />
5
5
  <rect x="16.666666666666664" y="58.333333333333336" width="16.666666666666668" height="8.333333333333334" rx="4.166666666666667" ry="4.166666666666667" style="fill: #495057" />
@@ -24,6 +24,7 @@ from runtimepy.net.http.request_target import PathMaybeQuery
24
24
  from runtimepy.net.http.response import ResponseHeader
25
25
  from runtimepy.net.server.html import HtmlApp, HtmlApps, get_html, html_handler
26
26
  from runtimepy.net.server.json import encode_json, json_handler
27
+ from runtimepy.net.server.markdown import markdown_for_dir
27
28
  from runtimepy.net.tcp.http import HttpConnection
28
29
  from runtimepy.util import normalize_root, path_has_part, read_binary
29
30
 
@@ -177,10 +178,20 @@ class RuntimepyServerConnection(HttpConnection):
177
178
 
178
179
  result = None
179
180
 
180
- # Try serving the path as a file.
181
+ # Keep track of directories encountered.
182
+ directories: list[Path] = []
183
+
184
+ # Build a list of all candidate files to check.
185
+ candidates: list[Path] = []
181
186
  for search in self.paths:
182
187
  candidate = search.joinpath(path[0][1:])
188
+ if candidate.is_dir():
189
+ directories.append(candidate)
190
+ candidates.append(candidate.joinpath("index.html"))
191
+ else:
192
+ candidates.append(candidate)
183
193
 
194
+ for candidate in candidates:
184
195
  # Handle markdown sources.
185
196
  if candidate.name:
186
197
  md_candidate = candidate.with_suffix(".md")
@@ -189,6 +200,7 @@ class RuntimepyServerConnection(HttpConnection):
189
200
  md_candidate, response, path[1]
190
201
  )
191
202
 
203
+ # Handle files.
192
204
  if candidate.is_file():
193
205
  mime, encoding = mimetypes.guess_type(candidate, strict=False)
194
206
 
@@ -203,9 +215,18 @@ class RuntimepyServerConnection(HttpConnection):
203
215
 
204
216
  # Return the file data.
205
217
  result = await read_binary(candidate)
206
-
207
218
  break
208
219
 
220
+ # Handle a directory as a last resort.
221
+ if not result and directories:
222
+ result = self.render_markdown(
223
+ markdown_for_dir(
224
+ directories[0], {"applications": self.apps.keys()}
225
+ ),
226
+ response,
227
+ path[1],
228
+ )
229
+
209
230
  return result
210
231
 
211
232
  def handle_command(
@@ -278,7 +299,7 @@ class RuntimepyServerConnection(HttpConnection):
278
299
  return self.favicon_data
279
300
 
280
301
  # Try serving a file and handling redirects.
281
- for handler in [self.try_file, self.try_redirect]:
302
+ for handler in [self.try_redirect, self.try_file]:
282
303
  result = await handler(
283
304
  request.target.origin_form, response
284
305
  )
@@ -0,0 +1,53 @@
1
+ """
2
+ A module implementing web server markdown interfaces.
3
+ """
4
+
5
+ # built-in
6
+ from io import StringIO
7
+ from pathlib import Path
8
+ from typing import Iterable, cast
9
+
10
+ # third-party
11
+ from vcorelib.io.file_writer import IndentedFileWriter
12
+ from vcorelib.paths import rel
13
+
14
+ LOGO_MARKDOWN = (
15
+ "[![logo](https://libre-embedded.com/static/"
16
+ "png/chip-circle-bootstrap/128x128.png)](https://libre-embedded.com)"
17
+ )
18
+
19
+
20
+ def markdown_for_dir(
21
+ path: Path, extra_links: dict[str, Iterable[str]] = None
22
+ ) -> str:
23
+ """Get markdown data for a directory."""
24
+
25
+ with IndentedFileWriter.string() as writer:
26
+ writer.write(f"# Directory {LOGO_MARKDOWN} Viewer")
27
+ with writer.padding():
28
+ writer.write("---")
29
+
30
+ if extra_links:
31
+ for category, apps in extra_links.items():
32
+ writer.write(f"## {category}")
33
+ with writer.padding():
34
+ for app in apps:
35
+ writer.write(f"* [{app}]({app})")
36
+
37
+ writer.write(f"## `{path}`")
38
+ writer.empty()
39
+
40
+ writer.write("* [..](..)")
41
+
42
+ for item in path.iterdir():
43
+ curr = rel(item, base=path)
44
+
45
+ name = f"`{curr}`"
46
+ if item.is_dir():
47
+ name = f"**{name}**"
48
+
49
+ writer.write(f"* [{name}]({curr})")
50
+
51
+ result: str = cast(StringIO, writer.stream).getvalue()
52
+
53
+ return result
@@ -50,7 +50,6 @@ class TftpConnection(BaseTftpConnection):
50
50
 
51
51
  def ack_sender() -> None:
52
52
  """Send acks."""
53
- nonlocal idx
54
53
  endpoint.ack_sender(idx - 1, endpoint.addr)
55
54
 
56
55
  def send_rrq() -> None:
@@ -180,44 +180,50 @@ class PrimitiveArray(Serializable):
180
180
 
181
181
  def add_primitive(
182
182
  self, kind: _Primitivelike, array_length: int = None
183
- ) -> int:
183
+ ) -> list[_AnyPrimitive]:
184
184
  """Add to the array by specifying the type of element to add."""
185
+
185
186
  return self.add(_create(kind), array_length=array_length)
186
187
 
187
- def add(self, primitive: _AnyPrimitive, array_length: int = None) -> int:
188
+ def add(
189
+ self, primitive: _AnyPrimitive, array_length: int = None
190
+ ) -> list[_AnyPrimitive]:
188
191
  """Add another primitive to manage."""
189
192
 
193
+ result = []
194
+
190
195
  end = self.end
191
196
  if isinstance(end, PrimitiveArray):
192
197
  if end is self:
193
198
  self._primitives.append(primitive)
194
199
  self._format += primitive.kind.format
195
200
  self.size += primitive.size
201
+ result.append(primitive)
196
202
 
197
203
  # Handle array length.
198
204
  if array_length is not None:
199
205
  for _ in range(array_length - 1):
206
+ inst = primitive.copy()
200
207
  self._primitives.append(
201
- primitive.copy(), # type: ignore
208
+ inst, # type: ignore
202
209
  )
203
- self._format += primitive.kind.format
204
- self.size += primitive.size
210
+ self._format += inst.kind.format
211
+ self.size += inst.size
212
+ result.append(inst) # type: ignore
205
213
 
206
214
  # Add tracking information for the current tail.
207
215
  curr_idx = len(self._primitives)
208
216
  self._bytes_to_index[self.size] = curr_idx
209
217
  self._index_to_bytes[curr_idx] = self.size
210
- result = self.size
211
218
  else:
212
- result = end.add(primitive, array_length=array_length)
219
+ result.extend(end.add(primitive, array_length=array_length))
213
220
 
214
221
  # Add a new primitive array to the end of this chain for this
215
222
  # primitive.
216
223
  else:
217
224
  new_array = PrimitiveArray(byte_order=self.byte_order)
218
225
  end.assign(new_array)
219
-
220
- result = new_array.add(primitive, array_length=array_length)
226
+ result.extend(new_array.add(primitive, array_length=array_length))
221
227
 
222
228
  return result
223
229
 
@@ -245,3 +251,16 @@ class PrimitiveArray(Serializable):
245
251
  ) -> None:
246
252
  """Update a fragment by index."""
247
253
  self._fragments[index].update(data, timestamp_ns=timestamp_ns)
254
+
255
+ def randomize(self, timestamp_ns: int = None, chain: bool = True) -> None:
256
+ """Randomize array contents."""
257
+
258
+ for prim in self._primitives:
259
+ prim.randomize(timestamp_ns=timestamp_ns)
260
+
261
+ if (
262
+ chain
263
+ and self.chain is not None
264
+ and isinstance(self.chain, PrimitiveArray)
265
+ ):
266
+ self.chain.randomize(timestamp_ns=timestamp_ns, chain=chain)
@@ -2,6 +2,9 @@
2
2
  A module implementing a boolean-primitive interface.
3
3
  """
4
4
 
5
+ # built-in
6
+ from random import getrandbits
7
+
5
8
  # internal
6
9
  from runtimepy.primitives.base import Primitive as _Primitive
7
10
  from runtimepy.primitives.evaluation import EvalResult, evaluate
@@ -18,17 +21,21 @@ class BooleanPrimitive(_Primitive[bool]):
18
21
  """Initialize this boolean primitive."""
19
22
  super().__init__(value=value, **kwargs)
20
23
 
21
- def toggle(self) -> None:
24
+ def randomize(self, timestamp_ns: int = None) -> None:
25
+ """Set this primitive to a random integer."""
26
+ self.set_value(bool(getrandbits(1)), timestamp_ns=timestamp_ns)
27
+
28
+ def toggle(self, timestamp_ns: int = None) -> None:
22
29
  """Toggle the underlying value."""
23
- self.value = not self.raw.value
30
+ self.set_value(not self.raw.value, timestamp_ns=timestamp_ns)
24
31
 
25
- def set(self) -> None:
32
+ def set(self, timestamp_ns: int = None) -> None:
26
33
  """Coerce the underlying value to true."""
27
- self.value = True
34
+ self.set_value(True, timestamp_ns=timestamp_ns)
28
35
 
29
- def clear(self) -> None:
36
+ def clear(self, timestamp_ns: int = None) -> None:
30
37
  """Coerce the underlying value to false."""
31
- self.value = False
38
+ self.set_value(False, timestamp_ns=timestamp_ns)
32
39
 
33
40
  async def wait_for_state(self, state: bool, timeout: float) -> EvalResult:
34
41
  """Wait for this primitive to reach a specified state."""
@@ -4,6 +4,7 @@ A module implementing a floating-point primitive interface.
4
4
 
5
5
  # built-in
6
6
  import math
7
+ from random import random
7
8
 
8
9
  # internal
9
10
  from runtimepy.primitives.evaluation import (
@@ -27,6 +28,10 @@ class BaseFloatPrimitive(PrimitiveIsCloseMixin[float]):
27
28
  """Initialize this floating-point primitive."""
28
29
  super().__init__(value=value, scaling=scaling, **kwargs)
29
30
 
31
+ def randomize(self, timestamp_ns: int = None) -> None:
32
+ """Set this primitive to a random integer."""
33
+ self.set_value(random(), timestamp_ns=timestamp_ns)
34
+
30
35
  def _check_callbacks(self, curr: float, new: float) -> None:
31
36
  """Determine if any callbacks should be serviced."""
32
37
 
@@ -33,6 +33,13 @@ class BaseIntPrimitive(PrimitiveIsCloseMixin[int]):
33
33
 
34
34
  super().__init__(value=value, scaling=scaling, **kwargs)
35
35
 
36
+ def randomize(self, timestamp_ns: int = None) -> None:
37
+ """Set this primitive to a random integer."""
38
+
39
+ assert self.kind.int_bounds is not None
40
+ result = self.kind.int_bounds.random()
41
+ self.set_value(result, timestamp_ns=timestamp_ns)
42
+
36
43
  def increment(self, amount: int = 1, timestamp_ns: int = None) -> int:
37
44
  """Increment this primitive by some amount and return the new value."""
38
45
 
@@ -7,5 +7,5 @@ from runtimepy.primitives.serializable.base import Serializable
7
7
  from runtimepy.primitives.serializable.fixed import FixedChunk
8
8
  from runtimepy.primitives.serializable.prefixed import PrefixedChunk
9
9
 
10
- SerializableMap = dict[str, Serializable]
10
+ SerializableMap = dict[str, list[Serializable]]
11
11
  __all__ = ["Serializable", "SerializableMap", "FixedChunk", "PrefixedChunk"]
@@ -5,6 +5,7 @@ A module defining a base interface fore serializable objects.
5
5
  # built-in
6
6
  from abc import ABC, abstractmethod
7
7
  from copy import copy as _copy
8
+ from io import BytesIO as _BytesIO
8
9
  from typing import BinaryIO as _BinaryIO
9
10
  from typing import TypeVar
10
11
 
@@ -46,10 +47,17 @@ class Serializable(ABC):
46
47
  result += self.chain.length()
47
48
  return result
48
49
 
49
- def length_trace(self) -> str:
50
+ def resolve_alias(self, alias: str = None) -> str:
51
+ """Resolve a possible alias string."""
52
+
53
+ if not alias:
54
+ alias = getattr(self, "alias") or self.__class__.__name__
55
+ return alias
56
+
57
+ def length_trace(self, alias: str = None) -> str:
50
58
  """Get a length-tracing string for this instance."""
51
59
 
52
- current = f"{self.__class__.__name__}({self.size})"
60
+ current = f"{self.resolve_alias(alias=alias)}({self.size})"
53
61
  if self.chain is not None:
54
62
  current += " -> " + self.chain.length_trace()
55
63
  return current
@@ -71,7 +79,6 @@ class Serializable(ABC):
71
79
  """A method for copying instances without chain references."""
72
80
 
73
81
  orig = self._copy_impl()
74
- assert orig.chain is None
75
82
  orig.byte_order = self.byte_order
76
83
  return orig
77
84
 
@@ -80,7 +87,7 @@ class Serializable(ABC):
80
87
 
81
88
  result = self.copy_without_chain()
82
89
 
83
- if self.chain is not None:
90
+ if self.chain is not None and result.chain is None:
84
91
  result.assign(self.chain.copy())
85
92
 
86
93
  return result
@@ -104,6 +111,27 @@ class Serializable(ABC):
104
111
 
105
112
  return result
106
113
 
114
+ def chain_bytes(self) -> bytes:
115
+ """Get the fully encoded chain."""
116
+ with _BytesIO() as stream:
117
+ self.to_stream(stream)
118
+ return stream.getvalue()
119
+
120
+ def __eq__(self, other) -> bool:
121
+ """Equivalent if full byte chains are equal."""
122
+
123
+ result = False
124
+ if isinstance(other, Serializable):
125
+ result = self.chain_bytes() == other.chain_bytes()
126
+ return result
127
+
128
+ def update_with(self: T, other: T, timestamp_ns: int = None) -> int:
129
+ """Update this instance from another of the same type."""
130
+
131
+ return self.update_chain(
132
+ other.chain_bytes(), timestamp_ns=timestamp_ns
133
+ )
134
+
107
135
  @abstractmethod
108
136
  def update(self, data: bytes, timestamp_ns: int = None) -> int:
109
137
  """Update this serializable from a bytes instance."""
@@ -130,32 +158,32 @@ class Serializable(ABC):
130
158
 
131
159
  return result
132
160
 
133
- def assign(self, chain: T) -> int:
161
+ def update_chain(self, data: bytes, timestamp_ns: int = None) -> int:
162
+ """Update this serializable from a bytes instance."""
163
+
164
+ with _BytesIO(data) as stream:
165
+ return self.from_stream(stream, timestamp_ns=timestamp_ns)
166
+
167
+ def assign(self, chain: T) -> None:
134
168
  """Assign a next serializable."""
135
169
 
136
170
  assert self.chain is None, self.chain
137
-
138
171
  # mypy regression?
139
172
  self.chain = chain # type: ignore
140
- assert self.chain is not None
141
-
142
- return self.chain.size
143
173
 
144
- def add_to_end(self, chain: T, array_length: int = None) -> int:
174
+ def add_to_end(self, chain: T, array_length: int = None) -> list[T]:
145
175
  """Add a new serializable to the end of this chain."""
146
176
 
147
- # Copy the chain element before it becomes part of the current chain
148
- # if an array is created.
149
- copy_base = None
150
- if array_length is not None:
151
- copy_base = chain.copy()
177
+ result = []
152
178
 
153
- size = self.end.assign(chain)
179
+ self.end.assign(chain)
180
+ result.append(chain)
154
181
 
155
182
  # Add additional array elements as copies.
156
183
  if array_length is not None:
157
- assert copy_base is not None
158
184
  for _ in range(array_length - 1):
159
- size += self.end.assign(copy_base.copy())
185
+ inst = chain.copy()
186
+ self.end.assign(inst)
187
+ result.append(inst)
160
188
 
161
- return size
189
+ return result
@@ -4,6 +4,7 @@ bounds (based on bit width).
4
4
  """
5
5
 
6
6
  # built-in
7
+ from random import randint
7
8
  from typing import NamedTuple
8
9
 
9
10
 
@@ -13,6 +14,10 @@ class IntegerBounds(NamedTuple):
13
14
  min: int
14
15
  max: int
15
16
 
17
+ def random(self) -> int:
18
+ """Get a random integer."""
19
+ return randint(self.min, self.max)
20
+
16
21
  def validate(self, val: int) -> bool:
17
22
  """Determine if the value is within bounds."""
18
23
  return self.min <= val <= self.max
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: runtimepy
3
- Version: 5.10.1
3
+ Version: 5.11.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,9 +17,9 @@ Classifier: License :: OSI Approved :: MIT License
17
17
  Requires-Python: >=3.12
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
+ Requires-Dist: svgen>=0.7.4
20
21
  Requires-Dist: websockets
21
22
  Requires-Dist: vcorelib>=3.5.1
22
- Requires-Dist: svgen>=0.7.4
23
23
  Requires-Dist: psutil
24
24
  Requires-Dist: aiofiles
25
25
  Provides-Extra: test
@@ -42,6 +42,7 @@ Requires-Dist: types-setuptools; extra == "test"
42
42
  Requires-Dist: uvloop; (sys_platform != "win32" and sys_platform != "cygwin") and extra == "test"
43
43
  Dynamic: author
44
44
  Dynamic: home-page
45
+ Dynamic: license-file
45
46
  Dynamic: requires-dist
46
47
  Dynamic: requires-python
47
48
 
@@ -49,11 +50,11 @@ Dynamic: requires-python
49
50
  =====================================
50
51
  generator=datazen
51
52
  version=3.1.4
52
- hash=a8fb82d7f11d94c5c3ad4adea1026d9e
53
+ hash=12eb2a9d91dea3ae83e38a864f6cab4b
53
54
  =====================================
54
55
  -->
55
56
 
56
- # runtimepy ([5.10.1](https://pypi.org/project/runtimepy/))
57
+ # runtimepy ([5.11.0](https://pypi.org/project/runtimepy/))
57
58
 
58
59
  [![python](https://img.shields.io/pypi/pyversions/runtimepy.svg)](https://pypi.org/project/runtimepy/)
59
60
  ![Build Status](https://github.com/vkottler/runtimepy/workflows/Python%20Package/badge.svg)
@@ -1,4 +1,4 @@
1
- runtimepy/__init__.py,sha256=uZ2pA4H_IPEEbU0jsR1ZRf1g4gyxUbUuexk35KLNE3g,391
1
+ runtimepy/__init__.py,sha256=_iqys5Hal_wrIAXPN1-B9DZRorvDPmYsRk12jBL_9hg,391
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
@@ -11,23 +11,23 @@ runtimepy/util.py,sha256=GuyIHVFGMS02OR6-O3LnlV3DqG5hj4-IUud0QM6WicA,1684
11
11
  runtimepy/channel/__init__.py,sha256=pf0RJ5g37_FVV8xoUNgzFGuIfbZEYSBA_cQlJSDTPDo,4774
12
12
  runtimepy/channel/registry.py,sha256=nk36qM_Bf6qK6AFR0plaZHR1PU7b4LZqbQ0feJqk4lc,4784
13
13
  runtimepy/channel/environment/__init__.py,sha256=0Jj8g7Y4bdDvmWtzpegB9D4milGPhsZokoYxmps5zgU,1612
14
- runtimepy/channel/environment/array.py,sha256=D5lm8VyPiNghkb7vdwTLTqPmSyWunYEzT8Kq9zvgwRw,3728
14
+ runtimepy/channel/environment/array.py,sha256=f9cWaYsRXUw8qE629h6jQxbYKDpOwC2GLBo4QaMa1JM,3748
15
15
  runtimepy/channel/environment/base.py,sha256=tpD_6OHJv1bpfXmfFCNckoNo8ATOOi-XX66NQrkSNYU,14331
16
16
  runtimepy/channel/environment/create.py,sha256=DHjoNmzZsARjfB_CfutXQ1PDdxPETi6yQoRMhM0FbwA,5319
17
17
  runtimepy/channel/environment/file.py,sha256=PV05KZ3-CvftbKUM8acQmawOMeGGCcMrEESEBuymykg,6949
18
18
  runtimepy/channel/environment/sample.py,sha256=Geinp2Q_qYkzYKpUJroepv6A4JWypUnAPl57jn_r_R4,4976
19
- runtimepy/channel/environment/telemetry.py,sha256=tw7SLfWHTwCT8hXBzsIVvj546MDsVAtiqDgLZXhCKJ4,5356
19
+ runtimepy/channel/environment/telemetry.py,sha256=3A7Xcp-4eHJWz_oR1SnI6rsl4o8wiSUaiMHrnK1IaQ8,5338
20
20
  runtimepy/channel/environment/command/__init__.py,sha256=mymqk5roA-7evUovXlD2dmWaprSzrPb_3ae6bA9oEZ0,8162
21
21
  runtimepy/channel/environment/command/parser.py,sha256=cMOsEsXnfFlATiWTNSxlgvc_XoICsJlcZigFJlQ47tk,1804
22
22
  runtimepy/channel/environment/command/processor.py,sha256=NiiWRdwBHOFEisjqNOW5oawprxcpR25ONNABoZpELdg,7122
23
23
  runtimepy/channel/environment/command/result.py,sha256=Ko5lK4d04Z266WuCi2sHQItbUHJFYv7qUdrDi-OVKOU,624
24
- runtimepy/channel/event/__init__.py,sha256=eEtubsqiqP8DqZbju3XHvG3_xHtrPIBlb8HgYyITSrk,2799
24
+ runtimepy/channel/event/__init__.py,sha256=9LCSNa1Iiwr6Q6JkwQGELDQ7rWfU_xjAMh6qM1I-ueM,2793
25
25
  runtimepy/channel/event/header.py,sha256=eDRZgzzM5HZQ8QtV4DjyAsrAhEZyM7IfwqK6WB5ACEw,868
26
26
  runtimepy/codec/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
- runtimepy/codec/protocol/__init__.py,sha256=e2tNBytsAc-hwhYhV_7UEHLyqXew1RMTcNe5DZGGFWk,1462
28
- runtimepy/codec/protocol/base.py,sha256=-BzSteAAG1XmKFztgUwOdMACztQ1YGSzwe_AM22V1kI,8971
29
- runtimepy/codec/protocol/json.py,sha256=pnj3UFmlXDjXzEa-l9jD6UhxsDA0aTnXhh8uIdBKhYg,4107
30
- runtimepy/codec/system/__init__.py,sha256=M9rIw0RvVGwz4JQhxbGWynUUE4H4L5HwJfXa5vI0CS8,6939
27
+ runtimepy/codec/protocol/__init__.py,sha256=Rg7RSKGn2UBpGMpwq1aCLUBA5h4pORdh53NfR7Cjw0U,1530
28
+ runtimepy/codec/protocol/base.py,sha256=NRLB1ld0oB2IED5QRkwDwYsnHFG8gKtg3T7OHJ9_VkQ,10432
29
+ runtimepy/codec/protocol/json.py,sha256=oiaJLCzptJ5uajnpO8EDYET8gIspZIrVuyLjLuAC5dw,4142
30
+ runtimepy/codec/system/__init__.py,sha256=cW_Y-hC9GgIXhFQBmNO0Lx8HdFNh-wxx1mTck14PFzw,6913
31
31
  runtimepy/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  runtimepy/commands/all.py,sha256=jH2dsmkqyBFe_2ZlPFpko0UCMW3fFfJsuGIbeJFbDoQ,1619
33
33
  runtimepy/commands/arbiter.py,sha256=CtTMRYpqCAN3vWHkkr9jqWpoF7JGNXafKIBFmkarAfc,1567
@@ -45,7 +45,7 @@ runtimepy/data/404.html,sha256=sn0Mcntzb1-AekukYZAQqz43xfmZtTWa2n2HD2ItqDM,4697
45
45
  runtimepy/data/browser.yaml,sha256=oc5KEV1C1uAJ4MkhNo4hyVVfJtZvHelRNqzNvD313Ow,79
46
46
  runtimepy/data/dummy_load.yaml,sha256=PfKRXXgZnENRMSd68eznSMTV8xanVH5JY4FmoZRPFGY,1985
47
47
  runtimepy/data/factories.yaml,sha256=esuoouMre8w7siZfBoZKqC-5myghJ_WwOOCDIq135zg,1899
48
- runtimepy/data/favicon.ico,sha256=iPOtqnqrFVr5tNBM6kTsw7nh0KullEz4TpS6CEQ2HVM,362870
48
+ runtimepy/data/favicon.ico,sha256=boxAGaHbUjMFrOO2TZpsO0nIRC-LUgwHVQYOiG1YQnM,362870
49
49
  runtimepy/data/sample_telemetry.yaml,sha256=OpdFurkvtWJGaNl9LMlU2rKo15AaVVr-U_hoZfsbp-Y,695
50
50
  runtimepy/data/server.yaml,sha256=wS_Ceiu2TpkfPurpqoYoPlgzc9DAWtUd24MW7t-S5rU,97
51
51
  runtimepy/data/server_base.yaml,sha256=R_varVgGPGV4nxWYYwKUnHC9ufINi4V92YVhxCCC5wg,875
@@ -107,7 +107,7 @@ runtimepy/data/schemas/has_factory.yaml,sha256=Rsqrxv3HEBUGxPcW6CN7QczA96iBSgtm0
107
107
  runtimepy/data/schemas/has_markdown.yaml,sha256=LbnEQRYdk5KR_GgORmIOiKjRg-HF8wjhA6Dm6pSm66I,45
108
108
  runtimepy/data/schemas/has_name.yaml,sha256=I5YkXxQRYz-1-49CLyr_NA2zVqyRVQCAsb-icTaxF4s,59
109
109
  runtimepy/data/schemas/has_request_flag.yaml,sha256=S8ly8g-FkrCVNEizbXPqicg_hpGvtH7WRsHZAA4uhjc,66
110
- runtimepy/data/static/svg/chip-circle-bootstrap.svg,sha256=cGCiwiVWoO39vzdAt683Fk3W42XE6-RGT0QZtsDAv4o,3124
110
+ runtimepy/data/static/svg/chip-circle-bootstrap.svg,sha256=uwo45eymlKmV3AOQ217Etarjb0UBzJJNFLa8JgQx86Y,3124
111
111
  runtimepy/data/static/woff2/CascadiaCode-Bold.woff2,sha256=vnrwH_YbbVnq0_aedSECg-eieky43TOK3MxtCWIc_Ug,154520
112
112
  runtimepy/data/static/woff2/CascadiaCode-BoldItalic.woff2,sha256=OOWBMRXRJJKxEfEIlKKnT-tFqoZKqGw5hMa69t-gp1c,113236
113
113
  runtimepy/data/static/woff2/CascadiaCode-Italic.woff2,sha256=RbBc8ehMSl_ixGWnEe7zn841xDsyxdDJTo3rkYY-bf8,111628
@@ -178,9 +178,10 @@ runtimepy/net/http/request_target.py,sha256=EE1aI5VSARw1h93jyZvP56ir5O5fjd6orYK-
178
178
  runtimepy/net/http/response.py,sha256=Sup8W_A0ADNzR5olKrQsVNhsQXUwPOD-eJLlLOgYlAY,2316
179
179
  runtimepy/net/http/state.py,sha256=qCMN8aWfCRfU9XP-cIhSOo2RqfljTjbQRCflfcy2bfY,1626
180
180
  runtimepy/net/http/version.py,sha256=mp6rgIM7-VUVKLCA0Uw96CmBkL0ET860lDVVEewpZ7w,1098
181
- runtimepy/net/server/__init__.py,sha256=Kg1NenLXSDWYa7NFvQLu2hHU4p6sYXwspv2NSZ7VfSo,9647
181
+ runtimepy/net/server/__init__.py,sha256=RojwvQgqc0rTl36rXDSiM56tVXtD15T5OkpuJanlNjY,10438
182
182
  runtimepy/net/server/html.py,sha256=ufg0PQF_iUE7PT0n3Pn3jTcun7mspZUI6_ooblcNnvI,1217
183
183
  runtimepy/net/server/json.py,sha256=a7vM5yfq2er4DexzFqEMnxoMGDeuywKkVH4-uNJBAik,2522
184
+ runtimepy/net/server/markdown.py,sha256=DFjGbvIST4HXRhtTTvXVQ9ZAwrIfVzPlcU1dW8JYya8,1381
184
185
  runtimepy/net/server/app/__init__.py,sha256=beU67t7zoKGlO7aldjQMUwYLm9mSlc78eMQazri-otw,3012
185
186
  runtimepy/net/server/app/base.py,sha256=HF_Qa3ufrZNaYBVnBwGi-Siv3nneqEo01j5h5pK-ZTk,1871
186
187
  runtimepy/net/server/app/create.py,sha256=eRT8qxubht5A7149Xol3Z8rkdYd_pjNLqrlyMnXk-Zg,2660
@@ -221,7 +222,7 @@ runtimepy/net/udp/connection.py,sha256=Hv963vT9tnglpZOx4KUFuoYTBPr2rltYQWDNV3mhY
221
222
  runtimepy/net/udp/create.py,sha256=84YDfJbNBlN0ZwbNpvh6Dl7ZPecbZfmpjMNRRWcvJDk,2005
222
223
  runtimepy/net/udp/protocol.py,sha256=A4SRHf0CgcL2zDs1nAsGDqz0RxKBy1soS8wtNdS5S0I,1492
223
224
  runtimepy/net/udp/queue.py,sha256=DF-YscxQcGbGCYQLz_l_BMaSRfraZOhRwieTEdXLMds,637
224
- runtimepy/net/udp/tftp/__init__.py,sha256=zDVUga9ZApSnvx8UwlmVUY4SDFrAHafcD9oe5Q7fGOw,9012
225
+ runtimepy/net/udp/tftp/__init__.py,sha256=lR8_bFo8ECHMQygWsmbnNpsLqmwu6818k-cjKRE57yk,8983
225
226
  runtimepy/net/udp/tftp/base.py,sha256=vpvjitZSD8R4Ggb21eIMNIsm54VkLBbBrTgMo65gn2o,11331
226
227
  runtimepy/net/udp/tftp/endpoint.py,sha256=so60LdPTG66N5tdhHhiX7j_TBHvNOTi4JIgLcg2MAm0,10890
227
228
  runtimepy/net/udp/tftp/enums.py,sha256=06juMd__pJZsyL8zO8p3hRucnOratt1qtz9zcxzMg4s,1579
@@ -231,27 +232,27 @@ runtimepy/net/websocket/connection.py,sha256=BMR58bLpHuulCdbLGnmMdFJOF53wVxYcUe5
231
232
  runtimepy/noise/__init__.py,sha256=EJM7h3t_z74wwrn6FAFQwYE2yUcOZQ1K1IQqOb8Z0AI,384
232
233
  runtimepy/primitives/__init__.py,sha256=nwWJH1e0KN2NsVwQ3wvRtUpl9s9Ap8Q32NNZLGol0wU,2323
233
234
  runtimepy/primitives/base.py,sha256=BaGPUTeVMnLnTPcpjqnS2lzPN74Pe5C0XaQdgrTfW7A,9185
234
- runtimepy/primitives/bool.py,sha256=c-IRpVZ84m-pOreCHC382tOW0NFKEwSTiEeXAtlJjvk,1243
235
+ runtimepy/primitives/bool.py,sha256=lATPgb1e62rjLn5XlJX8lP3tVYR3DlxV8RT9HpOMdT0,1640
235
236
  runtimepy/primitives/byte_order.py,sha256=80mMk1Sj_l49XvAtvrPmoYFpFYSM1HgYuwR2-P7os3Q,767
236
237
  runtimepy/primitives/evaluation.py,sha256=0N7mT8uoiJaY-coF2PeEXU2WO-FmbyN2Io9_EaghO9Q,4657
237
- runtimepy/primitives/float.py,sha256=aeEsj0xRJM57Hcv04OtLfT_sTBocZldbn19HpQq3Hxs,1946
238
- runtimepy/primitives/int.py,sha256=OSJ5_lPrzJfLMDssMQHSbB0PzuqQRvCtJSRen7G9QBc,3352
238
+ runtimepy/primitives/float.py,sha256=6vzNKnnLzzM4vP10V4E0PHZQH6vTvIl34pId1oFtlqc,2146
239
+ runtimepy/primitives/int.py,sha256=zaZZjVSuDtZsOWRYt8eN38pPVGZfj7y4IyrfQ1rVKTk,3620
239
240
  runtimepy/primitives/scaling.py,sha256=Vtxp2CSBahqPp4i2-IS4wjbcC023xwf-dqZMbYWf3V4,1144
240
241
  runtimepy/primitives/string.py,sha256=ic5VKhXCSIwEOUfqIb1VUpZPwjdAcBul-cLLIihVkQI,2532
241
- runtimepy/primitives/array/__init__.py,sha256=qPH8SN8vqRZR-J3OZsm-46tKqfnXpGLb1lw9Jni1udI,8362
242
+ runtimepy/primitives/array/__init__.py,sha256=ZVJt4810hTFYMdolY_R75lRRHHaNKxZ4comBvuK_69E,8956
242
243
  runtimepy/primitives/field/__init__.py,sha256=iHZSQBozMpfiv_5KE_GIX1FAvDB9unUO0xmWZHiA_Jk,4491
243
244
  runtimepy/primitives/field/fields.py,sha256=jDNi1tl2Xc3GBmt6QJuqxbhP8MtxgertGbPFmDXa7b4,7481
244
245
  runtimepy/primitives/field/manager/__init__.py,sha256=BCRi6-_5OOJ8kz78JHkiLp8cZ71KA1uiF2zq5FFe9js,2586
245
246
  runtimepy/primitives/field/manager/base.py,sha256=EyWs5D9_reKOTLkh8PuW45ySjCh31fY_qrtFIcmIOV4,6914
246
- runtimepy/primitives/serializable/__init__.py,sha256=mZ8KZe2aQswGL6GTjQ8-IaUQwg-6aUUBP5RXyzR6crc,393
247
- runtimepy/primitives/serializable/base.py,sha256=piviwBXWV6dvbppaVnH-k1fmgVbE8rHvHvZogb2jsLw,4496
247
+ runtimepy/primitives/serializable/__init__.py,sha256=R9_derxnK1OCaYyqBZA4CCjPkXCBw6InkE8-3Zy75Uk,399
248
+ runtimepy/primitives/serializable/base.py,sha256=ij-9WaUZIMljS1iztciuz0XTWJdlKuyNjvf5nu0HxrE,5471
248
249
  runtimepy/primitives/serializable/fixed.py,sha256=rhr6uVbo0Lvazk4fLI7iei-vVNEwP1J8-LoUjW1NaMI,1077
249
250
  runtimepy/primitives/serializable/framer.py,sha256=rsoGQz6vD7v_EMu67aqxVqbvmbs6hjytXZ8dHLBM0SQ,1815
250
251
  runtimepy/primitives/serializable/prefixed.py,sha256=oQXW0pGRovKolheL5ZL2m9aNVMCtKTAi5OlC9KW0iKI,2855
251
252
  runtimepy/primitives/types/__init__.py,sha256=JUJpDFIjDUYo-Jnx5sZnkmbGLVjIHVsRcvBjVlJ7fsA,1588
252
253
  runtimepy/primitives/types/base.py,sha256=rR1JkExMV2FJmvU-WfgnikG-UtFfsXGs_eBCIV6II1A,4879
253
254
  runtimepy/primitives/types/bool.py,sha256=J0SYdp-9eIRs337d9qnw2f7IXTPQj_F21ugMIJ8-Hy4,547
254
- runtimepy/primitives/types/bounds.py,sha256=zLUR-hVD_M6RfpGBnLEmhm_vajsBF04V9-sVvPUTAz8,1009
255
+ runtimepy/primitives/types/bounds.py,sha256=Hc2GWu4qJMEAF8IiDWFA_6VCNUtrNf5RmiEbSJ76dJI,1145
255
256
  runtimepy/primitives/types/float.py,sha256=yjiU1AGzekY8PjDcLNdoXMGDsdP5aDYWjLb3_RntRpA,1388
256
257
  runtimepy/primitives/types/int.py,sha256=Gjro7RgPL1girVVXhvDErvRwxlfHclMr7tZEFYj-Uj4,3807
257
258
  runtimepy/registry/__init__.py,sha256=NpMxvdmjRtE3jCCOuYU_CPpYWqCZ5byXWhJNTB6jZUk,3425
@@ -284,9 +285,9 @@ runtimepy/tui/task.py,sha256=nUZo9fuOC-k1Wpqdzkv9v1tQirCI28fZVgcC13Ijvus,1093
284
285
  runtimepy/tui/channels/__init__.py,sha256=evDaiIn-YS9uGhdo8ZGtP9VK1ek6sr_P1nJ9JuSET0o,4536
285
286
  runtimepy/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
286
287
  runtimepy/ui/controls.py,sha256=yvT7h3thbYaitsakcIAJ90EwKzJ4b-jnc6p3UuVf_XE,1241
287
- runtimepy-5.10.1.dist-info/LICENSE,sha256=yKBRwbO-cOPBrlpsZmJkkSa33DfY31aE8t7lZ0DwlUo,1071
288
- runtimepy-5.10.1.dist-info/METADATA,sha256=XmFGbIXFW_oILfJuI2DX6HoAxjr92SGzxX07oiUJx1c,9373
289
- runtimepy-5.10.1.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
290
- runtimepy-5.10.1.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
291
- runtimepy-5.10.1.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
292
- runtimepy-5.10.1.dist-info/RECORD,,
288
+ runtimepy-5.11.0.dist-info/licenses/LICENSE,sha256=yKBRwbO-cOPBrlpsZmJkkSa33DfY31aE8t7lZ0DwlUo,1071
289
+ runtimepy-5.11.0.dist-info/METADATA,sha256=mj58S6d33EOyEgeZSpDwo9UU5imTeKZ2r5pRdrkguFk,9395
290
+ runtimepy-5.11.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
291
+ runtimepy-5.11.0.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
292
+ runtimepy-5.11.0.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
293
+ runtimepy-5.11.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.0.0)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5