soia-client 1.1.5__py3-none-any.whl → 1.1.6__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.

Potentially problematic release.


This version of soia-client might be problematic. Click here for more details.

soia/_impl/arrays.py CHANGED
@@ -1,18 +1,20 @@
1
1
  from collections.abc import Callable
2
2
  from dataclasses import FrozenInstanceError
3
+ from functools import cached_property
3
4
  from typing import Generic, Optional
4
5
  from weakref import WeakValueDictionary
5
6
 
6
7
  from soia import _spec, reflection
8
+ from soia._impl.binary import decode_int64, encode_length_prefix
7
9
  from soia._impl.function_maker import Any, Expr, ExprLike, Line, make_function
8
10
  from soia._impl.keyed_items import Item, Key, KeyedItems
9
- from soia._impl.type_adapter import TypeAdapter
11
+ from soia._impl.type_adapter import T, ByteStream, TypeAdapter
10
12
 
11
13
 
12
14
  def get_array_adapter(
13
- item_adapter: TypeAdapter,
15
+ item_adapter: TypeAdapter[T],
14
16
  key_attributes: tuple[str, ...],
15
- ) -> TypeAdapter:
17
+ ) -> TypeAdapter[tuple[T, ...]]:
16
18
  if key_attributes:
17
19
  default_expr = item_adapter.default_expr()
18
20
  listuple_class = _new_keyed_items_class(key_attributes, default_expr)
@@ -24,7 +26,7 @@ def get_array_adapter(
24
26
  )
25
27
 
26
28
 
27
- class _ArrayAdapter(TypeAdapter):
29
+ class _ArrayAdapter(Generic[T], TypeAdapter[tuple[T, ...]]):
28
30
  __slots__ = (
29
31
  "item_adapter",
30
32
  "listuple_class",
@@ -32,13 +34,13 @@ class _ArrayAdapter(TypeAdapter):
32
34
  "empty_listuple",
33
35
  )
34
36
 
35
- item_adapter: TypeAdapter
37
+ item_adapter: TypeAdapter[T]
36
38
  listuple_class: type
37
39
  empty_listuple: tuple[()]
38
40
 
39
41
  def __init__(
40
42
  self,
41
- item_adapter: TypeAdapter,
43
+ item_adapter: TypeAdapter[T],
42
44
  listuple_class: type,
43
45
  key_attributes: tuple[str, ...],
44
46
  ):
@@ -87,13 +89,15 @@ class _ArrayAdapter(TypeAdapter):
87
89
  "]",
88
90
  )
89
91
 
90
- def from_json_expr(self, json_expr: ExprLike) -> Expr:
92
+ def from_json_expr(
93
+ self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
94
+ ) -> Expr:
91
95
  listuple_class_local = Expr.local("_lstpl?", self.listuple_class)
92
96
  empty_listuple_local = Expr.local("_emp?", self.empty_listuple)
93
97
  return Expr.join(
94
98
  listuple_class_local,
95
99
  "([",
96
- self.item_adapter.from_json_expr("_e"),
100
+ self.item_adapter.from_json_expr("_e", keep_unrecognized_expr),
97
101
  " for _e in ",
98
102
  json_expr,
99
103
  "] or ",
@@ -101,6 +105,53 @@ class _ArrayAdapter(TypeAdapter):
101
105
  ")",
102
106
  )
103
107
 
