soia-client 1.0.0__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.
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.1
2
+ Name: soia-client
3
+ Version: 1.0.0
4
+ Summary: Read the latest Real Python tutorials
5
+ Author-email: Tyler Fibonacci <gepheum@gmail.com>
6
+ Project-URL: Homepage, https://github.com/realpython/reader
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Programming Language :: Python
9
+ Classifier: Programming Language :: Python :: 3
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+
@@ -0,0 +1,21 @@
1
+ soialib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ soialib/keyed_items.py,sha256=q7MCn82obf-0jh7FcAhuw4eh9-wtuHIpkEFcSfc8EaY,338
3
+ soialib/module_initializer.py,sha256=Is0GI6vF5fSp_9b9lifJ2ND9s8CaACii43INhj6ienQ,3275
4
+ soialib/never.py,sha256=bYU63XyNX4e2wOUXQHhHWGO-an4IFr9_ur1ut6GmGN0,47
5
+ soialib/serializer.py,sha256=DP5b9JeDKGtONTMTs_Hk_AsLq0FocwzOyXslG4YvX0M,2622
6
+ soialib/spec.py,sha256=D-sgw1yAuVA3fB7GA4yBSKcscYgoKCUB8NkjRPqVoOs,3517
7
+ soialib/timestamp.py,sha256=H0sgFZra4vsk2wftPvwJ6YecEH6NGPAmgMlkSPbZBOU,4002
8
+ soialib/impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ soialib/impl/arrays.py,sha256=YFDT5WG4Lpmbt0d5-9_dML8kIL9AS3LfJvlA0tr5J4A,4970
10
+ soialib/impl/encoding.py,sha256=8k2j0nhGloFIBUEZLiiHANwhopusaQcDRePPS4sUhPc,1413
11
+ soialib/impl/enums.py,sha256=o3KylHoh66jcb1TUHeOorjaxHPoqHkbk7PXR5hKldrI,14121
12
+ soialib/impl/function_maker.py,sha256=PYqHqnZf8nELfEnRcoyyUbPAHyr99ROHhmHGj9ldB6U,5684
13
+ soialib/impl/optionals.py,sha256=8ImqyGbQPsOQd0155Irytzzza-t4b2qH4Swk78HZD8Q,2114
14
+ soialib/impl/primitives.py,sha256=X_ywRmVZI_Q5tatb1Qil6Xvdyh1J4ElfI-LOCn671Zk,5975
15
+ soialib/impl/repr.py,sha256=z-_jGNKuY__DfwQKW40xzZFf_eG6wGMJvuY_2hJrETA,1441
16
+ soialib/impl/structs.py,sha256=pXPn6SeyZH-FwQXbvUG_uoc5DZ8oZgDQ5v_jx8Uf5aY,23576
17
+ soialib/impl/type_adapter.py,sha256=IP3jF3wPgwKhWd7LKMmbbv1qUTv1HBAyMdpVrIg0_S0,2063
18
+ soia_client-1.0.0.dist-info/METADATA,sha256=rUCx-8k8AaUw6DHo4XFM6Gp0_EGARr4TwnhNwIA6L8w,419
19
+ soia_client-1.0.0.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91
20
+ soia_client-1.0.0.dist-info/top_level.txt,sha256=2vPmAo5G0SrCxYrNdJKJJVdpalYppgjO2mmz2PtsFUI,8
21
+ soia_client-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (74.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ soialib
soialib/__init__.py ADDED
File without changes
File without changes
soialib/impl/arrays.py ADDED
@@ -0,0 +1,163 @@
1
+ from collections.abc import Callable
2
+ from dataclasses import FrozenInstanceError
3
+ from typing import Generic, Optional
4
+ from weakref import WeakKeyDictionary
5
+
6
+ from soialib import spec
7
+ from soialib.impl.function_maker import Any, Expr, ExprLike, Line, make_function
8
+ from soialib.impl.type_adapter import TypeAdapter
9
+ from soialib.keyed_items import Item, Key, KeyedItems
10
+
11
+
12
+ def get_array_adapter(
13
+ item_adapter: TypeAdapter,
14
+ key_attributes: tuple[str, ...],
15
+ ) -> TypeAdapter:
16
+ if key_attributes:
17
+ default_expr = item_adapter.default_expr()
18
+ listuple_class = _new_keyed_items_class(key_attributes, default_expr)
19
+ else:
20
+ listuple_class = _new_listuple_class()
21
+ array_adapter = _ArrayAdapter(item_adapter, listuple_class)
22
+ key_spec_to_array_adapter = _item_to_array_adapters.setdefault(item_adapter, {})
23
+ return key_spec_to_array_adapter.setdefault(key_attributes, array_adapter)
24
+
25
+
26
+ class _ArrayAdapter(TypeAdapter):
27
+ __slots__ = (
28
+ "item_adapter",
29
+ "listuple_class",
30
+ "empty_listuple",
31
+ )
32
+
33
+ item_adapter: TypeAdapter
34
+ listuple_class: type
35
+ empty_listuple: tuple[()]
36
+
37
+ def __init__(
38
+ self,
39
+ item_adapter: TypeAdapter,
40
+ listuple_class: type,
41
+ ):
42
+ self.item_adapter = item_adapter
43
+ self.listuple_class = listuple_class
44
+ self.empty_listuple = listuple_class()
45
+
46
+ def default_expr(self) -> ExprLike:
47
+ return "()"
48
+
49
+ def to_frozen_expr(self, arg_expr: ExprLike) -> Expr:
50
+ listuple_class_local = Expr.local("_lstpl?", self.listuple_class)
51
+ empty_listuple_local = Expr.local("_emp?", self.empty_listuple)
52
+ return Expr.join(
53
+ "(",
54
+ arg_expr,
55
+ " if ",
56
+ arg_expr,
57
+ ".__class__ is ",
58
+ listuple_class_local,
59
+ " else (",
60
+ listuple_class_local,
61
+ "([",
62
+ self.item_adapter.to_frozen_expr("_e"),
63
+ " for _e in ",
64
+ arg_expr,
65
+ "]) or ",
66
+ empty_listuple_local,
67
+ "))",
68
+ )
69
+
70
+ def is_not_default_expr(self, arg_expr: ExprLike, attr_expr: ExprLike) -> ExprLike:
71
+ # Can't use arg_expr, an empty iterable is not guaranteed to evaluate to False.
72
+ return attr_expr
73
+
74
+ def to_json_expr(self, in_expr: ExprLike, readable: bool) -> ExprLike:
75
+ e = Expr.join("_e")
76
+ item_to_json = self.item_adapter.to_json_expr(e, readable)
77
+ if item_to_json == e:
78
+ return in_expr
79
+ return Expr.join(
80
+ "[",
81
+ item_to_json,
82
+ " for ",
83
+ e,
84
+ " in ",
85
+ in_expr,
86
+ "]",
87
+ )
88
+
89
+ def from_json_expr(self, json_expr: ExprLike) -> Expr:
90
+ listuple_class_local = Expr.local("_lstpl?", self.listuple_class)
91
+ empty_listuple_local = Expr.local("_emp?", self.empty_listuple)
92
+ return Expr.join(
93
+ listuple_class_local,
94
+ "([",
95
+ self.item_adapter.from_json_expr(Expr.join("_e")),
96
+ " for e in ",
97
+ json_expr,
98
+ "] or ",
99
+ empty_listuple_local,
100
+ ")",
101
+ )
102
+
103
+ def finalize(
104
+ self,
105
+ resolve_type_fn: Callable[[spec.Type], "TypeAdapter"],
106
+ ) -> None:
107
+ self.item_adapter.finalize(resolve_type_fn)
108
+
109
+
110
+ _KeyAttributesToArrayAdapter = dict[tuple[str, ...], _ArrayAdapter]
111
+ _ItemToArrayAdapters = WeakKeyDictionary[TypeAdapter, _KeyAttributesToArrayAdapter]
112
+
113
+ _item_to_array_adapters: _ItemToArrayAdapters = WeakKeyDictionary()
114
+
115
+
116
+ def _new_listuple_class() -> type:
117
+ class Listuple(Generic[Item], tuple[Item, ...]):
118
+ __slots__ = ()
119
+
120
+ return Listuple
121
+
122
+
123
+ def _new_keyed_items_class(attributes: tuple[str, ...], default_expr: ExprLike):
124
+ key_items = make_function(
125
+ name="key_items",
126
+ params=["items"],
127
+ body=[
128
+ "ret = {}",
129
+ "for item in items:",
130
+ f" ret[item.{'.'.join(attributes)}] = item",
131
+ "return ret",
132
+ ],
133
+ )
134
+
135
+ default = make_function(
136
+ name="get_default",
137
+ params="",
138
+ body=[Line.join("return ", default_expr)],
139
+ )()
140
+
141
+ class KeyedItemsImpl(KeyedItems[Item, Key]):
142
+ # nonempty __slots__ not supported for subtype of 'tuple'
143
+
144
+ _key_to_item: dict[Key, Item]
145
+
146
+ def find(self, key: Key) -> Optional[Item]:
147
+ try:
148
+ key_to_item = self._key_to_item
149
+ except AttributeError:
150
+ key_to_item = key_items(self)
151
+ object.__setattr__(self, "_key_to_item", key_to_item)
152
+ return key_to_item.get(key)
153
+
154
+ def find_or_default(self, key: Key) -> Any:
155
+ return self.find(key) or default
156
+
157
+ def __setattr__(self, name: str, value: Any):
158
+ raise FrozenInstanceError(self.__class__.__qualname__)
159
+
160
+ def __delattr__(self, name: str):
161
+ raise FrozenInstanceError(self.__class__.__qualname__)
162
+
163
+ return KeyedItemsImpl
@@ -0,0 +1,41 @@
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
+ )
soialib/impl/enums.py ADDED
@@ -0,0 +1,414 @@
1
+ # TODO: test
2
+ # TODO: unrecognized fields (and handles removed fields)
3
+
4
+ from collections.abc import Callable, Sequence
5
+ from dataclasses import FrozenInstanceError, dataclass
6
+ from typing import Any, Final, Union
7
+
8
+ 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
+ )
18
+ from soialib.impl.repr import repr_impl
19
+ from soialib.impl.type_adapter import TypeAdapter
20
+
21
+
22
+ class EnumAdapter(TypeAdapter):
23
+ __slots__ = (
24
+ "spec",
25
+ "gen_class",
26
+ "private_is_enum_attr",
27
+ "finalization_state",
28
+ )
29
+
30
+ spec: Final[_spec.Enum]
31
+ gen_class: Final[type] # AKA the base class
32
+ private_is_enum_attr: Final[str]
33
+ # 0: has not started; 1: in progress; 2: done
34
+ finalization_state: int
35
+
36
+ def __init__(self, spec: _spec.Enum):
37
+ self.finalization_state = 0
38
+ self.spec = spec
39
+ base_class = self.gen_class = _make_base_class(spec)
40
+
41
+ private_is_enum_attr = _name_private_is_enum_attr(spec.id)
42
+ self.private_is_enum_attr = private_is_enum_attr
43
+ # TODO: comment
44
+ setattr(base_class, private_is_enum_attr, True)
45
+
46
+ # Add the constants.
47
+ for constant_field in self.all_constant_fields:
48
+ constant_class = _make_constant_class(base_class, constant_field)
49
+ constant = constant_class()
50
+ setattr(base_class, constant_field.attribute, constant)
51
+
52
+ def finalize(
53
+ self,
54
+ resolve_type_fn: Callable[[_spec.Type], TypeAdapter],
55
+ ) -> None:
56
+ if self.finalization_state != 0:
57
+ # Finalization is either in progress or done.
58
+ return
59
+ # Mark finalization as in progress.
60
+ self.finalization_state = 1
61
+
62
+ base_class = self.gen_class
63
+
64
+ # Resolve the type of every value field.
65
+ value_fields = [
66
+ _make_value_field(f, resolve_type_fn(f.type), base_class)
67
+ for f in self.spec.value_fields
68
+ ]
69
+
70
+ # Aim to have dependencies finalized *before* the dependent. It's not always
71
+ # possible, because there can be cyclic dependencies.
72
+ # The function returned by the do_x_fn() method of a dependency is marginally
73
+ # faster if the dependency is finalized. If the dependency is not finalized,
74
+ # this function is a "forwarding" function.
75
+ for value_field in value_fields:
76
+ value_field.field_type.finalize(resolve_type_fn)
77
+
78
+ # Add the wrap static factory methods.
79
+ for value_field in value_fields:
80
+ wrap_fn = _make_wrap_fn(value_field)
81
+ setattr(base_class, f"wrap_{value_field.spec.name}", wrap_fn)
82
+
83
+ base_class._fj = _make_from_json_fn(
84
+ self.all_constant_fields, value_fields, base_class
85
+ )
86
+
87
+ # Mark finalization as done.
88
+ self.finalization_state = 2
89
+
90
+ @property
91
+ def all_constant_fields(self) -> list[_spec.ConstantField]:
92
+ unknown_field = _spec.ConstantField(
93
+ name="?",
94
+ number=0,
95
+ _attribute="UNKNOWN",
96
+ )
97
+ return list(self.spec.constant_fields) + [unknown_field]
98
+
99
+ def default_expr(self) -> Expr:
100
+ return Expr.local("_d?", self.gen_class.UNKNOWN)
101
+
102
+ def to_frozen_expr(self, arg_expr: ExprLike) -> Expr:
103
+ return Expr.join(
104
+ "(",
105
+ arg_expr,
106
+ f".{self.private_is_enum_attr} and ",
107
+ arg_expr,
108
+ ")",
109
+ )
110
+
111
+ def is_not_default_expr(self, arg_expr: ExprLike, attr_expr: ExprLike) -> Expr:
112
+ return Expr.join(arg_expr, "._number")
113
+
114
+ def to_json_expr(self, in_expr: ExprLike, readable: bool) -> Expr:
115
+ return Expr.join(in_expr, "._rj" if readable else "._dj")
116
+
117
+ def from_json_expr(self, json_expr: ExprLike) -> Expr:
118
+ fn_name = "_fj"
119
+ # TODO: comment
120
+ from_json_fn = getattr(self.gen_class, fn_name, None)
121
+ if from_json_fn:
122
+ return Expr.join(Expr.local("_fj?", from_json_fn), "(", json_expr, ")")
123
+ else:
124
+ return Expr.join(
125
+ Expr.local("_cls?", self.gen_class), f".{fn_name}(", json_expr, ")"
126
+ )
127
+
128
+
129
+ def _make_base_class(spec: _spec.Enum) -> type:
130
+ record_hash = hash(spec.id)
131
+
132
+ class BaseClass:
133
+ __slots__ = ("value",)
134
+
135
+ kind: str
136
+ value: Any
137
+
138
+ def __init__(self, never: Any):
139
+ raise TypeError("Cannot call the constructor of a soia enum")
140
+
141
+ @property
142
+ def union(self) -> Any:
143
+ return self
144
+
145
+ def __setattr__(self, name: str, value: Any):
146
+ raise FrozenInstanceError(self.__class__.__qualname__)
147
+
148
+ def __delattr__(self, name: str):
149
+ raise FrozenInstanceError(self.__class__.__qualname__)
150
+
151
+ def __eq__(self, other: Any) -> bool:
152
+ # TODO: make it work with unrecognized versus UNKNOWN
153
+ if isinstance(other, BaseClass):
154
+ return other.kind == self.kind and other.value == self.value
155
+ return NotImplemented
156
+
157
+ def __hash__(self) -> int:
158
+ return hash((record_hash, self.kind, self.value))
159
+
160
+ BaseClass.__name__ = spec.class_name
161
+ BaseClass.__qualname__ = spec.class_qualname
162
+
163
+ return BaseClass
164
+
165
+
166
+ def _make_constant_class(base_class: type, spec: _spec.ConstantField) -> type:
167
+ class ConstantClass(base_class):
168
+ __slots__ = ()
169
+
170
+ kind: Final[str] = spec.name
171
+ _number: Final[int] = spec.number
172
+ # dense JSON
173
+ _dj: Final[int] = spec.number
174
+ # readable JSON
175
+ _rj: Final[str] = spec.name
176
+ # has value
177
+ _hv: Final[bool] = False
178
+
179
+ def __init__(self):
180
+ # Do not call super().__init__().
181
+ object.__setattr__(self, "value", None)
182
+
183
+ def __repr__(self) -> str:
184
+ return f"{base_class.__qualname__}.{spec.attribute}"
185
+
186
+ return ConstantClass
187
+
188
+
189
+ def _make_value_class(
190
+ base_class: type,
191
+ field_spec: _spec.ValueField,
192
+ field_type: TypeAdapter,
193
+ ) -> type:
194
+ number = field_spec.number
195
+
196
+ class ValueClass(base_class):
197
+ __slots__ = ()
198
+
199
+ kind: Final[str] = field_spec.name
200
+ _number: Final[int] = number
201
+ # has value
202
+ _hv: Final[bool] = True
203
+
204
+ def __init__(self):
205
+ # Do not call super().__init__().
206
+ pass
207
+
208
+ def __repr__(self) -> str:
209
+ value_repr = repr_impl(self.value)
210
+ if value_repr.complex:
211
+ body = f"\n {value_repr.indented}\n"
212
+ else:
213
+ body = value_repr.repr
214
+ return f"{base_class.__qualname__}.wrap_{field_spec.name}({body})"
215
+
216
+ ret = ValueClass
217
+
218
+ ret._dj = property(
219
+ make_function(
220
+ name="to_dense_json",
221
+ params=["self"],
222
+ body=[
223
+ Line.join(
224
+ f"return [{field_spec.number}, ",
225
+ field_type.to_json_expr("self.value", readable=False),
226
+ "]",
227
+ ),
228
+ ],
229
+ )
230
+ )
231
+
232
+ ret._rj = property(
233
+ make_function(
234
+ name="to_readable_json",
235
+ params=["self"],
236
+ body=[
237
+ Line.join(
238
+ "return {",
239
+ f'"kind": "{field_spec.name}", "value": ',
240
+ field_type.to_json_expr("self.value", readable=True),
241
+ "}",
242
+ ),
243
+ ],
244
+ )
245
+ )
246
+
247
+ return ret
248
+
249
+
250
+ @dataclass(frozen=True)
251
+ class _ValueField:
252
+ spec: _spec.ValueField
253
+ field_type: TypeAdapter
254
+ value_class: type
255
+
256
+
257
+ def _make_value_field(
258
+ spec: _spec.ValueField, field_type: TypeAdapter, base_class: type
259
+ ) -> _ValueField:
260
+ return _ValueField(
261
+ spec=spec,
262
+ field_type=field_type,
263
+ value_class=_make_value_class(
264
+ base_class=base_class, field_spec=spec, field_type=field_type
265
+ ),
266
+ )
267
+
268
+
269
+ def _make_wrap_fn(field: _ValueField) -> Callable[[Any], Any]:
270
+ builder = BodyBuilder()
271
+ builder.append_ln("ret = ", Expr.local("value_class", field.value_class), "()")
272
+ builder.append_ln(
273
+ Expr.local("setattr", object.__setattr__),
274
+ "(ret, 'value', ",
275
+ field.field_type.to_frozen_expr(Expr.join("value")),
276
+ ")",
277
+ )
278
+ builder.append_ln("return ret")
279
+ return make_function(
280
+ name="wrap",
281
+ params=["value"],
282
+ body=builder.build(),
283
+ )
284
+
285
+
286
+ def _make_from_json_fn(
287
+ constant_fields: Sequence[_spec.ConstantField],
288
+ value_fields: Sequence[_ValueField],
289
+ base_class: type,
290
+ ) -> Callable[[Any], Any]:
291
+ obj_setattr_local = Expr.local("obj_settatr", object.__setattr__)
292
+
293
+ key_to_constant: dict[Union[int, str], Any] = {}
294
+ for field in constant_fields:
295
+ constant = getattr(base_class, field.attribute)
296
+ key_to_constant[field.number] = constant
297
+ key_to_constant[field.name] = constant
298
+ key_to_constant_local = Expr.local("key_to_constant", key_to_constant)
299
+ unknown_constant = key_to_constant[0]
300
+ unknown_constant_local = Expr.local("unknown_constant", unknown_constant)
301
+
302
+ numbers: list[int] = []
303
+ names: list[str] = []
304
+ key_to_field: dict[Union[int, str], _ValueField] = {}
305
+ for field in value_fields:
306
+ numbers.append(field.spec.number)
307
+ names.append(field.spec.name)
308
+ key_to_field[field.spec.number] = field
309
+ key_to_field[field.spec.name] = field
310
+ value_keys_local = Expr.local("value_keys", set(key_to_field.keys()))
311
+
312
+ builder = BodyBuilder()
313
+ # The reason why we wrap the function inside a 'while' is explained below.
314
+ builder.append_ln("while True:")
315
+
316
+ # DENSE FORMAT
317
+ if len(constant_fields) == 1:
318
+ builder.append_ln(" if json == 0:")
319
+ builder.append_ln(" return ", unknown_constant_local)
320
+ else:
321
+ builder.append_ln(" if json.__class__ is int:")
322
+ builder.append_ln(" try:")
323
+ builder.append_ln(" return ", key_to_constant_local, "[json]")
324
+ builder.append_ln(" except:")
325
+ # TODO: handle unrecognized fields!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
326
+ builder.append_ln(" return ...")
327
+
328
+ def append_number_branches(numbers: list[int], indent: str) -> None:
329
+ if len(numbers) == 1:
330
+ number = numbers[0]
331
+ field = key_to_field[number]
332
+ value_class_local = Expr.local("cls?", field.value_class)
333
+ value_expr = field.field_type.from_json_expr(Expr.join("json[1]"))
334
+ builder.append_ln(f"{indent}ret = ", value_class_local, "()")
335
+ builder.append_ln(indent, obj_setattr_local, "(ret, ", value_expr, ")")
336
+ builder.append_ln(f"{indent}return ret")
337
+ else:
338
+ indented = f" {indent}"
339
+ mid_index = int(len(numbers) / 2)
340
+ mid_number = numbers[mid_index - 1]
341
+ operator = "==" if mid_index == 1 else "<="
342
+ builder.append_ln(f"{indent}if number {operator} {mid_number}:")
343
+ append_number_branches(numbers[0:mid_index], indented)
344
+ builder.append_ln(f"{indent}else:")
345
+ append_number_branches(numbers[mid_index:], indented)
346
+
347
+ builder.append_ln(" elif json.__class__ is list:")
348
+ if not value_fields:
349
+ # TODO: handle unrecognized fields!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
350
+ builder.append_ln(" return ...")
351
+ else:
352
+ if len(value_fields) == 1:
353
+ builder.append_ln(f" if number != {value_fields[0].spec.number}:")
354
+ else:
355
+ 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]")
359
+ append_number_branches(sorted(numbers), " ")
360
+
361
+ # READABLE FORMAT
362
+ if len(constant_fields) == 1:
363
+ builder.append_ln(" elif json == '?':")
364
+ builder.append_ln(" return ", unknown_constant_local)
365
+ else:
366
+ builder.append_ln(" if isinstance(json, str):")
367
+ builder.append_ln(" try:")
368
+ builder.append_ln(" return ", key_to_constant_local, "[json]")
369
+ builder.append_ln(" except:")
370
+ # TODO: comment
371
+ builder.append_ln(" return ", unknown_constant_local)
372
+
373
+ def append_name_branches(names: list[str], indent: str) -> None:
374
+ if len(names) == 1:
375
+ name = names[0]
376
+ field = key_to_field[name]
377
+ value_class_local = Expr.local("cls?", field.value_class)
378
+ value_expr = field.field_type.from_json_expr("json['value']")
379
+ builder.append_ln(f"{indent}ret = ", value_class_local, "()")
380
+ builder.append_ln(indent, obj_setattr_local, "(ret, ", value_expr, ")")
381
+ builder.append_ln(f"{indent}return ret")
382
+ else:
383
+ indented = f" {indent}"
384
+ mid_index = int(len(names) / 2)
385
+ mid_name = names[mid_index - 1]
386
+ operator = "==" if mid_index == 1 else "<="
387
+ builder.append_ln(f"{indent}if name {operator} '{mid_name}':")
388
+ append_name_branches(names[0:mid_index], indented)
389
+ builder.append_ln(f"{indent}else:")
390
+ append_name_branches(names[mid_index:], indented)
391
+
392
+ builder.append_ln(" elif isinstance(json, dict):")
393
+ if not value_fields:
394
+ # TODO: comment
395
+ builder.append_ln(" return ", unknown_constant_local)
396
+ else:
397
+ builder.append_ln(" name = json['name']")
398
+ builder.append_ln(" if name not in ", value_keys_local, ":")
399
+ # TODO: comment
400
+ builder.append_ln(" return ", unknown_constant_local)
401
+ builder.append_ln(" else:")
402
+ append_name_branches(sorted(names), " ")
403
+
404
+ return make_function(
405
+ name="from_json",
406
+ params=["json"],
407
+ body=builder.build(),
408
+ )
409
+
410
+
411
+ def _name_private_is_enum_attr(record_id: str) -> str:
412
+ record_name = _spec.RecordId.parse(record_id).name
413
+ hex_hash = hex(abs(hash(record_id)))[:6]
414
+ return f"_is_{record_name}_{hex_hash}"