soia-client 1.0.7__py3-none-any.whl → 1.0.8__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: soia-client
3
- Version: 1.0.7
3
+ Version: 1.0.8
4
4
  Author-email: Tyler Fibonacci <gepheum@gmail.com>
5
5
  License: MIT License
6
6
 
@@ -0,0 +1,23 @@
1
+ soialib/__init__.py,sha256=h_ENkLJEdrh1mGhCoKgIadDasSulW3zK9_qhEsTviXY,429
2
+ soialib/keyed_items.py,sha256=q7MCn82obf-0jh7FcAhuw4eh9-wtuHIpkEFcSfc8EaY,338
3
+ soialib/method.py,sha256=2qWG4jMqYhS3hA8y8YDu3iqzhXA_AKebpB38RWNmsYQ,452
4
+ soialib/module_initializer.py,sha256=Riq6B6cS9HUG1W8tyaa0GkiG3F4fGX70bKU0UOCnrRw,4205
5
+ soialib/never.py,sha256=bYU63XyNX4e2wOUXQHhHWGO-an4IFr9_ur1ut6GmGN0,47
6
+ soialib/serializer.py,sha256=Dkn1sVSwer2EurwKJy73WIT2ZSihN-WgZDis4KHbrWg,3119
7
+ soialib/serializers.py,sha256=7zmCdtaUkuy1vIGP1OBn06x8FryYep15bujrFoeGw9A,2572
8
+ soialib/spec.py,sha256=w-eMMUqOchOlCJaXppyAa2JpSBrMo9lrMvqz8VaEX4I,3699
9
+ soialib/timestamp.py,sha256=SG0SMEMR2UiBI_fK-b7vF5mGft9ZqRks_zTm-iRyTw8,4067
10
+ soialib/impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ soialib/impl/arrays.py,sha256=xi0prorQlJYBHrVaLpQI7azzJuY8YnSMGMiI2c3K8ko,4903
12
+ soialib/impl/enums.py,sha256=rmfGvJONfcMVGi3uW0gDN4tXiv0m4eosnu8ZPM3Da9E,16012
13
+ soialib/impl/function_maker.py,sha256=jyd7itpXoYgnV7Rbchh7LIqbsO8HOgivgX96M4Pz3zw,5631
14
+ soialib/impl/optionals.py,sha256=pWuhfTIYM7669Rko-oVlBhHLqO3vgASW7fL0Yos3AWM,2076
15
+ soialib/impl/primitives.py,sha256=HrMaNk_7pyzKVwM8cpz7L2rBWRJo6AW93fYklEkhH-A,6406
16
+ soialib/impl/repr.py,sha256=7WX0bEAVENTjlyZIcbT8TcJylS7IRIyafGCmqaIMxFM,1413
17
+ soialib/impl/structs.py,sha256=llvQjJpdcJRt-wV_j0gxNNXbIARjH8r7HHplhorihV0,25564
18
+ soialib/impl/type_adapter.py,sha256=e72nBDqOP0uWNY10EtG7qOvRTzujA-LUVA30Ff7eeac,1935
19
+ soia_client-1.0.8.dist-info/LICENSE,sha256=SaAftKkX6hfSOiPdENQPS70tifH3PDHgazq8eK2Pwfw,1064
20
+ soia_client-1.0.8.dist-info/METADATA,sha256=mVA0asChj6huzo13A_adFiddi6pN9FPS9AvcWk-1n9k,1645
21
+ soia_client-1.0.8.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
22
+ soia_client-1.0.8.dist-info/top_level.txt,sha256=2vPmAo5G0SrCxYrNdJKJJVdpalYppgjO2mmz2PtsFUI,8
23
+ soia_client-1.0.8.dist-info/RECORD,,
soialib/impl/arrays.py CHANGED
@@ -94,7 +94,7 @@ class _ArrayAdapter(TypeAdapter):
94
94
  listuple_class_local,
95
95
  "([",
96
96
  self.item_adapter.from_json_expr(Expr.join("_e")),
97
- " for e in ",
97
+ " for _e in ",
98
98
  json_expr,
99
99
  "] or ",
100
100
  empty_listuple_local,
soialib/impl/enums.py CHANGED
@@ -1,20 +1,10 @@
1
- # TODO: test
2
- # TODO: unrecognized fields (and handles removed fields)
3
-
1
+ import copy
4
2
  from collections.abc import Callable, Sequence
5
3
  from dataclasses import FrozenInstanceError, dataclass
6
4
  from typing import Any, Final, Union
7
5
 
8
6
  from soialib import spec as _spec
9
- from soialib.impl import primitives
10
- from soialib.impl.function_maker import (
11
- BodyBuilder,
12
- BodySpan,
13
- Expr,
14
- ExprLike,
15
- Line,
16
- make_function,
17
- )
7
+ from soialib.impl.function_maker import BodyBuilder, Expr, ExprLike, Line, make_function
18
8
  from soialib.impl.repr import repr_impl
19
9
  from soialib.impl.type_adapter import TypeAdapter
20
10
 
@@ -40,7 +30,6 @@ class EnumAdapter(TypeAdapter):
40
30
 
41
31
  private_is_enum_attr = _name_private_is_enum_attr(spec.id)
42
32
  self.private_is_enum_attr = private_is_enum_attr
43
- # TODO: comment
44
33
  setattr(base_class, private_is_enum_attr, True)
