runtimepy 5.14.2__py3-none-any.whl → 5.15.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.
Files changed (96) hide show
  1. runtimepy/__init__.py +2 -2
  2. runtimepy/channel/__init__.py +1 -4
  3. runtimepy/channel/environment/__init__.py +93 -2
  4. runtimepy/channel/environment/create.py +16 -1
  5. runtimepy/channel/environment/sample.py +10 -2
  6. runtimepy/channel/registry.py +2 -3
  7. runtimepy/codec/protocol/base.py +34 -14
  8. runtimepy/codec/protocol/json.py +5 -3
  9. runtimepy/codec/system/__init__.py +6 -2
  10. runtimepy/control/source.py +1 -1
  11. runtimepy/data/404.md +16 -0
  12. runtimepy/data/base.yaml +3 -0
  13. runtimepy/data/css/bootstrap_extra.css +59 -44
  14. runtimepy/data/css/main.css +23 -4
  15. runtimepy/data/dummy_load.yaml +2 -2
  16. runtimepy/data/factories.yaml +1 -0
  17. runtimepy/data/js/classes/App.js +54 -2
  18. runtimepy/data/js/classes/ChannelTable.js +6 -8
  19. runtimepy/data/js/classes/Plot.js +9 -4
  20. runtimepy/data/js/classes/TabFilter.js +47 -9
  21. runtimepy/data/js/classes/TabInterface.js +106 -11
  22. runtimepy/data/js/classes/WindowHashManager.js +30 -15
  23. runtimepy/data/js/init.js +18 -1
  24. runtimepy/data/js/markdown_page.js +10 -0
  25. runtimepy/data/schemas/BitFields.yaml +9 -0
  26. runtimepy/data/schemas/RuntimeEnum.yaml +6 -0
  27. runtimepy/data/schemas/StructConfig.yaml +9 -1
  28. runtimepy/data/static/css/bootstrap-icons.min.css +4 -3
  29. runtimepy/data/static/css/bootstrap.min.css +3 -4
  30. runtimepy/data/static/css/fonts/bootstrap-icons.woff +0 -0
  31. runtimepy/data/static/css/fonts/bootstrap-icons.woff2 +0 -0
  32. runtimepy/data/static/js/bootstrap.bundle.min.js +5 -4
  33. runtimepy/data/static/js/webglplot.umd.min.js +2 -1
  34. runtimepy/data/static/svg/outline-dark.svg +22 -0
  35. runtimepy/data/static/svg/outline-light.svg +22 -0
  36. runtimepy/enum/__init__.py +13 -1
  37. runtimepy/enum/registry.py +13 -1
  38. runtimepy/message/__init__.py +3 -3
  39. runtimepy/mixins/logging.py +6 -1
  40. runtimepy/net/__init__.py +0 -2
  41. runtimepy/net/arbiter/info.py +36 -4
  42. runtimepy/net/arbiter/struct/__init__.py +3 -2
  43. runtimepy/net/connection.py +4 -5
  44. runtimepy/net/html/__init__.py +29 -11
  45. runtimepy/net/html/bootstrap/__init__.py +2 -2
  46. runtimepy/net/html/bootstrap/elements.py +44 -24
  47. runtimepy/net/html/bootstrap/tabs.py +18 -11
  48. runtimepy/net/http/__init__.py +3 -3
  49. runtimepy/net/http/request_target.py +3 -3
  50. runtimepy/net/mixin.py +4 -2
  51. runtimepy/net/server/__init__.py +16 -9
  52. runtimepy/net/server/app/__init__.py +1 -0
  53. runtimepy/net/server/app/create.py +3 -3
  54. runtimepy/net/server/app/env/__init__.py +28 -4
  55. runtimepy/net/server/app/env/settings.py +4 -7
  56. runtimepy/net/server/app/env/tab/controls.py +141 -27
  57. runtimepy/net/server/app/env/tab/html.py +68 -26
  58. runtimepy/net/server/app/env/widgets.py +115 -61
  59. runtimepy/net/server/app/landing_page.py +1 -1
  60. runtimepy/net/server/html.py +2 -2
  61. runtimepy/net/server/json.py +1 -1
  62. runtimepy/net/server/markdown.py +18 -12
  63. runtimepy/net/server/mux.py +29 -0
  64. runtimepy/net/stream/__init__.py +6 -5
  65. runtimepy/net/stream/base.py +4 -2
  66. runtimepy/net/tcp/connection.py +5 -3
  67. runtimepy/net/tcp/http/__init__.py +10 -9
  68. runtimepy/net/tcp/protocol.py +2 -2
  69. runtimepy/net/tcp/scpi/__init__.py +5 -2
  70. runtimepy/net/tcp/telnet/__init__.py +2 -1
  71. runtimepy/net/udp/connection.py +10 -6
  72. runtimepy/net/udp/protocol.py +5 -6
  73. runtimepy/net/udp/queue.py +5 -2
  74. runtimepy/net/udp/tftp/base.py +2 -1
  75. runtimepy/net/websocket/connection.py +50 -8
  76. runtimepy/primitives/array/__init__.py +7 -5
  77. runtimepy/primitives/base.py +3 -2
  78. runtimepy/primitives/field/__init__.py +35 -2
  79. runtimepy/primitives/field/fields.py +11 -2
  80. runtimepy/primitives/field/manager/base.py +19 -2
  81. runtimepy/primitives/serializable/base.py +5 -2
  82. runtimepy/primitives/serializable/fixed.py +5 -2
  83. runtimepy/primitives/serializable/prefixed.py +4 -1
  84. runtimepy/primitives/types/base.py +4 -1
  85. runtimepy/primitives/types/bounds.py +10 -4
  86. runtimepy/registry/__init__.py +20 -0
  87. runtimepy/registry/name.py +6 -0
  88. runtimepy/requirements.txt +2 -2
  89. runtimepy/ui/controls.py +20 -1
  90. {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/METADATA +6 -6
  91. {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/RECORD +95 -92
  92. {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/WHEEL +1 -1
  93. runtimepy/data/404.html +0 -7
  94. {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/entry_points.txt +0 -0
  95. {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/licenses/LICENSE +0 -0
  96. {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/top_level.txt +0 -0
@@ -14,6 +14,7 @@ from runtimepy.channel.environment.command.processor import (
14
14
  ChannelCommandProcessor,
15
15
  )
16
16
  from runtimepy.enum import RuntimeEnum
17
+ from runtimepy.net.html.bootstrap import icon_str
17
18
  from runtimepy.net.html.bootstrap.elements import (
18
19
  flex,
19
20
  input_box,
@@ -25,7 +26,9 @@ from runtimepy.net.html.bootstrap.elements import (
25
26
  def plot_checkbox(parent: Element, name: str) -> None:
26
27
  """Add a checkbox for individual channel plot status."""
27
28
 
28
- container = div(tag="td", parent=parent, class_str="text-center p-0")
29
+ container = div(tag="td", parent=parent, class_str="text-center p-0 fs-5")
30
+
31
+ title = f"Enable plotting channel '{name}'."
29
32
 
30
33
  set_tooltip(
31
34
  div(
@@ -34,10 +37,11 @@ def plot_checkbox(parent: Element, name: str) -> None:
34
37
  value="",
35
38
  id=f"plot-{name}",
36
39
  allow_no_end_tag=True,
37
- parent=container,
38
- class_str="form-check-input",
40
+ parent=div(tag="label", parent=container),
41
+ class_str="form-check-input rounded-0",
42
+ title=title,
39
43
  ),
40
- f"Enable plotting channel '{name}'.",
44
+ title,
41
45
  placement="left",
42
46
  )
43
47
 
@@ -45,7 +49,10 @@ def plot_checkbox(parent: Element, name: str) -> None:
45
49
  def select_element(**kwargs) -> Element:
46
50
  """Create a select element."""
47
51
 
48
- select = div(tag="select", class_str="form-select m-1", **kwargs)
52
+ select = div(tag="select", **kwargs)
53
+ select.add_class(
54
+ "form-select", "rounded-0", "border-top-0", "border-bottom-0"
55
+ )
49
56
  if "title" in kwargs:
50
57
  select["aria-label"] = kwargs["title"]
51
58
  return select
@@ -53,7 +60,7 @@ def select_element(**kwargs) -> Element:
53
60
 
54
61
  def enum_dropdown(
55
62
  parent: Element, name: str, enum: RuntimeEnum, current: int | bool
56
- ) -> None:
63
+ ) -> Element:
57
64
  """Implement a drop down for enumeration options."""
58
65
 
59
66
  select = select_element(
@@ -67,8 +74,10 @@ def enum_dropdown(
67
74
  if current == val:
68
75
  opt.booleans.add("selected")
69
76
 
77
+ return select
78
+
70
79
 
71
- TABLE_BUTTON_CLASSES = ("p-1", "pt-2")
80
+ TABLE_BUTTON_CLASSES = ("border-top-0", "border-bottom-0")
72
81
 
73
82
 
74
83
  def channel_table_header(
@@ -78,73 +87,45 @@ def channel_table_header(
78
87
 
79
88
  env = command.env
80
89
 
81
- # Add header.
82
- header_row = div(
83
- tag="tr", parent=parent, class_str="border-start border-end"
84
- )
85
- for heading, desc in [
86
- ("plot", "Toggle plotting for channels."),
87
- ("name", "Channel names."),
88
- ("value", "Channel values."),
89
- ("ctl", "Type-specific channel controls."),
90
- ("type", "Channel types."),
91
- ]:
92
- set_tooltip(
93
- div(
94
- tag="th",
95
- scope="col",
96
- parent=header_row,
97
- text=heading,
98
- class_str="text-secondary p-1",
99
- ),
100
- desc,
101
- placement="left",
102
- )
103
-
104
90
  # Add some controls.
105
- ctl_row = div(tag="tr", parent=parent, class_str="border-start border-end")
91
+ ctl_row = div(
92
+ tag="tr",
93
+ parent=parent,
94
+ class_str="bg-body-tertiary border-start border-end",
95
+ )
106
96
 
107
97
  # Button for clearing plotted channels.
108
98
  toggle_button(
109
99
  div(tag="th", parent=ctl_row, class_str="text-center p-0"),
110
100
  tooltip="Clear plotted channels.",
111
101
  icon="x-lg",
102
+ placement="left",
112
103
  id="clear-plotted-channels",
113
104
  title="button for clearing plotted channels",
114
- ).add_class("pb-2")
105
+ ).add_class(*TABLE_BUTTON_CLASSES)
115
106
 
116
- input_box(
117
- div(tag="th", parent=ctl_row, class_str="p-0 pb-1"),
107
+ _, label, box = input_box(
108
+ div(tag="th", parent=ctl_row, colspan="2", class_str="p-0"),
118
109
  description="Channel name filter.",
110
+ pattern=".* ! @ $",
111
+ label="filter",
119
112
  id="channel-filter",
113
+ icon="funnel",
114
+ spellcheck="false",
120
115
  )
116
+ label.add_class("border-top-0", "border-bottom-0")
117
+ box.add_class("border-top-0", "border-bottom-0")
121
118
 
122
- # Button for clearing plot points.
123
- toggle_button(
124
- div(tag="th", parent=ctl_row, class_str="p-0"),
125
- icon="trash",
126
- tooltip="Clear all plot points.",
127
- id="clear-plotted-points",
128
- title="button for clearing plot point data",
129
- ).add_class("pb-2")
130
-
131
- cell = flex(tag="th", parent=ctl_row)
132
- cell.add_class("p-0")
133
-
134
- # Button for 'reset all defaults' if this tab has more than one channel
135
- # with a default value.
136
- if env.num_defaults > 1:
137
- toggle_button(
138
- cell,
139
- id="set-defaults",
140
- icon="arrow-counterclockwise",
141
- tooltip="Reset all channels to their default values.",
142
- ).add_class(*TABLE_BUTTON_CLASSES)
119
+ cell = flex(
120
+ parent=div(tag="th", parent=ctl_row, colspan="2", class_str="p-0")
121
+ )
143
122
 
144
123
  # Add a selection menu for custom commands.
145
124
  select = select_element(
146
125
  parent=cell, id="custom-commands", title="Custom command selector."
147
126
  )
127
+ select.add_class("border-start-0")
128
+
148
129
  if command.custom_commands:
149
130
  for key in command.custom_commands:
150
131
  opt = div(tag="option", value=key, text=key, parent=select)
@@ -156,7 +137,8 @@ def channel_table_header(
156
137
  cell,
157
138
  icon="send",
158
139
  id="send-custom-commands",
159
- title="Send selected command.",
140
+ title="send selected command button",
141
+ tooltip="Send selected command (left dropdown).",
160
142
  ).add_class(*TABLE_BUTTON_CLASSES)
161
143
  else:
162
144
  div(
@@ -167,8 +149,79 @@ def channel_table_header(
167
149
  )
168
150
  select.booleans.add("disabled")
169
151
 
170
- # Empty for now.
171
- div(tag="th", parent=ctl_row, class_str="p-0")
152
+ # Button for 'reset all defaults' if this tab has more than one channel
153
+ # with a default value.
154
+ if env.num_defaults > 1:
155
+ toggle_button(
156
+ cell,
157
+ id="set-defaults",
158
+ icon="arrow-counterclockwise",
159
+ tooltip="Reset all channels to their default values.",
160
+ ).add_class(*TABLE_BUTTON_CLASSES)
161
+
162
+ toggle_button(
163
+ cell,
164
+ icon="eye-slash",
165
+ tooltip="Toggle command channels.",
166
+ id="toggle-command-channels",
167
+ title="button for toggling command-channel visibility",
168
+ icon_classes=["text-info-emphasis"],
169
+ ).add_class(*TABLE_BUTTON_CLASSES)
170
+ toggle_button(
171
+ cell,
172
+ icon="eye-slash-fill",
173
+ tooltip="Toggle regular channels.",
174
+ id="toggle-regular-channels",
175
+ title="button for toggling regular-channel visibility",
176
+ ).add_class(*TABLE_BUTTON_CLASSES)
177
+ toggle_button(
178
+ cell,
179
+ icon="trash",
180
+ tooltip="Clear all plot points.",
181
+ id="clear-plotted-points",
182
+ title="button for clearing plot point data",
183
+ ).add_class("me-auto", *TABLE_BUTTON_CLASSES)
184
+
185
+ # Add header.
186
+ header_row = div(
187
+ tag="tr",
188
+ parent=parent,
189
+ class_str="border-end text-center bg-body-tertiary",
190
+ )
191
+ icon_classes = ["text-primary-emphasis"]
192
+ for heading, desc in [
193
+ (
194
+ icon_str("activity", classes=icon_classes) + "?",
195
+ "Toggle plotting for channels.",
196
+ ),
197
+ (
198
+ icon_str("pen", classes=icon_classes) + " name",
199
+ "Channel names.",
200
+ ),
201
+ (
202
+ icon_str("database", classes=icon_classes) + " value",
203
+ "Channel values.",
204
+ ),
205
+ (
206
+ icon_str("controller", classes=icon_classes) + " controls",
207
+ "Type-specific channel controls.",
208
+ ),
209
+ (
210
+ icon_str("braces", classes=icon_classes) + " type",
211
+ "Channel types.",
212
+ ),
213
+ ]:
214
+ set_tooltip(
215
+ div(
216
+ tag="th",
217
+ scope="col",
218
+ parent=header_row,
219
+ text=heading,
220
+ class_str="text-secondary p-1 border-start text-nowrap",
221
+ ),
222
+ "(column) " + desc,
223
+ placement="left",
224
+ )
172
225
 
173
226
 
174
227
  def value_input_box(name: str, parent: Element) -> Element:
@@ -186,15 +239,16 @@ def value_input_box(name: str, parent: Element) -> Element:
186
239
  "rounded-0",
187
240
  "font-monospace",
188
241
  "form-control",
189
- "m-1",
190
242
  "p-0",
191
- "ps-1",
243
+ "ps-2",
244
+ "border-0",
245
+ "text-secondary-emphasis",
192
246
  )
193
247
  toggle_button(
194
248
  input_container,
195
249
  icon="send",
196
250
  id=name,
197
251
  title=f"Send command value for '{name}'.",
198
- ).add_class(*TABLE_BUTTON_CLASSES)
252
+ ).add_class("pt-0", "pb-0", *TABLE_BUTTON_CLASSES)
199
253
 
200
254
  return input_container
@@ -20,7 +20,7 @@ def landing_page(
20
20
  document: Html,
21
21
  request: RequestHeader,
22
22
  response: ResponseHeader,
23
- request_data: Optional[bytes],
23
+ request_data: Optional[bytearray],
24
24
  ) -> Html:
25
25
  """Create a landing page application"""
26
26
 
@@ -16,7 +16,7 @@ from runtimepy.net.http.response import ResponseHeader
16
16
  from runtimepy.net.tcp.http import HttpConnection
17
17
 
18
18
  HtmlApp = Callable[
19
- [Html, RequestHeader, ResponseHeader, Optional[bytes]], Awaitable[Html]
19
+ [Html, RequestHeader, ResponseHeader, Optional[bytearray]], Awaitable[Html]
20
20
  ]
21
21
  HtmlApps = dict[str, HtmlApp]
22
22
 
@@ -58,7 +58,7 @@ async def html_handler(
58
58
  stream: TextIO,
59
59
  request: RequestHeader,
60
60
  response: ResponseHeader,
61
- request_data: Optional[bytes],
61
+ request_data: Optional[bytearray],
62
62
  default_app: HtmlApp = None,
63
63
  **kwargs,
64
64
  ) -> bool:
@@ -87,7 +87,7 @@ def json_handler(
87
87
  stream: TextIO,
88
88
  request: RequestHeader,
89
89
  response: ResponseHeader,
90
- request_data: Optional[bytes],
90
+ request_data: Optional[bytearray],
91
91
  data: JsonObject,
92
92
  ) -> None:
93
93
  """Create an HTTP response from some JSON object data."""
@@ -12,10 +12,12 @@ from vcorelib.io.file_writer import IndentedFileWriter
12
12
  from vcorelib.paths import rel
13
13
 
14
14
  LOGO_MARKDOWN = "[![logo](/static/png/chip-circle-bootstrap/128x128.png)](/)"
15
+ DIR_FILE = "dir.html"
15
16
 
16
17
 
17
18
  def markdown_for_dir(
18
- path: Path, base: Path, extra_links: dict[str, Iterable[str]] = None
19
+ paths_bases: Iterable[tuple[Path, Path]],
20
+ extra_links: dict[str, Iterable[str]] = None,
19
21
  ) -> str:
20
22
  """Get markdown data for a directory."""
21
23
 
@@ -32,22 +34,26 @@ def markdown_for_dir(
32
34
  for app in apps:
33
35
  writer.write(f"* [{app}]({app})")
34
36
 
35
- curr_dir = rel(path, base=base)
36
- writer.write(f"## `{curr_dir}`")
37
+ writer.write("## directories")
37
38
  writer.empty()
38
39
 
39
- # Link to go up a directory.
40
- if curr_dir != Path():
41
- writer.write(f"* [..](/{curr_dir.parent})")
40
+ for path, base in paths_bases:
41
+ curr_dir = rel(path, base=base)
42
+ writer.write(f"### `{base.name}/{curr_dir}`")
43
+ writer.empty()
42
44
 
43
- for item in path.iterdir():
44
- curr = rel(item, base=base)
45
+ # Link to go up a directory.
46
+ if curr_dir != Path():
47
+ writer.write(f"* [..](/{curr_dir.parent}/{DIR_FILE})")
45
48
 
46
- name = f"`{curr.name}`"
47
- if item.is_dir():
48
- name = f"**{name}**"
49
+ for item in sorted(path.iterdir()):
50
+ curr = rel(item, base=base)
49
51
 
50
- writer.write(f"* [{name}](/{curr})")
52
+ name = f"`{curr.name}`"
53
+ if item.is_dir():
54
+ name = f"**{name}**"
55
+
56
+ writer.write(f"* [{name}](/{curr})")
51
57
 
52
58
  result: str = cast(StringIO, writer.stream).getvalue()
53
59
 
@@ -0,0 +1,29 @@
1
+ """
2
+ A module implementing an interface for serving a web application that allows
3
+ iframe-based grid multiplexing.
4
+ """
5
+
6
+ # built-in
7
+ from typing import Optional
8
+
9
+ # third-party
10
+ from svgen.element.html import Html
11
+
12
+ # internal
13
+ from runtimepy.net.http.header import RequestHeader
14
+ from runtimepy.net.http.response import ResponseHeader
15
+
16
+
17
+ async def mux_app(
18
+ document: Html,
19
+ request: RequestHeader,
20
+ response: ResponseHeader,
21
+ request_data: Optional[bytearray],
22
+ ) -> Html:
23
+ """An iframe multiplexing application."""
24
+
25
+ del request
26
+ del response
27
+ del request_data
28
+
29
+ return document
@@ -4,10 +4,11 @@ A module aggregating stream-oriented connection interfaces.
4
4
 
5
5
  # built-in
6
6
  from typing import BinaryIO as _BinaryIO
7
- from typing import Tuple
7
+
8
+ # third-party
9
+ from vcorelib.io import BinaryMessage
8
10
 
9
11
  # internal
10
- from runtimepy.net.connection import BinaryMessage
11
12
  from runtimepy.net.stream.base import PrefixedMessageConnection
12
13
  from runtimepy.net.stream.string import StringMessageConnection
13
14
  from runtimepy.net.tcp.connection import TcpConnection
@@ -34,14 +35,14 @@ class UdpPrefixedMessageConnection(PrefixedMessageConnection, UdpConnection):
34
35
  """A UDP implementation for size-prefixed messages."""
35
36
 
36
37
  async def process_datagram(
37
- self, data: bytes, addr: Tuple[str, int]
38
+ self, data: BinaryMessage, addr: tuple[str, int]
38
39
  ) -> bool:
39
40
  """Process a datagram."""
40
41
 
41
42
  return await self.process_binary(data, addr=addr)
42
43
 
43
44
  def _send_message(
44
- self, data: BinaryMessage, addr: Tuple[str, int] = None
45
+ self, data: BinaryMessage, addr: tuple[str, int] = None
45
46
  ) -> None:
46
47
  """Underlying data send."""
47
48
 
@@ -52,7 +53,7 @@ class EchoMessageConnection(PrefixedMessageConnection):
52
53
  """A connection that just echoes what it was sent."""
53
54
 
54
55
  async def process_single(
55
- self, stream: _BinaryIO, addr: Tuple[str, int] = None
56
+ self, stream: _BinaryIO, addr: tuple[str, int] = None
56
57
  ) -> bool:
57
58
  """Process a single message."""
58
59
 
@@ -6,9 +6,11 @@ A module implementing a base, stream-oriented connection interface.
6
6
  from io import BytesIO as _BytesIO
7
7
  from typing import BinaryIO as _BinaryIO
8
8
 
9
+ # third-party
10
+ from vcorelib.io import BinaryMessage
11
+
9
12
  # internal
10
13
  from runtimepy.message import MessageProcessor
11
- from runtimepy.net.connection import BinaryMessage
12
14
  from runtimepy.net.connection import Connection as _Connection
13
15
 
14
16
 
@@ -58,7 +60,7 @@ class PrefixedMessageConnection(_Connection):
58
60
  return True
59
61
 
60
62
  async def process_binary(
61
- self, data: bytes, addr: tuple[str, int] = None
63
+ self, data: BinaryMessage, addr: tuple[str, int] = None
62
64
  ) -> bool:
63
65
  """Process an incoming message."""
64
66
 
@@ -17,10 +17,12 @@ from typing import Optional as _Optional
17
17
  from typing import TypeVar as _TypeVar
18
18
  from typing import Union as _Union
19
19
 
20
+ # third-party
21
+ from vcorelib.io import BinaryMessage
22
+
20
23
  # internal
21
24
  from runtimepy.net import sockname as _sockname
22
25
  from runtimepy.net.backoff import ExponentialBackoff
23
- from runtimepy.net.connection import BinaryMessage as _BinaryMessage
24
26
  from runtimepy.net.connection import Connection as _Connection
25
27
  from runtimepy.net.connection import EchoConnection as _EchoConnection
26
28
  from runtimepy.net.connection import NullConnection as _NullConnection
@@ -91,7 +93,7 @@ class TcpConnection(_Connection, _TransportMixin):
91
93
  """Determine if this connection uses SSL."""
92
94
  return self._transport.get_extra_info("sslcontext") is not None
93
95
 
94
- async def _await_message(self) -> _Optional[_Union[_BinaryMessage, str]]:
96
+ async def _await_message(self) -> _Optional[_Union[BinaryMessage, str]]:
95
97
  """Await the next message. Return None on error or failure."""
96
98
 
97
99
  data = await self._protocol.queue.get()
@@ -103,7 +105,7 @@ class TcpConnection(_Connection, _TransportMixin):
103
105
  """Enqueue a text message to send."""
104
106
  self.send_binary(data.encode())
105
107
 
106
- def send_binary(self, data: _BinaryMessage) -> None:
108
+ def send_binary(self, data: BinaryMessage) -> None:
107
109
  """Enqueue a binary message tos end."""
108
110
  self._transport.write(data)
109
111
  self.metrics.tx.increment(len(data))
@@ -11,6 +11,7 @@ from typing import Any, Awaitable, Callable, Optional, Tuple, Union, cast
11
11
 
12
12
  # third-party
13
13
  from vcorelib import DEFAULT_ENCODING
14
+ from vcorelib.io import BinaryMessage
14
15
 
15
16
  # internal
16
17
  from runtimepy import PKG_NAME, VERSION
@@ -19,18 +20,18 @@ from runtimepy.net.http.header import RequestHeader
19
20
  from runtimepy.net.http.response import AsyncResponse, ResponseHeader
20
21
  from runtimepy.net.tcp.connection import TcpConnection as _TcpConnection
21
22
 
22
- HttpResult = Optional[bytes | AsyncResponse]
23
+ HttpResult = Optional[BinaryMessage | AsyncResponse]
23
24
 
24
25
  #
25
26
  # async def handler(
26
27
  # response: ResponseHeader,
27
28
  # request: RequestHeader,
28
- # request_data: Optional[bytes],
29
+ # request_data: Optional[bytearray],
29
30
  # ) -> HttpResult:
30
31
  # """Sample handler."""
31
32
  #
32
33
  HttpRequestHandler = Callable[
33
- [ResponseHeader, RequestHeader, Optional[bytes]],
34
+ [ResponseHeader, RequestHeader, Optional[bytearray]],
34
35
  Awaitable[HttpResult],
35
36
  ]
36
37
  HttpResponse = Tuple[ResponseHeader, HttpResult]
@@ -99,7 +100,7 @@ class HttpConnection(_TcpConnection):
99
100
  self,
100
101
  response: ResponseHeader,
101
102
  request: RequestHeader,
102
- request_data: Optional[bytes],
103
+ request_data: Optional[bytearray],
103
104
  ) -> HttpResult:
104
105
  """Sample handler."""
105
106
 
@@ -107,7 +108,7 @@ class HttpConnection(_TcpConnection):
107
108
  self,
108
109
  response: ResponseHeader,
109
110
  request: RequestHeader,
110
- request_data: Optional[bytes],
111
+ request_data: Optional[bytearray],
111
112
  ) -> HttpResult:
112
113
  """Sample handler."""
113
114
 
@@ -115,7 +116,7 @@ class HttpConnection(_TcpConnection):
115
116
  self,
116
117
  response: ResponseHeader,
117
118
  request_header: RequestHeader,
118
- request_data: Optional[bytes] = None,
119
+ request_data: Optional[bytearray] = None,
119
120
  ) -> HttpResult:
120
121
  """Process an individual request."""
121
122
 
@@ -141,7 +142,7 @@ class HttpConnection(_TcpConnection):
141
142
  return result
142
143
 
143
144
  async def request(
144
- self, request: RequestHeader, data: Optional[bytes] = None
145
+ self, request: RequestHeader, data: Optional[BinaryMessage] = None
145
146
  ) -> HttpResponse:
146
147
  """Make an HTTP request."""
147
148
 
@@ -157,7 +158,7 @@ class HttpConnection(_TcpConnection):
157
158
  return result
158
159
 
159
160
  async def request_json(
160
- self, request: RequestHeader, data: Optional[bytes] = None
161
+ self, request: RequestHeader, data: Optional[BinaryMessage] = None
161
162
  ) -> Any:
162
163
  """
163
164
  Perform a request and convert the response to a data structure by
@@ -191,7 +192,7 @@ class HttpConnection(_TcpConnection):
191
192
 
192
193
  header.log(self.logger, True)
193
194
 
194
- async def process_binary(self, data: bytes) -> bool:
195
+ async def process_binary(self, data: BinaryMessage) -> bool:
195
196
  """Process a binary frame."""
196
197
 
197
198
  for header, payload in self.processor.ingest(
@@ -9,10 +9,10 @@ from logging import getLogger as _getLogger
9
9
  from typing import Optional as _Optional
10
10
 
11
11
  # third-party
12
+ from vcorelib.io import BinaryMessage
12
13
  from vcorelib.logging import LoggerType as _LoggerType
13
14
 
14
15
  # internal
15
- from runtimepy.net.connection import BinaryMessage as _BinaryMessage
16
16
  from runtimepy.net.connection import Connection as _Connection
17
17
  from runtimepy.net.mixin import (
18
18
  BinaryMessageQueueMixin as _BinaryMessageQueueMixin,
@@ -26,7 +26,7 @@ class QueueProtocol(_BinaryMessageQueueMixin, _Protocol):
26
26
  logger: _LoggerType
27
27
  conn: _Connection
28
28
 
29
- def data_received(self, data: _BinaryMessage) -> None:
29
+ def data_received(self, data: BinaryMessage) -> None:
30
30
  """Handle incoming data."""
31
31
  self.queue.put_nowait(data)
32
32
 
@@ -5,6 +5,9 @@ A module implementing an SCPI interface.
5
5
  # built-in
6
6
  import asyncio
7
7
 
8
+ # third-party
9
+ from vcorelib.io import BinaryMessage
10
+
8
11
  # internal
9
12
  from runtimepy.net.arbiter.tcp import TcpConnectionFactory
10
13
  from runtimepy.net.tcp import TcpConnection
@@ -34,9 +37,9 @@ class ScpiConnection(TcpConnection):
34
37
 
35
38
  return True
36
39
 
37
- async def process_binary(self, data: bytes) -> bool:
40
+ async def process_binary(self, data: BinaryMessage) -> bool:
38
41
  """Process a binary frame."""
39
- return await self.process_text(data.decode())
42
+ return await self.process_text(bytes(data).decode())
40
43
 
41
44
  async def send_command(
42
45
  self,
@@ -10,6 +10,7 @@ from typing import BinaryIO as _BinaryIO
10
10
 
11
11
  # third-party
12
12
  from vcorelib import DEFAULT_ENCODING
13
+ from vcorelib.io import BinaryMessage
13
14
 
14
15
  # internal
15
16
  from runtimepy.net.tcp.connection import TcpConnection as _TcpConnection
@@ -65,7 +66,7 @@ class Telnet(_TcpConnection):
65
66
  ) -> None:
66
67
  """Process a telnet option."""
67
68
 
68
- async def process_binary(self, data: bytes) -> bool:
69
+ async def process_binary(self, data: BinaryMessage) -> bool:
69
70
  """Process a binary frame."""
70
71
 
71
72
  result = True