soia-client 1.1.4__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 +63 -10
- soia/_impl/binary.py +270 -0
- soia/_impl/enums.py +246 -42
- soia/_impl/optionals.py +51 -10
- soia/_impl/primitives.py +181 -25
- soia/_impl/repr.py +1 -1
- soia/_impl/serializer.py +41 -18
- soia/_impl/service.py +1 -3
- soia/_impl/service_client.py +1 -2
- soia/_impl/structs.py +315 -89
- soia/_impl/timestamp.py +4 -5
- soia/_impl/type_adapter.py +38 -4
- soia/_module_initializer.py +1 -2
- {soia_client-1.1.4.dist-info → soia_client-1.1.6.dist-info}/METADATA +1 -1
- soia_client-1.1.6.dist-info/RECORD +28 -0
- soia_client-1.1.4.dist-info/RECORD +0 -27
- {soia_client-1.1.4.dist-info → soia_client-1.1.6.dist-info}/WHEEL +0 -0
- {soia_client-1.1.4.dist-info → soia_client-1.1.6.dist-info}/licenses/LICENSE +0 -0
- {soia_client-1.1.4.dist-info → soia_client-1.1.6.dist-info}/top_level.txt +0 -0
soia/_impl/enums.py
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
from collections.abc import Callable, Sequence
|
|
3
3
|
from dataclasses import FrozenInstanceError, dataclass
|
|
4
|
-
from typing import Any, Final, Union
|
|
4
|
+
from typing import Any, Final, Generic, Union
|
|
5
5
|
|
|
6
6
|
from soia import _spec, reflection
|
|
7
|
+
from soia._impl.binary import decode_int64, decode_unused, encode_int64
|
|
7
8
|
from soia._impl.function_maker import BodyBuilder, Expr, ExprLike, Line, make_function
|
|
8
9
|
from soia._impl.repr import repr_impl
|
|
9
|
-
from soia._impl.type_adapter import TypeAdapter
|
|
10
|
+
from soia._impl.type_adapter import T, ByteStream, TypeAdapter
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
class EnumAdapter(TypeAdapter):
|
|
13
|
+
class EnumAdapter(Generic[T], TypeAdapter[T]):
|
|
13
14
|
__slots__ = (
|
|
14
15
|
"spec",
|
|
15
16
|
"gen_class",
|
|
@@ -30,6 +31,12 @@ class EnumAdapter(TypeAdapter):
|
|
|
30
31
|
self.spec = spec
|
|
31
32
|
base_class = self.gen_class = _make_base_class(spec)
|
|
32
33
|
|
|
34
|
+
def forward_decode(stream: ByteStream) -> T:
|
|
35
|
+
return base_class._decode(stream)
|
|
36
|
+
|
|
37
|
+
# Will be overridden at finalization time.
|
|
38
|
+
base_class._decode = forward_decode
|
|
39
|
+
|
|
33
40
|
private_is_enum_attr = _name_private_is_enum_attr(spec.id)
|
|
34
41
|
self.private_is_enum_attr = private_is_enum_attr
|
|
35
42
|
setattr(base_class, private_is_enum_attr, True)
|
|
@@ -73,12 +80,28 @@ class EnumAdapter(TypeAdapter):
|
|
|
73
80
|
for value_field in value_fields:
|
|
74
81
|
wrap_fn = _make_wrap_fn(value_field)
|
|
75
82
|
setattr(base_class, f"wrap_{value_field.spec.name}", wrap_fn)
|
|
83
|
+
# Check if the field type is a struct type.
|
|
84
|
+
field_type = resolve_type_fn(value_field.spec.type)
|
|
85
|
+
frozen_class = field_type.frozen_class_of_struct()
|
|
86
|
+
if frozen_class:
|
|
87
|
+
create_fn = _make_create_fn(wrap_fn, frozen_class)
|
|
88
|
+
setattr(base_class, f"create_{value_field.spec.name}", create_fn)
|
|
89
|
+
|
|
90
|
+
unrecognized_class = _make_unrecognized_class(base_class)
|
|
76
91
|
|
|
77
92
|
base_class._fj = _make_from_json_fn(
|
|
78
93
|
self.all_constant_fields,
|
|
79
94
|
value_fields,
|
|
80
95
|
set(self.spec.removed_numbers),
|
|
81
|
-
base_class,
|
|
96
|
+
base_class=base_class,
|
|
97
|
+
unrecognized_class=unrecognized_class,
|
|
98
|
+
)
|
|
99
|
+
base_class._decode = _make_decode_fn(
|
|
100
|
+
self.all_constant_fields,
|
|
101
|
+
value_fields,
|
|
102
|
+
set(self.spec.removed_numbers),
|
|
103
|
+
base_class=base_class,
|
|
104
|
+
unrecognized_class=unrecognized_class,
|
|
82
105
|
)
|
|
83
106
|
|
|
84
107
|
# Mark finalization as done.
|
|
@@ -111,16 +134,36 @@ class EnumAdapter(TypeAdapter):
|
|
|
111
134
|
def to_json_expr(self, in_expr: ExprLike, readable: bool) -> Expr:
|
|
112
135
|
return Expr.join(in_expr, "._rj" if readable else "._dj")
|
|
113
136
|
|
|
114
|
-
def from_json_expr(
|
|
137
|
+
def from_json_expr(
|
|
138
|
+
self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
|
|
139
|
+
) -> Expr:
|
|
115
140
|
fn_name = "_fj"
|
|
116
141
|
from_json_fn = getattr(self.gen_class, fn_name, None)
|
|
117
142
|
if from_json_fn:
|
|
118
|
-
return Expr.join(
|
|
143
|
+
return Expr.join(
|
|
144
|
+
Expr.local("_fj?", from_json_fn),
|
|
145
|
+
"(",
|
|
146
|
+
json_expr,
|
|
147
|
+
", ",
|
|
148
|
+
keep_unrecognized_expr,
|
|
149
|
+
")",
|
|
150
|
+
)
|
|
119
151
|
else:
|
|
120
152
|
return Expr.join(
|
|
121
|
-
Expr.local("_cls?", self.gen_class),
|
|
153
|
+
Expr.local("_cls?", self.gen_class),
|
|
154
|
+
f".{fn_name}(",
|
|
155
|
+
json_expr,
|
|
156
|
+
", ",
|
|
157
|
+
keep_unrecognized_expr,
|
|
158
|
+
")",
|
|
122
159
|
)
|
|
123
160
|
|
|
161
|
+
def encode_fn(self) -> Callable[[T, bytearray], None]:
|
|
162
|
+
return _encode_impl
|
|
163
|
+
|
|
164
|
+
def decode_fn(self) -> Callable[[ByteStream], T]:
|
|
165
|
+
return self.gen_class._decode
|
|
166
|
+
|
|
124
167
|
def get_type(self) -> reflection.Type:
|
|
125
168
|
return reflection.RecordType(
|
|
126
169
|
kind="record",
|
|
@@ -143,8 +186,10 @@ class EnumAdapter(TypeAdapter):
|
|
|
143
186
|
number=field.number,
|
|
144
187
|
type=None,
|
|
145
188
|
)
|
|
146
|
-
for field in self.all_constant_fields
|
|
147
|
-
|
|
189
|
+
for field in self.all_constant_fields
|
|
190
|
+
if field.number != 0
|
|
191
|
+
)
|
|
192
|
+
+ tuple(
|
|
148
193
|
reflection.Field(
|
|
149
194
|
name=field.spec.name,
|
|
150
195
|
number=field.spec.number,
|
|
@@ -157,6 +202,9 @@ class EnumAdapter(TypeAdapter):
|
|
|
157
202
|
for field in self.value_fields:
|
|
158
203
|
field.field_type.register_records(registry)
|
|
159
204
|
|
|
205
|
+
def frozen_class_of_struct(self) -> type | None:
|
|
206
|
+
return None
|
|
207
|
+
|
|
160
208
|
|
|
161
209
|
def _make_base_class(spec: _spec.Enum) -> type:
|
|
162
210
|
record_hash = hash(spec.id)
|
|
@@ -174,6 +222,9 @@ def _make_base_class(spec: _spec.Enum) -> type:
|
|
|
174
222
|
def union(self) -> Any:
|
|
175
223
|
return self
|
|
176
224
|
|
|
225
|
+
def __bool__(self) -> bool:
|
|
226
|
+
return self.kind != "?"
|
|
227
|
+
|
|
177
228
|
def __setattr__(self, name: str, value: Any):
|
|
178
229
|
raise FrozenInstanceError(self.__class__.__qualname__)
|
|
179
230
|
|
|
@@ -195,6 +246,9 @@ def _make_base_class(spec: _spec.Enum) -> type:
|
|
|
195
246
|
|
|
196
247
|
|
|
197
248
|
def _make_constant_class(base_class: type, spec: _spec.ConstantField) -> type:
|
|
249
|
+
byte_array = bytearray()
|
|
250
|
+
encode_int64(spec.number, byte_array)
|
|
251
|
+
|
|
198
252
|
class Constant(base_class):
|
|
199
253
|
__slots__ = ()
|
|
200
254
|
|
|
@@ -206,6 +260,7 @@ def _make_constant_class(base_class: type, spec: _spec.ConstantField) -> type:
|
|
|
206
260
|
_rj: Final[str] = spec.name
|
|
207
261
|
# has value
|
|
208
262
|
_hv: Final[bool] = False
|
|
263
|
+
_bytes: Final[bytes | None] = bytes(byte_array)
|
|
209
264
|
|
|
210
265
|
def __init__(self):
|
|
211
266
|
# Do not call super().__init__().
|
|
@@ -225,20 +280,22 @@ def _make_unrecognized_class(base_class: type) -> type:
|
|
|
225
280
|
"""
|
|
226
281
|
|
|
227
282
|
class Unrecognized(base_class):
|
|
228
|
-
__slots__ = ("_dj",)
|
|
283
|
+
__slots__ = ("_dj", "_bytes")
|
|
229
284
|
|
|
230
285
|
kind: Final[str] = "?"
|
|
231
286
|
_number: Final[int] = 0
|
|
232
287
|
# dense JSON
|
|
233
|
-
_dj: Any
|
|
288
|
+
_dj: list[Any] | int
|
|
289
|
+
_bytes: bytes
|
|
234
290
|
# readable JSON
|
|
235
291
|
_rj: Final[str] = "?"
|
|
236
292
|
# has value
|
|
237
293
|
_hv: Final[bool] = False
|
|
238
294
|
|
|
239
|
-
def __init__(self, dj: Any):
|
|
295
|
+
def __init__(self, dj: list[Any] | int, bytes: bytes):
|
|
240
296
|
# Do not call super().__init__().
|
|
241
297
|
object.__setattr__(self, "_dj", copy.deepcopy(dj))
|
|
298
|
+
object.__setattr__(self, "_bytes", bytes)
|
|
242
299
|
object.__setattr__(self, "value", None)
|
|
243
300
|
|
|
244
301
|
def __repr__(self) -> str:
|
|
@@ -261,6 +318,7 @@ def _make_value_class(
|
|
|
261
318
|
_number: Final[int] = number
|
|
262
319
|
# has value
|
|
263
320
|
_hv: Final[bool] = True
|
|
321
|
+
_bytes: Final[None] = None
|
|
264
322
|
|
|
265
323
|
def __init__(self):
|
|
266
324
|
# Do not call super().__init__().
|
|
@@ -305,9 +363,38 @@ def _make_value_class(
|
|
|
305
363
|
)
|
|
306
364
|
)
|
|
307
365
|
|
|
366
|
+
bytes_prefix = bytearray()
|
|
367
|
+
if number in range(1, 5):
|
|
368
|
+
bytes_prefix.append(250 + number)
|
|
369
|
+
else:
|
|
370
|
+
bytes_prefix.append(248)
|
|
371
|
+
encode_int64(number, bytes_prefix)
|
|
372
|
+
|
|
373
|
+
ret._enc = make_function(
|
|
374
|
+
name="encode",
|
|
375
|
+
params=["self", "buffer"],
|
|
376
|
+
body=[
|
|
377
|
+
f"buffer.extend({bytes_prefix})",
|
|
378
|
+
Line.join(
|
|
379
|
+
Expr.local("encode_value", field_type.encode_fn()),
|
|
380
|
+
"(self.value, buffer)",
|
|
381
|
+
),
|
|
382
|
+
],
|
|
383
|
+
)
|
|
384
|
+
|
|
308
385
|
return ret
|
|
309
386
|
|
|
310
387
|
|
|
388
|
+
def _encode_impl(
|
|
389
|
+
value: Any,
|
|
390
|
+
buffer: bytearray,
|
|
391
|
+
) -> None:
|
|
392
|
+
if value._bytes:
|
|
393
|
+
buffer.extend(value._bytes)
|
|
394
|
+
else:
|
|
395
|
+
value._enc(buffer)
|
|
396
|
+
|
|
397
|
+
|
|
311
398
|
@dataclass(frozen=True)
|
|
312
399
|
class _ValueField:
|
|
313
400
|
spec: _spec.ValueField
|
|
@@ -344,16 +431,23 @@ def _make_wrap_fn(field: _ValueField) -> Callable[[Any], Any]:
|
|
|
344
431
|
)
|
|
345
432
|
|
|
346
433
|
|
|
434
|
+
def _make_create_fn(wrap_fn: Callable[[Any], Any], frozen_class: type) -> Callable:
|
|
435
|
+
def create(**kwargs):
|
|
436
|
+
return wrap_fn(frozen_class(**kwargs))
|
|
437
|
+
|
|
438
|
+
return create
|
|
439
|
+
|
|
440
|
+
|
|
347
441
|
def _make_from_json_fn(
|
|
348
442
|
constant_fields: Sequence[_spec.ConstantField],
|
|
349
443
|
value_fields: Sequence[_ValueField],
|
|
350
444
|
removed_numbers: set[int],
|
|
351
445
|
base_class: type,
|
|
446
|
+
unrecognized_class: type,
|
|
352
447
|
) -> Callable[[Any], Any]:
|
|
353
|
-
unrecognized_class = _make_unrecognized_class(base_class)
|
|
354
448
|
unrecognized_class_local = Expr.local("Unrecognized", unrecognized_class)
|
|
355
449
|
obj_setattr_local = Expr.local("obj_settatr", object.__setattr__)
|
|
356
|
-
|
|
450
|
+
removed_numbers_tuple = tuple(sorted(removed_numbers))
|
|
357
451
|
|
|
358
452
|
key_to_constant: dict[Union[int, str], Any] = {}
|
|
359
453
|
for field in constant_fields:
|
|
@@ -364,15 +458,13 @@ def _make_from_json_fn(
|
|
|
364
458
|
unknown_constant = key_to_constant[0]
|
|
365
459
|
unknown_constant_local = Expr.local("unknown_constant", unknown_constant)
|
|
366
460
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
key_to_field: dict[Union[int, str], _ValueField] = {}
|
|
461
|
+
number_to_value_field: dict[int, _ValueField] = {}
|
|
462
|
+
name_to_value_field: dict[str, _ValueField] = {}
|
|
370
463
|
for field in value_fields:
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
value_keys_local = Expr.local("value_keys", set(key_to_field.keys()))
|
|
464
|
+
number_to_value_field[field.spec.number] = field
|
|
465
|
+
name_to_value_field[field.spec.name] = field
|
|
466
|
+
value_field_numbers = tuple(sorted(number_to_value_field.keys()))
|
|
467
|
+
value_field_names = tuple(sorted(name_to_value_field.keys()))
|
|
376
468
|
|
|
377
469
|
builder = BodyBuilder()
|
|
378
470
|
# The reason why we wrap the function inside a 'while' is explained below.
|
|
@@ -389,16 +481,20 @@ def _make_from_json_fn(
|
|
|
389
481
|
builder.append_ln(" return ", key_to_constant_local, "[json]")
|
|
390
482
|
builder.append_ln(" except:")
|
|
391
483
|
if removed_numbers:
|
|
392
|
-
builder.append_ln(
|
|
484
|
+
builder.append_ln(
|
|
485
|
+
f" if json in {removed_numbers_tuple} or not keep_unrecognized_fields:"
|
|
486
|
+
)
|
|
393
487
|
builder.append_ln(" return ", unknown_constant_local)
|
|
394
|
-
builder.append_ln(" return ", unrecognized_class_local, "(json)")
|
|
488
|
+
builder.append_ln(" return ", unrecognized_class_local, "(json, b'\\0')")
|
|
395
489
|
|
|
396
|
-
def append_number_branches(numbers:
|
|
490
|
+
def append_number_branches(numbers: Sequence[int], indent: str) -> None:
|
|
397
491
|
if len(numbers) == 1:
|
|
398
492
|
number = numbers[0]
|
|
399
|
-
field =
|
|
493
|
+
field = number_to_value_field[number]
|
|
400
494
|
value_class_local = Expr.local("cls?", field.value_class)
|
|
401
|
-
value_expr = field.field_type.from_json_expr(
|
|
495
|
+
value_expr = field.field_type.from_json_expr(
|
|
496
|
+
"json[1]", "keep_unrecognized_fields"
|
|
497
|
+
)
|
|
402
498
|
builder.append_ln(f"{indent}ret = ", value_class_local, "()")
|
|
403
499
|
builder.append_ln(
|
|
404
500
|
indent, obj_setattr_local, '(ret, "value", ', value_expr, ")"
|
|
@@ -420,19 +516,20 @@ def _make_from_json_fn(
|
|
|
420
516
|
if not value_fields:
|
|
421
517
|
# The field was either removed or is an unrecognized field.
|
|
422
518
|
if removed_numbers:
|
|
423
|
-
builder.append_ln(
|
|
519
|
+
builder.append_ln(
|
|
520
|
+
f" if number in {removed_numbers_tuple} or not keep_unrecognized_fields:"
|
|
521
|
+
)
|
|
424
522
|
builder.append_ln(" return ", unknown_constant_local)
|
|
425
|
-
builder.append_ln(" return ", unrecognized_class_local, "(json)")
|
|
523
|
+
builder.append_ln(" return ", unrecognized_class_local, "(json, b'\\0')")
|
|
426
524
|
else:
|
|
427
|
-
if
|
|
428
|
-
builder.append_ln(f" if number != {value_fields[0].spec.number}:")
|
|
429
|
-
else:
|
|
430
|
-
builder.append_ln(" if number not in ", value_keys_local, ":")
|
|
525
|
+
builder.append_ln(f" if number not in {value_field_numbers}:")
|
|
431
526
|
if removed_numbers:
|
|
432
|
-
builder.append_ln(
|
|
527
|
+
builder.append_ln(
|
|
528
|
+
f" if number in {removed_numbers_tuple} or not keep_unrecognized_fields:"
|
|
529
|
+
)
|
|
433
530
|
builder.append_ln(" return ", unknown_constant_local)
|
|
434
|
-
builder.append_ln(" return ", unrecognized_class_local, "(json)")
|
|
435
|
-
append_number_branches(
|
|
531
|
+
builder.append_ln(" return ", unrecognized_class_local, "(json, b'\\0')")
|
|
532
|
+
append_number_branches(value_field_numbers, " ")
|
|
436
533
|
|
|
437
534
|
# READABLE FORMAT
|
|
438
535
|
if len(constant_fields) == 1:
|
|
@@ -446,12 +543,14 @@ def _make_from_json_fn(
|
|
|
446
543
|
# In readable mode, drop unrecognized values and use UNKNOWN instead.
|
|
447
544
|
builder.append_ln(" return ", unknown_constant_local)
|
|
448
545
|
|
|
449
|
-
def append_name_branches(names:
|
|
546
|
+
def append_name_branches(names: Sequence[str], indent: str) -> None:
|
|
450
547
|
if len(names) == 1:
|
|
451
548
|
name = names[0]
|
|
452
|
-
field =
|
|
549
|
+
field = name_to_value_field[name]
|
|
453
550
|
value_class_local = Expr.local("cls?", field.value_class)
|
|
454
|
-
value_expr = field.field_type.from_json_expr(
|
|
551
|
+
value_expr = field.field_type.from_json_expr(
|
|
552
|
+
"json['value']", "keep_unrecognized_fields"
|
|
553
|
+
)
|
|
455
554
|
builder.append_ln(f"{indent}ret = ", value_class_local, "()")
|
|
456
555
|
builder.append_ln(
|
|
457
556
|
indent, obj_setattr_local, '(ret, "value", ', value_expr, ")"
|
|
@@ -472,10 +571,10 @@ def _make_from_json_fn(
|
|
|
472
571
|
builder.append_ln(" return ", unknown_constant_local)
|
|
473
572
|
else:
|
|
474
573
|
builder.append_ln(" kind = json['kind']")
|
|
475
|
-
builder.append_ln(" if kind not in
|
|
574
|
+
builder.append_ln(f" if kind not in {value_field_names}:")
|
|
476
575
|
builder.append_ln(" return ", unknown_constant_local)
|
|
477
576
|
builder.append_ln(" else:")
|
|
478
|
-
append_name_branches(
|
|
577
|
+
append_name_branches(value_field_names, " ")
|
|
479
578
|
|
|
480
579
|
# In the unlikely event that json.loads() returns an instance of a subclass of int.
|
|
481
580
|
builder.append_ln(" elif isinstance(json, int):")
|
|
@@ -487,7 +586,112 @@ def _make_from_json_fn(
|
|
|
487
586
|
|
|
488
587
|
return make_function(
|
|
489
588
|
name="from_json",
|
|
490
|
-
params=["json"],
|
|
589
|
+
params=["json", "keep_unrecognized_fields"],
|
|
590
|
+
body=builder.build(),
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def _make_decode_fn(
|
|
595
|
+
constant_fields: Sequence[_spec.ConstantField],
|
|
596
|
+
value_fields: Sequence[_ValueField],
|
|
597
|
+
removed_numbers: set[int],
|
|
598
|
+
base_class: type,
|
|
599
|
+
unrecognized_class: type,
|
|
600
|
+
) -> Callable[[ByteStream], Any]:
|
|
601
|
+
unrecognized_class_local = Expr.local("Unrecognized", unrecognized_class)
|
|
602
|
+
obj_setattr_local = Expr.local("obj_settatr", object.__setattr__)
|
|
603
|
+
|
|
604
|
+
number_to_constant: dict[int, Any] = {}
|
|
605
|
+
for field in constant_fields:
|
|
606
|
+
constant = getattr(base_class, field.attribute)
|
|
607
|
+
number_to_constant[field.number] = constant
|
|
608
|
+
number_to_constant_local = Expr.local("number_to_constant", number_to_constant)
|
|
609
|
+
removed_numbers_tuple = tuple(sorted(removed_numbers))
|
|
610
|
+
unknown_constant = number_to_constant[0]
|
|
611
|
+
unknown_constant_local = Expr.local("unknown_constant", unknown_constant)
|
|
612
|
+
|
|
613
|
+
number_to_value_field: dict[int, _ValueField] = {}
|
|
614
|
+
for field in value_fields:
|
|
615
|
+
number_to_value_field[field.spec.number] = field
|
|
616
|
+
value_field_numbers = tuple(sorted(number_to_value_field.keys()))
|
|
617
|
+
|
|
618
|
+
builder = BodyBuilder()
|
|
619
|
+
builder.append_ln("start_offset = stream.position")
|
|
620
|
+
builder.append_ln("wire = stream.buffer[start_offset]")
|
|
621
|
+
builder.append_ln("if wire <= 238:")
|
|
622
|
+
# A number
|
|
623
|
+
builder.append_ln(" if wire < 232:")
|
|
624
|
+
builder.append_ln(" stream.position += 1")
|
|
625
|
+
builder.append_ln(" number = wire")
|
|
626
|
+
builder.append_ln(" else:")
|
|
627
|
+
builder.append_ln(
|
|
628
|
+
" number = ", Expr.local("decode_int64", decode_int64), "(stream)"
|
|
629
|
+
)
|
|
630
|
+
builder.append_ln(" try:")
|
|
631
|
+
builder.append_ln(" return ", number_to_constant_local, "[number]")
|
|
632
|
+
builder.append_ln(" except:")
|
|
633
|
+
builder.append_ln(" ", Expr.local("decode_unused", decode_unused), "(stream)")
|
|
634
|
+
if removed_numbers:
|
|
635
|
+
builder.append_ln(
|
|
636
|
+
f" if number in {removed_numbers_tuple} or not keep_unrecognized_fields:"
|
|
637
|
+
)
|
|
638
|
+
builder.append_ln(" return ", unknown_constant_local)
|
|
639
|
+
builder.append_ln(" bytes = stream.buffer[start_offset:stream.position]")
|
|
640
|
+
builder.append_ln(" return ", unrecognized_class_local, "(0, bytes)")
|
|
641
|
+
# An array of 2
|
|
642
|
+
builder.append_ln("stream.position += 1")
|
|
643
|
+
builder.append_ln("if wire == 248:")
|
|
644
|
+
builder.append_ln(
|
|
645
|
+
" number = ", Expr.local("decode_int64", decode_int64), "(stream)"
|
|
646
|
+
)
|
|
647
|
+
builder.append_ln("else:")
|
|
648
|
+
builder.append_ln(" number = wire - 250")
|
|
649
|
+
|
|
650
|
+
def append_number_branches(numbers: Sequence[int], indent: str) -> None:
|
|
651
|
+
if len(numbers) == 1:
|
|
652
|
+
number = numbers[0]
|
|
653
|
+
field = number_to_value_field[number]
|
|
654
|
+
value_class_local = Expr.local("cls?", field.value_class)
|
|
655
|
+
decode_local = Expr.local("decode?", field.field_type.decode_fn())
|
|
656
|
+
value_expr = Expr.join(decode_local, "(stream)")
|
|
657
|
+
builder.append_ln(f"{indent}ret = ", value_class_local, "()")
|
|
658
|
+
builder.append_ln(
|
|
659
|
+
indent, obj_setattr_local, '(ret, "value", ', value_expr, ")"
|
|
660
|
+
)
|
|
661
|
+
builder.append_ln(f"{indent}return ret")
|
|
662
|
+
else:
|
|
663
|
+
indented = f" {indent}"
|
|
664
|
+
mid_index = int(len(numbers) / 2)
|
|
665
|
+
mid_number = numbers[mid_index - 1]
|
|
666
|
+
operator = "==" if mid_index == 1 else "<="
|
|
667
|
+
builder.append_ln(f"{indent}if number {operator} {mid_number}:")
|
|
668
|
+
append_number_branches(numbers[0:mid_index], indented)
|
|
669
|
+
builder.append_ln(f"{indent}else:")
|
|
670
|
+
append_number_branches(numbers[mid_index:], indented)
|
|
671
|
+
|
|
672
|
+
if not value_fields:
|
|
673
|
+
# The field was either removed or is an unrecognized field.
|
|
674
|
+
if removed_numbers:
|
|
675
|
+
builder.append_ln(
|
|
676
|
+
f"if number in {removed_numbers_tuple} or not keep_unrecognized_fields:"
|
|
677
|
+
)
|
|
678
|
+
builder.append_ln(" return ", unknown_constant_local)
|
|
679
|
+
builder.append_ln("bytes = stream.buffer[start_offset:stream.position]")
|
|
680
|
+
builder.append_ln("return ", unrecognized_class_local, "(0, bytes)")
|
|
681
|
+
else:
|
|
682
|
+
builder.append_ln(f"if number not in {value_field_numbers}:")
|
|
683
|
+
if removed_numbers:
|
|
684
|
+
builder.append_ln(
|
|
685
|
+
f" if number in {removed_numbers_tuple} or not keep_unrecognized_fields:"
|
|
686
|
+
)
|
|
687
|
+
builder.append_ln(" return ", unknown_constant_local)
|
|
688
|
+
builder.append_ln(" bytes = stream.buffer[start_offset:stream.position]")
|
|
689
|
+
builder.append_ln(" return ", unrecognized_class_local, "(0, bytes)")
|
|
690
|
+
append_number_branches(value_field_numbers, "")
|
|
691
|
+
|
|
692
|
+
return make_function(
|
|
693
|
+
name="decode",
|
|
694
|
+
params=["stream"],
|
|
491
695
|
body=builder.build(),
|
|
492
696
|
)
|
|
493
697
|
|
soia/_impl/optionals.py
CHANGED
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
2
|
from dataclasses import dataclass
|
|
3
|
-
from
|
|
3
|
+
from functools import cached_property
|
|
4
|
+
from typing import Generic
|
|
4
5
|
from weakref import WeakValueDictionary
|
|
5
6
|
|
|
6
|
-
from soia._impl.function_maker import Expr, ExprLike
|
|
7
|
-
from soia._impl.type_adapter import TypeAdapter
|
|
8
|
-
|
|
9
7
|
from soia import _spec, reflection
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
from soia._impl.function_maker import Expr, ExprLike
|
|
9
|
+
from soia._impl.type_adapter import T, ByteStream, TypeAdapter
|
|
12
10
|
|
|
13
11
|
|
|
14
|
-
def get_optional_adapter(other_adapter: TypeAdapter) -> TypeAdapter:
|
|
12
|
+
def get_optional_adapter(other_adapter: TypeAdapter[T]) -> TypeAdapter[T | None]:
|
|
15
13
|
return _other_adapter_to_optional_adapter.setdefault(
|
|
16
14
|
other_adapter, _OptionalAdapter(other_adapter)
|
|
17
15
|
)
|
|
18
16
|
|
|
19
17
|
|
|
20
18
|
@dataclass(frozen=True)
|
|
21
|
-
class _OptionalAdapter(TypeAdapter):
|
|
19
|
+
class _OptionalAdapter(Generic[T], TypeAdapter[T | None]):
|
|
22
20
|
__slots__ = ("other_adapter",)
|
|
23
21
|
|
|
24
22
|
other_adapter: TypeAdapter
|
|
@@ -50,8 +48,12 @@ class _OptionalAdapter(TypeAdapter):
|
|
|
50
48
|
")",
|
|
51
49
|
)
|
|
52
50
|
|
|
53
|
-
def from_json_expr(
|
|
54
|
-
|
|
51
|
+
def from_json_expr(
|
|
52
|
+
self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
|
|
53
|
+
) -> ExprLike:
|
|
54
|
+
other_from_json = self.other_adapter.from_json_expr(
|
|
55
|
+
json_expr, keep_unrecognized_expr
|
|
56
|
+
)
|
|
55
57
|
if other_from_json == json_expr:
|
|
56
58
|
return json_expr
|
|
57
59
|
return Expr.join(
|
|
@@ -62,6 +64,42 @@ class _OptionalAdapter(TypeAdapter):
|
|
|
62
64
|
")",
|
|
63
65
|
)
|
|
64
66
|
|
|
67
|
+
@cached_property
|
|
68
|
+
def encode_fn_impl(self) -> Callable[[T | None, bytearray], None]:
|
|
69
|
+
encode_value = self.other_adapter.encode_fn()
|
|
70
|
+
|
|
71
|
+
def encode(
|
|
72
|
+
value: T | None,
|
|
73
|
+
buffer: bytearray,
|
|
74
|
+
) -> None:
|
|
75
|
+
if value is None:
|
|
76
|
+
buffer.append(255)
|
|
77
|
+
else:
|
|
78
|
+
encode_value(value, buffer)
|
|
79
|
+
|
|
80
|
+
return encode
|
|
81
|
+
|
|
82
|
+
def encode_fn(self) -> Callable[[T | None, bytearray], None]:
|
|
83
|
+
return self.encode_fn_impl
|
|
84
|
+
|
|
85
|
+
@cached_property
|
|
86
|
+
def decode_fn_impl(self) -> Callable[[ByteStream], T | None]:
|
|
87
|
+
decode_value = self.other_adapter.decode_fn()
|
|
88
|
+
|
|
89
|
+
def decode(
|
|
90
|
+
stream: ByteStream,
|
|
91
|
+
) -> T | None:
|
|
92
|
+
if stream.buffer[stream.position] == 255:
|
|
93
|
+
stream.position += 1
|
|
94
|
+
return None
|
|
95
|
+
else:
|
|
96
|
+
return decode_value(stream)
|
|
97
|
+
|
|
98
|
+
return decode
|
|
99
|
+
|
|
100
|
+
def decode_fn(self) -> Callable[[ByteStream], T | None]:
|
|
101
|
+
return self.decode_fn_impl
|
|
102
|
+
|
|
65
103
|
def finalize(
|
|
66
104
|
self,
|
|
67
105
|
resolve_type_fn: Callable[[_spec.Type], "TypeAdapter"],
|
|
@@ -80,6 +118,9 @@ class _OptionalAdapter(TypeAdapter):
|
|
|
80
118
|
) -> None:
|
|
81
119
|
self.other_adapter.register_records(registry)
|
|
82
120
|
|
|
121
|
+
def frozen_class_of_struct(self) -> type | None:
|
|
122
|
+
return None
|
|
123
|
+
|
|
83
124
|
|
|
84
125
|
_other_adapter_to_optional_adapter: WeakValueDictionary[TypeAdapter, TypeAdapter] = (
|
|
85
126
|
WeakValueDictionary()
|