108
+ @cached_property
109
+ def encode_fn_impl(self) -> Callable[[tuple[T, ...], bytearray], None]:
110
+ encode_item = self.item_adapter.encode_fn()
111
+
112
+ def encode(
113
+ value: tuple[T, ...],
114
+ buffer: bytearray,
115
+ ) -> None:
116
+ if not value:
117
+ buffer.append(246)
118
+ return
119
+ length = len(value)
120
+ if length <= 3:
121
+ buffer.append(246 + length)
122
+ else:
123
+ buffer.append(250)
124
+ encode_length_prefix(length, buffer)
125
+ for i in range(length):
126
+ encode_item(value[i], buffer)
127
+
128
+ return encode
129
+
130
+ def encode_fn(self) -> Callable[[tuple[T, ...], bytearray], None]:
131
+ return self.encode_fn_impl
132
+
133
+ @cached_property
134
+ def decode_fn_impl(self) -> Callable[[ByteStream], tuple[T, ...]]:
135
+ decode_item = self.item_adapter.decode_fn()
136
+
137
+ def decode(
138
+ stream: ByteStream,
139
+ ) -> tuple[T, ...]:
140
+ wire = stream.read_wire()
141
+ if wire in (0, 246):
142
+ return self.empty_listuple
143
+ length: int
144
+ if wire == 250:
145
+ length = decode_int64(stream)
146
+ else:
147
+ length = wire - 246
148
+ return self.listuple_class(decode_item(stream) for _ in range(length))
149
+
150
+ return decode
151
+
152
+ def decode_fn(self) -> Callable[[ByteStream], tuple[T, ...]]:
153
+ return self.decode_fn_impl
154
+
104
155
  def finalize(
105
156
  self,
106
157
  resolve_type_fn: Callable[[_spec.Type], "TypeAdapter"],
soia/_impl/binary.py ADDED
@@ -0,0 +1,270 @@
1
+ import struct
2
+ from typing import Callable, Final, Literal
3
+
4
+ from soia._impl.function_maker import BodyBuilder, Expr, ExprLike, make_function
5
+ from soia._impl.type_adapter import ByteStream
6
+
7
+
8
+ def encode_int32(input_val: int, buffer: bytearray) -> None:
9
+ """Encode a 32-bit integer using variable-length encoding."""
10
+ if input_val < 0:
11
+ if input_val >= -256:
12
+ buffer.append(235)
13
+ buffer.append((input_val + 256) & 0xFF)
14
+ elif input_val >= -65536:
15
+ buffer.append(236)
16
+ buffer.extend(struct.pack("<H", input_val + 65536))
17
+ else:
18
+ buffer.append(237)
19
+ buffer.extend(
20
+ struct.pack(
21
+ "<i", -2147483648 if input_val <= -2147483648 else input_val
22
+ )
23
+ )
24
+ elif input_val < 232:
25
+ buffer.append(input_val)
26
+ elif input_val < 65536:
27
+ buffer.append(232)
28
+ buffer.extend(struct.pack("<H", input_val))
29
+ else:
30
+ buffer.append(233)
31
+ buffer.extend(
32
+ struct.pack("<I", input_val if input_val <= 2147483647 else 2147483647)
33
+ )
34
+
35
+
36
+ def encode_int64(value: int, buffer: bytearray) -> None:
37
+ if -2147483648 <= value <= 2147483647:
38
+ encode_int32(value, buffer)
39
+ else:
40
+ buffer.append(238)
41
+ buffer.extend(
42
+ struct.pack(
43
+ "<q",
44
+ (
45
+ -9223372036854775808
46
+ if value <= -9223372036854775808
47
+ else value if value <= 9223372036854775807 else 9223372036854775807
48
+ ),
49
+ )
50
+ )
51
+
52
+
53
+ def encode_uint64(value: int, buffer: bytearray) -> None:
54
+ if value < 232:
55
+ buffer.append(0 if value <= 0 else value)
56
+ elif value < 65536:
57
+ buffer.append(232)
58
+ buffer.extend(struct.pack("<H", value))
59
+ elif value < 4294967296:
60
+ buffer.append(233)
61
+ buffer.extend(struct.pack("<I", value))
62
+ else:
63
+ buffer.append(234)
64
+ buffer.extend(
65
+ struct.pack(
66
+ "<Q", value if value <= 18446744073709551615 else 18446744073709551615
67
+ )
68
+ )
69
+
70
+
71
+ def encode_float32(value: float, buffer: bytearray) -> None:
72
+ if value == 0.0:
73
+ buffer.append(0)
74
+ else:
75
+ buffer.append(240)
76
+ buffer.extend(struct.pack("<f", value))
77
+
78
+
79
+ def encode_float64(value: float, buffer: bytearray) -> None:
80
+ if value == 0.0:
81
+ buffer.append(0)
82
+ else:
83
+ buffer.append(241)
84
+ buffer.extend(struct.pack("<d", value))
85
+
86
+
87
+ def encode_length_prefix(length: int, buffer: bytearray) -> None:
88
+ """Encode a length prefix using variable-length encoding."""
89
+ if length < 232:
90
+ if length >= 0:
91
+ buffer.append(length)
92
+ else:
93
+ raise ValueError(f"Length overflow: {length & 0xFFFFFFFF}")
94
+ elif length < 65536:
95
+ buffer.append(232)
96
+ buffer.extend(struct.pack("<H", length))
97
+ elif length < 4294967296:
98
+ buffer.append(233)
99
+ buffer.extend(struct.pack("<I", length))
100
+ else:
101
+ raise ValueError(f"Length overflow: {length & 0xFFFFFFFF}")
102
+
103
+
104
+ def make_decode_number_fn(
105
+ target_type: Literal[
106
+ "bool",
107
+ "int32",
108
+ "int64",
109
+ "uint64",
110
+ "float",
111
+ ],
112
+ ) -> Callable:
113
+ target_min_int: int
114
+ target_max_int: int
115
+ if target_type == "int32":
116
+ target_min_int = -(2**31)
117
+ target_max_int = 2**31 - 1
118
+ elif target_type == "int64":
119
+ target_min_int = -(2**63)
120
+ target_max_int = 2**63 - 1
121
+ elif target_type == "uint64":
122
+ target_min_int = 0
123
+ target_max_int = 2**64 - 1
124
+ else: # float
125
+ _: Literal["bool", "float"] = target_type
126
+ target_min_int = 0
127
+ target_max_int = 0
128
+
129
+ def clamp(number: int) -> int:
130
+ if number < target_min_int:
131
+ return target_min_int
132
+ elif number <= target_max_int:
133
+ return number
134
+ else:
135
+ return target_max_int
136
+
137
+ def make_return_expr(
138
+ raw: ExprLike, min_int: int, max_int: int, is_float: bool
139
+ ) -> ExprLike:
140
+ if target_type == "bool":
141
+ return Expr.join("True if ", raw, " else False")
142
+ elif target_type == "float":
143
+ if is_float:
144
+ return raw
145
+ else:
146
+ return Expr.join(raw, " + 0.0")
147
+ elif is_float:
148
+ return Expr.join("int(", raw, ")")
149
+ elif target_min_int <= min_int and max_int <= target_max_int:
150
+ return raw
151
+ else:
152
+ return Expr.join(Expr.local("clamp", clamp), "(", raw, ")")
153
+
154
+ def make_unpack_expr(fmt: str, num_bytes: int, offset: int = 0) -> Expr:
155
+ return Expr.join(
156
+ Expr.local("unpack", struct.unpack),
157
+ f"('{fmt}', stream.read({num_bytes}))[0]",
158
+ "" if offset == 0 else f" + {offset}",
159
+ )
160
+
161
+ body_builder = BodyBuilder()
162
+ body_builder.append_ln("wire = stream.buffer[stream.position]")
163
+ body_builder.append_ln("stream.position += 1")
164
+ body_builder.append_ln("if wire < 232:")
165
+ body_builder.append_ln(" return ", make_return_expr("wire", 0, 231, False))
166
+ body_builder.append_ln("elif wire <= 236:") # 232-236
167
+ body_builder.append_ln(" if wire <= 234:") # 232-234
168
+ body_builder.append_ln(" if wire == 232:")
169
+ # 16-bit unsigned integer
170
+ body_builder.append_ln(
171
+ " return ",
172
+ make_return_expr(make_unpack_expr("<H", 2), 0, 65535, False),
173
+ )
174
+ body_builder.append_ln(" elif wire == 233:")
175
+ # 32-bit unsigned integer
176
+ body_builder.append_ln(
177
+ " return ",
178
+ make_return_expr(make_unpack_expr("<I", 4), 0, 4294967295, False),
179
+ )
180
+ body_builder.append_ln(" elif wire == 234:")
181
+ # 64-bit unsigned integer
182
+ body_builder.append_ln(
183
+ " return ",
184
+ make_return_expr(make_unpack_expr("<Q", 8), 0, 18446744073709551615, False),
185
+ )
186
+ body_builder.append_ln(" elif wire == 235:")
187
+ # 8-bit signed integer (offset by 256)
188
+ body_builder.append_ln(
189
+ " return ",
190
+ make_return_expr(make_unpack_expr("<B", 1, -256), -256, -1, False),
191
+ )
192
+ body_builder.append_ln(" else:") # 236
193
+ # 16-bit signed integer (offset by 65536)
194
+ body_builder.append_ln(
195
+ " return ",
196
+ make_return_expr(make_unpack_expr("<H", 2, -65536), -65536, -1, False),
197
+ )
198
+ body_builder.append_ln("elif wire <= 239:") # 237-239
199
+ body_builder.append_ln(" if wire == 237:")
200
+ # 32-bit signed integer
201
+ body_builder.append_ln(
202
+ " return ",
203
+ make_return_expr(make_unpack_expr("<i", 4), -2147483648, 2147483647, False),
204
+ )
205
+ body_builder.append_ln(" else:") # 238-239
206
+ # 64-bit signed integer
207
+ body_builder.append_ln(
208
+ " return ",
209
+ make_return_expr(
210
+ make_unpack_expr("<q", 8), -9223372036854775808, 9223372036854775807, False
211
+ ),
212
+ )
213
+ body_builder.append_ln("elif wire == 240:")
214
+ # 32-bit float
215
+ body_builder.append_ln(
216
+ " return ",
217
+ make_return_expr(make_unpack_expr("<f", 4), 0, 0, True),
218
+ )
219
+ body_builder.append_ln("elif wire == 241:")
220
+ # 64-bit float
221
+ body_builder.append_ln(
222
+ " return ",
223
+ make_return_expr(make_unpack_expr("<d", 8), 0, 0, True),
224
+ )
225
+ body_builder.append_ln("else:")
226
+ body_builder.append_ln(" raise ValueError(f'Unsupported wire type: {wire}')")
227
+
228
+ return make_function(
229
+ f"decode_{target_type}",
230
+ ["stream"],
231
+ body_builder.build(),
232
+ )
233
+
234
+
235
+ decode_bool: Final[Callable[[ByteStream], bool]] = make_decode_number_fn("bool")
236
+ decode_int32: Final[Callable[[ByteStream], int]] = make_decode_number_fn("int32")
237
+ decode_int64: Final[Callable[[ByteStream], int]] = make_decode_number_fn("int64")
238
+ decode_uint64: Final[Callable[[ByteStream], int]] = make_decode_number_fn("uint64")
239
+ decode_float: Final[Callable[[ByteStream], float]] = make_decode_number_fn("float")
240
+
241
+
242
+ def decode_unused(stream: ByteStream) -> None:
243
+ wire_offset = stream.read_wire() - 232
244
+ if wire_offset < 0:
245
+ return
246
+ elif wire_offset <= 9:
247
+ if wire_offset in (0, 4): # uint16, uint16 - 65536
248
+ stream.position += 2
249
+ elif wire_offset in (1, 5, 8): # uint32, int32, float32
250
+ stream.position += 4
251
+ elif wire_offset == 3: # uint8 - 256
252
+ stream.position += 1
253
+ else: # 2, 6, 7, 9
254
+ stream.position += 8
255
+ elif wire_offset in (11, 13): # string, bytes
256
+ length = decode_int64(stream)
257
+ stream.position += length
258
+ elif wire_offset in (15, 19, 20, 21, 22): # array length==1, enum value kind==1-4
259
+ decode_unused(stream)
260
+ elif wire_offset == 16: # array length==2
261
+ decode_unused(stream)
262
+ decode_unused(stream)
263
+ elif wire_offset == 17: # array length==3
264
+ decode_unused(stream)
265
+ decode_unused(stream)
266
+ decode_unused(stream)
267
+ elif wire_offset == 18: # array length==N
268
+ length = decode_int64(stream)
269
+ for _ in range(length):
270
+ decode_unused(stream)