45
34
 
46
35
  # Add the constants.
@@ -81,7 +70,10 @@ class EnumAdapter(TypeAdapter):
81
70
  setattr(base_class, f"wrap_{value_field.spec.name}", wrap_fn)
82
71
 
83
72
  base_class._fj = _make_from_json_fn(
84
- self.all_constant_fields, value_fields, base_class
73
+ self.all_constant_fields,
74
+ value_fields,
75
+ set(self.spec.removed_numbers),
76
+ base_class,
85
77
  )
86
78
 
87
79
  # Mark finalization as done.
@@ -116,7 +108,6 @@ class EnumAdapter(TypeAdapter):
116
108
 
117
109
  def from_json_expr(self, json_expr: ExprLike) -> Expr:
118
110
  fn_name = "_fj"
119
- # TODO: comment
120
111
  from_json_fn = getattr(self.gen_class, fn_name, None)
121
112
  if from_json_fn:
122
113
  return Expr.join(Expr.local("_fj?", from_json_fn), "(", json_expr, ")")
@@ -149,7 +140,6 @@ def _make_base_class(spec: _spec.Enum) -> type:
149
140
  raise FrozenInstanceError(self.__class__.__qualname__)
150
141
 
151
142
  def __eq__(self, other: Any) -> bool:
152
- # TODO: make it work with unrecognized versus UNKNOWN
153
143
  if isinstance(other, BaseClass):
154
144
  return other.kind == self.kind and other.value == self.value
155
145
  return NotImplemented
@@ -164,7 +154,7 @@ def _make_base_class(spec: _spec.Enum) -> type:
164
154
 
165
155
 
166
156
  def _make_constant_class(base_class: type, spec: _spec.ConstantField) -> type:
167
- class ConstantClass(base_class):
157
+ class Constant(base_class):
168
158
  __slots__ = ()
169
159
 
170
160
  kind: Final[str] = spec.name
@@ -183,7 +173,37 @@ def _make_constant_class(base_class: type, spec: _spec.ConstantField) -> type:
183
173
  def __repr__(self) -> str:
184
174
  return f"{base_class.__qualname__}.{spec.attribute}"
185
175
 
186
- return ConstantClass
176
+ return Constant
177
+
178
+
179
+ def _make_unrecognized_class(base_class: type) -> type:
180
+ """Wraps around an unrecognized dense JSON.
181
+
182
+ Looks and acts just like the UNKNOWN constant, except that its JSON representation
183
+ is the original unrecognized dense JSON.
184
+ """
185
+
186
+ class Unrecognized(base_class):
187
+ __slots__ = ("_dj",)
188
+
189
+ kind: Final[str] = "?"
190
+ _number: Final[int] = 0
191
+ # dense JSON
192
+ _dj: Any
193
+ # readable JSON
194
+ _rj: Final[str] = "?"
195
+ # has value
196
+ _hv: Final[bool] = False
197
+
198
+ def __init__(self, dj: Any):
199
+ # Do not call super().__init__().
200
+ object.__setattr__(self, "_dj", copy.deepcopy(dj))
201
+ object.__setattr__(self, "value", None)
202
+
203
+ def __repr__(self) -> str:
204
+ return f"{base_class.__qualname__}.UNKNOWN"
205
+
206
+ return Unrecognized
187
207
 
188
208
 
