soia-client 1.1.5__py3-none-any.whl → 1.1.7__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 +59 -8
- soia/_impl/binary.py +270 -0
- soia/_impl/enums.py +223 -40
- soia/_impl/optionals.py +47 -8
- soia/_impl/primitives.py +176 -23
- soia/_impl/serializer.py +40 -16
- soia/_impl/service.py +1 -3
- soia/_impl/structs.py +314 -88
- soia/_impl/timestamp.py +4 -5
- soia/_impl/type_adapter.py +38 -3
- {soia_client-1.1.5.dist-info → soia_client-1.1.7.dist-info}/METADATA +1 -1
- soia_client-1.1.7.dist-info/RECORD +28 -0
- soia_client-1.1.5.dist-info/RECORD +0 -27
- {soia_client-1.1.5.dist-info → soia_client-1.1.7.dist-info}/WHEEL +0 -0
- {soia_client-1.1.5.dist-info → soia_client-1.1.7.dist-info}/licenses/LICENSE +0 -0
- {soia_client-1.1.5.dist-info → soia_client-1.1.7.dist-info}/top_level.txt +0 -0
soia/_impl/structs.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import copy
|
|
2
|
+
import itertools
|
|
2
3
|
from collections.abc import Callable, Sequence
|
|
3
4
|
from dataclasses import FrozenInstanceError, dataclass
|
|
4
|
-
from typing import Any, Final, Union, cast
|
|
5
|
+
from typing import Any, Final, Generic, Union, cast
|
|
5
6
|
|
|
6
7
|
from soia import _spec, reflection
|
|
8
|
+
from soia._impl.binary import decode_int64, decode_unused, encode_length_prefix
|
|
7
9
|
from soia._impl.function_maker import (
|
|
8
10
|
BodyBuilder,
|
|
9
11
|
Expr,
|
|
@@ -17,10 +19,10 @@ from soia._impl.function_maker import (
|
|
|
17
19
|
)
|
|
18
20
|
from soia._impl.keep import KEEP
|
|
19
21
|
from soia._impl.repr import repr_impl
|
|
20
|
-
from soia._impl.type_adapter import TypeAdapter
|
|
22
|
+
from soia._impl.type_adapter import ByteStream, T, TypeAdapter
|
|
21
23
|
|
|
22
24
|
|
|
23
|
-
class StructAdapter(TypeAdapter):
|
|
25
|
+
class StructAdapter(Generic[T], TypeAdapter[T]):
|
|
24
26
|
__slots__ = (
|
|
25
27
|
"spec",
|
|
26
28
|
"record_hash",
|
|
@@ -43,6 +45,8 @@ class StructAdapter(TypeAdapter):
|
|
|
43
45
|
# 0: has not started; 1: in progress; 2: done
|
|
44
46
|
finalization_state: int
|
|
45
47
|
fields: tuple["_Field", ...]
|
|
48
|
+
num_slots_incl_removed: int
|
|
49
|
+
slot_to_field: list["_Field | None"]
|
|
46
50
|
|
|
47
51
|
def __init__(self, spec: _spec.Struct):
|
|
48
52
|
self.finalization_state = 0
|
|
@@ -82,6 +86,17 @@ class StructAdapter(TypeAdapter):
|
|
|
82
86
|
# Reason why it's faster: we don't need to call object.__setattr__().
|
|
83
87
|
self.simple_class = _make_dataclass(slots)
|
|
84
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
|
+
|
|
85
100
|
def finalize(
|
|
86
101
|
self,
|
|
87
102
|
resolve_type_fn: Callable[[_spec.Type], TypeAdapter],
|
|
@@ -100,6 +115,18 @@ class StructAdapter(TypeAdapter):
|
|
|
100
115
|
)
|
|
101
116
|
)
|
|
102
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
|
+
|
|
103
130
|
# Aim to have dependencies finalized *before* the dependent. It's not always
|
|
104
131
|
# possible, because there can be cyclic dependencies.
|
|
105
132
|
# The function returned by the do_x_fn() method of a dependency is marginally
|
|
@@ -107,6 +134,7 @@ class StructAdapter(TypeAdapter):
|
|
|
107
134
|
# this function is a "forwarding" function.
|
|
108
135
|
for field in fields:
|
|
109
136
|
field.type.finalize(resolve_type_fn)
|
|
137
|
+
self.slot_to_field[field.field.number] = field
|
|
110
138
|
|
|
111
139
|
frozen_class = self.gen_class
|
|
112
140
|
mutable_class = self.mutable_class
|
|
@@ -147,13 +175,29 @@ class StructAdapter(TypeAdapter):
|
|
|
147
175
|
Any, _make_repr_fn(fields)
|
|
148
176
|
)
|
|
149
177
|
|
|
150
|
-
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
|
+
)
|
|
151
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
|
+
|
|
152
195
|
frozen_class._fj = _make_from_json_fn(
|
|
153
196
|
frozen_class=frozen_class,
|
|
154
197
|
simple_class=simple_class,
|
|
155
198
|
fields=fields,
|
|
156
199
|
removed_numbers=self.spec.removed_numbers,
|
|
200
|
+
num_slots_incl_removed=self.num_slots_incl_removed,
|
|
157
201
|
)
|
|
158
202
|
|
|
159
203
|
# Initialize DEFAULT.
|
|
@@ -186,7 +230,13 @@ class StructAdapter(TypeAdapter):
|
|
|
186
230
|
)
|
|
187
231
|
|
|
188
232
|
def is_not_default_expr(self, arg_expr: ExprLike, attr_expr: ExprLike) -> Expr:
|
|
189
|
-
return Expr.join(
|
|
233
|
+
return Expr.join(
|
|
234
|
+
"(",
|
|
235
|
+
attr_expr,
|
|
236
|
+
"._unrecognized or ",
|
|
237
|
+
attr_expr,
|
|
238
|
+
"._array_len)",
|
|
239
|
+
)
|
|
190
240
|
|
|
191
241
|
def to_json_expr(
|
|
192
242
|
self,
|
|
@@ -195,15 +245,29 @@ class StructAdapter(TypeAdapter):
|
|
|
195
245
|
) -> Expr:
|
|
196
246
|
return Expr.join(in_expr, "._trj" if readable else "._tdj", "()")
|
|
197
247
|
|
|
198
|
-
def from_json_expr(
|
|
248
|
+
def from_json_expr(
|
|
249
|
+
self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
|
|
250
|
+
) -> Expr:
|
|
199
251
|
fn_name = "_fj"
|
|
200
252
|
# The _fj method may not have been added to the class yet.
|
|
201
253
|
from_json_fn = getattr(self.gen_class, fn_name, None)
|
|
202
254
|
if from_json_fn:
|
|
203
|
-
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
|
+
)
|
|
204
263
|
else:
|
|
205
264
|
return Expr.join(
|
|
206
|
-
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
|
+
")",
|
|
207
271
|
)
|
|
208
272
|
|
|
209
273
|
def get_type(self) -> reflection.Type:
|
|
@@ -235,9 +299,15 @@ class StructAdapter(TypeAdapter):
|
|
|
235
299
|
for field in self.fields:
|
|
236
300
|
field.type.register_records(registry)
|
|
237
301
|
|
|
238
|
-
def frozen_class_of_struct(self) -> type
|
|
302
|
+
def frozen_class_of_struct(self) -> type:
|
|
239
303
|
return self.gen_class
|
|
240
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
|
+
|
|
241
311
|
|
|
242
312
|
class _Frozen:
|
|
243
313
|
def __setattr__(self, name: str, value: Any):
|
|
@@ -316,7 +386,7 @@ def _make_frozen_class_init_fn(
|
|
|
316
386
|
")",
|
|
317
387
|
)
|
|
318
388
|
# Set the _unrecognized field.
|
|
319
|
-
builder.append_ln(obj_setattr, '(_self, "_unrecognized",
|
|
389
|
+
builder.append_ln(obj_setattr, '(_self, "_unrecognized", None)')
|
|
320
390
|
# Set array length.
|
|
321
391
|
builder.append_ln(
|
|
322
392
|
obj_setattr,
|
|
@@ -343,7 +413,7 @@ def _make_frozen_class_init_fn(
|
|
|
343
413
|
field.type.to_frozen_expr(attribute),
|
|
344
414
|
)
|
|
345
415
|
# Set the _unrecognized field.
|
|
346
|
-
builder.append_ln("_self._unrecognized =
|
|
416
|
+
builder.append_ln("_self._unrecognized = None")
|
|
347
417
|
# Set array length.
|
|
348
418
|
builder.append_ln("_self._array_len = ", array_len_expr())
|
|
349
419
|
# Change back the __class__.
|
|
@@ -377,7 +447,7 @@ def _make_mutable_class_init_fn(fields: Sequence[_Field]) -> Callable[..., None]
|
|
|
377
447
|
" = ",
|
|
378
448
|
attribute,
|
|
379
449
|
)
|
|
380
|
-
builder.append_ln("_self._unrecognized =
|
|
450
|
+
builder.append_ln("_self._unrecognized = None")
|
|
381
451
|
return make_function(
|
|
382
452
|
name="__init__",
|
|
383
453
|
params=params,
|
|
@@ -526,26 +596,19 @@ def _make_to_frozen_fn(
|
|
|
526
596
|
builder.append_ln("ret._unrecognized = self._unrecognized")
|
|
527
597
|
|
|
528
598
|
def array_len_expr() -> Expr:
|
|
529
|
-
|
|
530
|
-
spans.append(
|
|
531
|
-
LineSpan.join(
|
|
532
|
-
f"{_get_num_slots(fields)} + ",
|
|
533
|
-
Expr.local("_len", len),
|
|
534
|
-
"(ret._unrecognized) if ret._unrecognized else ",
|
|
535
|
-
)
|
|
536
|
-
)
|
|
599
|
+
exprs: list[ExprLike] = []
|
|
537
600
|
# Fields are sorted by number.
|
|
538
601
|
for field in reversed(fields):
|
|
539
602
|
attr_expr = f"ret.{field.field.attribute}"
|
|
540
|
-
|
|
603
|
+
exprs.append(
|
|
541
604
|
LineSpan.join(
|
|
542
605
|
f"{field.field.number + 1} if ",
|
|
543
606
|
field.type.is_not_default_expr(attr_expr, attr_expr),
|
|
544
607
|
" else ",
|
|
545
608
|
)
|
|
546
609
|
)
|
|
547
|
-
|
|
548
|
-
return Expr.join(*
|
|
610
|
+
exprs.append("0")
|
|
611
|
+
return Expr.join(*exprs)
|
|
549
612
|
|
|
550
613
|
# Set the _unrecognized field.
|
|
551
614
|
builder.append_ln("ret._unrecognized = self._unrecognized")
|
|
@@ -577,8 +640,14 @@ def _make_eq_fn(
|
|
|
577
640
|
builder.append_ln("if other.__class__ is self.__class__:")
|
|
578
641
|
operands: list[ExprLike]
|
|
579
642
|
if fields:
|
|
580
|
-
|
|
581
|
-
|
|
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
|
+
]
|
|
582
651
|
else:
|
|
583
652
|
operands = ["True"]
|
|
584
653
|
builder.append_ln(" return ", Expr.join(*operands, separator=" and "))
|
|
@@ -641,9 +710,13 @@ def _make_repr_fn(fields: Sequence[_Field]) -> Callable[[Any], str]:
|
|
|
641
710
|
)
|
|
642
711
|
|
|
643
712
|
|
|
644
|
-
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]:
|
|
645
716
|
builder = BodyBuilder()
|
|
646
|
-
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
|
+
)
|
|
647
720
|
builder.append_ln("ret = [0] * l")
|
|
648
721
|
for field in fields:
|
|
649
722
|
builder.append_ln(f"if l <= {field.field.number}:")
|
|
@@ -655,10 +728,8 @@ def _make_to_dense_json_fn(fields: Sequence[_Field]) -> Callable[[Any], Any]:
|
|
|
655
728
|
readable=False,
|
|
656
729
|
),
|
|
657
730
|
)
|
|
658
|
-
|
|
659
|
-
builder.append_ln(f"
|
|
660
|
-
builder.append_ln(" return ret")
|
|
661
|
-
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")
|
|
662
733
|
builder.append_ln("return ret")
|
|
663
734
|
return make_function(
|
|
664
735
|
name="to_dense_json",
|
|
@@ -692,11 +763,60 @@ def _make_to_readable_json_fn(fields: Sequence[_Field]) -> Callable[[Any], Any]:
|
|
|
692
763
|
)
|
|
693
764
|
|
|
694
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
|
+
|
|
695
814
|
def _make_from_json_fn(
|
|
696
815
|
frozen_class: type,
|
|
697
816
|
simple_class: type,
|
|
698
817
|
fields: Sequence[_Field],
|
|
699
818
|
removed_numbers: tuple[int, ...],
|
|
819
|
+
num_slots_incl_removed: int,
|
|
700
820
|
) -> Callable[[Any], Any]:
|
|
701
821
|
builder = BodyBuilder()
|
|
702
822
|
builder.append_ln("if not json:")
|
|
@@ -716,29 +836,39 @@ def _make_from_json_fn(
|
|
|
716
836
|
"(json)",
|
|
717
837
|
)
|
|
718
838
|
for field in fields:
|
|
719
|
-
name = field.field.name
|
|
720
839
|
number = field.field.number
|
|
721
840
|
item_expr = f"json[{number}]"
|
|
722
841
|
builder.append_ln(
|
|
723
842
|
f" ret.{field.field.attribute} = ",
|
|
724
843
|
field.type.default_expr(),
|
|
725
844
|
f" if array_len <= {number} else ",
|
|
726
|
-
field.type.from_json_expr(item_expr),
|
|
845
|
+
field.type.from_json_expr(item_expr, "keep_unrecognized_fields"),
|
|
727
846
|
)
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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")
|
|
731
855
|
builder.append_ln(
|
|
732
856
|
" ret._array_len = ",
|
|
733
|
-
_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
|
+
),
|
|
734
862
|
)
|
|
735
863
|
builder.append_ln(" else:")
|
|
736
864
|
builder.append_ln(
|
|
737
865
|
" ret._unrecognized = ",
|
|
866
|
+
Expr.local("UnrecognizedFields", _UnrecognizedFields.from_json),
|
|
867
|
+
"(json=",
|
|
738
868
|
Expr.local("deepcopy", copy.deepcopy),
|
|
739
|
-
f"(json[{
|
|
869
|
+
f"(json[{num_slots_incl_removed}:]), adjusted_json_array_len=array_len)",
|
|
740
870
|
)
|
|
741
|
-
builder.append_ln(" ret._array_len =
|
|
871
|
+
builder.append_ln(f" ret._array_len = {num_slots_excl_removed}")
|
|
742
872
|
|
|
743
873
|
builder.append_ln("else:")
|
|
744
874
|
builder.append_ln(" array_len = 0")
|
|
@@ -750,12 +880,12 @@ def _make_from_json_fn(
|
|
|
750
880
|
builder.append_ln(f" array_len = {field.field.number + 1}")
|
|
751
881
|
builder.append_ln(
|
|
752
882
|
f" {lvalue} = ",
|
|
753
|
-
field.type.from_json_expr(f'json["{name}"]'),
|
|
883
|
+
field.type.from_json_expr(f'json["{name}"]', "keep_unrecognized_fields"),
|
|
754
884
|
)
|
|
755
885
|
builder.append_ln(" else:")
|
|
756
886
|
builder.append_ln(f" {lvalue} = ", field.type.default_expr())
|
|
757
887
|
# Drop unrecognized fields in readable mode.
|
|
758
|
-
builder.append_ln(" ret._unrecognized =
|
|
888
|
+
builder.append_ln(" ret._unrecognized = None")
|
|
759
889
|
builder.append_ln(" ret._array_len = array_len")
|
|
760
890
|
|
|
761
891
|
builder.append_ln("ret.__class__ = ", Expr.local("Frozen", frozen_class))
|
|
@@ -763,60 +893,123 @@ def _make_from_json_fn(
|
|
|
763
893
|
|
|
764
894
|
return make_function(
|
|
765
895
|
name="from_json",
|
|
766
|
-
params=["json"],
|
|
896
|
+
params=["json", "keep_unrecognized_fields"],
|
|
767
897
|
body=builder.build(),
|
|
768
898
|
)
|
|
769
899
|
|
|
770
900
|
|
|
771
|
-
def
|
|
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(
|
|
953
|
+
" ", Expr.local("decode_unused", decode_unused), "(stream)"
|
|
954
|
+
)
|
|
955
|
+
builder.append_ln(" start_offset = stream.position")
|
|
956
|
+
builder.append_ln(" for _ in range(array_len - {num_slots_incl_removed}):")
|
|
957
|
+
builder.append_ln(" ", Expr.local("decode_unused", decode_unused), "(stream)")
|
|
958
|
+
builder.append_ln(" end_offset = stream.position")
|
|
959
|
+
builder.append_ln(
|
|
960
|
+
" ret._unrecognized = ",
|
|
961
|
+
Expr.local("UnrecognizedFields", _UnrecognizedFields.from_bytes),
|
|
962
|
+
"(raw_bytes=stream.buffer[start_offset:end_offset], adjusted_bytes_array_len=array_len)",
|
|
963
|
+
)
|
|
964
|
+
builder.append_ln(" else:")
|
|
965
|
+
builder.append_ln(f" for _ in range(array_len - {num_slots_excl_removed}):")
|
|
966
|
+
builder.append_ln(" ", Expr.local("decode_unused", decode_unused), "(stream)")
|
|
967
|
+
builder.append_ln(" ret._unrecognized = None")
|
|
968
|
+
builder.append_ln("else:")
|
|
969
|
+
builder.append_ln(" ret._unrecognized = None")
|
|
970
|
+
|
|
971
|
+
builder.append_ln(
|
|
972
|
+
" ret._array_len = ",
|
|
973
|
+
_adjust_array_len_expr(
|
|
974
|
+
"array_len",
|
|
975
|
+
removed_numbers=removed_numbers,
|
|
976
|
+
num_slots_excl_removed=num_slots_excl_removed,
|
|
977
|
+
),
|
|
978
|
+
)
|
|
979
|
+
|
|
980
|
+
builder.append_ln(
|
|
981
|
+
"ret.__class__ = ",
|
|
982
|
+
Expr.local("Frozen", frozen_class),
|
|
983
|
+
)
|
|
984
|
+
builder.append_ln("return ret")
|
|
985
|
+
return make_function(
|
|
986
|
+
name="decode",
|
|
987
|
+
params=["stream"],
|
|
988
|
+
body=builder.build(),
|
|
989
|
+
)
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
def _adjust_array_len_expr(
|
|
993
|
+
var: str,
|
|
994
|
+
removed_numbers: tuple[int, ...],
|
|
995
|
+
num_slots_excl_removed: int,
|
|
996
|
+
) -> str:
|
|
772
997
|
"""
|
|
773
998
|
When parsing a dense JSON or decoding a binary string, we can reuse the array length
|
|
774
999
|
in the decoded struct, but we need to account for possibly newly-removed fields. The
|
|
775
1000
|
last field of the adjusted array length cannot be a removed field.
|
|
776
|
-
|
|
777
|
-
Let's imagine that field number 3 was removed from a struct.
|
|
778
|
-
This function would return the following expression:
|
|
779
|
-
array_len if array_len <= 3 else 3 if array_len == 4 else array_len
|
|
780
1001
|
"""
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
begin: int = 0
|
|
788
|
-
# Number after the last removed field.
|
|
789
|
-
end: int = 0
|
|
790
|
-
|
|
791
|
-
def get_removed_spans() -> list[_RemovedSpan]:
|
|
792
|
-
ret: list[_RemovedSpan] = []
|
|
793
|
-
for number in sorted(removed_numbers):
|
|
794
|
-
last = ret[-1] if ret else None
|
|
795
|
-
if last and last.end == number:
|
|
796
|
-
last.end = number + 1
|
|
797
|
-
else:
|
|
798
|
-
ret.append(_RemovedSpan(number, number + 1))
|
|
799
|
-
return ret
|
|
800
|
-
|
|
801
|
-
removed_spans = get_removed_spans()
|
|
802
|
-
|
|
803
|
-
ret = ""
|
|
804
|
-
lower_bound = 0
|
|
805
|
-
for s in removed_spans:
|
|
806
|
-
if s.begin == lower_bound:
|
|
807
|
-
ret += f"{s.begin} if {var} <= {s.end} else "
|
|
808
|
-
elif s.end == s.begin + 1:
|
|
809
|
-
# Similar to the expression in 'else' but uses '==' instead of '<='
|
|
810
|
-
ret += (
|
|
811
|
-
f"{var} if {var} <= {s.begin} else {s.begin} if {var} == {s.end} else "
|
|
812
|
-
)
|
|
1002
|
+
removed_numbers_set = set(removed_numbers)
|
|
1003
|
+
table: list[int] = [0]
|
|
1004
|
+
last_len = 0
|
|
1005
|
+
for i in range(0, num_slots_excl_removed - 1):
|
|
1006
|
+
if i in removed_numbers_set:
|
|
1007
|
+
table.append(last_len)
|
|
813
1008
|
else:
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
ret += var
|
|
819
|
-
return ret
|
|
1009
|
+
table.append(i + 1)
|
|
1010
|
+
last_len = i + 1
|
|
1011
|
+
table_tuple = tuple(table)
|
|
1012
|
+
return f"{table_tuple}[{var}] if {var} < {num_slots_excl_removed} else {num_slots_excl_removed}"
|
|
820
1013
|
|
|
821
1014
|
|
|
822
1015
|
def _init_default(default: Any, fields: Sequence[_Field]) -> None:
|
|
@@ -828,7 +1021,7 @@ def _init_default(default: Any, fields: Sequence[_Field]) -> None:
|
|
|
828
1021
|
body=(Line.join("return ", field.type.default_expr()),),
|
|
829
1022
|
)
|
|
830
1023
|
object.__setattr__(default, attribute, get_field_default())
|
|
831
|
-
object.__setattr__(default, "_unrecognized",
|
|
1024
|
+
object.__setattr__(default, "_unrecognized", None)
|
|
832
1025
|
object.__setattr__(default, "_array_len", 0)
|
|
833
1026
|
|
|
834
1027
|
|
|
@@ -876,5 +1069,38 @@ def _name_private_is_frozen_attr(record_id: str) -> str:
|
|
|
876
1069
|
return f"_is_{record_name}_{hex_hash}"
|
|
877
1070
|
|
|
878
1071
|
|
|
879
|
-
|
|
880
|
-
|
|
1072
|
+
@dataclass(frozen=True)
|
|
1073
|
+
class _UnrecognizedFields:
|
|
1074
|
+
__slots__ = (
|
|
1075
|
+
"json",
|
|
1076
|
+
"raw_bytes",
|
|
1077
|
+
"adjusted_json_array_len",
|
|
1078
|
+
"adjusted_bytes_array_len",
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
json: list[Any] | None
|
|
1082
|
+
raw_bytes: bytes | None
|
|
1083
|
+
adjusted_json_array_len: int | None
|
|
1084
|
+
adjusted_bytes_array_len: int | None
|
|
1085
|
+
|
|
1086
|
+
@staticmethod
|
|
1087
|
+
def from_json(
|
|
1088
|
+
json: list[Any], adjusted_json_array_len: int
|
|
1089
|
+
) -> "_UnrecognizedFields":
|
|
1090
|
+
return _UnrecognizedFields(
|
|
1091
|
+
json=json,
|
|
1092
|
+
raw_bytes=None,
|
|
1093
|
+
adjusted_json_array_len=adjusted_json_array_len,
|
|
1094
|
+
adjusted_bytes_array_len=None,
|
|
1095
|
+
)
|
|
1096
|
+
|
|
1097
|
+
@staticmethod
|
|
1098
|
+
def from_bytes(
|
|
1099
|
+
raw_bytes: bytes, adjusted_bytes_array_len: int
|
|
1100
|
+
) -> "_UnrecognizedFields":
|
|
1101
|
+
return _UnrecognizedFields(
|
|
1102
|
+
json=None,
|
|
1103
|
+
raw_bytes=raw_bytes,
|
|
1104
|
+
adjusted_json_array_len=None,
|
|
1105
|
+
adjusted_bytes_array_len=adjusted_bytes_array_len,
|
|
1106
|
+
)
|
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
|
|