runtimepy 5.15.3__py3-none-any.whl → 5.15.5__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.
Files changed (36) hide show
  1. runtimepy/__init__.py +2 -2
  2. runtimepy/channel/environment/base.py +5 -0
  3. runtimepy/channel/environment/command/processor.py +11 -1
  4. runtimepy/channel/environment/file.py +4 -0
  5. runtimepy/data/css/bootstrap_extra.css +4 -0
  6. runtimepy/data/css/main.css +8 -0
  7. runtimepy/data/dummy_load.yaml +47 -17
  8. runtimepy/data/js/classes/ChannelTable.js +4 -0
  9. runtimepy/data/js/classes/TabInterface.js +13 -0
  10. runtimepy/data/js/classes/WorkerInterface.js +1 -1
  11. runtimepy/data/js/init.js +4 -2
  12. runtimepy/data/js/sample.js +1 -1
  13. runtimepy/data/schemas/Button.yaml +17 -0
  14. runtimepy/data/schemas/ClientConnectionConfig.yaml +2 -0
  15. runtimepy/data/schemas/ConnectionArbiterConfig.yaml +1 -8
  16. runtimepy/data/schemas/PeerProcessConfig.yaml +1 -0
  17. runtimepy/data/schemas/TwoTupleArray.yaml +9 -0
  18. runtimepy/data/schemas/has_buttons.yaml +6 -0
  19. runtimepy/data/schemas/has_views.yaml +4 -0
  20. runtimepy/net/arbiter/base.py +20 -0
  21. runtimepy/net/arbiter/config/__init__.py +3 -1
  22. runtimepy/net/arbiter/factory/connection.py +9 -1
  23. runtimepy/net/server/app/env/tab/base.py +11 -0
  24. runtimepy/net/server/app/env/tab/html.py +10 -0
  25. runtimepy/net/server/app/env/widgets.py +25 -9
  26. runtimepy/net/server/websocket/__init__.py +1 -1
  27. runtimepy/requirements.txt +1 -1
  28. runtimepy/struct/__init__.py +3 -0
  29. runtimepy/task/basic/periodic.py +3 -0
  30. runtimepy/ui/button.py +76 -0
  31. {runtimepy-5.15.3.dist-info → runtimepy-5.15.5.dist-info}/METADATA +6 -6
  32. {runtimepy-5.15.3.dist-info → runtimepy-5.15.5.dist-info}/RECORD +36 -31
  33. {runtimepy-5.15.3.dist-info → runtimepy-5.15.5.dist-info}/WHEEL +0 -0
  34. {runtimepy-5.15.3.dist-info → runtimepy-5.15.5.dist-info}/entry_points.txt +0 -0
  35. {runtimepy-5.15.3.dist-info → runtimepy-5.15.5.dist-info}/licenses/LICENSE +0 -0
  36. {runtimepy-5.15.3.dist-info → runtimepy-5.15.5.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.2.3
4
- # hash=f5155e85b2694b012648fdbe9c12b1cd
4
+ # hash=8ce7abe0f8c0e64d9afc0b5e4de6e2ad
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.15.3"
13
+ VERSION = "5.15.5"
14
14
 
15
15
  # runtimepy-specific content.
16
16
  METRICS_NAME = "metrics"
@@ -54,6 +54,7 @@ class BaseChannelEnvironment(_NamespaceMixin, FinalizeMixin):
54
54
  fields: _Iterable[_BitFields] = None,
55
55
  namespace: Namespace = None,
56
56
  namespace_delim: str = DEFAULT_DELIM,
57
+ views: dict[str, str] = None,
57
58
  ) -> None:
58
59
  """Initialize this channel environment."""
59
60
 
@@ -105,6 +106,10 @@ class BaseChannelEnvironment(_NamespaceMixin, FinalizeMixin):
105
106
  if values is not None:
106
107
  self.apply(values)
107
108
 
109
+ if not views:
110
+ views = {}
111
+ self.views = views
112
+
108
113
  def __setitem__(self, key: _RegistryKey, value: ChannelValue) -> None:
109
114
  """Mapping-set interface."""
110
115
  return self.set(key, value)
@@ -20,6 +20,7 @@ from runtimepy.channel.environment.command.result import SUCCESS, CommandResult
20
20
  from runtimepy.mixins.environment import ChannelEnvironmentMixin
21
21
  from runtimepy.primitives.bool import Bool
22
22
  from runtimepy.primitives.field import BitField
23
+ from runtimepy.ui.button import ActionButton
23
24
 
24
25
  CommandHook = Callable[[Namespace, Optional[FieldOrChannel]], None]
25
26
 
@@ -33,7 +34,11 @@ class ChannelCommandProcessor(ChannelEnvironmentMixin):
33
34
  """A command processing interface for channel environments."""
34
35
 
35
36
  def __init__(
36
- self, env: ChannelEnvironment, logger: LoggerType, **kwargs
37
+ self,
38
+ env: ChannelEnvironment,
39
+ logger: LoggerType,
40
+ buttons: list[ActionButton] = None,
41
+ **kwargs,
37
42
  ) -> None:
38
43
  """Initialize this instance."""
39
44
 
@@ -49,6 +54,11 @@ class ChannelCommandProcessor(ChannelEnvironmentMixin):
49
54
 
50
55
  self.parser.initialize()
51
56
 
57
+ # Action buttons.
58
+ if buttons is None:
59
+ buttons = []
60
+ self.buttons: list[ActionButton] = buttons
61
+
52
62
  def register_custom_commands(
53
63
  self, *custom_commands: CustomCommand
54
64
  ) -> None:
@@ -42,6 +42,8 @@ ENUMS_FILE = f"{ENUMS_KEY}.json"
42
42
  VALUES_FILE = f"{VALUES_KEY}.json"
43
43
  FIELDS_FILE = f"{FIELDS_KEY}.json"
44
44
  NAMES_FILE = f"{NAMES_KEY}.json"
45
+ VIEWS_KEY = "views"
46
+ # VIEWS_FILE = f"{VIEWS_KEY}.json"
45
47
 
46
48
 
47
49
  class FileChannelEnvironment(_BaseChannelEnvironment):
@@ -64,6 +66,7 @@ class FileChannelEnvironment(_BaseChannelEnvironment):
64
66
  VALUES_KEY: _cast(
65
67
  _JsonObject, self.values(resolve_enum=resolve_enum)
66
68
  ),
69
+ VIEWS_KEY: _cast(_JsonObject, self.views),
67
70
  }
68
71
 
69
72
  def export(
@@ -143,6 +146,7 @@ class FileChannelEnvironment(_BaseChannelEnvironment):
143
146
  enums=enum_reg,
144
147
  values=_cast(_Optional[_ValueMap], data.get(VALUES_KEY)),
145
148
  fields=_fields_from_dict(data[FIELDS_KEY]),
149
+ views=_cast(dict[str, str], data.get(VIEWS_KEY, {})),
146
150
  )
147
151
 
148
152
  # Typically, externally loaded environments should be final at load
@@ -110,6 +110,10 @@ select:hover {
110
110
  background-image: linear-gradient(to bottom, var(--bs-tertiary-bg), rgba(var(--bs-tertiary-bg-rgb), 0), var(--bs-tertiary-bg));
111
111
  }
112
112
 
113
+ .bg-gradient-tertiary-left-right {
114
+ background-image: linear-gradient(to right, var(--bs-tertiary-bg), rgba(var(--bs-tertiary-bg-rgb), 0), var(--bs-tertiary-bg));
115
+ }
116
+
113
117
  .border-start-info-subtle {
114
118
  border-left: var(--bs-border-width) var(--bs-border-style) var(--bs-info-border-subtle);
115
119
  }
