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/enums.py CHANGED
@@ -1,15 +1,16 @@
1
1
  import copy
2
2
  from collections.abc import Callable, Sequence
3
3
  from dataclasses import FrozenInstanceError, dataclass
4
- from typing import Any, Final, Union
4
+ from typing import Any, Final, Generic, Union
5
5
 
6
6
  from soia import _spec, reflection
7
+ from soia._impl.binary import decode_int64, decode_unused, encode_int64
7
8
  from soia._impl.function_maker import BodyBuilder, Expr, ExprLike, Line, make_function
8
9
  from soia._impl.repr import repr_impl
9
- from soia._impl.type_adapter import TypeAdapter
10
+ from soia._impl.type_adapter import T, ByteStream, TypeAdapter
10
11
 
11
12
 
12
- class EnumAdapter(TypeAdapter):
13
+ class EnumAdapter(Generic[T], TypeAdapter[T]):
13
14
  __slots__ = (
14
15
  "spec",
15
16
  "gen_class",
@@ -30,6 +31,12 @@ class EnumAdapter(TypeAdapter):
30
31
  self.spec = spec
31
32
  base_class = self.gen_class = _make_base_class(spec)
32
33
 
34
+ def forward_decode(stream: ByteStream) -> T:
35
+ return base_class._decode(stream)
36
+
37
+ # Will be overridden at finalization time.
38
+ base_class._decode = forward_decode
39
+
33
40
  private_is_enum_attr = _name_private_is_enum_attr(spec.id)
34
41
  self.private_is_enum_attr = private_is_enum_attr
35
42
  setattr(base_class, private_is_enum_attr, True)
@@ -73,12 +80,28 @@ class EnumAdapter(TypeAdapter):
73
80
  for value_field in value_fields:
74
81
  wrap_fn = _make_wrap_fn(value_field)
75
82
  setattr(base_class, f"wrap_{value_field.spec.name}", wrap_fn)
83
+ # Check if the field type is a struct type.
84
+ field_type = resolve_type_fn(value_field.spec.type)
85
+ frozen_class = field_type.frozen_class_of_struct()
86
+ if frozen_class:
87
+ create_fn = _make_create_fn(wrap_fn, frozen_class)
88
+ setattr(base_class, f"create_{value_field.spec.name}", create_fn)
89
+
90
+ unrecognized_class = _make_unrecognized_class(base_class)
76
91
 
77
92
  base_class._fj = _make_from_json_fn(
78
93
  self.all_constant_fields,
79
94
  value_fields,
80
95
  set(self.spec.removed_numbers),
81
- base_class,
96
+ base_class=base_class,
97
+ unrecognized_class=unrecognized_class,
98
+ )
99
+ base_class._decode = _make_decode_fn(
100
+ self.all_constant_fields,
101
+ value_fields,
102
+ set(self.spec.removed_numbers),
103
+ base_class=base_class,
104
+ unrecognized_class=unrecognized_class,
82
105
  )
83
106
 
84
107
  # Mark finalization as done.
@@ -111,16 +134,36 @@ class EnumAdapter(TypeAdapter):
111
134
  def to_json_expr(self, in_expr: ExprLike, readable: bool) -> Expr:
112
135
  return Expr.join(in_expr, "._rj" if readable else "._dj")
113
136
 
114
- def from_json_expr(self, json_expr: ExprLike) -> Expr:
137
+ def from_json_expr(
138
+ self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
139
+ ) -> Expr:
115
140
  fn_name = "_fj"
116
141
  from_json_fn = getattr(self.gen_class, fn_name, None)
117
142
  if from_json_fn:
118
- return Expr.join(Expr.local("_fj?", from_json_fn), "(", json_expr, ")")
143
+ return Expr.join(
144
+ Expr.local("_fj?", from_json_fn),
145
+ "(",
146
+ json_expr,
147
+ ", ",
148
+ keep_unrecognized_expr,
149
+ ")",
150
+ )
119
151
  else:
120
152
  return Expr.join(
121
- Expr.local("_cls?", self.gen_class), f".{fn_name}(", json_expr, ")"
153
+ Expr.local("_cls?", self.gen_class),
154
+ f".{fn_name}(",
155
+ json_expr,
156
+ ", ",
157
+ keep_unrecognized_expr,
158
+ ")",
122
159
  )
123
160
 
161
+ def encode_fn(self) -> Callable[[T, bytearray], None]:
162
+ return _encode_impl
163
+
164
+ def decode_fn(self) -> Callable[[ByteStream], T]:
165
+ return self.gen_class._decode
166
+
124
167
  def get_type(self) -> reflection.Type:
125
168
  return reflection.RecordType(
126
169
  kind="record",
@@ -143,8 +186,10 @@ class EnumAdapter(TypeAdapter):
143
186
  number=field.number,
144
187
  type=None,
145
188
  )
146
- for field in self.all_constant_fields if field.number != 0
147
- ) + tuple(
189
+ for field in self.all_constant_fields
190
+ if field.number != 0
191
+ )
192
+ + tuple(
148
193
  reflection.Field(
149
194
  name=field.spec.name,
150
195
  number=field.spec.number,
@@ -157,6 +202,9 @@ class EnumAdapter(TypeAdapter):
157
202
  for field in self.value_fields:
158
203
  field.field_type.register_records(registry)
159
204
 
205
+ def frozen_class_of_struct(self) -> type | None:
206
+ return None
207
+
160
208
 
161
209
  def _make_base_class(spec: _spec.Enum) -> type:
162
210
  record_hash = hash(spec.id)
@@ -174,6 +222,9 @@ def _make_base_class(spec: _spec.Enum) -> type:
174
222
  def union(self) -> Any:
175
223
  return self
176
224
 
225
+ def __bool__(self) -> bool:
226
+ return self.kind != "?"
227
+
177
228
  def __setattr__(self, name: str, value: Any):
178
229
  raise FrozenInstanceError(self.__class__.__qualname__)
179
230
 
@@ -195,6 +246,9 @@ def _make_base_class(spec: _spec.Enum) -> type:
195
246
 
196
247
 
197
248
  def _make_constant_class(base_class: type, spec: _spec.ConstantField) -> type:
249
+ byte_array = bytearray()
250
+ encode_int64(spec.number, byte_array)
251
+
198
252
  class Constant(base_class):
199
253
  __slots__ = ()
200
254
 
@@ -206,6 +260,7 @@ def _make_constant_class(base_class: type, spec: _spec.ConstantField) -> type:
206
260
  _rj: Final[str] = spec.name
207
261
  # has value
208
262
  _hv: Final[bool] = False
263
+ _bytes: Final[bytes | None] = bytes(byte_array)
209
264
 
210
265
  def __init__(self):
211
266
  # Do not call super().__init__().
@@ -225,20 +280,22 @@ def _make_unrecognized_class(base_class: type) -> type:
225
280
  """
226
281
 
227
282
  class Unrecognized(base_class):
228
- __slots__ = ("_dj",)
283
+ __slots__ = ("_dj", "_bytes")
229
284
 
230
285
  kind: Final[str] = "?"
231
286
  _number: Final[int] = 0
232
287
  # dense JSON
233
- _dj: Any
288
+ _dj: list[Any] | int
289
+ _bytes: bytes
234
290
  # readable JSON
235
291
  _rj: Final[str] = "?"
236
292
  # has value
237
293
  _hv: Final[bool] = False
238
294
 
239
- def __init__(self, dj: Any):
295
+ def __init__(self, dj: list[Any] | int, bytes: bytes):
240
296
  # Do not call super().__init__().
241
297
  object.__setattr__(self, "_dj", copy.deepcopy(dj))
298
+ object.__setattr__(self, "_bytes", bytes)
242
299
  object.__setattr__(self, "value", None)
243
300
 
244
301
  def __repr__(self) -> str:
@@ -261,6 +318,7 @@ def _make_value_class(
261
318
  _number: Final[int] = number
262
319
  # has value
263
320
  _hv: Final[bool] = True
321
+ _bytes: Final[None] = None
264
322
 
265
323
  def __init__(self):
266
324
  # Do not call super().__init__().
@@ -305,9 +363,38 @@ def _make_value_class(
305
363
  )
306
364
  )
307
365
 
366
+ bytes_prefix = bytearray()
367
+ if number in range(1, 5):
368
+ bytes_prefix.append(250 + number)
369
+ else:
370
+ bytes_prefix.append(248)
371
+ encode_int64(number, bytes_prefix)
372
+
373
+ ret._enc = make_function(
374
+ name="encode",
375
+ params=["self", "buffer"],
376
+ body=[
377
+ f"buffer.extend({bytes_prefix})",
378
+ Line.join(
379
+ Expr.local("encode_value", field_type.encode_fn()),
380
+ "(self.value, buffer)",
381
+ ),
382
+ ],
383
+ )
384
+
308
385
  return ret
309
386
 
310
387
 
388
+ def _encode_impl(
389
+ value: Any,
390
+ buffer: bytearray,
391
+ ) -> None:
392
+ if value._bytes:
393
+ buffer.extend(value._bytes)
394
+ else:
395
+ value._enc(buffer)
396
+
397
+
311
398
  @dataclass(frozen=True)
312
399
  class _ValueField:
313
400
  spec: _spec.ValueField
@@ -344,16 +431,23 @@ def _make_wrap_fn(field: _ValueField) -> Callable[[Any], Any]:
344
431
  )
345
432
 
346
433
 
434
+ def _make_create_fn(wrap_fn: Callable[[Any], Any], frozen_class: type) -> Callable:
435
+ def create(**kwargs):
436
+ return wrap_fn(frozen_class(**kwargs))
437
+
438
+ return create
439
+
440
+
347
441
  def _make_from_json_fn(
348
442
  constant_fields: Sequence[_spec.ConstantField],
349
443
  value_fields: Sequence[_ValueField],
350
444
  removed_numbers: set[int],
351
445
  base_class: type,
446
+ unrecognized_class: type,
352
447
  ) -> Callable[[Any], Any]:
353
- unrecognized_class = _make_unrecognized_class(base_class)
354
448
  unrecognized_class_local = Expr.local("Unrecognized", unrecognized_class)
355
449
  obj_setattr_local = Expr.local("obj_settatr", object.__setattr__)
356
- removed_numbers_local = Expr.local("removed_numbers", removed_numbers)
450
+ removed_numbers_tuple = tuple(sorted(removed_numbers))
357
451
 
358
452
  key_to_constant: dict[Union[int, str], Any] = {}
359
453
  for field in constant_fields:
@@ -364,15 +458,13 @@ def _make_from_json_fn(
364
458
  unknown_constant = key_to_constant[0]
365
459
  unknown_constant_local = Expr.local("unknown_constant", unknown_constant)
366
460
 
367
- numbers: list[int] = []
368
- names: list[str] = []
369
- key_to_field: dict[Union[int, str], _ValueField] = {}
461
+ number_to_value_field: dict[int, _ValueField] = {}
462
+ name_to_value_field: dict[str, _ValueField] = {}
370
463
  for field in value_fields:
371
- numbers.append(field.spec.number)
372
- names.append(field.spec.name)
373
- key_to_field[field.spec.number] = field
374
- key_to_field[field.spec.name] = field
375
- value_keys_local = Expr.local("value_keys", set(key_to_field.keys()))
464
+ number_to_value_field[field.spec.number] = field
465
+ name_to_value_field[field.spec.name] = field
466
+ value_field_numbers = tuple(sorted(number_to_value_field.keys()))
467
+ value_field_names = tuple(sorted(name_to_value_field.keys()))
376
468
 
377
469
  builder = BodyBuilder()
378
470
  # The reason why we wrap the function inside a 'while' is explained below.
@@ -389,16 +481,20 @@ def _make_from_json_fn(
389
481
  builder.append_ln(" return ", key_to_constant_local, "[json]")
390
482
  builder.append_ln(" except:")
391
483
  if removed_numbers:
392
- builder.append_ln(" if json in ", removed_numbers_local, ":")
484
+ builder.append_ln(
485
+ f" if json in {removed_numbers_tuple} or not keep_unrecognized_fields:"
486
+ )
393
487
  builder.append_ln(" return ", unknown_constant_local)
394
- builder.append_ln(" return ", unrecognized_class_local, "(json)")
488
+ builder.append_ln(" return ", unrecognized_class_local, "(json, b'\\0')")
395
489
 
396
- def append_number_branches(numbers: list[int], indent: str) -> None:
490
+ def append_number_branches(numbers: Sequence[int], indent: str) -> None:
397
491
  if len(numbers) == 1:
398
492
  number = numbers[0]
399
- field = key_to_field[number]
493
+ field = number_to_value_field[number]
400
494
  value_class_local = Expr.local("cls?", field.value_class)
401
- value_expr = field.field_type.from_json_expr("json[1]")
495
+ value_expr = field.field_type.from_json_expr(
496
+ "json[1]", "keep_unrecognized_fields"
497
+ )
402
498
  builder.append_ln(f"{indent}ret = ", value_class_local, "()")
403
499
  builder.append_ln(
404
500
  indent, obj_setattr_local, '(ret, "value", ', value_expr, ")"
@@ -420,19 +516,20 @@ def _make_from_json_fn(
420
516
  if not value_fields:
421
517
  # The field was either removed or is an unrecognized field.
422
518
  if removed_numbers:
423
- builder.append_ln(" if number in ", removed_numbers_local, ":")
519
+ builder.append_ln(
520
+ f" if number in {removed_numbers_tuple} or not keep_unrecognized_fields:"
521
+ )
424
522
  builder.append_ln(" return ", unknown_constant_local)
425
- builder.append_ln(" return ", unrecognized_class_local, "(json)")
523
+ builder.append_ln(" return ", unrecognized_class_local, "(json, b'\\0')")
426
524
  else:
427
- if len(value_fields) == 1:
428
- builder.append_ln(f" if number != {value_fields[0].spec.number}:")
429
- else:
430
- builder.append_ln(" if number not in ", value_keys_local, ":")
525
+ builder.append_ln(f" if number not in {value_field_numbers}:")
431
526
  if removed_numbers:
432
- builder.append_ln(" if number in ", removed_numbers_local, ":")
527
+ builder.append_ln(
528
+ f" if number in {removed_numbers_tuple} or not keep_unrecognized_fields:"
529
+ )
433
530
  builder.append_ln(" return ", unknown_constant_local)
434
- builder.append_ln(" return ", unrecognized_class_local, "(json)")
435
- append_number_branches(sorted(numbers), " ")
531
+ builder.append_ln(" return ", unrecognized_class_local, "(json, b'\\0')")
532
+ append_number_branches(value_field_numbers, " ")
436
533
 
437
534
  # READABLE FORMAT
438
535
  if len(constant_fields) == 1:
@@ -446,12 +543,14 @@ def _make_from_json_fn(
446
543
  # In readable mode, drop unrecognized values and use UNKNOWN instead.
447
544
  builder.append_ln(" return ", unknown_constant_local)
448
545
 
449
- def append_name_branches(names: list[str], indent: str) -> None:
546
+ def append_name_branches(names: Sequence[str], indent: str) -> None:
450
547
  if len(names) == 1:
451
548
  name = names[0]
452
- field = key_to_field[name]
549
+ field = name_to_value_field[name]
453
550
  value_class_local = Expr.local("cls?", field.value_class)
454
- value_expr = field.field_type.from_json_expr("json['value']")
551
+ value_expr = field.field_type.from_json_expr(
552
+ "json['value']", "keep_unrecognized_fields"
553
+ )
455
554
  builder.append_ln(f"{indent}ret = ", value_class_local, "()")
456
555
  builder.append_ln(
457
556
  indent, obj_setattr_local, '(ret, "value", ', value_expr, ")"
@@ -472,10 +571,10 @@ def _make_from_json_fn(
472
571
  builder.append_ln(" return ", unknown_constant_local)
473
572
  else:
474
573
  builder.append_ln(" kind = json['kind']")
475
- builder.append_ln(" if kind not in ", value_keys_local, ":")
574
+ builder.append_ln(f" if kind not in {value_field_names}:")
476
575
  builder.append_ln(" return ", unknown_constant_local)
477
576
  builder.append_ln(" else:")
478
- append_name_branches(sorted(names), " ")
577
+ append_name_branches(value_field_names, " ")
479
578
 
480
579
  # In the unlikely event that json.loads() returns an instance of a subclass of int.
481
580
  builder.append_ln(" elif isinstance(json, int):")
@@ -487,7 +586,112 @@ def _make_from_json_fn(
487
586
 
488
587
  return make_function(
489
588
  name="from_json",
490
- params=["json"],
589
+ params=["json", "keep_unrecognized_fields"],
590
+ body=builder.build(),
591
+ )
592
+
593
+
594
+ def _make_decode_fn(
595
+ constant_fields: Sequence[_spec.ConstantField],
596
+ value_fields: Sequence[_ValueField],
597
+ removed_numbers: set[int],
598
+ base_class: type,
599
+ unrecognized_class: type,
600
+ ) -> Callable[[ByteStream], Any]:
601
+ unrecognized_class_local = Expr.local("Unrecognized", unrecognized_class)
602
+ obj_setattr_local = Expr.local("obj_settatr", object.__setattr__)
603
+
604
+ number_to_constant: dict[int, Any] = {}
605
+ for field in constant_fields:
606
+ constant = getattr(base_class, field.attribute)
607
+ number_to_constant[field.number] = constant
608
+ number_to_constant_local = Expr.local("number_to_constant", number_to_constant)
609
+ removed_numbers_tuple = tuple(sorted(removed_numbers))
610
+ unknown_constant = number_to_constant[0]
611
+ unknown_constant_local = Expr.local("unknown_constant", unknown_constant)
612
+
613
+ number_to_value_field: dict[int, _ValueField] = {}
614
+ for field in value_fields:
615
+ number_to_value_field[field.spec.number] = field
616
+ value_field_numbers = tuple(sorted(number_to_value_field.keys()))
617
+
618
+ builder = BodyBuilder()
619
+ builder.append_ln("start_offset = stream.position")
620
+ builder.append_ln("wire = stream.buffer[start_offset]")
621
+ builder.append_ln("if wire <= 238:")
622
+ # A number
623
+ builder.append_ln(" if wire < 232:")
624
+ builder.append_ln(" stream.position += 1")
625
+ builder.append_ln(" number = wire")
626
+ builder.append_ln(" else:")
627
+ builder.append_ln(
628
+ " number = ", Expr.local("decode_int64", decode_int64), "(stream)"
629
+ )
630
+ builder.append_ln(" try:")
631
+ builder.append_ln(" return ", number_to_constant_local, "[number]")
632
+ builder.append_ln(" except:")
633
+ builder.append_ln(" ", Expr.local("decode_unused", decode_unused), "(stream)")
634
+ if removed_numbers:
635
+ builder.append_ln(
636
+ f" if number in {removed_numbers_tuple} or not keep_unrecognized_fields:"
637
+ )
638
+ builder.append_ln(" return ", unknown_constant_local)
639
+ builder.append_ln(" bytes = stream.buffer[start_offset:stream.position]")
640
+ builder.append_ln(" return ", unrecognized_class_local, "(0, bytes)")
641
+ # An array of 2
642
+ builder.append_ln("stream.position += 1")
643
+ builder.append_ln("if wire == 248:")
644
+ builder.append_ln(
645
+ " number = ", Expr.local("decode_int64", decode_int64), "(stream)"
646
+ )
647
+ builder.append_ln("else:")
648
+ builder.append_ln(" number = wire - 250")
649
+
650
+ def append_number_branches(numbers: Sequence[int], indent: str) -> None:
651
+ if len(numbers) == 1:
652
+ number = numbers[0]
653
+ field = number_to_value_field[number]
654
+ value_class_local = Expr.local("cls?", field.value_class)
655
+ decode_local = Expr.local("decode?", field.field_type.decode_fn())
656
+ value_expr = Expr.join(decode_local, "(stream)")
657
+ builder.append_ln(f"{indent}ret = ", value_class_local, "()")
658
+ builder.append_ln(
659
+ indent, obj_setattr_local, '(ret, "value", ', value_expr, ")"
660
+ )
661
+ builder.append_ln(f"{indent}return ret")
662
+ else:
663
+ indented = f" {indent}"
664
+ mid_index = int(len(numbers) / 2)
665
+ mid_number = numbers[mid_index - 1]
666
+ operator = "==" if mid_index == 1 else "<="
667
+ builder.append_ln(f"{indent}if number {operator} {mid_number}:")
668
+ append_number_branches(numbers[0:mid_index], indented)
669
+ builder.append_ln(f"{indent}else:")
670
+ append_number_branches(numbers[mid_index:], indented)
671
+
672
+ if not value_fields:
673
+ # The field was either removed or is an unrecognized field.
674
+ if removed_numbers:
675
+ builder.append_ln(
676
+ f"if number in {removed_numbers_tuple} or not keep_unrecognized_fields:"
677
+ )
678
+ builder.append_ln(" return ", unknown_constant_local)
679
+ builder.append_ln("bytes = stream.buffer[start_offset:stream.position]")
680
+ builder.append_ln("return ", unrecognized_class_local, "(0, bytes)")
681
+ else:
682
+ builder.append_ln(f"if number not in {value_field_numbers}:")
683
+ if removed_numbers:
684
+ builder.append_ln(
685
+ f" if number in {removed_numbers_tuple} or not keep_unrecognized_fields:"
686
+ )
687
+ builder.append_ln(" return ", unknown_constant_local)
688
+ builder.append_ln(" bytes = stream.buffer[start_offset:stream.position]")
689
+ builder.append_ln(" return ", unrecognized_class_local, "(0, bytes)")
690
+ append_number_branches(value_field_numbers, "")
691
+
692
+ return make_function(
693
+ name="decode",
694
+ params=["stream"],
491
695
  body=builder.build(),
492
696
  )
493
697
 
soia/_impl/optionals.py CHANGED
@@ -1,24 +1,22 @@
1
1
  from collections.abc import Callable
2
2
  from dataclasses import dataclass
3
- from typing import TypeVar
3
+ from functools import cached_property
4
+ from typing import Generic
4
5
  from weakref import WeakValueDictionary
5
6
 
6
- from soia._impl.function_maker import Expr, ExprLike
7
- from soia._impl.type_adapter import TypeAdapter
8
-
9
7
  from soia import _spec, reflection
10
-
11
- Other = TypeVar("Other")
8
+ from soia._impl.function_maker import Expr, ExprLike
9
+ from soia._impl.type_adapter import T, ByteStream, TypeAdapter
12
10
 
13
11
 
14
- def get_optional_adapter(other_adapter: TypeAdapter) -> TypeAdapter:
12
+ def get_optional_adapter(other_adapter: TypeAdapter[T]) -> TypeAdapter[T | None]:
15
13
  return _other_adapter_to_optional_adapter.setdefault(
16
14
  other_adapter, _OptionalAdapter(other_adapter)
17
15
  )
18
16
 
19
17
 
20
18
  @dataclass(frozen=True)
21
- class _OptionalAdapter(TypeAdapter):
19
+ class _OptionalAdapter(Generic[T], TypeAdapter[T | None]):
22
20
  __slots__ = ("other_adapter",)
23
21
 
24
22
  other_adapter: TypeAdapter
@@ -50,8 +48,12 @@ class _OptionalAdapter(TypeAdapter):
50
48
  ")",
51
49
  )
52
50
 
53
- def from_json_expr(self, json_expr: ExprLike) -> ExprLike:
54
- other_from_json = self.other_adapter.from_json_expr(json_expr)
51
+ def from_json_expr(
52
+ self, json_expr: ExprLike, keep_unrecognized_expr: ExprLike
53
+ ) -> ExprLike:
54
+ other_from_json = self.other_adapter.from_json_expr(
55
+ json_expr, keep_unrecognized_expr
56
+ )
55
57
  if other_from_json == json_expr:
56
58
  return json_expr
57
59
  return Expr.join(
@@ -62,6 +64,42 @@ class _OptionalAdapter(TypeAdapter):
62
64
  ")",
63
65
  )
64
66
 
67
+ @cached_property
68
+ def encode_fn_impl(self) -> Callable[[T | None, bytearray], None]:
69
+ encode_value = self.other_adapter.encode_fn()
70
+
71
+ def encode(
72
+ value: T | None,
73
+ buffer: bytearray,
74
+ ) -> None:
75
+ if value is None:
76
+ buffer.append(255)
77
+ else:
78
+ encode_value(value, buffer)
79
+
80
+ return encode
81
+
82
+ def encode_fn(self) -> Callable[[T | None, bytearray], None]:
83
+ return self.encode_fn_impl
84
+
85
+ @cached_property
86
+ def decode_fn_impl(self) -> Callable[[ByteStream], T | None]:
87
+ decode_value = self.other_adapter.decode_fn()
88
+
89
+ def decode(
90
+ stream: ByteStream,
91
+ ) -> T | None:
92
+ if stream.buffer[stream.position] == 255:
93
+ stream.position += 1
94
+ return None
95
+ else:
96
+ return decode_value(stream)
97
+
98
+ return decode
99
+
100
+ def decode_fn(self) -> Callable[[ByteStream], T | None]:
101
+ return self.decode_fn_impl
102
+
65
103
  def finalize(
66
104
  self,
67
105
  resolve_type_fn: Callable[[_spec.Type], "TypeAdapter"],
@@ -80,6 +118,9 @@ class _OptionalAdapter(TypeAdapter):
80
118
  ) -> None:
81
119
  self.other_adapter.register_records(registry)
82
120
 
121
+ def frozen_class_of_struct(self) -> type | None:
122
+ return None
123
+
83
124
 
84
125
  _other_adapter_to_optional_adapter: WeakValueDictionary[TypeAdapter, TypeAdapter] = (
85
126
  WeakValueDictionary()