189
209
  def _make_value_class(
@@ -193,7 +213,7 @@ def _make_value_class(
193
213
  ) -> type:
194
214
  number = field_spec.number
195
215
 
196
- class ValueClass(base_class):
216
+ class Value(base_class):
197
217
  __slots__ = ()
198
218
 
199
219
  kind: Final[str] = field_spec.name
@@ -213,7 +233,7 @@ def _make_value_class(
213
233
  body = value_repr.repr
214
234
  return f"{base_class.__qualname__}.wrap_{field_spec.name}({body})"
215
235
 
216
- ret = ValueClass
236
+ ret = Value
217
237
 
218
238
  ret._dj = property(
219
239
  make_function(
@@ -286,9 +306,13 @@ def _make_wrap_fn(field: _ValueField) -> Callable[[Any], Any]:
286
306
  def _make_from_json_fn(
287
307
  constant_fields: Sequence[_spec.ConstantField],
288
308
  value_fields: Sequence[_ValueField],
309
+ removed_numbers: set[int],
289
310
  base_class: type,
290
311
  ) -> Callable[[Any], Any]:
312
+ unrecognized_class = _make_unrecognized_class(base_class)
313
+ unrecognized_class_local = Expr.local("Unrecognized", unrecognized_class)
291
314
  obj_setattr_local = Expr.local("obj_settatr", object.__setattr__)
315
+ removed_numbers_local = Expr.local("removed_numbers", removed_numbers)
292
316
 
293
317
  key_to_constant: dict[Union[int, str], Any] = {}
294
318
  for field in constant_fields:
@@ -318,12 +342,15 @@ def _make_from_json_fn(
318
342
  builder.append_ln(" if json == 0:")
319
343
  builder.append_ln(" return ", unknown_constant_local)
320
344
  else:
345
+ # `json.__class__ is int` is significantly faster than `isinstance(json, int)`
321
346
  builder.append_ln(" if json.__class__ is int:")
322
347
  builder.append_ln(" try:")
323
348
  builder.append_ln(" return ", key_to_constant_local, "[json]")
324
349
  builder.append_ln(" except:")
325
- # TODO: handle unrecognized fields!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
326
- builder.append_ln(" return ...")
350
+ if removed_numbers:
351
+ builder.append_ln(" if json in ", removed_numbers_local, ":")
352
+ builder.append_ln(" return ", unknown_constant_local)
353
+ builder.append_ln(" return ", unrecognized_class_local, "(json)")
327
354
 
328
355
  def append_number_branches(numbers: list[int], indent: str) -> None:
329
356
  if len(numbers) == 1:
@@ -332,7 +359,9 @@ def _make_from_json_fn(
332
359
  value_class_local = Expr.local("cls?", field.value_class)
333
360
  value_expr = field.field_type.from_json_expr(Expr.join("json[1]"))
334
361
  builder.append_ln(f"{indent}ret = ", value_class_local, "()")
335
- builder.append_ln(indent, obj_setattr_local, "(ret, ", value_expr, ")")
362
+ builder.append_ln(
363
+ indent, obj_setattr_local, '(ret, "value", ', value_expr, ")"
364
+ )
336
365
  builder.append_ln(f"{indent}return ret")
337
366
  else:
338
367
  indented = f" {indent}"
@@ -344,18 +373,24 @@ def _make_from_json_fn(
344
373
  builder.append_ln(f"{indent}else:")
345
374
  append_number_branches(numbers[mid_index:], indented)
346
375
 
376
+ # `json.__class__ is list` is significantly faster than `isinstance(json, list)`
347
377
  builder.append_ln(" elif json.__class__ is list:")
378
+ builder.append_ln(" number = json[0]")
348
379
  if not value_fields:
349
- # TODO: handle unrecognized fields!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
350
- builder.append_ln(" return ...")
380
+ # The field was either removed or is an unrecognized field.
381
+ if removed_numbers:
382
+ builder.append_ln(" if number in ", removed_numbers_local, ":")
383
+ builder.append_ln(" return ", unknown_constant_local)
384
+ builder.append_ln(" return ", unrecognized_class_local, "(json)")
351
385
  else:
352
386
  if len(value_fields) == 1:
353
387
  builder.append_ln(f" if number != {value_fields[0].spec.number}:")
354
388
  else:
355
389
  builder.append_ln(" if number not in ", value_keys_local, ":")
356
- # TODO: handle unrecognized fields!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
357
- builder.append_ln(" return ...")
358
- builder.append_ln(" number = json[0]")
390
+ if removed_numbers:
391
+ builder.append_ln(" if number in ", removed_numbers_local, ":")
392
+ builder.append_ln(" return ", unknown_constant_local)
393
+ builder.append_ln(" return ", unrecognized_class_local, "(json)")
359
394
  append_number_branches(sorted(numbers), " ")
360
395
 
361
396
  # READABLE FORMAT
@@ -367,7 +402,7 @@ def _make_from_json_fn(
367
402
  builder.append_ln(" try:")
368
403
  builder.append_ln(" return ", key_to_constant_local, "[json]")
369
404
  builder.append_ln(" except:")
370
- # TODO: comment
405
+ # In readable mode, drop unrecognized values and use UNKNOWN instead.
371
406
  builder.append_ln(" return ", unknown_constant_local)
372
407
 
373
408
  def append_name_branches(names: list[str], indent: str) -> None:
@@ -377,30 +412,38 @@ def _make_from_json_fn(
377
412
  value_class_local = Expr.local("cls?", field.value_class)
378
413
  value_expr = field.field_type.from_json_expr("json['value']")
379
414
  builder.append_ln(f"{indent}ret = ", value_class_local, "()")
380
- builder.append_ln(indent, obj_setattr_local, "(ret, ", value_expr, ")")
415
+ builder.append_ln(
416
+ indent, obj_setattr_local, '(ret, "value", ', value_expr, ")"
417
+ )
381
418
  builder.append_ln(f"{indent}return ret")
382
419
  else:
383
420
  indented = f" {indent}"
384
421
  mid_index = int(len(names) / 2)
385
422
  mid_name = names[mid_index - 1]
386
423
  operator = "==" if mid_index == 1 else "<="
387
- builder.append_ln(f"{indent}if name {operator} '{mid_name}':")
424
+ builder.append_ln(f"{indent}if kind {operator} '{mid_name}':")
388
425
  append_name_branches(names[0:mid_index], indented)
389
426
  builder.append_ln(f"{indent}else:")
390
427
  append_name_branches(names[mid_index:], indented)
391
428
 
392
429
  builder.append_ln(" elif isinstance(json, dict):")
393
430
  if not value_fields:
394
- # TODO: comment
395
431
  builder.append_ln(" return ", unknown_constant_local)
396
432
  else:
397
- builder.append_ln(" name = json['name']")
398
- builder.append_ln(" if name not in ", value_keys_local, ":")
399
- # TODO: comment
433
+ builder.append_ln(" kind = json['kind']")
434
+ builder.append_ln(" if kind not in ", value_keys_local, ":")
400
435
  builder.append_ln(" return ", unknown_constant_local)
401
436
  builder.append_ln(" else:")
402
437
  append_name_branches(sorted(names), " ")
403
438
 
439
+ # In the unlikely event that json.loads() returns an instance of a subclass of int.
440
+ builder.append_ln(" elif isinstance(json, int):")
441
+ builder.append_ln(" json = int(json)")
442
+ builder.append_ln(" elif isinstance(json, list):")
443
+ builder.append_ln(" json = list(json)")
444
+ builder.append_ln(" else:")
445
+ builder.append_ln(" return TypeError()")
446
+
404
447
  return make_function(
405
448
  name="from_json",
406
449
  params=["json"],
@@ -1,6 +1,6 @@
1
1
  import itertools
2
2
  from dataclasses import dataclass
3
- from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union
3
+ from typing import Any, Callable, Iterable, Optional, Sequence, Union
4
4
 
5
5
 
6
6
  def make_function(
@@ -120,15 +120,11 @@ class LineSpan:
120
120
  _EMPTY_LINE_SPAN = LineSpan(())
121
121
 
122
122
 
123
- # TODO: comment
124
123
  LineSpanLike = Union[str, LineSpan]
125
124
 
126
125
 
127
126
  # A type alias to use when the line span is a Python expression.
128
127
  Expr = LineSpan
129
-
130
-
131
- # TODO: comment
132
128
  ExprLike = LineSpanLike
133
129
 
134
130
 
soialib/impl/optionals.py CHANGED
@@ -4,7 +4,6 @@ from typing import TypeVar
4
4
  from weakref import WeakValueDictionary
5
5
 
6
6
  from soialib import spec
7
- from soialib.impl.encoding import NULL_WIRE
8
7
  from soialib.impl.function_maker import Expr, ExprLike
9
8
  from soialib.impl.type_adapter import TypeAdapter
10
9
 
@@ -33,9 +33,9 @@ class _BoolAdapter(AbstractPrimitiveAdapter):
33
33
  readable: bool,
34
34
  ) -> ExprLike:
35
35
  if readable:
36
- return Expr.join("(1 if ", in_expr, " else 0)")
36
+ return Expr.join("(True if ", in_expr, " else False)")
37
37
  else:
38
- return in_expr
38
+ return Expr.join("(1 if ", in_expr, " else 0)")
39
39
 
40
40
  def from_json_expr(self, json_expr: ExprLike) -> Expr:
41
41
  return Expr.join("(True if ", json_expr, " else False)")
@@ -52,6 +52,8 @@ class _AbstractIntAdapter(AbstractPrimitiveAdapter):
52
52
  return "0"
53
53
 
54
54
  def to_frozen_expr(self, arg_expr: ExprLike) -> Expr:
55
+ round_expr = Expr.local("round", round)
56
+ # Must accept float inputs and turn them into ints.
55
57
  return Expr.join("(", arg_expr, " | 0)")
56
58
 
57
59
  def is_not_default_expr(self, arg_expr: ExprLike, attr_expr: ExprLike) -> ExprLike:
@@ -61,7 +63,18 @@ class _AbstractIntAdapter(AbstractPrimitiveAdapter):
61
63
  return in_expr
62
64
 
63
65
  def from_json_expr(self, json_expr: ExprLike) -> Expr:
64
- return Expr.join("(", json_expr, " | 0)")
66
+ # Must accept float inputs and turn them into ints.
67
+ return Expr.join(
68
+ "(",
69
+ json_expr,
70
+ " if ",
71
+ json_expr,
72
+ ".__class__ is (0).__class__ else ",
73
+ Expr.local("_round", round),
74
+ "(",
75
+ json_expr,
76
+ "))",
77
+ )
65
78
 
66
79
 
67
80
  @dataclass(frozen=True)
@@ -203,7 +216,7 @@ class _BytesAdapter(AbstractPrimitiveAdapter):
203
216
 
204
217
  def from_json_expr(self, json_expr: ExprLike) -> Expr:
205
218
  return Expr.join(
206
- Expr.local("fromhex", _BytesAdapter._fromhex_fn), "(", json_expr, ")"
219
+ Expr.local("fromhex", _BytesAdapter._fromhex_fn), "(", json_expr, ' or "")'
207
220
  )
208
221
 
209
222
 
soialib/impl/repr.py CHANGED
@@ -31,7 +31,6 @@ def repr_impl(input: Any) -> ReprResult:
31
31
  return ReprResult(f"[\n{body}]", complex=True)
32
32
  elif isinstance(input, str):
33
33
  if "\n" in input:
34
- # TODO: comment
35
34
  lines = input.split("\n")
36
35
  body = "".join(f" {repr(line)},\n" for line in lines)
37
36
  return ReprResult(f"'\\n'.join([\n{body}])", complex=True)
soialib/impl/structs.py CHANGED
@@ -1,12 +1,11 @@
1
+ import copy
1
2
  from collections.abc import Callable, Sequence
2
3
  from dataclasses import FrozenInstanceError, dataclass
3
- from typing import Any, Final, Union
4
+ from typing import Any, Final, Union, cast
4
5
 
5
6
  from soialib import spec as _spec
6
- from soialib.impl.encoding import ARRAY_WIRE, LEN_BYTES, SMALL_ARRAY_WIRES
7
7
  from soialib.impl.function_maker import (
8
8
  BodyBuilder,
9
- BodySpan,
10
9
  Expr,
11
10
  ExprLike,
12
11
  Line,
@@ -50,8 +49,8 @@ class StructAdapter(TypeAdapter):
50
49
  self.private_is_frozen_attr = _name_private_is_frozen_attr(spec.id)
51
50
 
52
51
  slots = tuple(f.attribute for f in self.spec.fields) + (
53
- # Unknown fields encountered during deserialization.
54
- "_unknown",
52
+ # Unrecognized fields encountered during deserialization.
53
+ "_unrecognized",
55
54
  # Lowest number greater than the number of every field with a non-default
56
55
  # value.
57
56
  "_array_len",
@@ -59,8 +58,8 @@ class StructAdapter(TypeAdapter):
59
58
  frozen_class = self.gen_class = _make_dataclass(slots)
60
59
  frozen_class.__name__ = spec.class_name
61
60
  frozen_class.__qualname__ = spec.class_qualname
62
- frozen_class.__setattr__ = _Frozen.__setattr__
63
- frozen_class.__delattr__ = _Frozen.__delattr__
61
+ frozen_class.__setattr__ = cast(Any, _Frozen.__setattr__)
62
+ frozen_class.__delattr__ = cast(Any, _Frozen.__delattr__)
64
63
  # We haven't added an __init__ method to the frozen class yet, so frozen_class()
65
64
  # returns an object with no attribute set. We'll set the attributes of DEFAULT
66
65
  # at the finalization step.
@@ -98,7 +97,6 @@ class StructAdapter(TypeAdapter):
98
97
  )
99
98
  )
100
99
 
101
- # TODO: do I even need this?
102
100
  # Aim to have dependencies finalized *before* the dependent. It's not always
103
101
  # possible, because there can be cyclic dependencies.
104
102
  # The function returned by the do_x_fn() method of a dependency is marginally
@@ -125,16 +123,21 @@ class StructAdapter(TypeAdapter):
125
123
  setattr(frozen_class, self.private_is_frozen_attr, True)
126
124
  setattr(mutable_class, self.private_is_frozen_attr, False)
127
125
 
128
- frozen_class.__init__ = _make_frozen_class_init_fn(
129
- fields,
130
- frozen_class=frozen_class,
131
- simple_class=simple_class,
126
+ frozen_class.__init__ = cast(
127
+ Any,
128
+ _make_frozen_class_init_fn(
129
+ fields,
130
+ frozen_class=frozen_class,
131
+ simple_class=simple_class,
132
+ ),
132
133
  )
133
- mutable_class.__init__ = _make_mutable_class_init_fn(fields)
134
+ mutable_class.__init__ = cast(Any, _make_mutable_class_init_fn(fields))
134
135
 
135
136
  frozen_class.__eq__ = _make_eq_fn(fields)
136
- frozen_class.__hash__ = _make_hash_fn(fields, self.record_hash)
137
- frozen_class.__repr__ = mutable_class.__repr__ = _make_repr_fn(fields)
137
+ frozen_class.__hash__ = cast(Any, _make_hash_fn(fields, self.record_hash))
138
+ frozen_class.__repr__ = mutable_class.__repr__ = cast(
139
+ Any, _make_repr_fn(fields)
140
+ )
138
141
 
139
142
  frozen_class._tdj = _make_to_dense_json_fn(fields=fields)
140
143
  frozen_class._trj = _make_to_readable_json_fn(fields=fields)
@@ -162,7 +165,8 @@ class StructAdapter(TypeAdapter):
162
165
  return Expr.local("_d?", self.gen_class.DEFAULT)
163
166
 
164
167
  def to_frozen_expr(self, arg_expr: ExprLike) -> Expr:
165
- # TODO: comment
168
+ # The goal of referring to private_is_frozen_attr is to raise an error if the
169
+ # struct has the wrong type.
166
170
  return Expr.join(
167
171
  "(",
168
172
  arg_expr,
@@ -185,7 +189,7 @@ class StructAdapter(TypeAdapter):
185
189
 
186
190
  def from_json_expr(self, json_expr: ExprLike) -> Expr:
187
191
  fn_name = "_fj"
188
- # TODO: comment
192
+ # The _fj method may not have been added to the class yet.
189
193
  from_json_fn = getattr(self.gen_class, fn_name, None)
190
194
  if from_json_fn:
191
195
  return Expr.join(Expr.local("_fj?", from_json_fn), "(", json_expr, ")")
@@ -231,6 +235,8 @@ def _make_frozen_class_init_fn(
231
235
 
232
236
  # Set the params.
233
237
  params: Params = ["_self"]
238
+ if fields:
239
+ params.append("*")
234
240
  for field in fields:
235
241
  params.append(
236
242
  Param(
@@ -262,8 +268,8 @@ def _make_frozen_class_init_fn(
262
268
  spans.append("0")
263
269
  return Expr.join(*spans)
264
270
 
265
- if len(fields) < 4:
266
- # If the class has less than 4 fields, it is faster to assign every field with
271
+ if len(fields) < 3:
272
+ # If the class has less than 3 fields, it is faster to assign every field with
267
273
  # object.__setattr__().
268
274
  for field in fields:
269
275
  attribute = field.field.attribute
@@ -275,6 +281,8 @@ def _make_frozen_class_init_fn(
275
281
  field.type.to_frozen_expr(attribute),
276
282
  ")",
277
283
  )
284
+ # Set the _unrecognized field.
285
+ builder.append_ln(obj_setattr, '(_self, "_unrecognized", ())')
278
286
  # Set array length.
279
287
  builder.append_ln(
280
288
  obj_setattr,
@@ -300,6 +308,8 @@ def _make_frozen_class_init_fn(
300
308
  " = ",
301
309
  field.type.to_frozen_expr(attribute),
302
310
  )
311
+ # Set the _unrecognized field.
312
+ builder.append_ln("_self._unrecognized = ()")
303
313
  # Set array length.
304
314
  builder.append_ln(f"_self._array_len = ", array_len_expr())
305
315
  # Change back the __class__.
@@ -333,6 +343,7 @@ def _make_mutable_class_init_fn(fields: Sequence[_Field]) -> Callable[[Any], Non
333
343
  " = ",
334
344
  attribute,
335
345
  )
346
+ builder.append_ln("_self._unrecognized = ()")
336
347
  return make_function(
337
348
  name="__init__",
338
349
  params=params,
@@ -358,7 +369,7 @@ def _make_to_mutable_fn(
358
369
  for field in fields:
359
370
  attribute = field.field.attribute
360
371
  builder.append_ln("ret.", attribute, " = self.", attribute)
361
- # TODO: unknown field
372
+ builder.append_ln("ret._unrecognized = self._unrecognized")
362
373
  builder.append_ln(
363
374
  "ret.__class__ = ",
364
375
  Expr.local("mutable_class", mutable_class),
@@ -396,10 +407,18 @@ def _make_to_frozen_fn(
396
407
  field.type.to_frozen_expr(f"self.{attribute}"),
397
408
  )
398
409
 
399
- # TODO: unknown field
410
+ # Set the _unrecognized field.
411
+ builder.append_ln("ret._unrecognized = self._unrecognized")
400
412
 
401
413
  def array_len_expr() -> Expr:
402
414
  spans: list[LineSpanLike] = []
415
+ spans.append(
416
+ LineSpan.join(
417
+ f"{_get_num_slots(fields)} + ",
418
+ Expr.local("_len", len),
419
+ "(ret._unrecognized) if ret._unrecognized else ",
420
+ )
421
+ )
403
422
  # Fields are sorted by number.
404
423
  for field in reversed(fields):
405
424
  attr_expr = f"ret.{field.field.attribute}"
@@ -413,8 +432,10 @@ def _make_to_frozen_fn(
413
432
  spans.append("0")
414
433
  return Expr.join(*spans)
415
434
 
435
+ # Set the _unrecognized field.
436
+ builder.append_ln("ret._unrecognized = self._unrecognized")
416
437
  # Set array length.
417
- builder.append_ln(f"ret._array_len = ", array_len_expr())
438
+ builder.append_ln("ret._array_len = ", array_len_expr())
418
439
 
419
440
  builder.append_ln(
420
441
  "ret.__class__ = ",
@@ -461,7 +482,6 @@ def _make_hash_fn(
461
482
  """
462
483
 
463
484
  components: list[ExprLike] = []
464
- # TODO: comment
465
485
  components.append(str(record_hash))
466
486
  for field in fields:
467
487
  components.append(f"self.{field.field.attribute}")
@@ -487,8 +507,7 @@ def _make_repr_fn(fields: Sequence[_Field]) -> Callable[[Any], Any]:
487
507
  repr_local = Expr.local("repr", repr_impl)
488
508
  for field in fields:
489
509
  attribute = field.field.attribute
490
- # TODO: comment, is_not_default_expr only works on a frozen expression...
491
- # TODO: comment; we use try because maybe in mutable we have the wrong type and we still want to be able to repr it
510
+ # is_not_default_expr only works on a frozen expression.
492
511
  to_frozen_expr = field.type.to_frozen_expr(f"(self.{attribute})")
493
512
  is_not_default_expr = field.type.is_not_default_expr(
494
513
  to_frozen_expr, to_frozen_expr
@@ -512,7 +531,7 @@ def _make_repr_fn(fields: Sequence[_Field]) -> Callable[[Any], Any]:
512
531
 
513
532
  def _make_to_dense_json_fn(fields: Sequence[_Field]) -> Callable[[Any], Any]:
514
533
  builder = BodyBuilder()
515
- builder.append_ln(f"l = self._array_len")
534
+ builder.append_ln("l = self._array_len")
516
535
  builder.append_ln("ret = [0] * l")
517
536
  for field in fields:
518
537
  builder.append_ln(f"if l <= {field.field.number}:")
@@ -524,7 +543,10 @@ def _make_to_dense_json_fn(fields: Sequence[_Field]) -> Callable[[Any], Any]:
524
543
  readable=False,
525
544
  ),
526
545
  )
527
- # TODO: unknown fields
546
+ num_slots = _get_num_slots(fields)
547
+ builder.append_ln(f"if l <= {num_slots}:")
548
+ builder.append_ln(" return ret")
549
+ builder.append_ln(f"ret[{num_slots}:] = self._unrecognized")
528
550
  builder.append_ln("return ret")
529
551
  return make_function(
530
552
  name="to_dense_json",
@@ -550,7 +572,6 @@ def _make_to_readable_json_fn(fields: Sequence[_Field]) -> Callable[[Any], Any]:
550
572
  readable=True,
551
573
  ),
552
574
  )
553
- # TODO: unknown fields
554
575
  builder.append_ln("return ret")
555
576
  return make_function(
556
577
  name="to_readable_json",
@@ -592,10 +613,20 @@ def _make_from_json_fn(
592
613
  f" if array_len <= {number} else ",
593
614
  field.type.from_json_expr(item_expr),
594
615
  )
616
+ num_slots = _get_num_slots(fields)
617
+ builder.append_ln(f" if array_len <= {num_slots}:")
618
+ builder.append_ln(" ret._unrecognized = ()")
595
619
  builder.append_ln(
596
- f" ret._array_len = ",
620
+ " ret._array_len = ",
597
621
  _adjust_array_len_expr("array_len", removed_numbers),
598
622
  )
623
+ builder.append_ln(" else:")
624
+ builder.append_ln(
625
+ f" ret._unrecognized = ",
626
+ Expr.local("deepcopy", copy.deepcopy),
627
+ f"(json[{num_slots}:])",
628
+ )
629
+ builder.append_ln(" ret._array_len = array_len")
599
630
 
600
631
  builder.append_ln("else:")
601
632
  builder.append_ln(" array_len = 0")
@@ -611,7 +642,9 @@ def _make_from_json_fn(
611
642
  )
612
643
  builder.append_ln(" else:")
613
644
  builder.append_ln(f" {lvalue} = ", field.type.default_expr())
614
- builder.append_ln(f" ret._array_len = array_len")
645
+ # Drop unrecognized fields in readable mode.
646
+ builder.append_ln(" ret._unrecognized = ()")
647
+ builder.append_ln(" ret._array_len = array_len")
615
648
 
616
649
  builder.append_ln("ret.__class__ = ", Expr.local("Frozen", frozen_class))
617
650
  builder.append_ln("return ret")
@@ -683,6 +716,7 @@ def _init_default(default: Any, fields: Sequence[_Field]) -> None:
683
716
  body=(Line.join("return ", field.type.default_expr()),),
684
717
  )
685
718
  object.__setattr__(default, attribute, get_field_default())
719
+ object.__setattr__(default, "_unrecognized", ())
686
720
  object.__setattr__(default, "_array_len", 0)
687
721
 
688
722
 
@@ -728,3 +762,7 @@ def _name_private_is_frozen_attr(record_id: str) -> str:
728
762
  record_name = _spec.RecordId.parse(record_id).name
729
763
  hex_hash = hex(abs(hash(record_id)))[:6]
730
764
  return f"_is_{record_name}_{hex_hash}"
765
+
766
+
767
+ def _get_num_slots(fields: Sequence[_Field]) -> int:
768
+ return (fields[-1].field.number + 1) if fields else 0
@@ -49,7 +49,6 @@ class TypeAdapter(Protocol):
49
49
  """
50
50
  ...
51
51
 
52
- # TODO: comment!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
53
52
  def finalize(
54
53
  self,
55
54
  resolve_type_fn: Callable[[spec.Type], "TypeAdapter"],
@@ -77,7 +77,6 @@ def init_module(
77
77
  else:
78
78
  globals[class_name] = gen_class
79
79
  gen_class._parent_class = None
80
- # TODO: comment
81
80
  gen_class.SERIALIZER = make_serializer(adapter)
82
81
 
83
82
  # Now that al the classes have been initialized, create the methods.
soialib/serializer.py CHANGED
@@ -39,14 +39,17 @@ class Serializer(Generic[T]):
39
39
  )
40
40
  object.__setattr__(self, "_from_json_fn", _make_from_json_fn(adapter))
41
41
 
42
- def to_json(self, input: T, readable_flavor=False) -> Any:
43
- if readable_flavor:
42
+ def to_json(self, input: T, readable=False) -> Any:
43
+ if readable:
44
44
  return self._to_readable_json_fn(input)
45
45
  else:
46
46
  return self._to_dense_json_fn(input)
47
47
 
48
- def to_json_code(self, input: T, readable_flavor=False) -> str:
49
- return jsonlib.dumps(self.to_json(input, readable_flavor))
48
+ def to_json_code(self, input: T, readable=False) -> str:
49
+ if readable:
50
+ return jsonlib.dumps(self._to_readable_json_fn(input), indent=2)
51
+ else:
52
+ return jsonlib.dumps(self._to_dense_json_fn(input), separators=(",", ":"))
50
53
 
51
54
  def from_json(self, json: Any) -> T:
52
55
  return self._from_json_fn(json)
soialib/timestamp.py CHANGED
@@ -97,7 +97,7 @@ class Timestamp:
97
97
 
98
98
  def _trj(self) -> Any:
99
99
  """To readable JSON."""
100
- iso = self._iso_format
100
+ iso = self._iso_format()
101
101
  if iso:
102
102
  return {
103
103
  "unix_millis": self.unix_millis,
@@ -1,24 +0,0 @@
1
- soialib/__init__.py,sha256=h_ENkLJEdrh1mGhCoKgIadDasSulW3zK9_qhEsTviXY,429
2
- soialib/keyed_items.py,sha256=q7MCn82obf-0jh7FcAhuw4eh9-wtuHIpkEFcSfc8EaY,338
3
- soialib/method.py,sha256=2qWG4jMqYhS3hA8y8YDu3iqzhXA_AKebpB38RWNmsYQ,452
4
- soialib/module_initializer.py,sha256=dDdYtlWlN7kEWlxuI0lguPsI-7zXAShswjw9j-_-AWY,4229
5
- soialib/never.py,sha256=bYU63XyNX4e2wOUXQHhHWGO-an4IFr9_ur1ut6GmGN0,47
6
- soialib/serializer.py,sha256=2R1BYEvpG3eDYMFXSKljPTMweOiuu8OMq7Xf1Bv2Wsk,3008
7
- soialib/serializers.py,sha256=7zmCdtaUkuy1vIGP1OBn06x8FryYep15bujrFoeGw9A,2572
8
- soialib/spec.py,sha256=w-eMMUqOchOlCJaXppyAa2JpSBrMo9lrMvqz8VaEX4I,3699
9
- soialib/timestamp.py,sha256=6VNKAJs7EwxBm3tEEk1Cy1FhAD6lSNdlB3K2Sg6QHIA,4065
10
- soialib/impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- soialib/impl/arrays.py,sha256=NOKIfD3tvYnWycVk00aaf4m5Nx4_G5sVxdh2cwu4-l0,4902
12
- soialib/impl/encoding.py,sha256=8k2j0nhGloFIBUEZLiiHANwhopusaQcDRePPS4sUhPc,1413
13
- soialib/impl/enums.py,sha256=o3KylHoh66jcb1TUHeOorjaxHPoqHkbk7PXR5hKldrI,14121
14
- soialib/impl/function_maker.py,sha256=PYqHqnZf8nELfEnRcoyyUbPAHyr99ROHhmHGj9ldB6U,5684
15
- soialib/impl/optionals.py,sha256=QUcxx0i7oAQl7IcnxKyNW11xi7fbs2hmuSP1pfrTSTY,2120
16
- soialib/impl/primitives.py,sha256=X_ywRmVZI_Q5tatb1Qil6Xvdyh1J4ElfI-LOCn671Zk,5975
17
- soialib/impl/repr.py,sha256=z-_jGNKuY__DfwQKW40xzZFf_eG6wGMJvuY_2hJrETA,1441
18
- soialib/impl/structs.py,sha256=vkOklmPzvWyFLFkMYbuDF8Rv_lkhzA66J-Uo9FJcVQc,24028
19
- soialib/impl/type_adapter.py,sha256=IP3jF3wPgwKhWd7LKMmbbv1qUTv1HBAyMdpVrIg0_S0,2063
20
- soia_client-1.0.7.dist-info/LICENSE,sha256=SaAftKkX6hfSOiPdENQPS70tifH3PDHgazq8eK2Pwfw,1064
21
- soia_client-1.0.7.dist-info/METADATA,sha256=s5Ll-b2a11xX7z7c1yWbXt1-zu4BZEh9ZxOP5y8jXsA,1645
22
- soia_client-1.0.7.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
23
- soia_client-1.0.7.dist-info/top_level.txt,sha256=2vPmAo5G0SrCxYrNdJKJJVdpalYppgjO2mmz2PtsFUI,8
24
- soia_client-1.0.7.dist-info/RECORD,,
soialib/impl/encoding.py DELETED
@@ -1,41 +0,0 @@
1
- from struct import pack
2
- from typing import Final
3
-
4
- from soialib.impl.function_maker import Expr
5
-
6
- EMPTY_STRING_WIRE: Final[Expr] = Expr.local("EMPTY_STRING_WIRE", bytes([242]))
7
- STRING_WIRE: Final[Expr] = Expr.local("STRING_WIRE", bytes([243]))
8
- EMPTY_BYTE_STRING_WIRE: Final[Expr] = Expr.local("EMPTY_BYTE_STRING_WIRE", bytes([244]))
9
- BYTE_STRING_WIRE: Final[Expr] = Expr.local("BYTE_STRING_WIRE", bytes([245]))
10
- SMALL_ARRAY_WIRES: Final[Expr] = Expr.local(
11
- "SMALL_ARRAY_WIRES", tuple(bytes(b) for b in range(246, 250))
12
- )
13
- ARRAY_WIRE: Final[Expr] = Expr.local("ARRAY_WIRE", bytes(250))
14
- NULL_WIRE: Final[Expr] = Expr.local("NULL_WIRE", bytes([255]))
15
-
16
- _LOW_INT_BYTES: Final[tuple[bytes, ...]] = tuple([bytes([i]) for i in range(232)])
17
- _ZERO_BYTES: Final[bytes] = _LOW_INT_BYTES[0]
18
- _ONE_BYTES: Final[bytes] = _LOW_INT_BYTES[1]
19
-
20
- LOW_INT_BYTES: Final[Expr] = Expr.local("LOW_INT_BYTES", _LOW_INT_BYTES)
21
- ZERO_BYTES: Final[Expr] = Expr.local("ZERO_BYTES_LOCAL", _ZERO_BYTES)
22
- ONE_BYTES: Final[Expr] = Expr.local("ONE_BYTES_LOCAL", _ONE_BYTES)
23
-
24
- PACK: Final[Expr] = Expr.local("pack", pack)
25
-
26
-
27
- def _len_bytes_high(l: int) -> bytes:
28
- if l < 65536:
29
- return pack("H", l)
30
- elif l < 2147483648:
31
- return pack("I", l)
32
- raise OverflowError(f"len={l}")
33
-
34
-
35
- LEN_BYTES: Final[Expr] = Expr.join(
36
- "(",
37
- LOW_INT_BYTES,
38
- "[l] if l < 232 else ",
39
- Expr.local("len_bytes_high", _len_bytes_high),
40
- "(l))",
41
- )