refract-io-lib 0.1.0__tar.gz

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.
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.3
2
+ Name: refract-io-lib
3
+ Version: 0.1.0
4
+ Summary: Refract I/O library
5
+ Author: Richard Chen
6
+ Author-email: Richard Chen <richardchen.93@gmail.com>
7
+ Requires-Dist: grpcio>=1.60.0
8
+ Requires-Dist: protobuf>=4.25.0
9
+ Requires-Dist: zeroconf>=0.131.0
10
+ Requires-Python: >=3.10
11
+ Project-URL: Homepage, https://refractvisualizer.ca/
@@ -0,0 +1,74 @@
1
+ [project]
2
+ name = "refract-io-lib"
3
+ version = "0.1.0"
4
+ description = "Refract I/O library"
5
+ authors = [
6
+ { name = "Richard Chen", email = "richardchen.93@gmail.com" }
7
+ ]
8
+ requires-python = ">=3.10"
9
+ dependencies = [
10
+ "grpcio>=1.60.0",
11
+ "protobuf>=4.25.0",
12
+ "zeroconf>=0.131.0",
13
+ ]
14
+
15
+ [project.urls]
16
+ Homepage = "https://refractvisualizer.ca/"
17
+
18
+ [dependency-groups]
19
+ dev = [
20
+ "black>=25.1.0",
21
+ "grpc-stubs>=1.53.0",
22
+ "grpcio-tools>=1.60.0",
23
+ "isort>=6.0.0",
24
+ "mypy>=1.15.0",
25
+ "pre-commit>=4.0.0",
26
+ "ruff>=0.11.0",
27
+ "types-protobuf>=4.25.0",
28
+ ]
29
+
30
+ [build-system]
31
+ requires = ["uv_build>=0.9.21,<0.10.0"]
32
+ build-backend = "uv_build"
33
+
34
+ [tool.uv.build-backend]
35
+ module-name = "refract_io"
36
+
37
+ [tool.black]
38
+ line-length = 88
39
+ target-version = ["py310"]
40
+ extend-exclude = "src/refract_io/_proto"
41
+
42
+ [tool.isort]
43
+ profile = "black"
44
+ line_length = 88
45
+ src_paths = ["src"]
46
+ extend_skip_glob = ["src/refract_io/_proto/*"]
47
+
48
+ [tool.ruff]
49
+ line-length = 88
50
+ target-version = "py310"
51
+ src = ["src"]
52
+ extend-exclude = ["src/refract_io/_proto"]
53
+
54
+ [tool.ruff.lint]
55
+ # isort runs in pre-commit; ruff lint omits "I" to avoid conflicting import rules.
56
+ select = ["E", "W", "F", "B", "UP", "C4"]
57
+
58
+ [tool.mypy]
59
+ python_version = "3.10"
60
+ strict = true
61
+ warn_return_any = true
62
+ warn_unused_configs = true
63
+ mypy_path = "src"
64
+ explicit_package_bases = true
65
+ packages = ["refract_io"]
66
+
67
+ [[tool.mypy.overrides]]
68
+ module = "refract_io._proto.*"
69
+ ignore_errors = true
70
+ follow_imports = "skip"
71
+
72
+ [[tool.mypy.overrides]]
73
+ module = "zeroconf"
74
+ ignore_missing_imports = true
@@ -0,0 +1,30 @@
1
+ from refract_io._client import RefractStream
2
+ from refract_io._discovery import discover_refract
3
+ from refract_io._types import ValueType
4
+
5
+ float32 = ValueType.FLOAT32
6
+ float64 = ValueType.FLOAT64
7
+ int8 = ValueType.INT8
8
+ int16 = ValueType.INT16
9
+ int32 = ValueType.INT32
10
+ int64 = ValueType.INT64
11
+ uint8 = ValueType.UINT8
12
+ uint16 = ValueType.UINT16
13
+ uint32 = ValueType.UINT32
14
+ uint64 = ValueType.UINT64
15
+
16
+ __all__ = [
17
+ "RefractStream",
18
+ "ValueType",
19
+ "discover_refract",
20
+ "float32",
21
+ "float64",
22
+ "int8",
23
+ "int16",
24
+ "int32",
25
+ "int64",
26
+ "uint8",
27
+ "uint16",
28
+ "uint32",
29
+ "uint64",
30
+ ]
@@ -0,0 +1,269 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import queue
5
+ import struct
6
+ import threading
7
+ from collections.abc import Generator, Sequence
8
+ from types import TracebackType
9
+ from typing import Any
10
+
11
+ import grpc
12
+
13
+ from refract_io._discovery import discover_refract
14
+ from refract_io._proto import kvstream_pb2 as _pb2
15
+ from refract_io._proto import kvstream_pb2_grpc as _pb2_grpc
16
+ from refract_io._types import DTYPE_INFO, ValueType
17
+
18
+ # Re-bind as Any so mypy doesn't complain about dynamic proto attributes
19
+ kvstream_pb2: Any = _pb2
20
+ kvstream_pb2_grpc: Any = _pb2_grpc
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ _DEFAULT_HOST = "localhost"
25
+ _DEFAULT_PORT = 50051
26
+
27
+ # Sentinel used to signal the background thread to stop.
28
+ _STOP = object()
29
+
30
+
31
+ class RefractStream:
32
+ """Stream tabular data over gRPC to the Refract app.
33
+
34
+ ``host`` and ``port`` are optional. When both are ``None`` the client
35
+ attempts Bonjour autodiscovery and falls back to ``localhost:50051``.
36
+ ``port`` accepts either an ``int`` or a numeric ``str``.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ host: str | None = None,
42
+ port: int | str | None = None,
43
+ ) -> None:
44
+ resolved_host, resolved_port = self._resolve_address(host, port)
45
+ self._host: str = resolved_host
46
+ self._port: int = resolved_port
47
+
48
+ self._channel: grpc.Channel | None = None
49
+ self._stub: kvstream_pb2_grpc.KVStreamStub | None = None
50
+ self._queue: queue.Queue[object] = queue.Queue()
51
+ self._thread: threading.Thread | None = None
52
+ self._running: bool = False
53
+
54
+ self._lock = threading.Lock()
55
+ # table_id -> list of (struct_fmt, byte_size) per column
56
+ self._tables: dict[int, list[tuple[str, int]]] = {}
57
+ self._pending_registrations: list[kvstream_pb2.StreamMessage] = []
58
+
59
+ # ------------------------------------------------------------------
60
+ # Address resolution
61
+ # ------------------------------------------------------------------
62
+
63
+ @staticmethod
64
+ def _resolve_address(
65
+ host: str | None,
66
+ port: int | str | None,
67
+ ) -> tuple[str, int]:
68
+ if host is not None or port is not None:
69
+ return (
70
+ host if host is not None else _DEFAULT_HOST,
71
+ int(port) if port is not None else _DEFAULT_PORT,
72
+ )
73
+ # Autodiscover
74
+ result = discover_refract()
75
+ if result is not None:
76
+ logger.info("Autodiscovered Refract at %s:%d", *result)
77
+ return result
78
+ logger.info(
79
+ "No Refract service found via Bonjour, " "falling back to %s:%d",
80
+ _DEFAULT_HOST,
81
+ _DEFAULT_PORT,
82
+ )
83
+ return (_DEFAULT_HOST, _DEFAULT_PORT)
84
+
85
+ # ------------------------------------------------------------------
86
+ # Table registration
87
+ # ------------------------------------------------------------------
88
+
89
+ def create_table(
90
+ self,
91
+ id: int,
92
+ name: str,
93
+ columns: dict[str, ValueType],
94
+ ) -> None:
95
+ """Register a table schema.
96
+
97
+ ``columns`` maps column names to :class:`ValueType` constants
98
+ (e.g. ``{'timestamp': refract_io.float64, ...}``).
99
+ """
100
+ col_defs: list[kvstream_pb2.ColumnDef] = []
101
+ fmt_parts: list[tuple[str, int]] = []
102
+ for col_name, vtype in columns.items():
103
+ vtype = ValueType(vtype) # validate
104
+ fmt, size = DTYPE_INFO[vtype]
105
+ col_defs.append(
106
+ kvstream_pb2.ColumnDef(name=col_name, value_type=int(vtype))
107
+ )
108
+ fmt_parts.append((fmt, size))
109
+
110
+ with self._lock:
111
+ self._tables[id] = fmt_parts
112
+
113
+ msg = kvstream_pb2.StreamMessage(
114
+ register_table=kvstream_pb2.RegisterTable(
115
+ table_id=id,
116
+ name=name,
117
+ columns=col_defs,
118
+ )
119
+ )
120
+ if self._running:
121
+ self._queue.put(msg)
122
+ else:
123
+ self._pending_registrations.append(msg)
124
+
125
+ # ------------------------------------------------------------------
126
+ # Sending rows
127
+ # ------------------------------------------------------------------
128
+
129
+ def send_row(self, table_id: int, values: Sequence[int | float]) -> None:
130
+ """Send a single row for *table_id*.
131
+
132
+ Values are packed in registration order using the column dtypes.
133
+ Auto-starts the connection if not already running.
134
+ """
135
+ self._ensure_started()
136
+
137
+ with self._lock:
138
+ fmt_parts = self._tables.get(table_id)
139
+ if fmt_parts is None:
140
+ raise ValueError(f"Table {table_id} not registered")
141
+ if len(values) != len(fmt_parts):
142
+ raise ValueError(f"Expected {len(fmt_parts)} values, got {len(values)}")
143
+
144
+ packed = b"".join(
145
+ struct.pack(fmt, val)
146
+ for val, (fmt, _) in zip(values, fmt_parts, strict=True)
147
+ )
148
+ msg = kvstream_pb2.StreamMessage(
149
+ table_row=kvstream_pb2.TableRow(
150
+ table_id=table_id,
151
+ values=packed,
152
+ )
153
+ )
154
+ self._queue.put(msg)
155
+
156
+ def send_rows(
157
+ self,
158
+ table_id: int,
159
+ rows: Sequence[Sequence[int | float]],
160
+ ) -> None:
161
+ """Send multiple rows for *table_id* in one call."""
162
+ self._ensure_started()
163
+
164
+ with self._lock:
165
+ fmt_parts = self._tables.get(table_id)
166
+ if fmt_parts is None:
167
+ raise ValueError(f"Table {table_id} not registered")
168
+
169
+ expected = len(fmt_parts)
170
+ for row in rows:
171
+ if len(row) != expected:
172
+ raise ValueError(f"Expected {expected} values per row, got {len(row)}")
173
+ packed = b"".join(
174
+ struct.pack(fmt, val)
175
+ for val, (fmt, _) in zip(row, fmt_parts, strict=True)
176
+ )
177
+ msg = kvstream_pb2.StreamMessage(
178
+ table_row=kvstream_pb2.TableRow(
179
+ table_id=table_id,
180
+ values=packed,
181
+ )
182
+ )
183
+ self._queue.put(msg)
184
+
185
+ # ------------------------------------------------------------------
186
+ # Lifecycle
187
+ # ------------------------------------------------------------------
188
+
189
+ def start(self) -> None:
190
+ """Open the gRPC channel and start the background streaming thread."""
191
+ if self._running:
192
+ return
193
+ self._running = True
194
+ self._channel = grpc.insecure_channel(f"{self._host}:{self._port}")
195
+ self._stub = kvstream_pb2_grpc.KVStreamStub(self._channel)
196
+ self._thread = threading.Thread(
197
+ target=self._stream_loop, daemon=True, name="refract-stream"
198
+ )
199
+ self._thread.start()
200
+
201
+ def close(self) -> None:
202
+ """Stop the background thread and close the gRPC channel."""
203
+ if not self._running:
204
+ return
205
+ self._running = False
206
+ self._queue.put(_STOP)
207
+ if self._thread is not None:
208
+ self._thread.join(timeout=5)
209
+ self._thread = None
210
+ if self._channel is not None:
211
+ self._channel.close()
212
+ self._channel = None
213
+
214
+ stop = close
215
+
216
+ def _ensure_started(self) -> None:
217
+ if not self._running:
218
+ self.start()
219
+
220
+ # ------------------------------------------------------------------
221
+ # Context manager
222
+ # ------------------------------------------------------------------
223
+
224
+ def __enter__(self) -> RefractStream:
225
+ self.start()
226
+ return self
227
+
228
+ def __exit__(
229
+ self,
230
+ exc_type: type[BaseException] | None,
231
+ exc_val: BaseException | None,
232
+ exc_tb: TracebackType | None,
233
+ ) -> None:
234
+ self.close()
235
+
236
+ # ------------------------------------------------------------------
237
+ # Background streaming
238
+ # ------------------------------------------------------------------
239
+
240
+ def _message_generator(
241
+ self,
242
+ ) -> Generator[kvstream_pb2.StreamMessage, None, None]:
243
+ # Send pending registrations first
244
+ yield from self._pending_registrations
245
+ self._pending_registrations.clear()
246
+
247
+ # Stream from queue
248
+ while self._running:
249
+ try:
250
+ item = self._queue.get(timeout=0.1)
251
+ if item is _STOP:
252
+ break
253
+ assert isinstance(item, kvstream_pb2.StreamMessage) # noqa: S101
254
+ yield item
255
+ except queue.Empty:
256
+ continue
257
+
258
+ def _stream_loop(self) -> None:
259
+ try:
260
+ assert self._stub is not None # noqa: S101
261
+ response = self._stub.Stream(self._message_generator())
262
+ if not response.ok:
263
+ logger.error("Server error: %s", response.error)
264
+ except grpc.RpcError as exc:
265
+ if self._running:
266
+ logger.error("gRPC error: %s", exc)
267
+ except Exception:
268
+ if self._running:
269
+ logger.exception("Unexpected error in stream loop")
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import socket
5
+ import threading
6
+
7
+ from zeroconf import ServiceBrowser, ServiceStateChange, Zeroconf
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ SERVICE_TYPE = "_refract-stream._tcp.local."
12
+
13
+
14
+ def discover_refract(timeout: float = 3.0) -> tuple[str, int] | None:
15
+ """Browse for a Refract Stream service advertised via Bonjour.
16
+
17
+ Returns ``(host, port)`` of the first instance found, or ``None``
18
+ if no service is discovered within *timeout* seconds.
19
+ """
20
+ result: tuple[str, int] | None = None
21
+ found = threading.Event()
22
+
23
+ def on_state_change(
24
+ zc: Zeroconf,
25
+ service_type: str,
26
+ name: str,
27
+ state_change: ServiceStateChange,
28
+ ) -> None:
29
+ nonlocal result
30
+ if state_change is not ServiceStateChange.Added:
31
+ return
32
+ info = zc.get_service_info(service_type, name)
33
+ if info is None:
34
+ return
35
+ addresses = info.parsed_addresses()
36
+ if not addresses or info.port is None:
37
+ return
38
+ port = info.port
39
+ # Prefer non-link-local IPv4 addresses
40
+ host = addresses[0]
41
+ for addr in addresses:
42
+ try:
43
+ packed = socket.inet_aton(addr)
44
+ if packed[0:2] != b"\xa9\xfe": # not 169.254.x.x
45
+ host = addr
46
+ break
47
+ except OSError:
48
+ continue
49
+ result = (host, port)
50
+ logger.debug("Discovered Refract at %s:%d", host, port)
51
+ found.set()
52
+
53
+ zc = Zeroconf()
54
+ try:
55
+ ServiceBrowser(zc, SERVICE_TYPE, handlers=[on_state_change])
56
+ found.wait(timeout=timeout)
57
+ finally:
58
+ zc.close()
59
+
60
+ return result
File without changes
@@ -0,0 +1,48 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: kvstream.proto
5
+ # Protobuf Python Version: 6.31.1
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 6,
15
+ 31,
16
+ 1,
17
+ '',
18
+ 'kvstream.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+
26
+
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0ekvstream.proto\x12\x10refract.kvstream\"J\n\tColumnDef\x12\x0c\n\x04name\x18\x01 \x01(\t\x12/\n\nvalue_type\x18\x02 \x01(\x0e\x32\x1b.refract.kvstream.ValueType\"]\n\rRegisterTable\x12\x10\n\x08table_id\x18\x01 \x01(\r\x12\x0c\n\x04name\x18\x02 \x01(\t\x12,\n\x07\x63olumns\x18\x03 \x03(\x0b\x32\x1b.refract.kvstream.ColumnDef\",\n\x08TableRow\x12\x10\n\x08table_id\x18\x01 \x01(\r\x12\x0e\n\x06values\x18\x02 \x01(\x0c\"\x86\x01\n\rStreamMessage\x12\x39\n\x0eregister_table\x18\x01 \x01(\x0b\x32\x1f.refract.kvstream.RegisterTableH\x00\x12/\n\ttable_row\x18\x02 \x01(\x0b\x32\x1a.refract.kvstream.TableRowH\x00\x42\t\n\x07payload\"+\n\x0eStreamResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t*\x7f\n\tValueType\x12\x0b\n\x07\x46LOAT32\x10\x00\x12\x0b\n\x07\x46LOAT64\x10\x01\x12\x08\n\x04INT8\x10\x02\x12\t\n\x05INT16\x10\x03\x12\t\n\x05INT32\x10\x04\x12\t\n\x05INT64\x10\x05\x12\t\n\x05UINT8\x10\x06\x12\n\n\x06UINT16\x10\x07\x12\n\n\x06UINT32\x10\x08\x12\n\n\x06UINT64\x10\t2Y\n\x08KVStream\x12M\n\x06Stream\x12\x1f.refract.kvstream.StreamMessage\x1a .refract.kvstream.StreamResponse(\x01\x62\x06proto3')
28
+
29
+ _globals = globals()
30
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
31
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'kvstream_pb2', _globals)
32
+ if not _descriptor._USE_C_DESCRIPTORS:
33
+ DESCRIPTOR._loaded_options = None
34
+ _globals['_VALUETYPE']._serialized_start=435
35
+ _globals['_VALUETYPE']._serialized_end=562
36
+ _globals['_COLUMNDEF']._serialized_start=36
37
+ _globals['_COLUMNDEF']._serialized_end=110
38
+ _globals['_REGISTERTABLE']._serialized_start=112
39
+ _globals['_REGISTERTABLE']._serialized_end=205
40
+ _globals['_TABLEROW']._serialized_start=207
41
+ _globals['_TABLEROW']._serialized_end=251
42
+ _globals['_STREAMMESSAGE']._serialized_start=254
43
+ _globals['_STREAMMESSAGE']._serialized_end=388
44
+ _globals['_STREAMRESPONSE']._serialized_start=390
45
+ _globals['_STREAMRESPONSE']._serialized_end=433
46
+ _globals['_KVSTREAM']._serialized_start=564
47
+ _globals['_KVSTREAM']._serialized_end=653
48
+ # @@protoc_insertion_point(module_scope)
@@ -0,0 +1,97 @@
1
+ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2
+ """Client and server classes corresponding to protobuf-defined services."""
3
+ import grpc
4
+ import warnings
5
+
6
+ from . import kvstream_pb2 as kvstream__pb2
7
+
8
+ GRPC_GENERATED_VERSION = '1.80.0'
9
+ GRPC_VERSION = grpc.__version__
10
+ _version_not_supported = False
11
+
12
+ try:
13
+ from grpc._utilities import first_version_is_lower
14
+ _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
15
+ except ImportError:
16
+ _version_not_supported = True
17
+
18
+ if _version_not_supported:
19
+ raise RuntimeError(
20
+ f'The grpc package installed is at version {GRPC_VERSION},'
21
+ + ' but the generated code in kvstream_pb2_grpc.py depends on'
22
+ + f' grpcio>={GRPC_GENERATED_VERSION}.'
23
+ + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
24
+ + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
25
+ )
26
+
27
+
28
+ class KVStreamStub(object):
29
+ """Missing associated documentation comment in .proto file."""
30
+
31
+ def __init__(self, channel):
32
+ """Constructor.
33
+
34
+ Args:
35
+ channel: A grpc.Channel.
36
+ """
37
+ self.Stream = channel.stream_unary(
38
+ '/refract.kvstream.KVStream/Stream',
39
+ request_serializer=kvstream__pb2.StreamMessage.SerializeToString,
40
+ response_deserializer=kvstream__pb2.StreamResponse.FromString,
41
+ _registered_method=True)
42
+
43
+
44
+ class KVStreamServicer(object):
45
+ """Missing associated documentation comment in .proto file."""
46
+
47
+ def Stream(self, request_iterator, context):
48
+ """Missing associated documentation comment in .proto file."""
49
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
50
+ context.set_details('Method not implemented!')
51
+ raise NotImplementedError('Method not implemented!')
52
+
53
+
54
+ def add_KVStreamServicer_to_server(servicer, server):
55
+ rpc_method_handlers = {
56
+ 'Stream': grpc.stream_unary_rpc_method_handler(
57
+ servicer.Stream,
58
+ request_deserializer=kvstream__pb2.StreamMessage.FromString,
59
+ response_serializer=kvstream__pb2.StreamResponse.SerializeToString,
60
+ ),
61
+ }
62
+ generic_handler = grpc.method_handlers_generic_handler(
63
+ 'refract.kvstream.KVStream', rpc_method_handlers)
64
+ server.add_generic_rpc_handlers((generic_handler,))
65
+ server.add_registered_method_handlers('refract.kvstream.KVStream', rpc_method_handlers)
66
+
67
+
68
+ # This class is part of an EXPERIMENTAL API.
69
+ class KVStream(object):
70
+ """Missing associated documentation comment in .proto file."""
71
+
72
+ @staticmethod
73
+ def Stream(request_iterator,
74
+ target,
75
+ options=(),
76
+ channel_credentials=None,
77
+ call_credentials=None,
78
+ insecure=False,
79
+ compression=None,
80
+ wait_for_ready=None,
81
+ timeout=None,
82
+ metadata=None):
83
+ return grpc.experimental.stream_unary(
84
+ request_iterator,
85
+ target,
86
+ '/refract.kvstream.KVStream/Stream',
87
+ kvstream__pb2.StreamMessage.SerializeToString,
88
+ kvstream__pb2.StreamResponse.FromString,
89
+ options,
90
+ channel_credentials,
91
+ insecure,
92
+ call_credentials,
93
+ compression,
94
+ wait_for_ready,
95
+ timeout,
96
+ metadata,
97
+ _registered_method=True)
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import IntEnum
4
+
5
+
6
+ class ValueType(IntEnum):
7
+ """Column data types matching the kvstream protobuf ValueType enum."""
8
+
9
+ FLOAT32 = 0
10
+ FLOAT64 = 1
11
+ INT8 = 2
12
+ INT16 = 3
13
+ INT32 = 4
14
+ INT64 = 5
15
+ UINT8 = 6
16
+ UINT16 = 7
17
+ UINT32 = 8
18
+ UINT64 = 9
19
+
20
+
21
+ # Internal mapping: ValueType -> (struct format, byte size)
22
+ DTYPE_INFO: dict[ValueType, tuple[str, int]] = {
23
+ ValueType.FLOAT32: ("<f", 4),
24
+ ValueType.FLOAT64: ("<d", 8),
25
+ ValueType.INT8: ("<b", 1),
26
+ ValueType.INT16: ("<h", 2),
27
+ ValueType.INT32: ("<i", 4),
28
+ ValueType.INT64: ("<q", 8),
29
+ ValueType.UINT8: ("<B", 1),
30
+ ValueType.UINT16: ("<H", 2),
31
+ ValueType.UINT32: ("<I", 4),
32
+ ValueType.UINT64: ("<Q", 8),
33
+ }
File without changes