soia-client 1.1.5__py3-none-any.whl → 1.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

soia/_impl/primitives.py CHANGED
@@ -1,15 +1,29 @@
1
1
  import base64
2
+ import struct
2
3
  from collections.abc import Callable
3
4
  from dataclasses import dataclass
4
5
  from typing import Any, Final, final
5
6
 
6
7
  from soia import _spec, reflection
8
+ from soia._impl.binary import (
9
+ decode_bool,
10
+ decode_float,
11
+ decode_int32,
12
+ decode_int64,
13
+ decode_uint64,
14
+ encode_float32,
15
+ encode_float64,
16
+ encode_int32,
17
+ encode_int64,
18
+ encode_length_prefix,
19
+ encode_uint64,
20
+ )
7
21
  from soia._impl.function_maker import Expr, ExprLike
8
22
  from soia._impl.timestamp import Timestamp
9
- from soia._impl.type_adapter import TypeAdapter
23
+ from soia._impl.type_adapter import ByteStream, TypeAdapter, T
10
24
 
11
25
 
12
- class AbstractPrimitiveAdapter(TypeAdapter):
26
+ class AbstractPrimitiveAdapter(TypeAdapter[T]):
13
27
  @final
14
28
  def finalize(
15
29
  self,
@@ -29,7 +43,7 @@ class AbstractPrimitiveAdapter(TypeAdapter):
29
43
  return None
30
44
 
31
45
 
32
- class _BoolAdapter(AbstractPrimitiveAdapter):
46
+ class _BoolAdapter(AbstractPrimitiveAdapter[bool]):
33
47
  def default_expr(self) -> ExprLike:
34
48
  return "False"
35
49
 
@@ -49,9 +63,24 @@ class _BoolAdapter(AbstractPrimitiveAdapter):
49
63
  else:
50
64
  return Expr.join("(1 if ", in_expr, " else 0)")
51
65
 
52
- def from_json_expr(self, json_expr: ExprLike) -> Expr:
66
+ def from_json_expr(
67
+ self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
68
+ ) -> Expr:
53
69
  return Expr.join("(True if ", json_expr, " else False)")
54
70
 
71
+ @staticmethod
72
+ def encode(
73
+ value: bool,
74
+ buffer: bytearray,
75
+ ) -> None:
76
+ buffer.append(1 if value else 0)
77
+
78
+ def encode_fn(self) -> Callable[[bool, bytearray], None]:
79
+ return _BoolAdapter.encode
80
+
81
+ def decode_fn(self) -> Callable[[ByteStream], bool]:
82
+ return decode_bool
83
+
55
84
  def get_type(self) -> reflection.Type:
56
85
  return reflection.PrimitiveType(
57
86
  kind="primitive",
@@ -59,11 +88,11 @@ class _BoolAdapter(AbstractPrimitiveAdapter):
59
88
  )
60
89
 
61
90
 
62
- BOOL_ADAPTER: Final[TypeAdapter] = _BoolAdapter()
91
+ BOOL_ADAPTER: Final[TypeAdapter[bool]] = _BoolAdapter()
63
92
 
64
93
 
65
94
  @dataclass(frozen=True)
66
- class _AbstractIntAdapter(AbstractPrimitiveAdapter):
95
+ class _AbstractIntAdapter(AbstractPrimitiveAdapter[int]):
67
96
  """Type adapter implementation for int32, int64 and uint64."""
68
97
 
69
98
  def default_expr(self) -> ExprLike:
@@ -76,7 +105,9 @@ class _AbstractIntAdapter(AbstractPrimitiveAdapter):
76
105
  def is_not_default_expr(self, arg_expr: ExprLike, attr_expr: ExprLike) -> ExprLike:
77
106
  return arg_expr
78
107
 
79
- def from_json_expr(self, json_expr: ExprLike) -> Expr:
108
+ def from_json_expr(
109
+ self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
110
+ ) -> Expr:
80
111
  # Must accept float inputs and string inputs and turn them into ints.
81
112
  return Expr.join(
82
113
  "(0).__class__(",
@@ -98,6 +129,12 @@ class _Int32Adapter(_AbstractIntAdapter):
98
129
  " < 2147483647 else 2147483647)",
99
130
  )
100
131
 
132
+ def encode_fn(self) -> Callable[[int, bytearray], None]:
133
+ return encode_int32
134
+
135
+ def decode_fn(self) -> Callable[[ByteStream], int]:
136
+ return decode_int32
137
+
101
138
  def get_type(self) -> reflection.Type:
102
139
  return reflection.PrimitiveType(
103
140
  kind="primitive",
@@ -124,6 +161,12 @@ class _Int64Adapter(_AbstractIntAdapter):
124
161
  def to_json_expr(self, in_expr: ExprLike, readable: bool) -> Expr:
125
162
  return Expr.join(Expr.local("int64_to_json", _int64_to_json), "(", in_expr, ")")
126
163
 
164
+ def encode_fn(self) -> Callable[[int, bytearray], None]:
165
+ return encode_int64
166
+
167
+ def decode_fn(self) -> Callable[[ByteStream], int]:
168
+ return decode_int64
169
+
127
170
  def get_type(self) -> reflection.Type:
128
171
  return reflection.PrimitiveType(
129
172
  kind="primitive",
@@ -149,6 +192,12 @@ class _Uint64Adapter(_AbstractIntAdapter):
149
192
  Expr.local("uint64_to_json", _uint64_to_json), "(", in_expr, ")"
150
193
  )
151
194
 
195
+ def encode_fn(self) -> Callable[[int, bytearray], None]:
196
+ return encode_uint64
197
+
198
+ def decode_fn(self) -> Callable[[ByteStream], int]:
199
+ return decode_uint64
200
+
152
201
  def get_type(self) -> reflection.Type:
153
202
  return reflection.PrimitiveType(
154
203
  kind="primitive",
@@ -156,13 +205,13 @@ class _Uint64Adapter(_AbstractIntAdapter):
156
205
  )
157
206
 
158
207
 
159
- INT32_ADAPTER: Final[TypeAdapter] = _Int32Adapter()
160
- INT64_ADAPTER: Final[TypeAdapter] = _Int64Adapter()
161
- UINT64_ADAPTER: Final[TypeAdapter] = _Uint64Adapter()
208
+ INT32_ADAPTER: Final[TypeAdapter[int]] = _Int32Adapter()
209
+ INT64_ADAPTER: Final[TypeAdapter[int]] = _Int64Adapter()
210
+ UINT64_ADAPTER: Final[TypeAdapter[int]] = _Uint64Adapter()
162
211
 
163
212
 
164
213
  @dataclass(frozen=True)
165
- class _AbstractFloatAdapter(AbstractPrimitiveAdapter):
214
+ class _AbstractFloatAdapter(AbstractPrimitiveAdapter[float]):
166
215
  """Type adapter implementation for float32 and float64."""
167
216
 
168
217
  def default_expr(self) -> ExprLike:
@@ -177,14 +226,22 @@ class _AbstractFloatAdapter(AbstractPrimitiveAdapter):
177
226
  def to_json_expr(self, in_expr: ExprLike, readable: bool) -> ExprLike:
178
227
  return in_expr
179
228
 
180
- def from_json_expr(self, json_expr: ExprLike) -> Expr:
229
+ def from_json_expr(
230
+ self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
231
+ ) -> Expr:
181
232
  return Expr.join("(", json_expr, " + 0.0)")
182
233
 
234
+ def decode_fn(self) -> Callable[[ByteStream], float]:
235
+ return decode_float
236
+
183
237
 
184
238
  @dataclass(frozen=True)
185
239
  class _Float32Adapter(_AbstractFloatAdapter):
186
240
  """Type adapter implementation for float32."""
187
241
 
242
+ def encode_fn(self) -> Callable[[float, bytearray], None]:
243
+ return encode_float32
244
+
188
245
  def get_type(self) -> reflection.Type:
189
246
  return reflection.PrimitiveType(
190
247
  kind="primitive",
@@ -194,7 +251,10 @@ class _Float32Adapter(_AbstractFloatAdapter):
194
251
 
195
252
  @dataclass(frozen=True)
196
253
  class _Float64Adapter(_AbstractFloatAdapter):
197
- """Type adapter implementation for float32."""
254
+ """Type adapter implementation for float64."""
255
+
256
+ def encode_fn(self) -> Callable[[float, bytearray], None]:
257
+ return encode_float64
198
258
 
199
259
  def get_type(self) -> reflection.Type:
200
260
  return reflection.PrimitiveType(
@@ -203,11 +263,16 @@ class _Float64Adapter(_AbstractFloatAdapter):
203
263
  )
204
264
 
205
265
 
206
- FLOAT32_ADAPTER: Final[TypeAdapter] = _Float32Adapter()
207
- FLOAT64_ADAPTER: Final[TypeAdapter] = _Float64Adapter()
266
+ FLOAT32_ADAPTER: Final[TypeAdapter[float]] = _Float32Adapter()
267
+ FLOAT64_ADAPTER: Final[TypeAdapter[float]] = _Float64Adapter()
268
+
269
+
270
+ def _clamp_unix_millis(unix_millis: int) -> int:
271
+ """Clamp unix milliseconds to valid range for JavaScript dates."""
272
+ return max(-8640000000000000, min(unix_millis, 8640000000000000))
208
273
 
209
274
 
210
- class _TimestampAdapter(AbstractPrimitiveAdapter):
275
+ class _TimestampAdapter(AbstractPrimitiveAdapter[Timestamp]):
211
276
  def default_expr(self) -> Expr:
212
277
  return Expr.local("_EPOCH", Timestamp.EPOCH)
213
278
 
@@ -236,10 +301,34 @@ class _TimestampAdapter(AbstractPrimitiveAdapter):
236
301
  else:
237
302
  return Expr.join(in_expr, ".unix_millis")
238
303
 
239
- def from_json_expr(self, json_expr: ExprLike) -> Expr:
304
+ def from_json_expr(
305
+ self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
306
+ ) -> Expr:
240
307
  fn = Expr.local("_timestamp_from_json", _timestamp_from_json)
241
308
  return Expr.join(fn, "(", json_expr, ")")
242
309
 
310
+ @staticmethod
311
+ def encode(
312
+ value: Timestamp,
313
+ buffer: bytearray,
314
+ ) -> None:
315
+ unix_millis = _clamp_unix_millis(value.unix_millis)
316
+ if unix_millis == 0:
317
+ buffer.append(0)
318
+ else:
319
+ buffer.append(239)
320
+ buffer.extend(struct.pack("<q", unix_millis))
321
+
322
+ def encode_fn(self) -> Callable[[Timestamp, bytearray], None]:
323
+ return _TimestampAdapter.encode
324
+
325
+ @staticmethod
326
+ def decode(stream: ByteStream) -> Timestamp:
327
+ return Timestamp(unix_millis=_clamp_unix_millis(decode_int64(stream)))
328
+
329
+ def decode_fn(self) -> Callable[[ByteStream], Timestamp]:
330
+ return _TimestampAdapter.decode
331
+
243
332
  def get_type(self) -> reflection.Type:
244
333
  return reflection.PrimitiveType(
245
334
  kind="primitive",
@@ -254,10 +343,10 @@ def _timestamp_from_json(json: Any) -> Timestamp:
254
343
  return Timestamp(unix_millis=json["unix_millis"])
255
344
 
256
345
 
257
- TIMESTAMP_ADAPTER: Final[TypeAdapter] = _TimestampAdapter()
346
+ TIMESTAMP_ADAPTER: Final[TypeAdapter[Timestamp]] = _TimestampAdapter()
258
347
 
259
348
 
260
- class _StringAdapter(AbstractPrimitiveAdapter):
349
+ class _StringAdapter(AbstractPrimitiveAdapter[str]):
261
350
  def default_expr(self) -> ExprLike:
262
351
  return '""'
263
352
 
@@ -270,9 +359,42 @@ class _StringAdapter(AbstractPrimitiveAdapter):
270
359
  def to_json_expr(self, in_expr: ExprLike, readable: bool) -> ExprLike:
271
360
  return in_expr
272
361
 
273
- def from_json_expr(self, json_expr: ExprLike) -> Expr:
362
+ def from_json_expr(
363
+ self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
364
+ ) -> Expr:
274
365
  return Expr.join("('' + (", json_expr, " or ''))")
275
366
 
367
+ @staticmethod
368
+ def encode(
369
+ value: str,
370
+ buffer: bytearray,
371
+ ) -> None:
372
+ if not value:
373
+ buffer.append(242)
374
+ else:
375
+ buffer.append(243)
376
+ bytes_data = value.encode("utf-8")
377
+ length = len(bytes_data)
378
+ encode_length_prefix(length, buffer)
379
+ buffer.extend(bytes_data)
380
+
381
+ def encode_fn(self) -> Callable[[str, bytearray], None]:
382
+ return _StringAdapter.encode
383
+
384
+ @staticmethod
385
+ def decode(stream: ByteStream) -> str:
386
+ wire = stream.read(1)[0]
387
+ if wire == 242:
388
+ return ""
389
+ else:
390
+ # Should be wire 243
391
+ length = decode_int64(stream)
392
+ bytes_data = stream.read(length)
393
+ return bytes_data.decode("utf-8")
394
+
395
+ def decode_fn(self) -> Callable[[ByteStream], str]:
396
+ return _StringAdapter.decode
397
+
276
398
  def get_type(self) -> reflection.Type:
277
399
  return reflection.PrimitiveType(
278
400
  kind="primitive",
@@ -283,7 +405,7 @@ class _StringAdapter(AbstractPrimitiveAdapter):
283
405
  STRING_ADAPTER: Final[TypeAdapter] = _StringAdapter()
284
406
 
285
407
 
286
- class _BytesAdapter(AbstractPrimitiveAdapter):
408
+ class _BytesAdapter(AbstractPrimitiveAdapter[bytes]):
287
409
  def default_expr(self) -> ExprLike:
288
410
  return 'b""'
289
411
 
@@ -305,11 +427,42 @@ class _BytesAdapter(AbstractPrimitiveAdapter):
305
427
  ").decode('utf-8')",
306
428
  )
307
429
 
308
- def from_json_expr(self, json_expr: ExprLike) -> Expr:
430
+ def from_json_expr(
431
+ self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
432
+ ) -> Expr:
309
433
  return Expr.join(
310
434
  Expr.local("b64decode", base64.b64decode), "(", json_expr, ' or "")'
311
435
  )
312
436
 
437
+ @staticmethod
438
+ def encode(
439
+ value: bytes,
440
+ buffer: bytearray,
441
+ ) -> None:
442
+ if len(value) == 0:
443
+ buffer.append(244)
444
+ else:
445
+ buffer.append(245)
446
+ length = len(value)
447
+ encode_length_prefix(length, buffer)
448
+ buffer.extend(value)
449
+
450
+ def encode_fn(self) -> Callable[[bytes, bytearray], None]:
451
+ return _BytesAdapter.encode
452
+
453
+ @staticmethod
454
+ def decode(stream: ByteStream) -> bytes:
455
+ wire = stream.read_wire()
456
+ if wire in (0, 244):
457
+ return b""
458
+ else:
459
+ # Should be wire 245
460
+ length = decode_int64(stream)
461
+ return stream.read(length)
462
+
463
+ def decode_fn(self) -> Callable[[ByteStream], bytes]:
464
+ return _BytesAdapter.decode
465
+
313
466
  def get_type(self) -> reflection.Type:
314
467
  return reflection.PrimitiveType(
315
468
  kind="primitive",
@@ -317,4 +470,4 @@ class _BytesAdapter(AbstractPrimitiveAdapter):
317
470
  )
318
471
 
319
472
 
320
- BYTES_ADAPTER: Final[TypeAdapter] = _BytesAdapter()
473
+ 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
- object.__setattr__(self, "_adapter", adapter)
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(adapter, readable=False)
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(adapter, readable=True),
46
+ _make_to_json_fn(as_adapter, readable=True),
42
47
  )
43
- object.__setattr__(self, "_from_json_fn", _make_from_json_fn(adapter))
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(self, json_code: str) -> T:
61
- return self._from_json_fn(jsonlib.loads(json_code))
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[[Any], Any]:
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], 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("return ", adapter.from_json_expr(Expr.join("json"))),
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(