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.
- soia_client-1.0.0.dist-info/METADATA +12 -0
- soia_client-1.0.0.dist-info/RECORD +21 -0
- soia_client-1.0.0.dist-info/WHEEL +5 -0
- soia_client-1.0.0.dist-info/top_level.txt +1 -0
- soialib/__init__.py +0 -0
- soialib/impl/__init__.py +0 -0
- soialib/impl/arrays.py +163 -0
- soialib/impl/encoding.py +41 -0
- soialib/impl/enums.py +414 -0
- soialib/impl/function_maker.py +196 -0
- soialib/impl/optionals.py +74 -0
- soialib/impl/primitives.py +210 -0
- soialib/impl/repr.py +43 -0
- soialib/impl/structs.py +718 -0
- soialib/impl/type_adapter.py +56 -0
- soialib/keyed_items.py +14 -0
- soialib/module_initializer.py +79 -0
- soialib/never.py +4 -0
- soialib/serializer.py +87 -0
- soialib/spec.py +148 -0
- soialib/timestamp.py +127 -0
|
@@ -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 @@
|
|
|
1
|
+
soialib
|
soialib/__init__.py
ADDED
|
File without changes
|
soialib/impl/__init__.py
ADDED
|
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
|
soialib/impl/encoding.py
ADDED
|
@@ -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}"
|