soia-client 1.1.5__py3-none-any.whl → 1.1.20__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.
- soia/_impl/arrays.py +61 -10
- soia/_impl/binary.py +270 -0
- soia/_impl/enums.py +224 -40
- soia/_impl/optionals.py +47 -8
- soia/_impl/primitives.py +245 -33
- soia/_impl/serializer.py +40 -16
- soia/_impl/service.py +1 -3
- soia/_impl/structs.py +314 -89
- soia/_impl/timestamp.py +4 -5
- soia/_impl/type_adapter.py +37 -3
- soia/reflection.py +4 -4
- {soia_client-1.1.5.dist-info → soia_client-1.1.20.dist-info}/METADATA +1 -1
- soia_client-1.1.20.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.20.dist-info}/WHEEL +0 -0
- {soia_client-1.1.5.dist-info → soia_client-1.1.20.dist-info}/licenses/LICENSE +0 -0
- {soia_client-1.1.5.dist-info → soia_client-1.1.20.dist-info}/top_level.txt +0 -0
soia/_impl/primitives.py
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
import base64
|
|
2
|
+
import math
|
|
3
|
+
import struct
|
|
2
4
|
from collections.abc import Callable
|
|
3
5
|
from dataclasses import dataclass
|
|
4
6
|
from typing import Any, Final, final
|
|
5
7
|
|
|
6
8
|
from soia import _spec, reflection
|
|
9
|
+
from soia._impl.binary import (
|
|
10
|
+
decode_bool,
|
|
11
|
+
decode_float,
|
|
12
|
+
decode_int32,
|
|
13
|
+
decode_int64,
|
|
14
|
+
decode_uint64,
|
|
15
|
+
encode_float32,
|
|
16
|
+
encode_float64,
|
|
17
|
+
encode_int32,
|
|
18
|
+
encode_int64,
|
|
19
|
+
encode_length_prefix,
|
|
20
|
+
encode_uint64,
|
|
21
|
+
)
|
|
7
22
|
from soia._impl.function_maker import Expr, ExprLike
|
|
8
23
|
from soia._impl.timestamp import Timestamp
|
|
9
|
-
from soia._impl.type_adapter import TypeAdapter
|
|
24
|
+
from soia._impl.type_adapter import ByteStream, T, TypeAdapter
|
|
10
25
|
|
|
11
26
|
|
|
12
|
-
class AbstractPrimitiveAdapter(TypeAdapter):
|
|
27
|
+
class AbstractPrimitiveAdapter(TypeAdapter[T]):
|
|
13
28
|
@final
|
|
14
29
|
def finalize(
|
|
15
30
|
self,
|
|
@@ -29,7 +44,7 @@ class AbstractPrimitiveAdapter(TypeAdapter):
|
|
|
29
44
|
return None
|
|
30
45
|
|
|
31
46
|
|
|
32
|
-
class _BoolAdapter(AbstractPrimitiveAdapter):
|
|
47
|
+
class _BoolAdapter(AbstractPrimitiveAdapter[bool]):
|
|
33
48
|
def default_expr(self) -> ExprLike:
|
|
34
49
|
return "False"
|
|
35
50
|
|
|
@@ -49,8 +64,23 @@ class _BoolAdapter(AbstractPrimitiveAdapter):
|
|
|
49
64
|
else:
|
|
50
65
|
return Expr.join("(1 if ", in_expr, " else 0)")
|
|
51
66
|
|
|
52
|
-
def from_json_expr(
|
|
53
|
-
|
|
67
|
+
def from_json_expr(
|
|
68
|
+
self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
|
|
69
|
+
) -> Expr:
|
|
70
|
+
return Expr.join("(", json_expr, " not in (0, '0'))")
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def encode(
|
|
74
|
+
value: bool,
|
|
75
|
+
buffer: bytearray,
|
|
76
|
+
) -> None:
|
|
77
|
+
buffer.append(1 if value else 0)
|
|
78
|
+
|
|
79
|
+
def encode_fn(self) -> Callable[[bool, bytearray], None]:
|
|
80
|
+
return _BoolAdapter.encode
|
|
81
|
+
|
|
82
|
+
def decode_fn(self) -> Callable[[ByteStream], bool]:
|
|
83
|
+
return decode_bool
|
|
54
84
|
|
|
55
85
|
def get_type(self) -> reflection.Type:
|
|
56
86
|
return reflection.PrimitiveType(
|
|
@@ -59,11 +89,11 @@ class _BoolAdapter(AbstractPrimitiveAdapter):
|
|
|
59
89
|
)
|
|
60
90
|
|
|
61
91
|
|
|
62
|
-
BOOL_ADAPTER: Final[TypeAdapter] = _BoolAdapter()
|
|
92
|
+
BOOL_ADAPTER: Final[TypeAdapter[bool]] = _BoolAdapter()
|
|
63
93
|
|
|
64
94
|
|
|
65
95
|
@dataclass(frozen=True)
|
|
66
|
-
class _AbstractIntAdapter(AbstractPrimitiveAdapter):
|
|
96
|
+
class _AbstractIntAdapter(AbstractPrimitiveAdapter[int]):
|
|
67
97
|
"""Type adapter implementation for int32, int64 and uint64."""
|
|
68
98
|
|
|
69
99
|
def default_expr(self) -> ExprLike:
|
|
@@ -76,7 +106,9 @@ class _AbstractIntAdapter(AbstractPrimitiveAdapter):
|
|
|
76
106
|
def is_not_default_expr(self, arg_expr: ExprLike, attr_expr: ExprLike) -> ExprLike:
|
|
77
107
|
return arg_expr
|
|
78
108
|
|
|
79
|
-
def from_json_expr(
|
|
109
|
+
def from_json_expr(
|
|
110
|
+
self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
|
|
111
|
+
) -> Expr:
|
|
80
112
|
# Must accept float inputs and string inputs and turn them into ints.
|
|
81
113
|
return Expr.join(
|
|
82
114
|
"(0).__class__(",
|
|
@@ -98,6 +130,12 @@ class _Int32Adapter(_AbstractIntAdapter):
|
|
|
98
130
|
" < 2147483647 else 2147483647)",
|
|
99
131
|
)
|
|
100
132
|
|
|
133
|
+
def encode_fn(self) -> Callable[[int, bytearray], None]:
|
|
134
|
+
return encode_int32
|
|
135
|
+
|
|
136
|
+
def decode_fn(self) -> Callable[[ByteStream], int]:
|
|
137
|
+
return decode_int32
|
|
138
|
+
|
|
101
139
|
def get_type(self) -> reflection.Type:
|
|
102
140
|
return reflection.PrimitiveType(
|
|
103
141
|
kind="primitive",
|
|
@@ -124,6 +162,12 @@ class _Int64Adapter(_AbstractIntAdapter):
|
|
|
124
162
|
def to_json_expr(self, in_expr: ExprLike, readable: bool) -> Expr:
|
|
125
163
|
return Expr.join(Expr.local("int64_to_json", _int64_to_json), "(", in_expr, ")")
|
|
126
164
|
|
|
165
|
+
def encode_fn(self) -> Callable[[int, bytearray], None]:
|
|
166
|
+
return encode_int64
|
|
167
|
+
|
|
168
|
+
def decode_fn(self) -> Callable[[ByteStream], int]:
|
|
169
|
+
return decode_int64
|
|
170
|
+
|
|
127
171
|
def get_type(self) -> reflection.Type:
|
|
128
172
|
return reflection.PrimitiveType(
|
|
129
173
|
kind="primitive",
|
|
@@ -149,6 +193,12 @@ class _Uint64Adapter(_AbstractIntAdapter):
|
|
|
149
193
|
Expr.local("uint64_to_json", _uint64_to_json), "(", in_expr, ")"
|
|
150
194
|
)
|
|
151
195
|
|
|
196
|
+
def encode_fn(self) -> Callable[[int, bytearray], None]:
|
|
197
|
+
return encode_uint64
|
|
198
|
+
|
|
199
|
+
def decode_fn(self) -> Callable[[ByteStream], int]:
|
|
200
|
+
return decode_uint64
|
|
201
|
+
|
|
152
202
|
def get_type(self) -> reflection.Type:
|
|
153
203
|
return reflection.PrimitiveType(
|
|
154
204
|
kind="primitive",
|
|
@@ -156,13 +206,27 @@ class _Uint64Adapter(_AbstractIntAdapter):
|
|
|
156
206
|
)
|
|
157
207
|
|
|
158
208
|
|
|
159
|
-
INT32_ADAPTER: Final[TypeAdapter] = _Int32Adapter()
|
|
160
|
-
INT64_ADAPTER: Final[TypeAdapter] = _Int64Adapter()
|
|
161
|
-
UINT64_ADAPTER: Final[TypeAdapter] = _Uint64Adapter()
|
|
209
|
+
INT32_ADAPTER: Final[TypeAdapter[int]] = _Int32Adapter()
|
|
210
|
+
INT64_ADAPTER: Final[TypeAdapter[int]] = _Int64Adapter()
|
|
211
|
+
UINT64_ADAPTER: Final[TypeAdapter[int]] = _Uint64Adapter()
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
_SPECIAL_FLOAT_TO_STRING: Final[dict[str, str]] = {
|
|
215
|
+
"nan": "NaN",
|
|
216
|
+
"inf": "Infinity",
|
|
217
|
+
"-inf": "-Infinity",
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
_STRING_TO_SPECIAL_FLOAT: Final[dict[str, float]] = {
|
|
222
|
+
"NaN": float("nan"),
|
|
223
|
+
"Infinity": float("inf"),
|
|
224
|
+
"-Infinity": float("-inf"),
|
|
225
|
+
}
|
|
162
226
|
|
|
163
227
|
|
|
164
228
|
@dataclass(frozen=True)
|
|
165
|
-
class _AbstractFloatAdapter(AbstractPrimitiveAdapter):
|
|
229
|
+
class _AbstractFloatAdapter(AbstractPrimitiveAdapter[float]):
|
|
166
230
|
"""Type adapter implementation for float32 and float64."""
|
|
167
231
|
|
|
168
232
|
def default_expr(self) -> ExprLike:
|
|
@@ -175,16 +239,52 @@ class _AbstractFloatAdapter(AbstractPrimitiveAdapter):
|
|
|
175
239
|
return arg_expr
|
|
176
240
|
|
|
177
241
|
def to_json_expr(self, in_expr: ExprLike, readable: bool) -> ExprLike:
|
|
178
|
-
return
|
|
242
|
+
return Expr.join(
|
|
243
|
+
"(",
|
|
244
|
+
in_expr,
|
|
245
|
+
" if ",
|
|
246
|
+
Expr.local("_isfinite", math.isfinite),
|
|
247
|
+
"(",
|
|
248
|
+
in_expr,
|
|
249
|
+
") else ",
|
|
250
|
+
Expr.local(
|
|
251
|
+
"_SPECIAL_FLOAT_TO_STRING",
|
|
252
|
+
_SPECIAL_FLOAT_TO_STRING,
|
|
253
|
+
),
|
|
254
|
+
"[f'{",
|
|
255
|
+
in_expr,
|
|
256
|
+
"}'])",
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
def from_json_expr(
|
|
260
|
+
self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
|
|
261
|
+
) -> Expr:
|
|
262
|
+
return Expr.join(
|
|
263
|
+
"(",
|
|
264
|
+
Expr.local(
|
|
265
|
+
"_STRING_TO_SPECIAL_FLOAT",
|
|
266
|
+
_STRING_TO_SPECIAL_FLOAT,
|
|
267
|
+
),
|
|
268
|
+
"[",
|
|
269
|
+
json_expr,
|
|
270
|
+
"] if ",
|
|
271
|
+
json_expr,
|
|
272
|
+
" in ('NaN', 'Infinity', '-Infinity') else (",
|
|
273
|
+
json_expr,
|
|
274
|
+
" + 0.0))",
|
|
275
|
+
)
|
|
179
276
|
|
|
180
|
-
def
|
|
181
|
-
return
|
|
277
|
+
def decode_fn(self) -> Callable[[ByteStream], float]:
|
|
278
|
+
return decode_float
|
|
182
279
|
|
|
183
280
|
|
|
184
281
|
@dataclass(frozen=True)
|
|
185
282
|
class _Float32Adapter(_AbstractFloatAdapter):
|
|
186
283
|
"""Type adapter implementation for float32."""
|
|
187
284
|
|
|
285
|
+
def encode_fn(self) -> Callable[[float, bytearray], None]:
|
|
286
|
+
return encode_float32
|
|
287
|
+
|
|
188
288
|
def get_type(self) -> reflection.Type:
|
|
189
289
|
return reflection.PrimitiveType(
|
|
190
290
|
kind="primitive",
|
|
@@ -194,7 +294,10 @@ class _Float32Adapter(_AbstractFloatAdapter):
|
|
|
194
294
|
|
|
195
295
|
@dataclass(frozen=True)
|
|
196
296
|
class _Float64Adapter(_AbstractFloatAdapter):
|
|
197
|
-
"""Type adapter implementation for
|
|
297
|
+
"""Type adapter implementation for float64."""
|
|
298
|
+
|
|
299
|
+
def encode_fn(self) -> Callable[[float, bytearray], None]:
|
|
300
|
+
return encode_float64
|
|
198
301
|
|
|
199
302
|
def get_type(self) -> reflection.Type:
|
|
200
303
|
return reflection.PrimitiveType(
|
|
@@ -203,11 +306,16 @@ class _Float64Adapter(_AbstractFloatAdapter):
|
|
|
203
306
|
)
|
|
204
307
|
|
|
205
308
|
|
|
206
|
-
FLOAT32_ADAPTER: Final[TypeAdapter] = _Float32Adapter()
|
|
207
|
-
FLOAT64_ADAPTER: Final[TypeAdapter] = _Float64Adapter()
|
|
309
|
+
FLOAT32_ADAPTER: Final[TypeAdapter[float]] = _Float32Adapter()
|
|
310
|
+
FLOAT64_ADAPTER: Final[TypeAdapter[float]] = _Float64Adapter()
|
|
311
|
+
|
|
208
312
|
|
|
313
|
+
def _clamp_unix_millis(unix_millis: int) -> int:
|
|
314
|
+
"""Clamp unix milliseconds to valid range for JavaScript dates."""
|
|
315
|
+
return max(-8640000000000000, min(unix_millis, 8640000000000000))
|
|
209
316
|
|
|
210
|
-
|
|
317
|
+
|
|
318
|
+
class _TimestampAdapter(AbstractPrimitiveAdapter[Timestamp]):
|
|
211
319
|
def default_expr(self) -> Expr:
|
|
212
320
|
return Expr.local("_EPOCH", Timestamp.EPOCH)
|
|
213
321
|
|
|
@@ -236,10 +344,34 @@ class _TimestampAdapter(AbstractPrimitiveAdapter):
|
|
|
236
344
|
else:
|
|
237
345
|
return Expr.join(in_expr, ".unix_millis")
|
|
238
346
|
|
|
239
|
-
def from_json_expr(
|
|
347
|
+
def from_json_expr(
|
|
348
|
+
self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
|
|
349
|
+
) -> Expr:
|
|
240
350
|
fn = Expr.local("_timestamp_from_json", _timestamp_from_json)
|
|
241
351
|
return Expr.join(fn, "(", json_expr, ")")
|
|
242
352
|
|
|
353
|
+
@staticmethod
|
|
354
|
+
def encode(
|
|
355
|
+
value: Timestamp,
|
|
356
|
+
buffer: bytearray,
|
|
357
|
+
) -> None:
|
|
358
|
+
unix_millis = _clamp_unix_millis(value.unix_millis)
|
|
359
|
+
if unix_millis == 0:
|
|
360
|
+
buffer.append(0)
|
|
361
|
+
else:
|
|
362
|
+
buffer.append(239)
|
|
363
|
+
buffer.extend(struct.pack("<q", unix_millis))
|
|
364
|
+
|
|
365
|
+
def encode_fn(self) -> Callable[[Timestamp, bytearray], None]:
|
|
366
|
+
return _TimestampAdapter.encode
|
|
367
|
+
|
|
368
|
+
@staticmethod
|
|
369
|
+
def decode(stream: ByteStream) -> Timestamp:
|
|
370
|
+
return Timestamp(unix_millis=_clamp_unix_millis(decode_int64(stream)))
|
|
371
|
+
|
|
372
|
+
def decode_fn(self) -> Callable[[ByteStream], Timestamp]:
|
|
373
|
+
return _TimestampAdapter.decode
|
|
374
|
+
|
|
243
375
|
def get_type(self) -> reflection.Type:
|
|
244
376
|
return reflection.PrimitiveType(
|
|
245
377
|
kind="primitive",
|
|
@@ -250,14 +382,16 @@ class _TimestampAdapter(AbstractPrimitiveAdapter):
|
|
|
250
382
|
def _timestamp_from_json(json: Any) -> Timestamp:
|
|
251
383
|
if json.__class__ is int or isinstance(json, int):
|
|
252
384
|
return Timestamp(unix_millis=json)
|
|
385
|
+
elif isinstance(json, float) or isinstance(json, str):
|
|
386
|
+
return Timestamp(unix_millis=int(json))
|
|
253
387
|
else:
|
|
254
388
|
return Timestamp(unix_millis=json["unix_millis"])
|
|
255
389
|
|
|
256
390
|
|
|
257
|
-
TIMESTAMP_ADAPTER: Final[TypeAdapter] = _TimestampAdapter()
|
|
391
|
+
TIMESTAMP_ADAPTER: Final[TypeAdapter[Timestamp]] = _TimestampAdapter()
|
|
258
392
|
|
|
259
393
|
|
|
260
|
-
class _StringAdapter(AbstractPrimitiveAdapter):
|
|
394
|
+
class _StringAdapter(AbstractPrimitiveAdapter[str]):
|
|
261
395
|
def default_expr(self) -> ExprLike:
|
|
262
396
|
return '""'
|
|
263
397
|
|
|
@@ -270,9 +404,42 @@ class _StringAdapter(AbstractPrimitiveAdapter):
|
|
|
270
404
|
def to_json_expr(self, in_expr: ExprLike, readable: bool) -> ExprLike:
|
|
271
405
|
return in_expr
|
|
272
406
|
|
|
273
|
-
def from_json_expr(
|
|
407
|
+
def from_json_expr(
|
|
408
|
+
self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
|
|
409
|
+
) -> Expr:
|
|
274
410
|
return Expr.join("('' + (", json_expr, " or ''))")
|
|
275
411
|
|
|
412
|
+
@staticmethod
|
|
413
|
+
def encode(
|
|
414
|
+
value: str,
|
|
415
|
+
buffer: bytearray,
|
|
416
|
+
) -> None:
|
|
417
|
+
if not value:
|
|
418
|
+
buffer.append(242)
|
|
419
|
+
else:
|
|
420
|
+
buffer.append(243)
|
|
421
|
+
bytes_data = value.encode("utf-8")
|
|
422
|
+
length = len(bytes_data)
|
|
423
|
+
encode_length_prefix(length, buffer)
|
|
424
|
+
buffer.extend(bytes_data)
|
|
425
|
+
|
|
426
|
+
def encode_fn(self) -> Callable[[str, bytearray], None]:
|
|
427
|
+
return _StringAdapter.encode
|
|
428
|
+
|
|
429
|
+
@staticmethod
|
|
430
|
+
def decode(stream: ByteStream) -> str:
|
|
431
|
+
wire = stream.read(1)[0]
|
|
432
|
+
if wire in (0, 242):
|
|
433
|
+
return ""
|
|
434
|
+
else:
|
|
435
|
+
# Should be wire 243
|
|
436
|
+
length = decode_int64(stream)
|
|
437
|
+
bytes_data = stream.read(length)
|
|
438
|
+
return bytes_data.decode("utf-8")
|
|
439
|
+
|
|
440
|
+
def decode_fn(self) -> Callable[[ByteStream], str]:
|
|
441
|
+
return _StringAdapter.decode
|
|
442
|
+
|
|
276
443
|
def get_type(self) -> reflection.Type:
|
|
277
444
|
return reflection.PrimitiveType(
|
|
278
445
|
kind="primitive",
|
|
@@ -283,7 +450,14 @@ class _StringAdapter(AbstractPrimitiveAdapter):
|
|
|
283
450
|
STRING_ADAPTER: Final[TypeAdapter] = _StringAdapter()
|
|
284
451
|
|
|
285
452
|
|
|
286
|
-
|
|
453
|
+
def _bytes_from_json(json: str) -> bytes:
|
|
454
|
+
if json.startswith("hex:"):
|
|
455
|
+
return bytes.fromhex(json[4:])
|
|
456
|
+
else:
|
|
457
|
+
return base64.b64decode(json)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
class _BytesAdapter(AbstractPrimitiveAdapter[bytes]):
|
|
287
461
|
def default_expr(self) -> ExprLike:
|
|
288
462
|
return 'b""'
|
|
289
463
|
|
|
@@ -297,18 +471,56 @@ class _BytesAdapter(AbstractPrimitiveAdapter):
|
|
|
297
471
|
self,
|
|
298
472
|
in_expr: ExprLike,
|
|
299
473
|
readable: bool,
|
|
474
|
+
) -> Expr:
|
|
475
|
+
if readable:
|
|
476
|
+
return Expr.join(
|
|
477
|
+
"('hex:' + ",
|
|
478
|
+
in_expr,
|
|
479
|
+
".hex())",
|
|
480
|
+
)
|
|
481
|
+
else:
|
|
482
|
+
return Expr.join(
|
|
483
|
+
Expr.local("b64encode", base64.b64encode),
|
|
484
|
+
"(",
|
|
485
|
+
in_expr,
|
|
486
|
+
").decode('utf-8')",
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
def from_json_expr(
|
|
490
|
+
self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
|
|
300
491
|
) -> Expr:
|
|
301
492
|
return Expr.join(
|
|
302
|
-
Expr.local("
|
|
303
|
-
"(",
|
|
304
|
-
in_expr,
|
|
305
|
-
").decode('utf-8')",
|
|
493
|
+
Expr.local("bytes_from_json", _bytes_from_json), "(", json_expr, ' or "")'
|
|
306
494
|
)
|
|
307
495
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
496
|
+
@staticmethod
|
|
497
|
+
def encode(
|
|
498
|
+
value: bytes,
|
|
499
|
+
buffer: bytearray,
|
|
500
|
+
) -> None:
|
|
501
|
+
if len(value) == 0:
|
|
502
|
+
buffer.append(244)
|
|
503
|
+
else:
|
|
504
|
+
buffer.append(245)
|
|
505
|
+
length = len(value)
|
|
506
|
+
encode_length_prefix(length, buffer)
|
|
507
|
+
buffer.extend(value)
|
|
508
|
+
|
|
509
|
+
def encode_fn(self) -> Callable[[bytes, bytearray], None]:
|
|
510
|
+
return _BytesAdapter.encode
|
|
511
|
+
|
|
512
|
+
@staticmethod
|
|
513
|
+
def decode(stream: ByteStream) -> bytes:
|
|
514
|
+
wire = stream.read_wire()
|
|
515
|
+
if wire in (0, 244):
|
|
516
|
+
return b""
|
|
517
|
+
else:
|
|
518
|
+
# Should be wire 245
|
|
519
|
+
length = decode_int64(stream)
|
|
520
|
+
return stream.read(length)
|
|
521
|
+
|
|
522
|
+
def decode_fn(self) -> Callable[[ByteStream], bytes]:
|
|
523
|
+
return _BytesAdapter.decode
|
|
312
524
|
|
|
313
525
|
def get_type(self) -> reflection.Type:
|
|
314
526
|
return reflection.PrimitiveType(
|
|
@@ -317,4 +529,4 @@ class _BytesAdapter(AbstractPrimitiveAdapter):
|
|
|
317
529
|
)
|
|
318
530
|
|
|
319
531
|
|
|
320
|
-
BYTES_ADAPTER: Final[TypeAdapter] = _BytesAdapter()
|
|
532
|
+
BYTES_ADAPTER: Final[TypeAdapter[bytes]] = _BytesAdapter()
|
soia/_impl/serializer.py
CHANGED
|
@@ -8,7 +8,7 @@ from weakref import WeakValueDictionary
|
|
|
8
8
|
from soia import reflection
|
|
9
9
|
from soia._impl.function_maker import Expr, LineSpan, make_function
|
|
10
10
|
from soia._impl.never import Never
|
|
11
|
-
from soia._impl.type_adapter import TypeAdapter
|
|
11
|
+
from soia._impl.type_adapter import ByteStream, TypeAdapter
|
|
12
12
|
|
|
13
13
|
T = TypeVar("T")
|
|
14
14
|
|
|
@@ -21,26 +21,33 @@ class Serializer(Generic[T]):
|
|
|
21
21
|
"_to_dense_json_fn",
|
|
22
22
|
"_to_readable_json_fn",
|
|
23
23
|
"_from_json_fn",
|
|
24
|
+
"_encode_fn",
|
|
25
|
+
"_decode_fn",
|
|
24
26
|
"__dict__",
|
|
25
27
|
)
|
|
26
28
|
|
|
27
|
-
_adapter: TypeAdapter
|
|
29
|
+
_adapter: TypeAdapter[T]
|
|
28
30
|
_to_dense_json_fn: Callable[[T], Any]
|
|
29
31
|
_to_readable_json_fn: Callable[[T], Any]
|
|
30
|
-
_from_json_fn: Callable[[Any], T]
|
|
32
|
+
_from_json_fn: Callable[[Any, bool], T]
|
|
33
|
+
_encode_fn: Callable[[T, bytearray], None]
|
|
34
|
+
_decode_fn: Callable[[ByteStream], T]
|
|
31
35
|
|
|
32
36
|
def __init__(self, adapter: Never):
|
|
33
37
|
# Use Never (^) as a trick to make the constructor internal.
|
|
34
|
-
|
|
38
|
+
as_adapter = cast(TypeAdapter[T], adapter)
|
|
39
|
+
object.__setattr__(self, "_adapter", as_adapter)
|
|
35
40
|
object.__setattr__(
|
|
36
|
-
self, "_to_dense_json_fn", _make_to_json_fn(
|
|
41
|
+
self, "_to_dense_json_fn", _make_to_json_fn(as_adapter, readable=False)
|
|
37
42
|
)
|
|
38
43
|
object.__setattr__(
|
|
39
44
|
self,
|
|
40
45
|
"_to_readable_json_fn",
|
|
41
|
-
_make_to_json_fn(
|
|
46
|
+
_make_to_json_fn(as_adapter, readable=True),
|
|
42
47
|
)
|
|
43
|
-
object.__setattr__(self, "_from_json_fn", _make_from_json_fn(
|
|
48
|
+
object.__setattr__(self, "_from_json_fn", _make_from_json_fn(as_adapter))
|
|
49
|
+
object.__setattr__(self, "_encode_fn", as_adapter.encode_fn())
|
|
50
|
+
object.__setattr__(self, "_decode_fn", as_adapter.decode_fn())
|
|
44
51
|
|
|
45
52
|
def to_json(self, input: T, *, readable=False) -> Any:
|
|
46
53
|
if readable:
|
|
@@ -54,11 +61,26 @@ class Serializer(Generic[T]):
|
|
|
54
61
|
else:
|
|
55
62
|
return jsonlib.dumps(self._to_dense_json_fn(input), separators=(",", ":"))
|
|
56
63
|
|
|
57
|
-
def from_json(self, json: Any) -> T:
|
|
58
|
-
return self._from_json_fn(json)
|
|
64
|
+
def from_json(self, json: Any, keep_unrecognized_fields: bool = False) -> T:
|
|
65
|
+
return self._from_json_fn(json, keep_unrecognized_fields)
|
|
59
66
|
|
|
60
|
-
def from_json_code(
|
|
61
|
-
|
|
67
|
+
def from_json_code(
|
|
68
|
+
self, json_code: str, keep_unrecognized_fields: bool = False
|
|
69
|
+
) -> T:
|
|
70
|
+
return self._from_json_fn(jsonlib.loads(json_code), keep_unrecognized_fields)
|
|
71
|
+
|
|
72
|
+
def to_bytes(self, input: T) -> bytes:
|
|
73
|
+
buffer = bytearray(b"soia")
|
|
74
|
+
self._encode_fn(input, buffer)
|
|
75
|
+
return bytes(buffer)
|
|
76
|
+
|
|
77
|
+
def from_bytes(self, bytes: bytes, keep_unrecognized_fields: bool = False) -> T:
|
|
78
|
+
if bytes.startswith(b"soia"):
|
|
79
|
+
stream = ByteStream(
|
|
80
|
+
bytes, position=4, keep_unrecognized_fields=keep_unrecognized_fields
|
|
81
|
+
)
|
|
82
|
+
return self._decode_fn(stream)
|
|
83
|
+
return self.from_json_code(bytes.decode("utf-8"))
|
|
62
84
|
|
|
63
85
|
@cached_property
|
|
64
86
|
def type_descriptor(self) -> reflection.TypeDescriptor:
|
|
@@ -82,13 +104,13 @@ _type_adapter_to_serializer: WeakValueDictionary[TypeAdapter, Serializer] = (
|
|
|
82
104
|
)
|
|
83
105
|
|
|
84
106
|
|
|
85
|
-
def make_serializer(adapter: TypeAdapter) -> Serializer:
|
|
107
|
+
def make_serializer(adapter: TypeAdapter[T]) -> Serializer[T]:
|
|
86
108
|
return _type_adapter_to_serializer.setdefault(
|
|
87
109
|
adapter, Serializer(cast(Never, adapter))
|
|
88
110
|
)
|
|
89
111
|
|
|
90
112
|
|
|
91
|
-
def _make_to_json_fn(adapter: TypeAdapter, readable: bool) -> Callable[[
|
|
113
|
+
def _make_to_json_fn(adapter: TypeAdapter[T], readable: bool) -> Callable[[T], Any]:
|
|
92
114
|
return make_function(
|
|
93
115
|
name="to_json",
|
|
94
116
|
params=["input"],
|
|
@@ -104,11 +126,13 @@ def _make_to_json_fn(adapter: TypeAdapter, readable: bool) -> Callable[[Any], An
|
|
|
104
126
|
)
|
|
105
127
|
|
|
106
128
|
|
|
107
|
-
def _make_from_json_fn(adapter: TypeAdapter) -> Callable[[Any],
|
|
129
|
+
def _make_from_json_fn(adapter: TypeAdapter[T]) -> Callable[[Any, bool], T]:
|
|
108
130
|
return make_function(
|
|
109
131
|
name="from_json",
|
|
110
|
-
params=["json"],
|
|
132
|
+
params=["json", "keep_unrecognized_fields"],
|
|
111
133
|
body=[
|
|
112
|
-
LineSpan.join(
|
|
134
|
+
LineSpan.join(
|
|
135
|
+
"return ", adapter.from_json_expr("json", "keep_unrecognized_fields")
|
|
136
|
+
),
|
|
113
137
|
],
|
|
114
138
|
)
|
soia/_impl/service.py
CHANGED
|
@@ -130,9 +130,7 @@ class _HandleRequestFlow(Generic[Request, Response, RequestHeaders, ResponseHead
|
|
|
130
130
|
try:
|
|
131
131
|
req_body_json = json.loads(self.req_body)
|
|
132
132
|
except json.JSONDecodeError:
|
|
133
|
-
return RawServiceResponse(
|
|
134
|
-
"bad request: invalid JSON", "bad-request"
|
|
135
|
-
)
|
|
133
|
+
return RawServiceResponse("bad request: invalid JSON", "bad-request")
|
|
136
134
|
method = req_body_json.get("method", ())
|
|
137
135
|
if method == ():
|
|
138
136
|
return RawServiceResponse(
|