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/primitives.py CHANGED
@@ -1,16 +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
 
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
+ )
6
21
  from soia._impl.function_maker import Expr, ExprLike
7
22
  from soia._impl.timestamp import Timestamp
8
- from soia._impl.type_adapter import TypeAdapter
9
-
10
- from soia import _spec, reflection
23
+ from soia._impl.type_adapter import ByteStream, TypeAdapter, T
11
24
 
12
25
 
13
- class AbstractPrimitiveAdapter(TypeAdapter):
26
+ class AbstractPrimitiveAdapter(TypeAdapter[T]):
14
27
  @final
15
28
  def finalize(
16
29
  self,
@@ -25,8 +38,12 @@ class AbstractPrimitiveAdapter(TypeAdapter):
25
38
  ) -> None:
26
39
  pass
27
40
 
41
+ @final
42
+ def frozen_class_of_struct(self) -> type | None:
43
+ return None
44
+
28
45
 
29
- class _BoolAdapter(AbstractPrimitiveAdapter):
46
+ class _BoolAdapter(AbstractPrimitiveAdapter[bool]):
30
47
  def default_expr(self) -> ExprLike:
31
48
  return "False"
32
49
 
@@ -46,9 +63,24 @@ class _BoolAdapter(AbstractPrimitiveAdapter):
46
63
  else:
47
64
  return Expr.join("(1 if ", in_expr, " else 0)")
48
65
 
49
- 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:
50
69
  return Expr.join("(True if ", json_expr, " else False)")
51
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
+
52
84
  def get_type(self) -> reflection.Type:
53
85
  return reflection.PrimitiveType(
54
86
  kind="primitive",
@@ -56,11 +88,11 @@ class _BoolAdapter(AbstractPrimitiveAdapter):
56
88
  )
57
89
 
58
90
 
59
- BOOL_ADAPTER: Final[TypeAdapter] = _BoolAdapter()
91
+ BOOL_ADAPTER: Final[TypeAdapter[bool]] = _BoolAdapter()
60
92
 
61
93
 
62
94
  @dataclass(frozen=True)
63
- class _AbstractIntAdapter(AbstractPrimitiveAdapter):
95
+ class _AbstractIntAdapter(AbstractPrimitiveAdapter[int]):
64
96
  """Type adapter implementation for int32, int64 and uint64."""
65
97
 
66
98
  def default_expr(self) -> ExprLike:
@@ -73,7 +105,9 @@ class _AbstractIntAdapter(AbstractPrimitiveAdapter):
73
105
  def is_not_default_expr(self, arg_expr: ExprLike, attr_expr: ExprLike) -> ExprLike:
74
106
  return arg_expr
75
107
 
76
- 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:
77
111
  # Must accept float inputs and string inputs and turn them into ints.
78
112
  return Expr.join(
79
113
  "(0).__class__(",
@@ -95,6 +129,12 @@ class _Int32Adapter(_AbstractIntAdapter):
95
129
  " < 2147483647 else 2147483647)",
96
130
  )
97
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
+
98
138
  def get_type(self) -> reflection.Type:
