soia-client 1.0.6__tar.gz → 1.0.8__tar.gz

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.

Files changed (31) hide show
  1. {soia_client-1.0.6 → soia_client-1.0.8}/PKG-INFO +1 -1
  2. {soia_client-1.0.6 → soia_client-1.0.8}/pyproject.toml +1 -1
  3. {soia_client-1.0.6 → soia_client-1.0.8}/soia_client.egg-info/PKG-INFO +1 -1
  4. {soia_client-1.0.6 → soia_client-1.0.8}/soia_client.egg-info/SOURCES.txt +0 -1
  5. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/impl/arrays.py +1 -1
  6. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/impl/enums.py +78 -35
  7. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/impl/function_maker.py +1 -5
  8. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/impl/optionals.py +0 -1
  9. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/impl/primitives.py +17 -4
  10. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/impl/repr.py +0 -1
  11. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/impl/structs.py +78 -40
  12. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/impl/type_adapter.py +0 -1
  13. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/module_initializer.py +0 -1
  14. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/serializer.py +7 -4
  15. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/timestamp.py +1 -1
  16. {soia_client-1.0.6 → soia_client-1.0.8}/tests/test_module_initializer.py +295 -25
  17. {soia_client-1.0.6 → soia_client-1.0.8}/tests/test_serializers.py +32 -3
  18. soia_client-1.0.6/soialib/impl/encoding.py +0 -41
  19. {soia_client-1.0.6 → soia_client-1.0.8}/LICENSE +0 -0
  20. {soia_client-1.0.6 → soia_client-1.0.8}/README +0 -0
  21. {soia_client-1.0.6 → soia_client-1.0.8}/setup.cfg +0 -0
  22. {soia_client-1.0.6 → soia_client-1.0.8}/soia_client.egg-info/dependency_links.txt +0 -0
  23. {soia_client-1.0.6 → soia_client-1.0.8}/soia_client.egg-info/top_level.txt +0 -0
  24. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/__init__.py +0 -0
  25. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/impl/__init__.py +0 -0
  26. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/keyed_items.py +0 -0
  27. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/method.py +0 -0
  28. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/never.py +0 -0
  29. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/serializers.py +0 -0
  30. {soia_client-1.0.6 → soia_client-1.0.8}/soialib/spec.py +0 -0
  31. {soia_client-1.0.6 → soia_client-1.0.8}/tests/test_timestamp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: soia-client
3
- Version: 1.0.6
3
+ Version: 1.0.8
4
4
  Author-email: Tyler Fibonacci <gepheum@gmail.com>
5
5
  License: MIT License
6
6
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "soia-client"
7
- version = "1.0.6"
7
+ version = "1.0.8"
8
8
  description = ""
9
9
  readme = "README.md"
10
10
  authors = [{ name = "Tyler Fibonacci", email = "gepheum@gmail.com" }]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: soia-client
3
- Version: 1.0.6
3
+ Version: 1.0.8
4
4
  Author-email: Tyler Fibonacci <gepheum@gmail.com>
5
5
  License: MIT License
6
6
 
@@ -16,7 +16,6 @@ soialib/spec.py
16
16
  soialib/timestamp.py
17
17
  soialib/impl/__init__.py
18
18
  soialib/impl/arrays.py
19
- soialib/impl/encoding.py
20
19
  soialib/impl/enums.py
21
20
  soialib/impl/function_maker.py
22
21
  soialib/impl/optionals.py
@@ -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,
@@ -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
 
@@ -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
 
@@ -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)