@@ -74,3 +74,11 @@ body > :first-child {
74
74
  .overscroll-behavior-none {
75
75
  overscroll-behavior: none;
76
76
  }
77
+
78
+ .channel-views-min-width {
79
+ min-width: 5.5em;
80
+ }
81
+
82
+ .channel-filter-min-width {
83
+ min-width: 5.5em;
84
+ }
@@ -1,4 +1,42 @@
1
1
  ---
2
+ # UDP JSON clients.
3
+ clients:
4
+ - factory: udp_json
5
+ name: udp_json_client
6
+ defer: true
7
+ kwargs:
8
+ remote_addr: [localhost, "$udp_json"]
9
+ views:
10
+ - [metrics, metrics]
11
+ - [transmit, tx]
12
+ - [receive, rx]
13
+ buttons: &buttons
14
+ - icon: tux
15
+ key: test
16
+ payload: {}
17
+ - icon: tux
18
+ key: test
19
+ text: asdf
20
+ payload: {}
21
+ - icon: tux
22
+ key: test
23
+ payload: {}
24
+ outline: false
25
+ - icon: tux
26
+ key: test
27
+ text: asdf
28
+ payload: {}
29
+ outline: false
30
+ markdown: |
31
+ # `udp_json_client`
32
+
33
+ Connects to `udp_json_server`.
34
+
35
+ - factory: udp_json
36
+ name: udp_json_server
37
+ kwargs:
38
+ local_addr: [localhost, "$udp_json"]
39
+
2
40
  # Add some sample tasks.
3
41
  tasks:
4
42
  # Chaos.
@@ -9,6 +47,10 @@ tasks:
9
47
  a: 1
10
48
  b: 2
11
49
  c: 3
50
+ buttons: *buttons
51
+ views:
52
+ - [zero, "\\.0\\."]
53
+ - [one, "\\.1\\."]
12
54
  markdown: |
13
55
  # This is a Test
14
56
 
@@ -29,6 +71,9 @@ tasks:
29
71
  - name: wave3
30
72
  factory: sinusoid
31
73
  period_s: 0.03
74
+ config:
75
+ views:
76
+ - [trig, sin cos]
32
77
  markdown: |
33
78
  # Markdown for wave3
34
79
 
@@ -43,23 +88,6 @@ tasks:
43
88
  # Drive interactions with runtime entities that won't otherwise be polled.
44
89
  - {name: app, factory: SampleApp, period_s: 0.25}
45
90
 
46
- # UDP JSON clients.
47
- clients:
48
- - factory: udp_json
49
- name: udp_json_client
50
- defer: true
51
- kwargs:
52
- remote_addr: [localhost, "$udp_json"]
53
- markdown: |
54
- # `udp_json_client`
55
-
56
- Connects to `udp_json_server`.
57
-
58
- - factory: udp_json
59
- name: udp_json_server
60
- kwargs:
61
- local_addr: [localhost, "$udp_json"]
62
-
63
91
  # Add some sample structs.
64
92
  structs:
65
93
  - name: example.struct1
@@ -70,6 +98,8 @@ structs:
70
98
  b: 2
71
99
  c: 3
72
100
 
101
+ buttons: *buttons
102
+
73
103
  markdown: |
74
104
  # Docs for `example.struct1`
75
105
 
@@ -56,6 +56,10 @@ class ChannelTable {
56
56
 
57
57
  if (Number.isInteger(val)) {
58
58
  /* Handle integer formatting. */
59
+ } else if (typeof val == "boolean") {
60
+ /* Use glyphs for booleans. */
61
+ val = val ? '<i class="bi bi-circle-fill"></i>'
62
+ : '<i class="bi bi-circle"></i>';
59
63
  } else {
60
64
  /* Handle floating-point numbers. */
61
65
  let checkFloat = Number.parseFloat(val);
@@ -263,6 +263,19 @@ class TabInterface {
263
263
  this.updateChannelStyles(this.channelFilter.value);
264
264
  };
265
265
  }
266
+
267
+ /* Initialize channel view dropdown. */
268
+ let channelView = this.query("#filter-view");
269
+ if (channelView) {
270
+ channelView.onchange = () => {
271
+ if (!channelView.value || channelView.value == '-') {
272
+ this.channelFilter.value = "";
273
+ } else {
274
+ this.channelFilter.value = channelView.value;
275
+ }
276
+ this.updateChannelStyles(this.channelFilter.value);
277
+ };
278
+ }
266
279
  }
267
280
 
268
281
  setHandler(elem) {
@@ -10,7 +10,7 @@ class WorkerInterface {
10
10
 
11
11
  command(data) { this.send({kind : "command", value : data}); }
12
12
 
13
- bus(data) { this.worker.postMessage({bus : data}); }
13
+ bus(key, data) { this.worker.postMessage({key : key, bus : data}); }
14
14
 
15
15
  toWorker(data, param) { return this.send({"worker" : data}, param); }
16
16
  }
runtimepy/data/js/init.js CHANGED
@@ -53,8 +53,10 @@ function isModifierKeyEvent(event) {
53
53
  }
54
54
 
55
55
  function ignoreFilterKeyEvent(event) {
56
- // home end pg up pg down delete f keys
57
- return isModifierKeyEvent(event) || event.key == "Tab";
56
+ return isModifierKeyEvent(event) || event.key == "Tab" ||
57
+ (event.key.startsWith("F") && event.key.length > 1) ||
58
+ event.key.startsWith("Del") || event.key == "Home" ||
59
+ event.key == "End" || event.key == "PageUp" || event.key == "PageDown";
58
60
  }
59
61
 
60
62
  function globalKeyEvent(event) {
@@ -1,2 +1,2 @@
1
1
  console.log(`sample.js included (${tab.name})`);
2
- // tab.worker.bus({a: 1, b: 2, c: 3});
2
+ // tab.worker.bus("test", {a: 1, b: 2, c: 3});
@@ -0,0 +1,17 @@
1
+ ---
2
+ type: object
3
+ additionalProperties: false
4
+ required: [key, payload]
5
+ properties:
6
+ key:
7
+ type: string
8
+ payload:
9
+ type: object
10
+ text:
11
+ type: string
12
+ icon:
13
+ type: string
14
+ variant:
15
+ type: string
16
+ outline:
17
+ type: boolean
@@ -3,6 +3,8 @@ includes:
3
3
  - has_factory.yaml
4
4
  - has_name.yaml
5
5
  - has_markdown.yaml
6
+ - has_views.yaml
7
+ - has_buttons.yaml
6
8
 
7
9
  properties:
8
10
  defer:
@@ -27,14 +27,7 @@ properties:
27
27
  $ref: package://runtimepy/schemas/StructConfig.yaml
28
28
 
29
29
  commands:
30
- type: array
31
- items:
32
- type: array
33
- minItems: 2
34
- maxItems: 2
35
- items:
36
- - type: string
37
- - type: string
30
+ $ref: package://runtimepy/schemas/TwoTupleArray.yaml
38
31
 
39
32
  tasks:
40
33
  type: array
@@ -4,6 +4,7 @@ includes:
4
4
  - has_name.yaml
5
5
  - has_config.yaml
6
6
  - has_markdown.yaml
7
+ - has_views.yaml
7
8
 
8
9
  properties:
9
10
  program:
@@ -0,0 +1,9 @@
1
+ ---
2
+ type: array
3
+ items:
4
+ type: array
5
+ minItems: 2
6
+ maxItems: 2
7
+ items:
8
+ - type: string
9
+ - type: string
@@ -0,0 +1,6 @@
1
+ ---
2
+ properties:
3
+ buttons:
4
+ type: array
5
+ items:
6
+ $ref: package://runtimepy/schemas/Button.yaml
@@ -0,0 +1,4 @@
1
+ ---
2
+ properties:
3
+ views:
4
+ $ref: package://runtimepy/schemas/TwoTupleArray.yaml
@@ -47,6 +47,7 @@ from runtimepy.net.manager import ConnectionManager as _ConnectionManager
47
47
  from runtimepy.net.server import RuntimepyServerConnection
48
48
  from runtimepy.subprocess.peer import RuntimepyPeer as _RuntimepyPeer
49
49
  from runtimepy.tui.mixin import CursesWindow, TuiMixin
50
+ from runtimepy.ui.button import ActionButton
50
51
 
51
52
  ServerTask = _Awaitable[None]
52
53
  RuntimeProcessTask = tuple[
@@ -147,6 +148,8 @@ class BaseConnectionArbiter(_NamespaceMixin, _LoggerMixin, TuiMixin):
147
148
  self._ports: dict[str, int] = {}
148
149
 
149
150
  self._commands: list[tuple[str, str]] = []
151
+ self._views: dict[str, dict[str, str]] = {}
152
+ self._buttons: dict[str, list[ActionButton]] = {}
150
153
 
151
154
  self._init()
152
155
 
@@ -156,6 +159,16 @@ class BaseConnectionArbiter(_NamespaceMixin, _LoggerMixin, TuiMixin):
156
159
  def _register_connection(self, connection: _Connection, name: str) -> None:
157
160
  """Perform connection registration."""
158
161
 
162
+ # Handle views.
163
+ if name in self._views:
164
+ connection.env.views.update(self._views[name])
165
+ del self._views[name]
166
+
167
+ # Handle buttons.
168
+ if name in self._buttons:
169
+ connection.command.buttons.extend(self._buttons[name])
170
+ del self._buttons[name]
171
+
159
172
  self._connections[name] = connection
160
173
  self.manager.queue.put_nowait(connection)
161
174
  connection.logger.info("Registered as '%s'.", name)
@@ -165,6 +178,8 @@ class BaseConnectionArbiter(_NamespaceMixin, _LoggerMixin, TuiMixin):
165
178
  connection: _Union[_Connection, _Awaitable[_Connection]],
166
179
  *names: str,
167
180
  delim: str = None,
181
+ views: dict[str, str] = None,
182
+ buttons: list[ActionButton] = None,
168
183
  ) -> bool:
169
184
  """Attempt to register a connection object."""
170
185
 
@@ -173,6 +188,11 @@ class BaseConnectionArbiter(_NamespaceMixin, _LoggerMixin, TuiMixin):
173
188
  with self.names_pushed(*names):
174
189
  name = self.namespace(delim=delim)
175
190
 
191
+ if views:
192
+ self._views[name] = views
193
+ if buttons:
194
+ self._buttons[name] = buttons
195
+
176
196
  if (
177
197
  name not in self._connections
178
198
  and name not in self._deferred_connections
@@ -170,7 +170,6 @@ class ConfigConnectionArbiter(_ImportConnectionArbiter):
170
170
  kwargs = dict_resolve_env_vars(
171
171
  client.get("kwargs", {}), env=self._ports # type: ignore
172
172
  )
173
- kwargs.setdefault("markdown", client.get("markdown"))
174
173
 
175
174
  assert await self.factory_client(
176
175
  factory,
@@ -179,6 +178,9 @@ class ConfigConnectionArbiter(_ImportConnectionArbiter):
179
178
  defer=client["defer"],
180
179
  # Perform some known fixes for common keyword arguments.
181
180
  **fix_kwargs(kwargs),
181
+ views=client.get("views"),
182
+ markdown=client.get("markdown"),
183
+ buttons=client.get("buttons", []),
182
184
  ), f"Couldn't register client '{name}' ({factory})!"
183
185
 
184
186
  # Register servers.
@@ -16,6 +16,7 @@ from runtimepy.net.arbiter.base import (
16
16
  from runtimepy.net.arbiter.base import ServerTask as _ServerTask
17
17
  from runtimepy.net.connection import Connection as _Connection
18
18
  from runtimepy.net.manager import ConnectionManager as _ConnectionManager
19
+ from runtimepy.ui.button import ActionButton
19
20
 
20
21
 
21
22
  class ConnectionFactory:
@@ -89,12 +90,19 @@ class FactoryConnectionArbiter(_BaseConnectionArbiter):
89
90
  if factory in self._conn_factories:
90
91
  factory_inst = self._conn_factories[factory]
91
92
 
93
+ views = kwargs.pop("views", {})
94
+ buttons = ActionButton.from_top_level(kwargs.pop("buttons", []))
95
+
92
96
  conn = factory_inst.client(name, *args, **kwargs)
93
97
  if not defer:
94
98
  conn = await conn # type: ignore
95
99
 
96
100
  result = self.register_connection(
97
- conn, *self._conn_names[factory_inst], name
101
+ conn,
102
+ *self._conn_names[factory_inst],
103
+ name,
104
+ views=views,
105
+ buttons=buttons,
98
106
  )
99
107
 
100
108
  return result
@@ -2,6 +2,9 @@
2
2
  A module implementing a channel-environment tab HTML interface.
3
3
  """
4
4
 
5
+ # built-in
6
+ from io import StringIO
7
+
5
8
  # third-party
6
9
  from vcorelib.io.markdown import MarkdownMixin
7
10
  from vcorelib.logging import LoggerMixin
@@ -39,3 +42,11 @@ class ChannelEnvironmentTabBase(Tab, LoggerMixin, MarkdownMixin):
39
42
  # Logging.
40
43
  LoggerMixin.__init__(self, logger=self.command.logger)
41
44
  self.log_limiter = RateLimiter.from_s(1.0)
45
+
46
+ def _action_markdown(self) -> str:
47
+ """Get action-button markdown."""
48
+
49
+ with StringIO() as stream:
50
+ for button in self.command.buttons:
51
+ button.element().encode(stream)
52
+ return stream.getvalue()
@@ -289,6 +289,16 @@ class ChannelEnvironmentTabHtml(ChannelEnvironmentTabControls):
289
289
  )
290
290
  logs.booleans.add("readonly")
291
291
 
292
+ if self.command.buttons:
293
+ centered_markdown(
294
+ vert_container,
295
+ self._action_markdown(),
296
+ "border-start",
297
+ "border-bottom",
298
+ "border-end",
299
+ "bg-gradient-tertiary-left-right",
300
+ )
301
+
292
302
  self.channel_table(vert_container)
293
303
 
294
304
  centered_markdown(
@@ -80,6 +80,22 @@ def enum_dropdown(
80
80
  TABLE_BUTTON_CLASSES = ("border-top-0", "border-bottom-0")
81
81
 
82
82
 
83
+ def views_dropdown(parent: Element, command: ChannelCommandProcessor) -> None:
84
+ """Dropdown menu for channel environment views."""
85
+
86
+ select = select_element(
87
+ parent=div(
88
+ tag="th", parent=parent, class_str="p-0 channel-views-min-width"
89
+ ),
90
+ id="filter-view",
91
+ title="Canonical channel filters.",
92
+ ).add_class("border-end-0", "w-100")
93
+ div(tag="option", value="", text="-", parent=select)
94
+ div(tag="option", value="^$", text="hide", parent=select)
95
+ for text, value in command.env.views.items():
96
+ div(tag="option", value=value, text=text, parent=select)
97
+
98
+
83
99
  def channel_table_header(
84
100
  parent: Element, command: ChannelCommandProcessor
85
101
  ) -> None:
@@ -105,7 +121,11 @@ def channel_table_header(
105
121
  ).add_class(*TABLE_BUTTON_CLASSES)
106
122
 
107
123
  _, label, box = input_box(
108
- div(tag="th", parent=ctl_row, colspan="2", class_str="p-0"),
124
+ div(
125
+ tag="th",
126
+ parent=ctl_row,
127
+ class_str="p-0 border-end-0 channel-filter-min-width",
128
+ ),
109
129
  description="Channel name filter.",
110
130
  pattern=".* ! @ $",
111
131
  label="filter",
@@ -114,7 +134,9 @@ def channel_table_header(
114
134
  spellcheck="false",
115
135
  )
116
136
  label.add_class("border-top-0", "border-bottom-0")
117
- box.add_class("border-top-0", "border-bottom-0")
137
+ box.add_class("border-top-0", "border-bottom-0", "border-end-0")
138
+
139
+ views_dropdown(ctl_row, command)
118
140
 
119
141
  cell = flex(
120
142
  parent=div(tag="th", parent=ctl_row, colspan="2", class_str="p-0")
@@ -124,7 +146,6 @@ def channel_table_header(
124
146
  select = select_element(
125
147
  parent=cell, id="custom-commands", title="Custom command selector."
126
148
  )
127
- select.add_class("border-start-0")
128
149
 
129
150
  if command.custom_commands:
130
151
  for key in command.custom_commands:
@@ -141,12 +162,7 @@ def channel_table_header(
141
162
  tooltip="Send selected command (left dropdown).",
142
163
  ).add_class(*TABLE_BUTTON_CLASSES)
143
164
  else:
144
- div(
145
- tag="option",
146
- parent=select,
147
- value="noop",
148
- text="no custom commands",
149
- )
165
+ div(tag="option", parent=select, value="noop", text="-")
150
166
  select.booleans.add("disabled")
151
167
 
152
168
  # Button for 'reset all defaults' if this tab has more than one channel
@@ -96,7 +96,7 @@ class RuntimepyWebsocketConnection(WebsocketJsonMessageConnection):
96
96
 
97
97
  # Handle bus messages.
98
98
  if "bus" in inbox:
99
- await BUS.send_ro("ui", inbox["bus"])
99
+ await BUS.send_ro(inbox["key"], inbox["bus"])
100
100
  return
101
101
 
102
102
  # Handle frame messages.
@@ -1,5 +1,5 @@
1
1
  aiofiles
2
- vcorelib>=3.6.3
2
+ vcorelib>=3.6.4
3
3
  svgen>=0.8.0
4
4
  websockets
5
5
  psutil
@@ -21,6 +21,7 @@ from runtimepy.channel.environment.command.processor import (
21
21
  from runtimepy.mixins.async_command import AsyncCommandProcessingMixin
22
22
  from runtimepy.mixins.environment import ChannelEnvironmentMixin
23
23
  from runtimepy.mixins.logging import LoggerMixinLevelControl
24
+ from runtimepy.ui.button import ActionButton
24
25
 
25
26
 
26
27
  class RuntimeStructBase(
@@ -45,10 +46,12 @@ class RuntimeStructBase(
45
46
  self.set_markdown(config=config, markdown=markdown, package=PKG_NAME)
46
47
  LoggerMixinLevelControl.__init__(self, logger=_getLogger(self.name))
47
48
  ChannelEnvironmentMixin.__init__(self)
49
+ self.env.views.update(config.get("views", {})) # type: ignore
48
50
  if self.log_level_channel:
49
51
  self.setup_level_channel(self.env)
50
52
  self.command = ChannelCommandProcessor(self.env, self.logger)
51
53
  self.config = config
54
+ self.command.buttons.extend(ActionButton.from_top_level(self.config))
52
55
 
53
56
  async def poll(args: Namespace, __: Optional[FieldOrChannel]) -> None:
54
57
  """Handle a test command."""
@@ -33,6 +33,7 @@ from runtimepy.primitives import Bool as _Bool
33
33
  from runtimepy.primitives import Double as _Double
34
34
  from runtimepy.primitives import Float as _Float
35
35
  from runtimepy.primitives.evaluation import EvalResult as _EvalResult
36
+ from runtimepy.ui.button import ActionButton
36
37
  from runtimepy.ui.controls import Controlslike
37
38
 
38
39
 
@@ -72,8 +73,10 @@ class PeriodicTask(
72
73
  self.metrics = metrics
73
74
 
74
75
  ChannelEnvironmentMixin.__init__(self, env=env)
76
+ self.env.views.update(self.config.get("views", {})) # type: ignore
75
77
  self.setup_level_channel(self.env)
76
78
  self.command = ChannelCommandProcessor(self.env, self.logger)
79
+ self.command.buttons.extend(ActionButton.from_top_level(self.config))
77
80
  self.register_task_metrics(self.metrics)
78
81
 
79
82
  # State.
runtimepy/ui/button.py ADDED
@@ -0,0 +1,76 @@
1
+ """
2
+ A module implementing an action-button interface.
3
+ """
4
+
5
+ # built-in
6
+ from json import dumps
7
+ from typing import NamedTuple
8
+
9
+ # third-party
10
+ from svgen.element import Element
11
+ from svgen.element.html import div
12
+ from vcorelib.dict import GenericStrDict
13
+
14
+ # internal
15
+ from runtimepy.net.html.bootstrap import icon_str
16
+
17
+
18
+ class ActionButton(NamedTuple):
19
+ """A class implementing an interface for action buttons."""
20
+
21
+ key: str
22
+ payload: GenericStrDict
23
+ text: str
24
+ icon: str
25
+ variant: str
26
+ outline: bool
27
+
28
+ @staticmethod
29
+ def from_dict(data: GenericStrDict) -> "ActionButton":
30
+ """Create an action button from dictionary data."""
31
+
32
+ return ActionButton(
33
+ data["key"],
34
+ data["payload"],
35
+ data.get("text", ""),
36
+ data.get("icon", ""),
37
+ data.get("variant", "primary"),
38
+ data.get("outline", True),
39
+ )
40
+
41
+ @staticmethod
42
+ def from_top_level(
43
+ data: GenericStrDict | list[GenericStrDict],
44
+ ) -> list["ActionButton"]:
45
+ """Create an action button from dictionary data."""
46
+
47
+ return (
48
+ [ActionButton.from_dict(x) for x in data.get("buttons", [])]
49
+ if isinstance(data, dict)
50
+ else [ActionButton.from_dict(x) for x in data]
51
+ )
52
+
53
+ def element(self) -> Element:
54
+ """Create an action button element."""
55
+
56
+ payload = dumps(self.payload).replace('"', "&quot;")
57
+
58
+ text_parts = []
59
+ if self.icon:
60
+ text_parts.append(
61
+ icon_str(
62
+ self.icon,
63
+ [f"text-{self.variant}-emphasis"] if self.text else [],
64
+ )
65
+ )
66
+ if self.text:
67
+ text_parts.append(self.text)
68
+
69
+ return div(
70
+ tag="button",
71
+ type="button",
72
+ onclick=f"tabs[shown_tab].worker.bus('{self.key}', {payload})",
73
+ class_str=f"btn btn{'-outline' if self.outline else ''}"
74
+ f"-{self.variant} m-2 ms-1 me-0",
75
+ text=" ".join(text_parts),
76
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: runtimepy
3
- Version: 5.15.3
3
+ Version: 5.15.5
4
4
  Summary: A framework for implementing Python services.
5
5
  Home-page: https://github.com/libre-embedded/runtimepy
6
6
  Author: Libre Embedded
@@ -17,11 +17,11 @@ Classifier: Development Status :: 5 - Production/Stable
17
17
  Requires-Python: >=3.12
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
- Requires-Dist: aiofiles
20
+ Requires-Dist: psutil
21
21
  Requires-Dist: svgen>=0.8.0
22
+ Requires-Dist: aiofiles
23
+ Requires-Dist: vcorelib>=3.6.4
22
24
  Requires-Dist: websockets
23
- Requires-Dist: psutil
24
- Requires-Dist: vcorelib>=3.6.3
25
25
  Provides-Extra: test
26
26
  Requires-Dist: pylint; extra == "test"
27
27
  Requires-Dist: flake8; extra == "test"
@@ -51,11 +51,11 @@ Dynamic: requires-python
51
51
  =====================================
52
52
  generator=datazen
53
53
  version=3.2.3
54
- hash=ed2f5b3731ba189c4a3ec2f2252b622e
54
+ hash=fd0e57419cf9437a64c4f82cae3625f0
55
55
  =====================================
56
56
  -->
57
57
 
58
- # runtimepy ([5.15.3](https://pypi.org/project/runtimepy/))
58
+ # runtimepy ([5.15.5](https://pypi.org/project/runtimepy/))
59
59
 
60
60
  [![python](https://img.shields.io/pypi/pyversions/runtimepy.svg)](https://pypi.org/project/runtimepy/)
61
61
  ![Build Status](https://github.com/libre-embedded/runtimepy/workflows/Python%20Package/badge.svg)
@@ -1,25 +1,25 @@
1
- runtimepy/__init__.py,sha256=p2KGfCnic5ku-9e4b4_crfVty8m2X40WLOHJiBsFOuo,391
1
+ runtimepy/__init__.py,sha256=wVKBUb0d3kdEu2RAyDqlguPD6weDDr_KQAXrx0YQlCY,391
2
2
  runtimepy/__main__.py,sha256=IKioH2xOtsXwrwb9zABDQEJvuAX--Lnh84TeSz0XSs0,332
3
3
  runtimepy/app.py,sha256=Er1ZKKrG9U0FV0gQg_GYF9xDb89HgYnVzS5SjxGa2Tg,970
4
4
  runtimepy/dev_requirements.txt,sha256=VZhW6bJ5YbwaoN4d_XxZFuN5BbDLaG7ngKrGnugVPRw,245
5
5
  runtimepy/entry.py,sha256=fy9irGEgyFAsu868bOymHX8I3CkkrMNThUWAm-4chiM,1930
6
6
  runtimepy/mapping.py,sha256=VQK1vzmQVvYYKI85_II37-hIEbvgL3PzNy-WI6TTo80,5091
7
7
  runtimepy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- runtimepy/requirements.txt,sha256=dBOVAEEsNN89zt8_WRCcQsge6pxvmJ5srJdFAC8uAL8,124
8
+ runtimepy/requirements.txt,sha256=74DDO5RyBpZ38ziwZQinykqNsj1x9AHz4iZd0z12VgA,124
9
9
  runtimepy/schemas.py,sha256=zTgxPm9DHZ0R_bmmOjNQMTXdtM_Hb1bE-Fog40jDCgg,839
10
10
  runtimepy/util.py,sha256=ZHSucNi-gbrcajoCv2dNjQs48dJPC3mTM_wZHx7AW1U,1719
11
11
  runtimepy/channel/__init__.py,sha256=bI1mfHmjUGrYcwePip2ddbMaGSkyQXQVfESL5dqXRdU,4716
12
12
  runtimepy/channel/registry.py,sha256=AiRhSSnNbWCxwC-m8vP2fRaN1cfm5JjvlphQzkETf9w,4742
13
13
  runtimepy/channel/environment/__init__.py,sha256=Mmq4S4NCX9pKSH7XEmD04zm0YcRhrTc8mGb25dTULgw,5333
14
14
  runtimepy/channel/environment/array.py,sha256=f9cWaYsRXUw8qE629h6jQxbYKDpOwC2GLBo4QaMa1JM,3748
15
- runtimepy/channel/environment/base.py,sha256=uCQKCH6wNrKqYSX2PMDAbIFoCZh68Xt-Cr_daGG7eeI,14356
15
+ runtimepy/channel/environment/base.py,sha256=vZAPydAZ58qGf3HvUR6KjB5RzARHfigW1s01VZNcCAs,14467
16
16
  runtimepy/channel/environment/create.py,sha256=UVO_R5rM6xLjpAh3j3wwix-0V3uJMAno2tbBIV4boo8,5868
17
- runtimepy/channel/environment/file.py,sha256=PV05KZ3-CvftbKUM8acQmawOMeGGCcMrEESEBuymykg,6949
17
+ runtimepy/channel/environment/file.py,sha256=keAlt9CrGXHVbHimZV0gv0WZkKYBQ-c9YHgQtojceAk,7125
18
18
  runtimepy/channel/environment/sample.py,sha256=6926v0ZssvfeWX_C8MSbUfYjE4sr3jt0CLVSJ_2Cl3U,5316
19
19
  runtimepy/channel/environment/telemetry.py,sha256=3A7Xcp-4eHJWz_oR1SnI6rsl4o8wiSUaiMHrnK1IaQ8,5338
20
20
  runtimepy/channel/environment/command/__init__.py,sha256=PRuIp29QYkTDHF95HL55G6ogQDkVbq0M3SnNre4JXpI,9150
21
21
  runtimepy/channel/environment/command/parser.py,sha256=cMOsEsXnfFlATiWTNSxlgvc_XoICsJlcZigFJlQ47tk,1804
22
- runtimepy/channel/environment/command/processor.py,sha256=NiiWRdwBHOFEisjqNOW5oawprxcpR25ONNABoZpELdg,7122
22
+ runtimepy/channel/environment/command/processor.py,sha256=5Vzqgd8GP-3qUGT7m-dY8i2laKRouDrPPD3sUS49XMg,7367
23
23
  runtimepy/channel/environment/command/result.py,sha256=Ko5lK4d04Z266WuCi2sHQItbUHJFYv7qUdrDi-OVKOU,624
24
24
  runtimepy/channel/event/__init__.py,sha256=9LCSNa1Iiwr6Q6JkwQGELDQ7rWfU_xjAMh6qM1I-ueM,2793
25
25
  runtimepy/channel/event/header.py,sha256=eDRZgzzM5HZQ8QtV4DjyAsrAhEZyM7IfwqK6WB5ACEw,868
@@ -45,7 +45,7 @@ runtimepy/control/env/__init__.py,sha256=RHJqysY7Pv4VDs2SGk0X-qc5xp_SrQ_oxb5Deug
45
45
  runtimepy/data/404.md,sha256=_yzjvp2GeMc5OBvZX2zDbX-Ns6T7HCO9esRbS5PmkXU,397
46
46
  runtimepy/data/base.yaml,sha256=PEsvcMV1qFb1Gr6Cx2SgmR6ScfPyuu3yTdnvSBTVSdA,453
47
47
  runtimepy/data/browser.yaml,sha256=oc5KEV1C1uAJ4MkhNo4hyVVfJtZvHelRNqzNvD313Ow,79
48
- runtimepy/data/dummy_load.yaml,sha256=gx3B-rGXj5iEcs7iUssy7aZxRvUNz0Q73whSlNwY1tU,2171
48
+ runtimepy/data/dummy_load.yaml,sha256=f6UYq6YVuv-lYZDN8h5OUwIWkpzSb7cQwqvL4BXX7ZI,2752
49
49
  runtimepy/data/factories.yaml,sha256=CdpnjK2glj8HewI2tFSNvCSUdcNPeVD_tFbGy2QBSQg,1952
50
50
  runtimepy/data/favicon.ico,sha256=boxAGaHbUjMFrOO2TZpsO0nIRC-LUgwHVQYOiG1YQnM,362870
51
51
  runtimepy/data/sample_telemetry.yaml,sha256=OpdFurkvtWJGaNl9LMlU2rKo15AaVVr-U_hoZfsbp-Y,695
@@ -53,21 +53,21 @@ runtimepy/data/server.yaml,sha256=wS_Ceiu2TpkfPurpqoYoPlgzc9DAWtUd24MW7t-S5rU,97
53
53
  runtimepy/data/server_base.yaml,sha256=Fiqz9C_ikxtYYFO8q17XzgaY5b3H9RuyqJr-QoTk-qg,524
54
54
  runtimepy/data/server_dev.yaml,sha256=nQsPh7LuQig3pzHfdg_aD3yOUiCj1sKKfI-WwW3hXmQ,523
55
55
  runtimepy/data/tftp_server.yaml,sha256=-bFOWJSagI-fEQQcT8k7eDMJVfSPm2XAxLVG3dqUTa4,204
56
- runtimepy/data/css/bootstrap_extra.css,sha256=hQV_f0DXboYPlsuI7PahFT11g5xjVw6nqp1DACOKWns,2934
56
+ runtimepy/data/css/bootstrap_extra.css,sha256=0NiTnZWnIYf04n9NHVe-nVSOLwePJ6X-3uCgUgAEKPI,3101
57
57
  runtimepy/data/css/font.css,sha256=Pe82E66rNi-cwlQ-_1GHAuhPGu5L4x5KqgV0dbDe51w,977
58
- runtimepy/data/css/main.css,sha256=SuSNaXXd3iT53bbp7bsCxPJGZtkC0cNZeCeEpvjIEhU,1215
58
+ runtimepy/data/css/main.css,sha256=B3cBrwz6Leox1tYHTzJ7dW7Le0gf5_qZDCNlRsu-0aY,1316
59
59
  runtimepy/data/js/DataConnection.js,sha256=DnX8FMehjJXqmI62UMYXSvl_XdfQMzq3XUDFbLu2GgI,98
60
60
  runtimepy/data/js/JsonConnection.js,sha256=rclZrbmWc_zSs6I_JhOgxnVPFIyPMo5WdjAe8alyZ3o,2729
61
61
  runtimepy/data/js/audio.js,sha256=bLkBqbeHMiGGidfL3iXjmVoF9seK-ZeZ3kwgOrcpgk4,1092
62
62
  runtimepy/data/js/events.js,sha256=rgz3Q_8J6sfU_7Sa7fG1mZD0pQ4S3vwN2mqcvQfePkM,554
63
- runtimepy/data/js/init.js,sha256=6RhwGRl_DBjMpztms2JOQiQlvdizcLeaiETNEh31KyU,1992
63
+ runtimepy/data/js/init.js,sha256=LSZQKlgcIWmq4ue4kD1okRnN1qaVv0RalxFnqRiKWzI,2160
64
64
  runtimepy/data/js/main.js,sha256=nYIQ6O76EWqlzwX7oEwPXqC-LCUFCZYDADK9QbYRDKk,404
65
65
  runtimepy/data/js/markdown_page.js,sha256=R7Z2CwCk0dXED_4lwvDMXRNNB9ki9RvfwEKLPSwNWec,982
66
- runtimepy/data/js/sample.js,sha256=-ezzcH-U4hchWYgE2mMzO9lUdkmNDA_OojJRzA17AGU,88
66
+ runtimepy/data/js/sample.js,sha256=1aLHC-dsQZIWYH58P_AWGTzGS9YHwzT1aqH3M6Y_JaU,96
67
67
  runtimepy/data/js/util.js,sha256=Xc8pHUiFDBDvIqTamWrCYUOpF7iR9VNvPDCSCQAfLDA,1424
68
68
  runtimepy/data/js/worker.js,sha256=V9deGAynjvUr1D-WGi3wUW8rxoaNLvBvayMoLFZk3w0,2444
69
69
  runtimepy/data/js/classes/App.js,sha256=t3XVC7zSi8L_sMJ4KP-lyWyuD4cysCKkptN-lgjewN8,5159
70
- runtimepy/data/js/classes/ChannelTable.js,sha256=v6CqDdJrxhbDZcp9c1k7a9cy1uIHfb_LsTmCfY8lvPM,2593
70
+ runtimepy/data/js/classes/ChannelTable.js,sha256=djnUVmZyG6NDMLBKol8lN1tjfLd7pnhMVR1MyYXSnL4,2792
71
71
  runtimepy/data/js/classes/DataConnection.js,sha256=DnX8FMehjJXqmI62UMYXSvl_XdfQMzq3XUDFbLu2GgI,98
72
72
  runtimepy/data/js/classes/JsonConnection.js,sha256=vgQ3bGvVU5dNXn_uwvH7HjOQm0PwUSx0239Vfi7h1vE,2858
73
73
  runtimepy/data/js/classes/OverlayManager.js,sha256=-eH8NtJi8doBUB2Czqand4cWuQlddo1p1wVUfjVx9w0,3362
@@ -78,10 +78,10 @@ runtimepy/data/js/classes/PlotModalManager.js,sha256=lEbACLC5VzV-aSAb7G-WacmLLf_
78
78
  runtimepy/data/js/classes/PointBuffer.js,sha256=iVtq_q5gBaV9IVX55pHVjQdMJ4phJ6QTdAiM7EZrLRA,5061
79
79
  runtimepy/data/js/classes/PointManager.js,sha256=0lr2AReLdDNrY47UuOjjCRDPMiSRsOVggFHDPf8V-6Y,460
80
80
  runtimepy/data/js/classes/TabFilter.js,sha256=xajU1ZzVI0jFGhaHkKFX4-d2n2KlxiTC4it3N53-KxM,2115
81
- runtimepy/data/js/classes/TabInterface.js,sha256=xBBPX9UqMXvo6yguaJ5p3-Za1lLj3FAMLQk5rx4azZ8,14864
81
+ runtimepy/data/js/classes/TabInterface.js,sha256=SXUSbhJyyLRWAPzbfKvn4IBMdk6fOgQcz4-DXh0JmR4,15280
82
82
  runtimepy/data/js/classes/UnitSystem.js,sha256=ys4OMabq47k_VvJpRItm82U0IequDvx3ysRJOQzDf94,906
83
83
  runtimepy/data/js/classes/WindowHashManager.js,sha256=aR8zs1gUq9vkStLjyQj4rssx4dujLdDcoWNs91ViV44,7461
84
- runtimepy/data/js/classes/WorkerInterface.js,sha256=5jn5JX3cx-1oBVBpZ6X6E5x6uk5vmKQ98EMBoRAaqxk,402
84
+ runtimepy/data/js/classes/WorkerInterface.js,sha256=JtRWnLDPL09aUfhniWG5tqgkkeOnRkvIPJPhwI7S76U,418
85
85
  runtimepy/data/js/tab/env.js,sha256=MB79l3XyXKELWRqHcTnwWHiwdiceLHl1N_s-mS33pyU,22
86
86
  runtimepy/data/js/tab/sound.js,sha256=RSKp0AXM_zGOCsUvIT-BUjIzOE7Dp5NHiQG4fy7gBgY,1388
87
87
  runtimepy/data/js/third-party/webgl-debug.js,sha256=AtuSr5qje8a37S2ZE-ztA01QuGlGj9bGl-ntjlaqJIo,36878
@@ -92,24 +92,28 @@ runtimepy/data/md/RuntimeStruct.md,sha256=Mu6fYUk2GYfGYQPFUZkXvTYkj2WSR0T_gKJXh0
92
92
  runtimepy/data/md/RuntimepyPeer.md,sha256=ayjL1V5KXRBEixG0vfxIajY2Dt_H5SYaZGSscXHSL4E,34
93
93
  runtimepy/data/md/SinusoidTask.md,sha256=nTGmZXFv-r9d-ZgmmIHr6SKSp-K-nzJW-B6NKhg7FQ8,739
94
94
  runtimepy/data/schemas/BitFields.yaml,sha256=iE_3y30CgcpxvzwoC96BDhwbu2PZC-Lqu5C7XUeZ1go,1086
95
+ runtimepy/data/schemas/Button.yaml,sha256=avEdgdMQzVnzfVAZU2hTrERCcty47y9MYpRKkEkHY1Q,241
95
96
  runtimepy/data/schemas/Channel.yaml,sha256=9OQ3mOtPOlcWugvObpWKoAdFW0WvR9mSEzfdDYNf3fc,471
96
97
  runtimepy/data/schemas/ChannelCommand.yaml,sha256=h7-n5WjNwWgteZ8U5YpaBRncR_Uiqaa_wVfuTWQFrTY,257
97
98
  runtimepy/data/schemas/ChannelRegistry.yaml,sha256=f51YngVC8zDM3HwRMicKBOyoqjJ9EWsx3GWD0EkHCZk,136
98
- runtimepy/data/schemas/ClientConnectionConfig.yaml,sha256=XUWkOiCOw175harOsDRV3TXJja-EAKnipYDQFf_XC4Q,187
99
- runtimepy/data/schemas/ConnectionArbiterConfig.yaml,sha256=tjqtu270LVhlBzP1Y-EdJqTSlC7yYLylaLzrW3Q1l-w,2286
99
+ runtimepy/data/schemas/ClientConnectionConfig.yaml,sha256=cfiQe3K7h3dAHsEFqBqN8qc-nWX7AntOKgrHkWIAvWU,227
100
+ runtimepy/data/schemas/ConnectionArbiterConfig.yaml,sha256=iZI3psx0_v7TBiC_qae0mf1FNs615ZMXBTcp7SQrF-c,2203
100
101
  runtimepy/data/schemas/EnumRegistry.yaml,sha256=BfLzEEUsHj6Fg_0JyGmgbNJ7F-auuLs5tAhJjSc86uU,145
101
102
  runtimepy/data/schemas/FindFile.yaml,sha256=dSPCDQy4FQZLMgOjtbR0-o2sZECvN_tvGa3LXNd1-wc,934
102
- runtimepy/data/schemas/PeerProcessConfig.yaml,sha256=RNVGG1NV9-Goa7aLE0ah7QNwKOTEoJj6EZDXTW-TALM,190
103
+ runtimepy/data/schemas/PeerProcessConfig.yaml,sha256=SA2DnZImkM8hfxLmg6N7r4sUHEdrL0FtYaDdsl1cIAQ,209
103
104
  runtimepy/data/schemas/RuntimeEnum.yaml,sha256=P3SqlxJ7d_tbZlHZBZy2cCcnOASP8APwYKzAwITd7Kg,518
104
105
  runtimepy/data/schemas/ServerConnectionConfig.yaml,sha256=IqQUQB09xte4zGmM6Qi0-LTm5McDkoi2tehYspoNUXM,100
105
106
  runtimepy/data/schemas/StructConfig.yaml,sha256=KXcHMYsM5JgsIlHhfXW8tLx5GuekS6m5jsgPCm-_tZ4,175
106
107
  runtimepy/data/schemas/TaskConfig.yaml,sha256=upEfToPllsqEcDoJf9D3rbvXk08nc0slmlUSMyCOYcQ,247
108
+ runtimepy/data/schemas/TwoTupleArray.yaml,sha256=HmZhgszwzSjj6j3GVAn6QNjPFaHGut9SZfskGWNoeXI,112
107
109
  runtimepy/data/schemas/channel_controls.yaml,sha256=pctqV_fVjRpiKdaPqET7yyoGVYQuUtOYc1FLQUdfVJc,546
110
+ runtimepy/data/schemas/has_buttons.yaml,sha256=DQt4wQKQewIOq4du-KDUfd-Tr6YrE3x7OBF9aj6rPYg,106
108
111
  runtimepy/data/schemas/has_config.yaml,sha256=SMuJ9tNWw06tsgDasz4myO9SSmMZGep9xBfJ89GJnqA,80
109
112
  runtimepy/data/schemas/has_factory.yaml,sha256=Rsqrxv3HEBUGxPcW6CN7QczA96iBSgtm0gQNsu2P8LM,106
110
113
  runtimepy/data/schemas/has_markdown.yaml,sha256=LbnEQRYdk5KR_GgORmIOiKjRg-HF8wjhA6Dm6pSm66I,45
111
114
  runtimepy/data/schemas/has_name.yaml,sha256=I5YkXxQRYz-1-49CLyr_NA2zVqyRVQCAsb-icTaxF4s,59
112
115
  runtimepy/data/schemas/has_request_flag.yaml,sha256=S8ly8g-FkrCVNEizbXPqicg_hpGvtH7WRsHZAA4uhjc,66
116
+ runtimepy/data/schemas/has_views.yaml,sha256=X-V30he_Y_l_T8teMCROp4byaJNK9I3tihE5YhP8Le8,82
113
117
  runtimepy/data/static/css/bootstrap-icons.min.css,sha256=YWcTOU0uDzjc0ziOtRfrECKA-JAeU4SYUgIHmPwkqSk,87029
114
118
  runtimepy/data/static/css/bootstrap.min.css,sha256=x7AwauKmt5DrGTlkPvW6439HiL-5Pjd1doeExMdCx3M,231973
115
119
  runtimepy/data/static/css/fonts/bootstrap-icons.woff,sha256=9VUTt7WRy4SjuH_w406iTUgx1v7cIuVLkRymS1tUShU,180288
@@ -167,17 +171,17 @@ runtimepy/net/ssl.py,sha256=dj9uECPKDT5k-5vlR5I3Z7Go3WWZhbaJ9nb0rC3kJvg,854
167
171
  runtimepy/net/util.py,sha256=XTQQ-Ql_ImhVd1_O8nSeDX9MY8xJwRBggvliSLCrsc8,5913
168
172
  runtimepy/net/apps/__init__.py,sha256=vjo7e19QXtJwe6V6B-QGvYiJveYobnYIfpkKZrnS17w,710
169
173
  runtimepy/net/arbiter/__init__.py,sha256=ptKF995rYKvkm4Mya92vA5QEDqcFq5NRD0IYGqZ6_do,740
170
- runtimepy/net/arbiter/base.py,sha256=IBM6m7BxPLHmHqndU2tbEbgo3VxevWnqBdGfJQlMg4Q,15195
174
+ runtimepy/net/arbiter/base.py,sha256=TDWIphAiBR_b7MuieVZM2_PgV-EEFmmmVq-Yjobm13w,15865
171
175
  runtimepy/net/arbiter/info.py,sha256=QcBB1LQELXPs-Ud-GVq3aDBL4mpSMDcjjLtuKgojiU8,10962
172
176
  runtimepy/net/arbiter/result.py,sha256=PHZo5qj4SI08ZeWPFk_8OZ1umI6L0dp5nJpjjS8QUpM,2871
173
177
  runtimepy/net/arbiter/task.py,sha256=APcc5QioAG8uueIzxJU-vktIn8Ys3yJd_CFlRWb6Ieo,795
174
178
  runtimepy/net/arbiter/udp.py,sha256=-ecHJs-utcsiTfr5wSb73hUUuYFBeRVT_D1znYp5q6A,893
175
179
  runtimepy/net/arbiter/websocket.py,sha256=vYTDA7sXMUTTqW3i1zTWUdaxGKxfrQcQoVSYZtmCvfQ,1524
176
- runtimepy/net/arbiter/config/__init__.py,sha256=D13gU5jLLP36fkPKKpXd2VLRvvA-Rgb4SolWB5UeiKQ,8749
180
+ runtimepy/net/arbiter/config/__init__.py,sha256=Z-E7hJpwReVUannu_ksUHts1nNAuOHtQLxMdbB7VZw4,8826
177
181
  runtimepy/net/arbiter/config/codec.py,sha256=JVutIe1AFVr3NN7GS4rZayxZR05LWzQZRqzEuJpEJd8,2635
178
182
  runtimepy/net/arbiter/config/util.py,sha256=MezLSil5mEwioI5kxDOZa9y118st1_t0El8DYH6hFU8,1254
179
183
  runtimepy/net/arbiter/factory/__init__.py,sha256=jC1szUYucH4Us3jbb0mhFgnklHlorya5DYGGw2pqfe4,334
180
- runtimepy/net/arbiter/factory/connection.py,sha256=uSwnvIWGJcDd2ReK-s9lBx-NcRQ1JH_7VbwkWjE_8zA,3690
184
+ runtimepy/net/arbiter/factory/connection.py,sha256=TxcwjK-Q2TEY20eBW-R5meko3RrIPcnvbrzHZlqV9jo,3952
181
185
  runtimepy/net/arbiter/factory/task.py,sha256=YLRv55dnDBqHRJIMYgO9_uHi0AW-8hwa0n3wHAKyncE,1959
182
186
  runtimepy/net/arbiter/housekeeping/__init__.py,sha256=-6P2x_qSWp1juml0wKZGJ343c-atrbp8YkDRWn162UA,3622
183
187
  runtimepy/net/arbiter/imports/__init__.py,sha256=W7-kkM4B_zDU42Fyh0PmZL56JslS_0_qzvpzUhN6aDU,5041
@@ -216,14 +220,14 @@ runtimepy/net/server/app/tab.py,sha256=N4NYvxeo0fllb404aCHn7YKhlY6ZnG6G4HBiMS17q
216
220
  runtimepy/net/server/app/env/__init__.py,sha256=vgiPIYkitx0aqtmgG_nkOehDM7vrQJHYkhRsGCP8pU8,4877
217
221
  runtimepy/net/server/app/env/modal.py,sha256=HTipCYgQaAUtsmlBjXKfhAM5JyhLqoIIwEwsPnKhrG8,1740
218
222
  runtimepy/net/server/app/env/settings.py,sha256=m87hHAapuNq0xKzDNSNTwG8D2Cb3QFSVrziszf8iVHs,1631
219
- runtimepy/net/server/app/env/widgets.py,sha256=sDfJYBVIXZuly4Rvch597Y-LTT6t3obxc60oejqGv7g,7104
223
+ runtimepy/net/server/app/env/widgets.py,sha256=LjEEXaqkKTelQ53YDpHEojbMH9PLfVpy9okdUv4yZaw,7734
220
224
  runtimepy/net/server/app/env/tab/__init__.py,sha256=stTVKyHljLQWnnhxkWPwa7bLdZtjhiMFbiVFgbiYaFI,647
221
- runtimepy/net/server/app/env/tab/base.py,sha256=mljE6n56HUwcWO1lrjj5PR7qh5dvY8CZYMwDcrV-yVk,1199
225
+ runtimepy/net/server/app/env/tab/base.py,sha256=LxheBq4P7jsDrDp5fiXsOL0cFeZgdZVw3ijI4gbz9Wc,1486
222
226
  runtimepy/net/server/app/env/tab/controls.py,sha256=11TidvphKyvLCgdW8Sjiyd6BpX4-ybGZPEhrn-hzkuU,8263
223
- runtimepy/net/server/app/env/tab/html.py,sha256=MbEQzxEPNcMCntGGm2qWli6ND_Ina46CEVzt7HF2yBg,8210
227
+ runtimepy/net/server/app/env/tab/html.py,sha256=nf4UNYEJn69MvUUISGEN4VSncX_MTOmp1SkxGMVNAns,8508
224
228
  runtimepy/net/server/app/env/tab/message.py,sha256=-J8wBo1KH0XoBi9VcUvl9LXZCaoFAmvFyMf_YFJVF6Q,3945
225
229
  runtimepy/net/server/struct/__init__.py,sha256=Zy37r6RLFu-XFr9vsanSq80BJdS6Dxr7zmPzQbb7xdw,1799
226
- runtimepy/net/server/websocket/__init__.py,sha256=A0RTRGVsC_Ug2AyhfMdUqHjIN7O2Dor_rnP260g6YSM,5455
230
+ runtimepy/net/server/websocket/__init__.py,sha256=2S2Qt_xf0fL4mfzvgVn70mcMRNAeUAWyNIAyEv3cdTg,5463
227
231
  runtimepy/net/server/websocket/state.py,sha256=tClzw_3pOV9fzsiqnmss0FUsZZPhInL2CnDi_jyrayI,2291
228
232
  runtimepy/net/stream/__init__.py,sha256=B4NipXGwA2AQ5UJ8YVJqqOg_X_Cm7p2nfIbIXJMiGBI,2359
229
233
  runtimepy/net/stream/base.py,sha256=lL2-t-T2ldfUJHuFIf8X4_WdXlsVZOue7w_FOLHtmQY,1987
@@ -283,7 +287,7 @@ runtimepy/registry/name.py,sha256=TkNm7kff_Id6p_b9jr8YTi1UN1xCNzwk38IEomE7o5Q,19
283
287
  runtimepy/sample/__init__.py,sha256=N7P6hnCLF9NwnkZA1h3LzS2C334SAO48Fu8adHxWGLU,56
284
288
  runtimepy/sample/peer.py,sha256=uNlIPAv1-wjGUWdR0AM3L9NUMjnIMJBqQ9ZsHdTLx3Q,1688
285
289
  runtimepy/sample/program.py,sha256=AFqinsnsjzatgJXePAO4yXYerxmzN7ajtDmJPwWigYc,2428
286
- runtimepy/struct/__init__.py,sha256=Q7t1qKpuYGjtRGXdC_x8IwJyyCQ1Xy2wYriud89XJnk,2229
290
+ runtimepy/struct/__init__.py,sha256=Rs3-aU3ffSNH6bVvFQWMHlSlhTJuBvYs7z_E-6GH0DA,2423
287
291
  runtimepy/subprocess/__init__.py,sha256=VAiFrYFCU5ETDVoWLOVlrrSsx2d1_atYXUfXYc_OQ9g,4059
288
292
  runtimepy/subprocess/interface.py,sha256=4nGVUfDThaZrfqgn2nZHMt1hfUwHezSvFjykBXGT36g,8041
289
293
  runtimepy/subprocess/peer.py,sha256=oYw9a0yKAPR18Z6Qt24wYWrcX6EizeQE64htL11WVjM,7593
@@ -294,7 +298,7 @@ runtimepy/task/asynchronous.py,sha256=w4oEUCyj-X-zDVFmlsAtRL1gv66ahQ78QKE0GmLGT1
294
298
  runtimepy/task/sample.py,sha256=_nbLj5Julwa1NJC_-WQsotI0890G-TlOWftVHEKiclY,2735
295
299
  runtimepy/task/basic/__init__.py,sha256=NryfG-JmSyqYh-TNG4NLO6-9RJFVq5vn1Z-oV3qTVvg,243
296
300
  runtimepy/task/basic/manager.py,sha256=4dd2k-azYfyKS6jiCK9MCL-yHomuq32maicpjZvrOjA,2357
297
- runtimepy/task/basic/periodic.py,sha256=81ACFNzttJh2PQryHUA8La9MJvDUOSTxfiXkqtaOK4M,7595
301
+ runtimepy/task/basic/periodic.py,sha256=F4xNR5L1QeE5n2q1uF0D5YZa3Rkc4R7IjEMHIYdQwO4,7794
298
302
  runtimepy/task/trig/__init__.py,sha256=GuGNb9eLuFEnl3nAI7Mi_eS5mqJ4MOhEy2Wfv48RFic,785
299
303
  runtimepy/telemetry/__init__.py,sha256=G_JLZsp0EZMGaxSQ12fYgbG2kN4QkvVG4lExV_TzEhM,268
300
304
  runtimepy/telemetry/sample.py,sha256=1weoGSH-yGg-fOaLysZAsO3dSTM6yAXX3XdulAiqi3s,3216
@@ -305,10 +309,11 @@ runtimepy/tui/mock.py,sha256=Vn0XK2OQ_ukSSHeS8xkzFfJPsmAplRiu7vIwb2SnUHI,1874
305
309
  runtimepy/tui/task.py,sha256=nUZo9fuOC-k1Wpqdzkv9v1tQirCI28fZVgcC13Ijvus,1093
306
310
  runtimepy/tui/channels/__init__.py,sha256=evDaiIn-YS9uGhdo8ZGtP9VK1ek6sr_P1nJ9JuSET0o,4536
307
311
  runtimepy/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
312
+ runtimepy/ui/button.py,sha256=8MXnhqmVvANwbbTA8gu3b8N4IIvRYicnwxFQD6SZy3U,2045
308
313
  runtimepy/ui/controls.py,sha256=L55Af-4vGq6ZHewdoA7C_mAYq35WXl8NzOdcsmQIo7M,1868
309
- runtimepy-5.15.3.dist-info/licenses/LICENSE,sha256=yKBRwbO-cOPBrlpsZmJkkSa33DfY31aE8t7lZ0DwlUo,1071
310
- runtimepy-5.15.3.dist-info/METADATA,sha256=r94cxl_YOL9LR18H30DZwOmNbIQ3S-EcTzvbBhCssRo,9268
311
- runtimepy-5.15.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
312
- runtimepy-5.15.3.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
313
- runtimepy-5.15.3.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
314
- runtimepy-5.15.3.dist-info/RECORD,,
314
+ runtimepy-5.15.5.dist-info/licenses/LICENSE,sha256=yKBRwbO-cOPBrlpsZmJkkSa33DfY31aE8t7lZ0DwlUo,1071
315
+ runtimepy-5.15.5.dist-info/METADATA,sha256=zAY7eYIZrG229MNSE6k3wZUyz7nVDQ78DJPS4nlPFGA,9268
316
+ runtimepy-5.15.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
317
+ runtimepy-5.15.5.dist-info/entry_points.txt,sha256=-btVBkYv7ybcopqZ_pRky-bEzu3vhbaG3W3Z7ERBiFE,51
318
+ runtimepy-5.15.5.dist-info/top_level.txt,sha256=0jPmh6yqHyyJJDwEID-LpQly-9kQ3WRMjH7Lix8peLg,10
319
+ runtimepy-5.15.5.dist-info/RECORD,,