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/structs.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
from collections.abc import Callable, Sequence
|
|
3
3
|
from dataclasses import FrozenInstanceError, dataclass
|
|
4
|
-
|
|
4
|
+
import itertools
|
|
5
|
+
from typing import Any, Final, Generic, Union, cast
|
|
5
6
|
|
|
7
|
+
from soia import _spec, reflection
|
|
6
8
|
from soia._impl.function_maker import (
|
|
7
9
|
BodyBuilder,
|
|
8
10
|
Expr,
|
|
@@ -16,12 +18,11 @@ from soia._impl.function_maker import (
|
|
|
16
18
|
)
|
|
17
19
|
from soia._impl.keep import KEEP
|
|
18
20
|
from soia._impl.repr import repr_impl
|
|
19
|
-
from soia._impl.type_adapter import TypeAdapter
|
|
20
|
-
|
|
21
|
-
from soia import _spec, reflection
|
|
21
|
+
from soia._impl.type_adapter import T, ByteStream, TypeAdapter
|
|
22
|
+
from soia._impl.binary import decode_int64, decode_unused, encode_length_prefix
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
class StructAdapter(TypeAdapter):
|
|
25
|
+
class StructAdapter(Generic[T], TypeAdapter[T]):
|
|
25
26
|
__slots__ = (
|
|
26
27
|
"spec",
|
|
27
28
|
"record_hash",
|
|
@@ -44,6 +45,8 @@ class StructAdapter(TypeAdapter):
|
|
|
44
45
|
# 0: has not started; 1: in progress; 2: done
|
|
45
46
|
finalization_state: int
|
|
46
47
|
fields: tuple["_Field", ...]
|
|
48
|
+
num_slots_incl_removed: int
|
|
49
|
+
slot_to_field: list["_Field | None"]
|
|
47
50
|
|
|
48
51
|
def __init__(self, spec: _spec.Struct):
|
|
49
52
|
self.finalization_state = 0
|
|
@@ -83,6 +86,17 @@ class StructAdapter(TypeAdapter):
|
|
|
83
86
|
# Reason why it's faster: we don't need to call object.__setattr__().
|
|
84
87
|
self.simple_class = _make_dataclass(slots)
|
|
85
88
|
|
|
89
|
+
def forward_encode(value: Any, buffer: bytearray) -> None:
|
|
90
|
+
frozen_class._encode(value, buffer)
|
|
91
|
+
|
|
92
|
+
# Will be overridden at finalization time.
|
|
93
|
+
frozen_class._encode = forward_encode
|
|
94
|
+
|
|
95
|
+
def forward_decode(stream: ByteStream) -> None:
|
|
96
|
+
return frozen_class._decode(stream)
|
|
97
|
+
|
|
98
|
+
frozen_class._decode = forward_decode
|
|
99
|
+
|
|
86
100
|
def finalize(
|
|
87
101
|
self,
|
|
88
102
|
resolve_type_fn: Callable[[_spec.Type], TypeAdapter],
|
|
@@ -101,6 +115,18 @@ class StructAdapter(TypeAdapter):
|
|
|
101
115
|
)
|
|
102
116
|
)
|
|
103
117
|
|
|
118
|
+
num_slots_incl_removed: Final = max(
|
|
119
|
+
itertools.chain(
|
|
120
|
+
(f.field.number + 1 for f in fields),
|
|
121
|
+
(n + 1 for n in self.spec.removed_numbers),
|
|
122
|
+
[0],
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
self.num_slots_incl_removed = num_slots_incl_removed
|
|
126
|
+
|
|
127
|
+
slot_to_field: Final[list[_Field | None]] = [None] * self.num_slots_incl_removed
|
|
128
|
+
self.slot_to_field = slot_to_field
|
|
129
|
+
|
|
104
130
|
# Aim to have dependencies finalized *before* the dependent. It's not always
|
|
105
131
|
# possible, because there can be cyclic dependencies.
|
|
106
132
|
# The function returned by the do_x_fn() method of a dependency is marginally
|
|
@@ -108,6 +134,7 @@ class StructAdapter(TypeAdapter):
|
|
|
108
134
|
# this function is a "forwarding" function.
|
|
109
135
|
for field in fields:
|
|
110
136
|
field.type.finalize(resolve_type_fn)
|
|
137
|
+
self.slot_to_field[field.field.number] = field
|
|
111
138
|
|
|
112
139
|
frozen_class = self.gen_class
|
|
113
140
|
mutable_class = self.mutable_class
|
|
@@ -148,13 +175,29 @@ class StructAdapter(TypeAdapter):
|
|
|
148
175
|
Any, _make_repr_fn(fields)
|
|
149
176
|
)
|
|
150
177
|
|
|
151
|
-
frozen_class._tdj = _make_to_dense_json_fn(
|
|
178
|
+
frozen_class._tdj = _make_to_dense_json_fn(
|
|
179
|
+
fields=fields,
|
|
180
|
+
num_slots_incl_removed=num_slots_incl_removed,
|
|
181
|
+
)
|
|
152
182
|
frozen_class._trj = _make_to_readable_json_fn(fields=fields)
|
|
183
|
+
frozen_class._encode = _make_encode_fn(
|
|
184
|
+
fields=fields,
|
|
185
|
+
num_slots_incl_removed=num_slots_incl_removed,
|
|
186
|
+
)
|
|
187
|
+
frozen_class._decode = _make_decode_fn(
|
|
188
|
+
frozen_class=frozen_class,
|
|
189
|
+
simple_class=simple_class,
|
|
190
|
+
fields=fields,
|
|
191
|
+
removed_numbers=self.spec.removed_numbers,
|
|
192
|
+
num_slots_incl_removed=num_slots_incl_removed,
|
|
193
|
+
)
|
|
194
|
+
|
|
153
195
|
frozen_class._fj = _make_from_json_fn(
|
|
154
196
|
frozen_class=frozen_class,
|
|
155
197
|
simple_class=simple_class,
|
|
156
198
|
fields=fields,
|
|
157
199
|
removed_numbers=self.spec.removed_numbers,
|
|
200
|
+
num_slots_incl_removed=self.num_slots_incl_removed,
|
|
158
201
|
)
|
|
159
202
|
|
|
160
203
|
# Initialize DEFAULT.
|
|
@@ -187,7 +230,13 @@ class StructAdapter(TypeAdapter):
|
|
|
187
230
|
)
|
|
188
231
|
|
|
189
232
|
def is_not_default_expr(self, arg_expr: ExprLike, attr_expr: ExprLike) -> Expr:
|
|
190
|
-
return Expr.join(
|
|
233
|
+
return Expr.join(
|
|
234
|
+
"(",
|
|
235
|
+
attr_expr,
|
|
236
|
+
"._unrecognized or ",
|
|
237
|
+
attr_expr,
|
|
238
|
+
"._array_len)",
|
|
239
|
+
)
|
|
191
240
|
|
|
192
241
|
def to_json_expr(
|
|
193
242
|
self,
|
|
@@ -196,15 +245,29 @@ class StructAdapter(TypeAdapter):
|
|
|
196
245
|
) -> Expr:
|
|
197
246
|
return Expr.join(in_expr, "._trj" if readable else "._tdj", "()")
|
|
198
247
|
|
|
199
|
-
def from_json_expr(
|
|
248
|
+
def from_json_expr(
|
|
249
|
+
self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
|
|
250
|
+
) -> Expr:
|
|
200
251
|
fn_name = "_fj"
|
|
201
252
|
# The _fj method may not have been added to the class yet.
|
|
202
253
|
from_json_fn = getattr(self.gen_class, fn_name, None)
|
|
203
254
|
if from_json_fn:
|
|
204
|
-
return Expr.join(
|
|
255
|
+
return Expr.join(
|
|
256
|
+
Expr.local("_fj?", from_json_fn),
|
|
257
|
+
"(",
|
|
258
|
+
json_expr,
|
|
259
|
+
", ",
|
|
260
|
+
keep_unrecognized_expr,
|
|
261
|
+
")",
|
|
262
|
+
)
|
|
205
263
|
else:
|
|
206
264
|
return Expr.join(
|
|
207
|
-
Expr.local("_cls?", self.gen_class),
|
|
265
|
+
Expr.local("_cls?", self.gen_class),
|
|
266
|
+
f".{fn_name}(",
|
|
267
|
+
json_expr,
|
|
268
|
+
", ",
|
|
269
|
+
keep_unrecognized_expr,
|
|
270
|
+
")",
|
|
208
271
|
)
|
|
209
272
|
|
|
210
273
|
def get_type(self) -> reflection.Type:
|
|
@@ -236,6 +299,15 @@ class StructAdapter(TypeAdapter):
|
|
|
236
299
|
for field in self.fields:
|
|
237
300
|
field.type.register_records(registry)
|
|
238
301
|
|
|
302
|
+
def frozen_class_of_struct(self) -> type:
|
|
303
|
+
return self.gen_class
|
|
304
|
+
|
|
305
|
+
def encode_fn(self) -> Callable[[T, bytearray], None]:
|
|
306
|
+
return self.gen_class._encode
|
|
307
|
+
|
|
308
|
+
def decode_fn(self) -> Callable[[ByteStream], T]:
|
|
309
|
+
return self.gen_class._decode
|
|
310
|
+
|
|
239
311
|
|
|
240
312
|
class _Frozen:
|
|
241
313
|
def __setattr__(self, name: str, value: Any):
|
|
@@ -314,7 +386,7 @@ def _make_frozen_class_init_fn(
|
|
|
314
386
|
")",
|
|
315
387
|
)
|
|
316
388
|
# Set the _unrecognized field.
|
|
317
|
-
builder.append_ln(obj_setattr, '(_self, "_unrecognized",
|
|
389
|
+
builder.append_ln(obj_setattr, '(_self, "_unrecognized", None)')
|
|
318
390
|
# Set array length.
|
|
319
391
|
builder.append_ln(
|
|
320
392
|
obj_setattr,
|
|
@@ -341,7 +413,7 @@ def _make_frozen_class_init_fn(
|
|
|
341
413
|
field.type.to_frozen_expr(attribute),
|
|
342
414
|
)
|
|
343
415
|
# Set the _unrecognized field.
|
|
344
|
-
builder.append_ln("_self._unrecognized =
|
|
416
|
+
builder.append_ln("_self._unrecognized = None")
|
|
345
417
|
# Set array length.
|
|
346
418
|
builder.append_ln("_self._array_len = ", array_len_expr())
|
|
347
419
|
# Change back the __class__.
|
|
@@ -375,7 +447,7 @@ def _make_mutable_class_init_fn(fields: Sequence[_Field]) -> Callable[..., None]
|
|
|
375
447
|
" = ",
|
|
376
448
|
attribute,
|
|
377
449
|
)
|
|
378
|
-
builder.append_ln("_self._unrecognized =
|
|
450
|
+
builder.append_ln("_self._unrecognized = None")
|
|
379
451
|
return make_function(
|
|
380
452
|
name="__init__",
|
|
381
453
|
params=params,
|
|
@@ -524,26 +596,19 @@ def _make_to_frozen_fn(
|
|
|
524
596
|
builder.append_ln("ret._unrecognized = self._unrecognized")
|
|
525
597
|
|
|
526
598
|
def array_len_expr() -> Expr:
|
|
527
|
-
|
|
528
|
-
spans.append(
|
|
529
|
-
LineSpan.join(
|
|
530
|
-
f"{_get_num_slots(fields)} + ",
|
|
531
|
-
Expr.local("_len", len),
|
|
532
|
-
"(ret._unrecognized) if ret._unrecognized else ",
|
|
533
|
-
)
|
|
534
|
-
)
|
|
599
|
+
exprs: list[ExprLike] = []
|
|
535
600
|
# Fields are sorted by number.
|
|
536
601
|
for field in reversed(fields):
|
|
537
602
|
attr_expr = f"ret.{field.field.attribute}"
|
|
538
|
-
|
|
603
|
+
exprs.append(
|
|
539
604
|
LineSpan.join(
|
|
540
605
|
f"{field.field.number + 1} if ",
|
|
541
606
|
field.type.is_not_default_expr(attr_expr, attr_expr),
|
|
542
607
|
" else ",
|
|
543
608
|
)
|
|
544
609
|
)
|
|
545
|
-
|
|
546
|
-
return Expr.join(*
|
|
610
|
+
exprs.append("0")
|
|
611
|
+
return Expr.join(*exprs)
|
|
547
612
|
|
|
548
613
|
# Set the _unrecognized field.
|
|
549
614
|
builder.append_ln("ret._unrecognized = self._unrecognized")
|
|
@@ -575,8 +640,14 @@ def _make_eq_fn(
|
|
|
575
640
|
builder.append_ln("if other.__class__ is self.__class__:")
|
|
576
641
|
operands: list[ExprLike]
|
|
577
642
|
if fields:
|
|
578
|
-
|
|
579
|
-
|
|
643
|
+
|
|
644
|
+
def get_attribute(f: _Field) -> str:
|
|
645
|
+
return f.field.attribute
|
|
646
|
+
|
|
647
|
+
operands = [
|
|
648
|
+
Expr.join(f"self.{get_attribute(f)} == other.{get_attribute(f)}")
|
|
649
|
+
for f in fields
|
|
650
|
+
]
|
|
580
651
|
else:
|
|
581
652
|
operands = ["True"]
|
|
582
653
|
builder.append_ln(" return ", Expr.join(*operands, separator=" and "))
|
|
@@ -639,9 +710,13 @@ def _make_repr_fn(fields: Sequence[_Field]) -> Callable[[Any], str]:
|
|
|
639
710
|
)
|
|
640
711
|
|
|
641
712
|
|
|
642
|
-
def _make_to_dense_json_fn(
|
|
713
|
+
def _make_to_dense_json_fn(
|
|
714
|
+
fields: Sequence[_Field], num_slots_incl_removed: int
|
|
715
|
+
) -> Callable[[Any], Any]:
|
|
643
716
|
builder = BodyBuilder()
|
|
644
|
-
builder.append_ln(
|
|
717
|
+
builder.append_ln(
|
|
718
|
+
"l = (self._unrecognized.adjusted_json_array_len if self._unrecognized else None) or self._array_len"
|
|
719
|
+
)
|
|
645
720
|
builder.append_ln("ret = [0] * l")
|
|
646
721
|
for field in fields:
|
|
647
722
|
builder.append_ln(f"if l <= {field.field.number}:")
|
|
@@ -653,10 +728,8 @@ def _make_to_dense_json_fn(fields: Sequence[_Field]) -> Callable[[Any], Any]:
|
|
|
653
728
|
readable=False,
|
|
654
729
|
),
|
|
655
730
|
)
|
|
656
|
-
|
|
657
|
-
builder.append_ln(f"
|
|
658
|
-
builder.append_ln(" return ret")
|
|
659
|
-
builder.append_ln(f"ret[{num_slots}:] = self._unrecognized")
|
|
731
|
+
builder.append_ln(f"if {num_slots_incl_removed} < l:")
|
|
732
|
+
builder.append_ln(f" ret[{num_slots_incl_removed}:] = self._unrecognized.json")
|
|
660
733
|
builder.append_ln("return ret")
|
|
661
734
|
return make_function(
|
|
662
735
|
name="to_dense_json",
|
|
@@ -690,11 +763,60 @@ def _make_to_readable_json_fn(fields: Sequence[_Field]) -> Callable[[Any], Any]:
|
|
|
690
763
|
)
|
|
691
764
|
|
|
692
765
|
|
|
766
|
+
def _make_encode_fn(
|
|
767
|
+
fields: Sequence[_Field],
|
|
768
|
+
num_slots_incl_removed: int,
|
|
769
|
+
) -> Callable[[Any, bytearray], None]:
|
|
770
|
+
builder = BodyBuilder()
|
|
771
|
+
builder.append_ln(
|
|
772
|
+
"l = (value._unrecognized.adjusted_bytes_array_len if ",
|
|
773
|
+
"value._unrecognized else None) or value._array_len",
|
|
774
|
+
)
|
|
775
|
+
builder.append_ln("if l < 4:")
|
|
776
|
+
builder.append_ln(" buffer.append(246 + l)")
|
|
777
|
+
builder.append_ln("else:")
|
|
778
|
+
builder.append_ln(" buffer.append(250)")
|
|
779
|
+
builder.append_ln(
|
|
780
|
+
" ",
|
|
781
|
+
Expr.local("_encode_length_prefix", encode_length_prefix),
|
|
782
|
+
"(l, buffer)",
|
|
783
|
+
)
|
|
784
|
+
last_number = -1
|
|
785
|
+
for field in fields:
|
|
786
|
+
number = field.field.number
|
|
787
|
+
builder.append_ln(f"if l <= {number}:")
|
|
788
|
+
builder.append_ln(" return")
|
|
789
|
+
missing_slots = number - last_number - 1
|
|
790
|
+
if missing_slots >= 1:
|
|
791
|
+
# There are removed fields between last_number and number.
|
|
792
|
+
zeros = "\\0" * (missing_slots)
|
|
793
|
+
builder.append_ln(f"buffer.extend(b'{zeros}')")
|
|
794
|
+
builder.append_ln(
|
|
795
|
+
Expr.local("encode?", field.type.encode_fn()),
|
|
796
|
+
f"(value.{field.field.attribute}, buffer)",
|
|
797
|
+
)
|
|
798
|
+
last_number = number
|
|
799
|
+
builder.append_ln(f"if l <= {num_slots_incl_removed}:")
|
|
800
|
+
builder.append_ln(" return")
|
|
801
|
+
num_slots_excl_removed = last_number + 1
|
|
802
|
+
if num_slots_excl_removed < num_slots_incl_removed:
|
|
803
|
+
missing_slots = num_slots_incl_removed - num_slots_excl_removed
|
|
804
|
+
zeros = "\\0" * (missing_slots)
|
|
805
|
+
builder.append_ln(f"buffer.extend(b'{zeros}')")
|
|
806
|
+
builder.append_ln("buffer.extend(value._unrecognized.raw_bytes)")
|
|
807
|
+
return make_function(
|
|
808
|
+
name="encode",
|
|
809
|
+
params=["value, buffer"],
|
|
810
|
+
body=builder.build(),
|
|
811
|
+
)
|
|
812
|
+
|
|
813
|
+
|
|
693
814
|
def _make_from_json_fn(
|
|
694
815
|
frozen_class: type,
|
|
695
816
|
simple_class: type,
|
|
696
817
|
fields: Sequence[_Field],
|
|
697
818
|
removed_numbers: tuple[int, ...],
|
|
819
|
+
num_slots_incl_removed: int,
|
|
698
820
|
) -> Callable[[Any], Any]:
|
|
699
821
|
builder = BodyBuilder()
|
|
700
822
|
builder.append_ln("if not json:")
|
|
@@ -714,29 +836,39 @@ def _make_from_json_fn(
|
|
|
714
836
|
"(json)",
|
|
715
837
|
)
|
|
716
838
|
for field in fields:
|
|
717
|
-
name = field.field.name
|
|
718
839
|
number = field.field.number
|
|
719
840
|
item_expr = f"json[{number}]"
|
|
720
841
|
builder.append_ln(
|
|
721
842
|
f" ret.{field.field.attribute} = ",
|
|
722
843
|
field.type.default_expr(),
|
|
723
844
|
f" if array_len <= {number} else ",
|
|
724
|
-
field.type.from_json_expr(item_expr),
|
|
845
|
+
field.type.from_json_expr(item_expr, "keep_unrecognized_fields"),
|
|
725
846
|
)
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
847
|
+
if fields:
|
|
848
|
+
num_slots_excl_removed = fields[-1].field.number + 1
|
|
849
|
+
else:
|
|
850
|
+
num_slots_excl_removed = 0
|
|
851
|
+
builder.append_ln(
|
|
852
|
+
f" if array_len <= {num_slots_incl_removed} or not keep_unrecognized_fields:"
|
|
853
|
+
)
|
|
854
|
+
builder.append_ln(" ret._unrecognized = None")
|
|
729
855
|
builder.append_ln(
|
|
730
856
|
" ret._array_len = ",
|
|
731
|
-
_adjust_array_len_expr(
|
|
857
|
+
_adjust_array_len_expr(
|
|
858
|
+
"array_len",
|
|
859
|
+
removed_numbers=removed_numbers,
|
|
860
|
+
num_slots_excl_removed=num_slots_excl_removed,
|
|
861
|
+
),
|
|
732
862
|
)
|
|
733
863
|
builder.append_ln(" else:")
|
|
734
864
|
builder.append_ln(
|
|
735
865
|
" ret._unrecognized = ",
|
|
866
|
+
Expr.local("UnrecognizedFields", _UnrecognizedFields.from_json),
|
|
867
|
+
"(json=",
|
|
736
868
|
Expr.local("deepcopy", copy.deepcopy),
|
|
737
|
-
f"(json[{
|
|
869
|
+
f"(json[{num_slots_incl_removed}:]), adjusted_json_array_len=array_len)",
|
|
738
870
|
)
|
|
739
|
-
builder.append_ln(" ret._array_len =
|
|
871
|
+
builder.append_ln(f" ret._array_len = {num_slots_excl_removed}")
|
|
740
872
|
|
|
741
873
|
builder.append_ln("else:")
|
|
742
874
|
builder.append_ln(" array_len = 0")
|
|
@@ -748,12 +880,12 @@ def _make_from_json_fn(
|
|
|
748
880
|
builder.append_ln(f" array_len = {field.field.number + 1}")
|
|
749
881
|
builder.append_ln(
|
|
750
882
|
f" {lvalue} = ",
|
|
751
|
-
field.type.from_json_expr(f'json["{name}"]'),
|
|
883
|
+
field.type.from_json_expr(f'json["{name}"]', "keep_unrecognized_fields"),
|
|
752
884
|
)
|
|
753
885
|
builder.append_ln(" else:")
|
|
754
886
|
builder.append_ln(f" {lvalue} = ", field.type.default_expr())
|
|
755
887
|
# Drop unrecognized fields in readable mode.
|
|
756
|
-
builder.append_ln(" ret._unrecognized =
|
|
888
|
+
builder.append_ln(" ret._unrecognized = None")
|
|
757
889
|
builder.append_ln(" ret._array_len = array_len")
|
|
758
890
|
|
|
759
891
|
builder.append_ln("ret.__class__ = ", Expr.local("Frozen", frozen_class))
|
|
@@ -761,60 +893,121 @@ def _make_from_json_fn(
|
|
|
761
893
|
|
|
762
894
|
return make_function(
|
|
763
895
|
name="from_json",
|
|
764
|
-
params=["json"],
|
|
896
|
+
params=["json", "keep_unrecognized_fields"],
|
|
897
|
+
body=builder.build(),
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
def _make_decode_fn(
|
|
902
|
+
frozen_class: type,
|
|
903
|
+
simple_class: type,
|
|
904
|
+
fields: Sequence[_Field],
|
|
905
|
+
removed_numbers: tuple[int, ...],
|
|
906
|
+
num_slots_incl_removed: int,
|
|
907
|
+
) -> Callable[[ByteStream], Any]:
|
|
908
|
+
builder = BodyBuilder()
|
|
909
|
+
builder.append_ln("wire = stream.buffer[stream.position]")
|
|
910
|
+
builder.append_ln("stream.position += 1")
|
|
911
|
+
builder.append_ln("if wire in (0, 246):")
|
|
912
|
+
builder.append_ln(" return ", Expr.local("DEFAULT", frozen_class.DEFAULT))
|
|
913
|
+
builder.append_ln("elif wire == 250:")
|
|
914
|
+
builder.append_ln(
|
|
915
|
+
" array_len = ",
|
|
916
|
+
Expr.local("decode_int64", decode_int64),
|
|
917
|
+
"(stream)",
|
|
918
|
+
)
|
|
919
|
+
builder.append_ln("else:")
|
|
920
|
+
builder.append_ln(" array_len = wire - 246")
|
|
921
|
+
# Create an instance of the simple class. We'll later change its __class__ attr.
|
|
922
|
+
builder.append_ln(
|
|
923
|
+
"ret = ",
|
|
924
|
+
Expr.local("Simple", simple_class),
|
|
925
|
+
"()",
|
|
926
|
+
)
|
|
927
|
+
last_number = -1
|
|
928
|
+
for field in fields:
|
|
929
|
+
number = field.field.number
|
|
930
|
+
for removed_number in range(last_number + 1, number):
|
|
931
|
+
builder.append_ln(f"if {removed_number} < array_len:")
|
|
932
|
+
builder.append_ln(
|
|
933
|
+
" ",
|
|
934
|
+
Expr.local("decode_unused", decode_unused),
|
|
935
|
+
"(stream)",
|
|
936
|
+
)
|
|
937
|
+
builder.append_ln(
|
|
938
|
+
f"ret.{field.field.attribute} = ",
|
|
939
|
+
field.type.default_expr(),
|
|
940
|
+
f" if array_len <= {number} else ",
|
|
941
|
+
Expr.local("decode?", field.type.decode_fn()),
|
|
942
|
+
"(stream)",
|
|
943
|
+
)
|
|
944
|
+
last_number = number
|
|
945
|
+
num_slots_excl_removed = last_number + 1
|
|
946
|
+
|
|
947
|
+
builder.append_ln(f"if array_len > {num_slots_excl_removed}:")
|
|
948
|
+
builder.append_ln(
|
|
949
|
+
f" if array_len > {num_slots_incl_removed} and keep_unrecognized_fields:"
|
|
950
|
+
)
|
|
951
|
+
for _ in range(num_slots_incl_removed - num_slots_excl_removed):
|
|
952
|
+
builder.append_ln(Expr.local(" decode_unused", decode_unused), "(stream)")
|
|
953
|
+
builder.append_ln(" start_offset = stream.position")
|
|
954
|
+
builder.append_ln(" for _ in range(array_len - {num_slots_incl_removed}):")
|
|
955
|
+
builder.append_ln(" ", Expr.local("decode_unused", decode_unused), "(stream)")
|
|
956
|
+
builder.append_ln(" end_offset = stream.position")
|
|
957
|
+
builder.append_ln(
|
|
958
|
+
" ret._unrecognized = ",
|
|
959
|
+
Expr.local("UnrecognizedFields", _UnrecognizedFields.from_bytes),
|
|
960
|
+
"(raw_bytes=stream.buffer[start_offset:end_offset], adjusted_bytes_array_len=array_len)",
|
|
961
|
+
)
|
|
962
|
+
builder.append_ln(" else:")
|
|
963
|
+
builder.append_ln(f" for _ in range(array_len - {num_slots_excl_removed}):")
|
|
964
|
+
builder.append_ln(" ", Expr.local("decode_unused", decode_unused), "(stream)")
|
|
965
|
+
builder.append_ln(" ret._unrecognized = None")
|
|
966
|
+
builder.append_ln("else:")
|
|
967
|
+
builder.append_ln(" ret._unrecognized = None")
|
|
968
|
+
|
|
969
|
+
builder.append_ln(
|
|
970
|
+
" ret._array_len = ",
|
|
971
|
+
_adjust_array_len_expr(
|
|
972
|
+
"array_len",
|
|
973
|
+
removed_numbers=removed_numbers,
|
|
974
|
+
num_slots_excl_removed=num_slots_excl_removed,
|
|
975
|
+
),
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
builder.append_ln(
|
|
979
|
+
"ret.__class__ = ",
|
|
980
|
+
Expr.local("Frozen", frozen_class),
|
|
981
|
+
)
|
|
982
|
+
builder.append_ln("return ret")
|
|
983
|
+
return make_function(
|
|
984
|
+
name="decode",
|
|
985
|
+
params=["stream"],
|
|
765
986
|
body=builder.build(),
|
|
766
987
|
)
|
|
767
988
|
|
|
768
989
|
|
|
769
|
-
def _adjust_array_len_expr(
|
|
990
|
+
def _adjust_array_len_expr(
|
|
991
|
+
var: str,
|
|
992
|
+
removed_numbers: tuple[int, ...],
|
|
993
|
+
num_slots_excl_removed: int,
|
|
994
|
+
) -> str:
|
|
770
995
|
"""
|
|
771
996
|
When parsing a dense JSON or decoding a binary string, we can reuse the array length
|
|
772
997
|
in the decoded struct, but we need to account for possibly newly-removed fields. The
|
|
773
998
|
last field of the adjusted array length cannot be a removed field.
|
|
774
|
-
|
|
775
|
-
Let's imagine that field number 3 was removed from a struct.
|
|
776
|
-
This function would return the following expression:
|
|
777
|
-
array_len if array_len <= 3 else 3 if array_len == 4 else array_len
|
|
778
999
|
"""
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
begin: int = 0
|
|
786
|
-
# Number after the last removed field.
|
|
787
|
-
end: int = 0
|
|
788
|
-
|
|
789
|
-
def get_removed_spans() -> list[_RemovedSpan]:
|
|
790
|
-
ret: list[_RemovedSpan] = []
|
|
791
|
-
for number in sorted(removed_numbers):
|
|
792
|
-
last = ret[-1] if ret else None
|
|
793
|
-
if last and last.end == number:
|
|
794
|
-
last.end = number + 1
|
|
795
|
-
else:
|
|
796
|
-
ret.append(_RemovedSpan(number, number + 1))
|
|
797
|
-
return ret
|
|
798
|
-
|
|
799
|
-
removed_spans = get_removed_spans()
|
|
800
|
-
|
|
801
|
-
ret = ""
|
|
802
|
-
lower_bound = 0
|
|
803
|
-
for s in removed_spans:
|
|
804
|
-
if s.begin == lower_bound:
|
|
805
|
-
ret += f"{s.begin} if {var} <= {s.end} else "
|
|
806
|
-
elif s.end == s.begin + 1:
|
|
807
|
-
# Similar to the expression in 'else' but uses '==' instead of '<='
|
|
808
|
-
ret += (
|
|
809
|
-
f"{var} if {var} <= {s.begin} else {s.begin} if {var} == {s.end} else "
|
|
810
|
-
)
|
|
1000
|
+
removed_numbers_set = set(removed_numbers)
|
|
1001
|
+
table: list[int] = [0]
|
|
1002
|
+
last_len = 0
|
|
1003
|
+
for i in range(0, num_slots_excl_removed - 1):
|
|
1004
|
+
if i in removed_numbers_set:
|
|
1005
|
+
table.append(last_len)
|
|
811
1006
|
else:
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
ret += var
|
|
817
|
-
return ret
|
|
1007
|
+
table.append(i + 1)
|
|
1008
|
+
last_len = i + 1
|
|
1009
|
+
table_tuple = tuple(table)
|
|
1010
|
+
return f"{table_tuple}[{var}] if {var} < {num_slots_excl_removed} else {num_slots_excl_removed}"
|
|
818
1011
|
|
|
819
1012
|
|
|
820
1013
|
def _init_default(default: Any, fields: Sequence[_Field]) -> None:
|
|
@@ -826,7 +1019,7 @@ def _init_default(default: Any, fields: Sequence[_Field]) -> None:
|
|
|
826
1019
|
body=(Line.join("return ", field.type.default_expr()),),
|
|
827
1020
|
)
|
|
828
1021
|
object.__setattr__(default, attribute, get_field_default())
|
|
829
|
-
object.__setattr__(default, "_unrecognized",
|
|
1022
|
+
object.__setattr__(default, "_unrecognized", None)
|
|
830
1023
|
object.__setattr__(default, "_array_len", 0)
|
|
831
1024
|
|
|
832
1025
|
|
|
@@ -874,5 +1067,38 @@ def _name_private_is_frozen_attr(record_id: str) -> str:
|
|
|
874
1067
|
return f"_is_{record_name}_{hex_hash}"
|
|
875
1068
|
|
|
876
1069
|
|
|
877
|
-
|
|
878
|
-
|
|
1070
|
+
@dataclass(frozen=True)
|
|
1071
|
+
class _UnrecognizedFields:
|
|
1072
|
+
__slots__ = (
|
|
1073
|
+
"json",
|
|
1074
|
+
"raw_bytes",
|
|
1075
|
+
"adjusted_json_array_len",
|
|
1076
|
+
"adjusted_bytes_array_len",
|
|
1077
|
+
)
|
|
1078
|
+
|
|
1079
|
+
json: list[Any] | None
|
|
1080
|
+
raw_bytes: bytes | None
|
|
1081
|
+
adjusted_json_array_len: int | None
|
|
1082
|
+
adjusted_bytes_array_len: int | None
|
|
1083
|
+
|
|
1084
|
+
@staticmethod
|
|
1085
|
+
def from_json(
|
|
1086
|
+
json: list[Any], adjusted_json_array_len: int
|
|
1087
|
+
) -> "_UnrecognizedFields":
|
|
1088
|
+
return _UnrecognizedFields(
|
|
1089
|
+
json=json,
|
|
1090
|
+
raw_bytes=None,
|
|
1091
|
+
adjusted_json_array_len=adjusted_json_array_len,
|
|
1092
|
+
adjusted_bytes_array_len=None,
|
|
1093
|
+
)
|
|
1094
|
+
|
|
1095
|
+
@staticmethod
|
|
1096
|
+
def from_bytes(
|
|
1097
|
+
raw_bytes: bytes, adjusted_bytes_array_len: int
|
|
1098
|
+
) -> "_UnrecognizedFields":
|
|
1099
|
+
return _UnrecognizedFields(
|
|
1100
|
+
json=None,
|
|
1101
|
+
raw_bytes=raw_bytes,
|
|
1102
|
+
adjusted_json_array_len=None,
|
|
1103
|
+
adjusted_bytes_array_len=adjusted_bytes_array_len,
|
|
1104
|
+
)
|
soia/_impl/timestamp.py
CHANGED
|
@@ -146,11 +146,10 @@ class Timestamp:
|
|
|
146
146
|
dt = self.to_datetime_or_raise()
|
|
147
147
|
except Exception:
|
|
148
148
|
return ""
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
ret = ret[0 : -len(bad_suffix)] + "Z"
|
|
149
|
+
ret = dt.isoformat()
|
|
150
|
+
bad_suffix = "+00:00"
|
|
151
|
+
if ret.endswith(bad_suffix):
|
|
152
|
+
ret = ret[0 : -len(bad_suffix)] + "Z"
|
|
154
153
|
return ret
|
|
155
154
|
|
|
156
155
|
|