99
139
  return reflection.PrimitiveType(
100
140
  kind="primitive",
@@ -121,6 +161,12 @@ class _Int64Adapter(_AbstractIntAdapter):
121
161
  def to_json_expr(self, in_expr: ExprLike, readable: bool) -> Expr:
122
162
  return Expr.join(Expr.local("int64_to_json", _int64_to_json), "(", in_expr, ")")
123
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
+
124
170
  def get_type(self) -> reflection.Type:
125
171
  return reflection.PrimitiveType(
126
172
  kind="primitive",
@@ -146,6 +192,12 @@ class _Uint64Adapter(_AbstractIntAdapter):
146
192
  Expr.local("uint64_to_json", _uint64_to_json), "(", in_expr, ")"
147
193
  )
148
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
+
149
201
  def get_type(self) -> reflection.Type:
150
202
  return reflection.PrimitiveType(
151
203
  kind="primitive",
@@ -153,13 +205,13 @@ class _Uint64Adapter(_AbstractIntAdapter):
153
205
  )
154
206
 
155
207
 
156
- INT32_ADAPTER: Final[TypeAdapter] = _Int32Adapter()
157
- INT64_ADAPTER: Final[TypeAdapter] = _Int64Adapter()
158
- 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()
159
211
 
160
212
 
161
213
  @dataclass(frozen=True)
162
- class _AbstractFloatAdapter(AbstractPrimitiveAdapter):
214
+ class _AbstractFloatAdapter(AbstractPrimitiveAdapter[float]):
163
215
  """Type adapter implementation for float32 and float64."""
164
216
 
165
217
  def default_expr(self) -> ExprLike:
@@ -174,14 +226,22 @@ class _AbstractFloatAdapter(AbstractPrimitiveAdapter):
174
226
  def to_json_expr(self, in_expr: ExprLike, readable: bool) -> ExprLike:
175
227
  return in_expr
176
228
 
177
- 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:
178
232
  return Expr.join("(", json_expr, " + 0.0)")
179
233
 
234
+ def decode_fn(self) -> Callable[[ByteStream], float]:
235
+ return decode_float
236
+
180
237
 
181
238
  @dataclass(frozen=True)
182
239
  class _Float32Adapter(_AbstractFloatAdapter):
183
240
  """Type adapter implementation for float32."""
184
241
 
242
+ def encode_fn(self) -> Callable[[float, bytearray], None]:
243
+ return encode_float32
244
+
185
245
  def get_type(self) -> reflection.Type:
186
246
  return reflection.PrimitiveType(
187
247
  kind="primitive",
@@ -191,7 +251,10 @@ class _Float32Adapter(_AbstractFloatAdapter):
191
251
 
192
252
  @dataclass(frozen=True)
193
253
  class _Float64Adapter(_AbstractFloatAdapter):
194
- """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
195
258
 
196
259
  def get_type(self) -> reflection.Type:
197
260
  return reflection.PrimitiveType(
@@ -200,11 +263,16 @@ class _Float64Adapter(_AbstractFloatAdapter):
200
263
  )
201
264
 
202
265
 
203
- FLOAT32_ADAPTER: Final[TypeAdapter] = _Float32Adapter()
204
- FLOAT64_ADAPTER: Final[TypeAdapter] = _Float64Adapter()
266
+ FLOAT32_ADAPTER: Final[TypeAdapter[float]] = _Float32Adapter()
267
+ FLOAT64_ADAPTER: Final[TypeAdapter[float]] = _Float64Adapter()
205
268
 
206
269
 
207
- class _TimestampAdapter(AbstractPrimitiveAdapter):
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))
273
+
274
+
275
+ class _TimestampAdapter(AbstractPrimitiveAdapter[Timestamp]):
208
276
  def default_expr(self) -> Expr:
209
277
  return Expr.local("_EPOCH", Timestamp.EPOCH)
210
278
 
@@ -233,10 +301,34 @@ class _TimestampAdapter(AbstractPrimitiveAdapter):
233
301
  else:
234
302
  return Expr.join(in_expr, ".unix_millis")
235
303
 
236
- 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:
237
307
  fn = Expr.local("_timestamp_from_json", _timestamp_from_json)
238
308
  return Expr.join(fn, "(", json_expr, ")")
239
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
+
240
332
  def get_type(self) -> reflection.Type:
241
333
  return reflection.PrimitiveType(
242
334
  kind="primitive",
@@ -251,10 +343,10 @@ def _timestamp_from_json(json: Any) -> Timestamp:
251
343
  return Timestamp(unix_millis=json["unix_millis"])
252
344
 
253
345
 
254
- TIMESTAMP_ADAPTER: Final[TypeAdapter] = _TimestampAdapter()
346
+ TIMESTAMP_ADAPTER: Final[TypeAdapter[Timestamp]] = _TimestampAdapter()
255
347
 
256
348
 
257
- class _StringAdapter(AbstractPrimitiveAdapter):
349
+ class _StringAdapter(AbstractPrimitiveAdapter[str]):
258
350
  def default_expr(self) -> ExprLike:
259
351
  return '""'
260
352
 
@@ -267,9 +359,42 @@ class _StringAdapter(AbstractPrimitiveAdapter):
267
359
  def to_json_expr(self, in_expr: ExprLike, readable: bool) -> ExprLike:
268
360
  return in_expr
269
361
 
270
- 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:
271
365
  return Expr.join("('' + (", json_expr, " or ''))")
272
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
+
273
398
  def get_type(self) -> reflection.Type:
274
399
  return reflection.PrimitiveType(
275
400
  kind="primitive",
@@ -280,7 +405,7 @@ class _StringAdapter(AbstractPrimitiveAdapter):
280
405
  STRING_ADAPTER: Final[TypeAdapter] = _StringAdapter()
281
406
 
282
407
 
283
- class _BytesAdapter(AbstractPrimitiveAdapter):
408
+ class _BytesAdapter(AbstractPrimitiveAdapter[bytes]):
284
409
  def default_expr(self) -> ExprLike:
285
410
  return 'b""'
286
411
 
@@ -302,11 +427,42 @@ class _BytesAdapter(AbstractPrimitiveAdapter):
302
427
  ").decode('utf-8')",
303
428
  )
304
429
 
305
- 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:
306
433
  return Expr.join(
307
434
  Expr.local("b64decode", base64.b64decode), "(", json_expr, ' or "")'
308
435
  )
309
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
+
310
466
  def get_type(self) -> reflection.Type:
311
467
  return reflection.PrimitiveType(
312
468
  kind="primitive",
@@ -314,4 +470,4 @@ class _BytesAdapter(AbstractPrimitiveAdapter):
314
470
  )
315
471
 
316
472
 
317
- BYTES_ADAPTER: Final[TypeAdapter] = _BytesAdapter()
473
+ BYTES_ADAPTER: Final[TypeAdapter[bytes]] = _BytesAdapter()
soia/_impl/repr.py CHANGED
@@ -10,7 +10,7 @@ class ReprResult:
10
10
 
11
11
  @property
12
12
  def indented(self) -> str:
13
- if complex:
13
+ if self.complex:
14
14
  return self.repr.replace("\n", "\n ")
15
15
  else:
16
16
  return self.repr
soia/_impl/serializer.py CHANGED
@@ -5,11 +5,10 @@ from functools import cached_property
5
5
  from typing import Any, Generic, TypeVar, cast, final
6
6
  from weakref import WeakValueDictionary
7
7
 
8
+ from soia import reflection
8
9
  from soia._impl.function_maker import Expr, LineSpan, make_function
9
10
  from soia._impl.never import Never
10
- from soia._impl.type_adapter import TypeAdapter
11
-
12
- from soia import reflection
11
+ from soia._impl.type_adapter import ByteStream, TypeAdapter
13
12
 
14
13
  T = TypeVar("T")
15
14
 
@@ -22,26 +21,33 @@ class Serializer(Generic[T]):
22
21
  "_to_dense_json_fn",
23
22
  "_to_readable_json_fn",
24
23
  "_from_json_fn",
24
+ "_encode_fn",
25
+ "_decode_fn",
25
26
  "__dict__",
26
27
  )
27
28
 
28
- _adapter: TypeAdapter
29
+ _adapter: TypeAdapter[T]
29
30
  _to_dense_json_fn: Callable[[T], Any]
30
31
  _to_readable_json_fn: Callable[[T], Any]
31
- _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]
32
35
 
33
36
  def __init__(self, adapter: Never):
34
37
  # Use Never (^) as a trick to make the constructor internal.
35
- object.__setattr__(self, "_adapter", adapter)
38
+ as_adapter = cast(TypeAdapter[T], adapter)
39
+ object.__setattr__(self, "_adapter", as_adapter)
36
40
  object.__setattr__(
37
- 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)
38
42
  )
39
43
  object.__setattr__(
40
44
  self,
41
45
  "_to_readable_json_fn",
42
- _make_to_json_fn(adapter, readable=True),
46
+ _make_to_json_fn(as_adapter, readable=True),
43
47
  )
44
- 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())
45
51
 
46
52
  def to_json(self, input: T, *, readable=False) -> Any:
47
53
  if readable:
@@ -55,11 +61,26 @@ class Serializer(Generic[T]):
55
61
  else:
56
62
  return jsonlib.dumps(self._to_dense_json_fn(input), separators=(",", ":"))
57
63
 
58
- def from_json(self, json: Any) -> T:
59
- 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)
66
+
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)
60
71
 
61
- def from_json_code(self, json_code: str) -> T:
62
- return self._from_json_fn(jsonlib.loads(json_code))
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"))
63
84
 
64
85
  @cached_property
65
86
  def type_descriptor(self) -> reflection.TypeDescriptor:
@@ -83,13 +104,13 @@ _type_adapter_to_serializer: WeakValueDictionary[TypeAdapter, Serializer] = (
83
104
  )
84
105
 
85
106
 
86
- def make_serializer(adapter: TypeAdapter) -> Serializer:
107
+ def make_serializer(adapter: TypeAdapter[T]) -> Serializer[T]:
87
108
  return _type_adapter_to_serializer.setdefault(
88
109
  adapter, Serializer(cast(Never, adapter))
89
110
  )
90
111
 
91
112
 
92
- 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]:
93
114
  return make_function(
94
115
  name="to_json",
95
116
  params=["input"],
@@ -105,11 +126,13 @@ def _make_to_json_fn(adapter: TypeAdapter, readable: bool) -> Callable[[Any], An
105
126
  )
106
127
 
107
128
 
108
- def _make_from_json_fn(adapter: TypeAdapter) -> Callable[[Any], Any]:
129
+ def _make_from_json_fn(adapter: TypeAdapter[T]) -> Callable[[Any, bool], T]:
109
130
  return make_function(
110
131
  name="from_json",
111
- params=["json"],
132
+ params=["json", "keep_unrecognized_fields"],
112
133
  body=[
113
- LineSpan.join("return ", adapter.from_json_expr(Expr.join("json"))),
134
+ LineSpan.join(
135
+ "return ", adapter.from_json_expr("json", "keep_unrecognized_fields")
136
+ ),
114
137
  ],
115
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(
@@ -160,5 +160,4 @@ class _AiohttpClientSession(Protocol):
160
160
  *,
161
161
  data: str,
162
162
  headers: Mapping[str, str],
163
- ) -> Any:
164
- ...
163
+ ) -> Any: ...