soia-client 1.0.11__py3-none-any.whl → 1.0.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of soia-client might be problematic. Click here for more details.
- {soia_client-1.0.11.dist-info → soia_client-1.0.12.dist-info}/METADATA +1 -1
- soia_client-1.0.12.dist-info/RECORD +27 -0
- soialib/impl/arrays.py +20 -1
- soialib/impl/enums.py +34 -2
- soialib/impl/optionals.py +13 -0
- soialib/impl/primitives.py +62 -0
- soialib/impl/structs.py +33 -1
- soialib/impl/type_adapter.py +8 -0
- soialib/reflection.py +343 -0
- soialib/serializer.py +12 -0
- soialib/service.py +147 -0
- soialib/service_client.py +3 -5
- soialib/spec.py +0 -2
- soia_client-1.0.11.dist-info/RECORD +0 -25
- {soia_client-1.0.11.dist-info → soia_client-1.0.12.dist-info}/WHEEL +0 -0
- {soia_client-1.0.11.dist-info → soia_client-1.0.12.dist-info}/licenses/LICENSE +0 -0
- {soia_client-1.0.11.dist-info → soia_client-1.0.12.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
soia_client-1.0.12.dist-info/licenses/LICENSE,sha256=SaAftKkX6hfSOiPdENQPS70tifH3PDHgazq8eK2Pwfw,1064
|
|
2
|
+
soialib/_.py,sha256=I4SxBM3brmE9D6hyrDrdkbfVnDLVRWaGwnj0C9Zxh54,52
|
|
3
|
+
soialib/__init__.py,sha256=UyNrMaKEwCmYecsEwgsglMJvjKzIbDp7idbIxLdBz_k,499
|
|
4
|
+
soialib/keyed_items.py,sha256=q7MCn82obf-0jh7FcAhuw4eh9-wtuHIpkEFcSfc8EaY,338
|
|
5
|
+
soialib/method.py,sha256=2qWG4jMqYhS3hA8y8YDu3iqzhXA_AKebpB38RWNmsYQ,452
|
|
6
|
+
soialib/module_initializer.py,sha256=Riq6B6cS9HUG1W8tyaa0GkiG3F4fGX70bKU0UOCnrRw,4205
|
|
7
|
+
soialib/never.py,sha256=bYU63XyNX4e2wOUXQHhHWGO-an4IFr9_ur1ut6GmGN0,47
|
|
8
|
+
soialib/reflection.py,sha256=U5knJGmawARCdcEhNxek4dvx48WLPETLqIqKBPWwT4Q,8771
|
|
9
|
+
soialib/serializer.py,sha256=Lprzy8RTWjwpQ5D4L0lj8NTpUAZxpzjfc-qUQs9egHs,3551
|
|
10
|
+
soialib/serializers.py,sha256=vNLNsfuu6dF0ivJF6v7wQj5Yr6uo2kZHXG5tMrYiVTA,2564
|
|
11
|
+
soialib/service.py,sha256=FGvC8f9AOEdAKtADGOImzCNIpYtiqKjIjwzbHgOsXO8,5197
|
|
12
|
+
soialib/service_client.py,sha256=zJ7MhrTiF-T1yXJwCXcHyKbiD19QfUSAoxfj32B62a0,2234
|
|
13
|
+
soialib/spec.py,sha256=Y5EHHQa6qNeJc29aaqGrFPnPFXxlL7TED9_AXUGBjf0,3663
|
|
14
|
+
soialib/timestamp.py,sha256=lXBNH8mPmzflkNjSKZSBl2XS-ot9N8N92B_zGO2SMtU,4078
|
|
15
|
+
soialib/impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
soialib/impl/arrays.py,sha256=jvyaCAralf-gmdbQxVd5-KobEs32LcMN6DUlBo3m9A8,5490
|
|
17
|
+
soialib/impl/enums.py,sha256=xzBfpN5lQDzulPicZNRuqe4eAZ82DxCd_zWr8XxCUDw,17032
|
|
18
|
+
soialib/impl/function_maker.py,sha256=MvCDv1WwzKGJzZbNCnJ_8-MP3m1xTIabumXA-9Ydd9M,5639
|
|
19
|
+
soialib/impl/optionals.py,sha256=rAtylhc6LM4kNLYkbLF5kVLCxNa0A1tYglHvlQszLMQ,2456
|
|
20
|
+
soialib/impl/primitives.py,sha256=E3rd6TZWDGqBzmrFwVGG2DZLMwDVoznLmilAgoCTT5Q,9106
|
|
21
|
+
soialib/impl/repr.py,sha256=7WX0bEAVENTjlyZIcbT8TcJylS7IRIyafGCmqaIMxFM,1413
|
|
22
|
+
soialib/impl/structs.py,sha256=3YW05rDRmp3G38FZ4CbIjudz6Txog3ymcD28NHf0uGA,26615
|
|
23
|
+
soialib/impl/type_adapter.py,sha256=Q-PacpFKXfYBRF6dqHaf3VO7BcvqG7C6k5aXDFACMGY,2133
|
|
24
|
+
soia_client-1.0.12.dist-info/METADATA,sha256=P8jlB9lUlkPMBDcT4_JtX6gUmvuxcBlC2ghwkxXFmBs,1667
|
|
25
|
+
soia_client-1.0.12.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
26
|
+
soia_client-1.0.12.dist-info/top_level.txt,sha256=2vPmAo5G0SrCxYrNdJKJJVdpalYppgjO2mmz2PtsFUI,8
|
|
27
|
+
soia_client-1.0.12.dist-info/RECORD,,
|
soialib/impl/arrays.py
CHANGED
|
@@ -3,6 +3,7 @@ from dataclasses import FrozenInstanceError
|
|
|
3
3
|
from typing import Generic, Optional
|
|
4
4
|
from weakref import WeakValueDictionary
|
|
5
5
|
|
|
6
|
+
import soialib.reflection
|
|
6
7
|
from soialib import spec
|
|
7
8
|
from soialib.impl.function_maker import Any, Expr, ExprLike, Line, make_function
|
|
8
9
|
from soialib.impl.type_adapter import TypeAdapter
|
|
@@ -18,7 +19,7 @@ def get_array_adapter(
|
|
|
18
19
|
listuple_class = _new_keyed_items_class(key_attributes, default_expr)
|
|
19
20
|
else:
|
|
20
21
|
listuple_class = _new_listuple_class()
|
|
21
|
-
array_adapter = _ArrayAdapter(item_adapter, listuple_class)
|
|
22
|
+
array_adapter = _ArrayAdapter(item_adapter, listuple_class, key_attributes)
|
|
22
23
|
return _item_to_array_adapter.setdefault(
|
|
23
24
|
(item_adapter, key_attributes), array_adapter
|
|
24
25
|
)
|
|
@@ -28,6 +29,7 @@ class _ArrayAdapter(TypeAdapter):
|
|
|
28
29
|
__slots__ = (
|
|
29
30
|
"item_adapter",
|
|
30
31
|
"listuple_class",
|
|
32
|
+
"key_attributes",
|
|
31
33
|
"empty_listuple",
|
|
32
34
|
)
|
|
33
35
|
|
|
@@ -39,9 +41,11 @@ class _ArrayAdapter(TypeAdapter):
|
|
|
39
41
|
self,
|
|
40
42
|
item_adapter: TypeAdapter,
|
|
41
43
|
listuple_class: type,
|
|
44
|
+
key_attributes: tuple[str, ...],
|
|
42
45
|
):
|
|
43
46
|
self.item_adapter = item_adapter
|
|
44
47
|
self.listuple_class = listuple_class
|
|
48
|
+
self.key_attributes = key_attributes
|
|
45
49
|
self.empty_listuple = listuple_class()
|
|
46
50
|
|
|
47
51
|
def default_expr(self) -> ExprLike:
|
|
@@ -104,6 +108,21 @@ class _ArrayAdapter(TypeAdapter):
|
|
|
104
108
|
) -> None:
|
|
105
109
|
self.item_adapter.finalize(resolve_type_fn)
|
|
106
110
|
|
|
111
|
+
def get_type(self) -> soialib.reflection.Type:
|
|
112
|
+
return soialib.reflection.ArrayType(
|
|
113
|
+
kind="array",
|
|
114
|
+
value=soialib.reflection.ArrayType.Array(
|
|
115
|
+
item=self.item_adapter.get_type(),
|
|
116
|
+
key_chain=self.key_attributes,
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def register_records(
|
|
121
|
+
self,
|
|
122
|
+
registry: dict[str, soialib.reflection.Record],
|
|
123
|
+
) -> None:
|
|
124
|
+
self.item_adapter.register_records(registry)
|
|
125
|
+
|
|
107
126
|
|
|
108
127
|
_ItemAndKeyAttributes = tuple[TypeAdapter, tuple[str, ...]]
|
|
109
128
|
_ItemToArrayAdapter = WeakValueDictionary[_ItemAndKeyAttributes, _ArrayAdapter]
|
soialib/impl/enums.py
CHANGED
|
@@ -3,6 +3,7 @@ from collections.abc import Callable, Sequence
|
|
|
3
3
|
from dataclasses import FrozenInstanceError, dataclass
|
|
4
4
|
from typing import Any, Final, Union
|
|
5
5
|
|
|
6
|
+
import soialib.reflection
|
|
6
7
|
from soialib import spec as _spec
|
|
7
8
|
from soialib.impl.function_maker import BodyBuilder, Expr, ExprLike, Line, make_function
|
|
8
9
|
from soialib.impl.repr import repr_impl
|
|
@@ -15,6 +16,7 @@ class EnumAdapter(TypeAdapter):
|
|
|
15
16
|
"gen_class",
|
|
16
17
|
"private_is_enum_attr",
|
|
17
18
|
"finalization_state",
|
|
19
|
+
"value_fields",
|
|
18
20
|
)
|
|
19
21
|
|
|
20
22
|
spec: Final[_spec.Enum]
|
|
@@ -22,6 +24,7 @@ class EnumAdapter(TypeAdapter):
|
|
|
22
24
|
private_is_enum_attr: Final[str]
|
|
23
25
|
# 0: has not started; 1: in progress; 2: done
|
|
24
26
|
finalization_state: int
|
|
27
|
+
value_fields: tuple["_ValueField", ...]
|
|
25
28
|
|
|
26
29
|
def __init__(self, spec: _spec.Enum):
|
|
27
30
|
self.finalization_state = 0
|
|
@@ -51,10 +54,10 @@ class EnumAdapter(TypeAdapter):
|
|
|
51
54
|
base_class = self.gen_class
|
|
52
55
|
|
|
53
56
|
# Resolve the type of every value field.
|
|
54
|
-
value_fields =
|
|
57
|
+
self.value_fields = value_fields = tuple(
|
|
55
58
|
_make_value_field(f, resolve_type_fn(f.type), base_class)
|
|
56
59
|
for f in self.spec.value_fields
|
|
57
|
-
|
|
60
|
+
)
|
|
58
61
|
|
|
59
62
|
# Aim to have dependencies finalized *before* the dependent. It's not always
|
|
60
63
|
# possible, because there can be cyclic dependencies.
|
|
@@ -116,6 +119,35 @@ class EnumAdapter(TypeAdapter):
|
|
|
116
119
|
Expr.local("_cls?", self.gen_class), f".{fn_name}(", json_expr, ")"
|
|
117
120
|
)
|
|
118
121
|
|
|
122
|
+
def get_type(self) -> soialib.reflection.Type:
|
|
123
|
+
return soialib.reflection.RecordType(
|
|
124
|
+
kind="record",
|
|
125
|
+
value=self.spec.id,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def register_records(
|
|
129
|
+
self,
|
|
130
|
+
registry: dict[str, soialib.reflection.Record],
|
|
131
|
+
) -> None:
|
|
132
|
+
record_id = self.spec.id
|
|
133
|
+
if record_id in registry:
|
|
134
|
+
return
|
|
135
|
+
registry[record_id] = soialib.reflection.Record(
|
|
136
|
+
kind="enum",
|
|
137
|
+
id=record_id,
|
|
138
|
+
fields=tuple(
|
|
139
|
+
soialib.reflection.Field(
|
|
140
|
+
name=field.spec.name,
|
|
141
|
+
number=field.spec.number,
|
|
142
|
+
type=field.field_type.get_type(),
|
|
143
|
+
)
|
|
144
|
+
for field in self.value_fields
|
|
145
|
+
),
|
|
146
|
+
removed_fields=self.spec.removed_numbers,
|
|
147
|
+
)
|
|
148
|
+
for field in self.value_fields:
|
|
149
|
+
field.field_type.register_records(registry)
|
|
150
|
+
|
|
119
151
|
|
|
120
152
|
def _make_base_class(spec: _spec.Enum) -> type:
|
|
121
153
|
record_hash = hash(spec.id)
|
soialib/impl/optionals.py
CHANGED
|
@@ -3,6 +3,7 @@ from dataclasses import dataclass
|
|
|
3
3
|
from typing import TypeVar
|
|
4
4
|
from weakref import WeakValueDictionary
|
|
5
5
|
|
|
6
|
+
import soialib.reflection
|
|
6
7
|
from soialib import spec
|
|
7
8
|
from soialib.impl.function_maker import Expr, ExprLike
|
|
8
9
|
from soialib.impl.type_adapter import TypeAdapter
|
|
@@ -67,6 +68,18 @@ class _OptionalAdapter(TypeAdapter):
|
|
|
67
68
|
) -> None:
|
|
68
69
|
self.other_adapter.finalize(resolve_type_fn)
|
|
69
70
|
|
|
71
|
+
def get_type(self) -> soialib.reflection.Type:
|
|
72
|
+
return soialib.reflection.OptionalType(
|
|
73
|
+
kind="optional",
|
|
74
|
+
value=self.other_adapter.get_type(),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def register_records(
|
|
78
|
+
self,
|
|
79
|
+
registry: dict[str, soialib.reflection.Record],
|
|
80
|
+
) -> None:
|
|
81
|
+
self.other_adapter.register_records(registry)
|
|
82
|
+
|
|
70
83
|
|
|
71
84
|
_other_adapter_to_optional_adapter: WeakValueDictionary[TypeAdapter, TypeAdapter] = (
|
|
72
85
|
WeakValueDictionary()
|
soialib/impl/primitives.py
CHANGED
|
@@ -2,6 +2,7 @@ from collections.abc import Callable
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from typing import Any, Final, final
|
|
4
4
|
|
|
5
|
+
import soialib.reflection
|
|
5
6
|
from soialib import spec
|
|
6
7
|
from soialib.impl.function_maker import Expr, ExprLike
|
|
7
8
|
from soialib.impl.type_adapter import TypeAdapter
|
|
@@ -16,6 +17,13 @@ class AbstractPrimitiveAdapter(TypeAdapter):
|
|
|
16
17
|
) -> None:
|
|
17
18
|
pass
|
|
18
19
|
|
|
20
|
+
@final
|
|
21
|
+
def register_records(
|
|
22
|
+
self,
|
|
23
|
+
records: dict[str, soialib.reflection.Record],
|
|
24
|
+
) -> None:
|
|
25
|
+
pass
|
|
26
|
+
|
|
19
27
|
|
|
20
28
|
class _BoolAdapter(AbstractPrimitiveAdapter):
|
|
21
29
|
def default_expr(self) -> ExprLike:
|
|
@@ -40,6 +48,12 @@ class _BoolAdapter(AbstractPrimitiveAdapter):
|
|
|
40
48
|
def from_json_expr(self, json_expr: ExprLike) -> Expr:
|
|
41
49
|
return Expr.join("(True if ", json_expr, " else False)")
|
|
42
50
|
|
|
51
|
+
def get_type(self) -> soialib.reflection.Type:
|
|
52
|
+
return soialib.reflection.PrimitiveType(
|
|
53
|
+
kind="primitive",
|
|
54
|
+
value="bool",
|
|
55
|
+
)
|
|
56
|
+
|
|
43
57
|
|
|
44
58
|
BOOL_ADAPTER: Final[TypeAdapter] = _BoolAdapter()
|
|
45
59
|
|
|
@@ -80,6 +94,12 @@ class _Int32Adapter(_AbstractIntAdapter):
|
|
|
80
94
|
" < 2147483647 else 2147483647)",
|
|
81
95
|
)
|
|
82
96
|
|
|
97
|
+
def get_type(self) -> soialib.reflection.Type:
|
|
98
|
+
return soialib.reflection.PrimitiveType(
|
|
99
|
+
kind="primitive",
|
|
100
|
+
value="int32",
|
|
101
|
+
)
|
|
102
|
+
|
|
83
103
|
|
|
84
104
|
def _int64_to_json(i: int) -> int | str:
|
|
85
105
|
if i < -9007199254740991: # min safe integer in JavaScript
|
|
@@ -100,6 +120,12 @@ class _Int64Adapter(_AbstractIntAdapter):
|
|
|
100
120
|
def to_json_expr(self, in_expr: ExprLike, readable: bool) -> Expr:
|
|
101
121
|
return Expr.join(Expr.local("int64_to_json", _int64_to_json), "(", in_expr, ")")
|
|
102
122
|
|
|
123
|
+
def get_type(self) -> soialib.reflection.Type:
|
|
124
|
+
return soialib.reflection.PrimitiveType(
|
|
125
|
+
kind="primitive",
|
|
126
|
+
value="int64",
|
|
127
|
+
)
|
|
128
|
+
|
|
103
129
|
|
|
104
130
|
def _uint64_to_json(i: int) -> int | str:
|
|
105
131
|
if i <= 0:
|
|
@@ -119,6 +145,12 @@ class _Uint64Adapter(_AbstractIntAdapter):
|
|
|
119
145
|
Expr.local("uint64_to_json", _uint64_to_json), "(", in_expr, ")"
|
|
120
146
|
)
|
|
121
147
|
|
|
148
|
+
def get_type(self) -> soialib.reflection.Type:
|
|
149
|
+
return soialib.reflection.PrimitiveType(
|
|
150
|
+
kind="primitive",
|
|
151
|
+
value="uint64",
|
|
152
|
+
)
|
|
153
|
+
|
|
122
154
|
|
|
123
155
|
INT32_ADAPTER: Final[TypeAdapter] = _Int32Adapter()
|
|
124
156
|
INT64_ADAPTER: Final[TypeAdapter] = _Int64Adapter()
|
|
@@ -149,11 +181,23 @@ class _AbstractFloatAdapter(AbstractPrimitiveAdapter):
|
|
|
149
181
|
class _Float32Adapter(_AbstractFloatAdapter):
|
|
150
182
|
"""Type adapter implementation for float32."""
|
|
151
183
|
|
|
184
|
+
def get_type(self) -> soialib.reflection.Type:
|
|
185
|
+
return soialib.reflection.PrimitiveType(
|
|
186
|
+
kind="primitive",
|
|
187
|
+
value="float32",
|
|
188
|
+
)
|
|
189
|
+
|
|
152
190
|
|
|
153
191
|
@dataclass(frozen=True)
|
|
154
192
|
class _Float64Adapter(_AbstractFloatAdapter):
|
|
155
193
|
"""Type adapter implementation for float32."""
|
|
156
194
|
|
|
195
|
+
def get_type(self) -> soialib.reflection.Type:
|
|
196
|
+
return soialib.reflection.PrimitiveType(
|
|
197
|
+
kind="primitive",
|
|
198
|
+
value="float64",
|
|
199
|
+
)
|
|
200
|
+
|
|
157
201
|
|
|
158
202
|
FLOAT32_ADAPTER: Final[TypeAdapter] = _Float32Adapter()
|
|
159
203
|
FLOAT64_ADAPTER: Final[TypeAdapter] = _Float64Adapter()
|
|
@@ -192,6 +236,12 @@ class _TimestampAdapter(AbstractPrimitiveAdapter):
|
|
|
192
236
|
fn = Expr.local("_timestamp_from_json", _timestamp_from_json)
|
|
193
237
|
return Expr.join(fn, "(", json_expr, ")")
|
|
194
238
|
|
|
239
|
+
def get_type(self) -> soialib.reflection.Type:
|
|
240
|
+
return soialib.reflection.PrimitiveType(
|
|
241
|
+
kind="primitive",
|
|
242
|
+
value="timestamp",
|
|
243
|
+
)
|
|
244
|
+
|
|
195
245
|
|
|
196
246
|
def _timestamp_from_json(json: Any) -> Timestamp:
|
|
197
247
|
if json.__class__ is int or isinstance(json, int):
|
|
@@ -219,6 +269,12 @@ class _StringAdapter(AbstractPrimitiveAdapter):
|
|
|
219
269
|
def from_json_expr(self, json_expr: ExprLike) -> Expr:
|
|
220
270
|
return Expr.join("('' + (", json_expr, " or ''))")
|
|
221
271
|
|
|
272
|
+
def get_type(self) -> soialib.reflection.Type:
|
|
273
|
+
return soialib.reflection.PrimitiveType(
|
|
274
|
+
kind="primitive",
|
|
275
|
+
value="string",
|
|
276
|
+
)
|
|
277
|
+
|
|
222
278
|
|
|
223
279
|
STRING_ADAPTER: Final[TypeAdapter] = _StringAdapter()
|
|
224
280
|
|
|
@@ -247,5 +303,11 @@ class _BytesAdapter(AbstractPrimitiveAdapter):
|
|
|
247
303
|
Expr.local("fromhex", _BytesAdapter._fromhex_fn), "(", json_expr, ' or "")'
|
|
248
304
|
)
|
|
249
305
|
|
|
306
|
+
def get_type(self) -> soialib.reflection.Type:
|
|
307
|
+
return soialib.reflection.PrimitiveType(
|
|
308
|
+
kind="primitive",
|
|
309
|
+
value="bytes",
|
|
310
|
+
)
|
|
311
|
+
|
|
250
312
|
|
|
251
313
|
BYTES_ADAPTER: Final[TypeAdapter] = _BytesAdapter()
|
soialib/impl/structs.py
CHANGED
|
@@ -3,6 +3,7 @@ from collections.abc import Callable, Sequence
|
|
|
3
3
|
from dataclasses import FrozenInstanceError, dataclass
|
|
4
4
|
from typing import Any, Final, Union, cast
|
|
5
5
|
|
|
6
|
+
import soialib.reflection
|
|
6
7
|
from soialib import spec as _spec
|
|
7
8
|
from soialib.impl.function_maker import (
|
|
8
9
|
BodyBuilder,
|
|
@@ -28,6 +29,7 @@ class StructAdapter(TypeAdapter):
|
|
|
28
29
|
"simple_class",
|
|
29
30
|
"private_is_frozen_attr",
|
|
30
31
|
"finalization_state",
|
|
32
|
+
"fields",
|
|
31
33
|
)
|
|
32
34
|
|
|
33
35
|
spec: Final[_spec.Struct]
|
|
@@ -40,6 +42,7 @@ class StructAdapter(TypeAdapter):
|
|
|
40
42
|
|
|
41
43
|
# 0: has not started; 1: in progress; 2: done
|
|
42
44
|
finalization_state: int
|
|
45
|
+
fields: tuple["_Field", ...]
|
|
43
46
|
|
|
44
47
|
def __init__(self, spec: _spec.Struct):
|
|
45
48
|
self.finalization_state = 0
|
|
@@ -90,7 +93,7 @@ class StructAdapter(TypeAdapter):
|
|
|
90
93
|
self.finalization_state = 1
|
|
91
94
|
|
|
92
95
|
# Resolve the type of every field.
|
|
93
|
-
fields = tuple(
|
|
96
|
+
self.fields = fields = tuple(
|
|
94
97
|
sorted(
|
|
95
98
|
(_Field(f, resolve_type_fn(f.type)) for f in self.spec.fields),
|
|
96
99
|
key=lambda f: f.field.number,
|
|
@@ -198,6 +201,35 @@ class StructAdapter(TypeAdapter):
|
|
|
198
201
|
Expr.local("_cls?", self.gen_class), f".{fn_name}(", json_expr, ")"
|
|
199
202
|
)
|
|
200
203
|
|
|
204
|
+
def get_type(self) -> soialib.reflection.Type:
|
|
205
|
+
return soialib.reflection.RecordType(
|
|
206
|
+
kind="record",
|
|
207
|
+
value=self.spec.id,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
def register_records(
|
|
211
|
+
self,
|
|
212
|
+
registry: dict[str, soialib.reflection.Record],
|
|
213
|
+
) -> None:
|
|
214
|
+
record_id = self.spec.id
|
|
215
|
+
if record_id in registry:
|
|
216
|
+
return
|
|
217
|
+
registry[record_id] = soialib.reflection.Record(
|
|
218
|
+
kind="struct",
|
|
219
|
+
id=record_id,
|
|
220
|
+
fields=tuple(
|
|
221
|
+
soialib.reflection.Field(
|
|
222
|
+
name=field.field.name,
|
|
223
|
+
number=field.field.number,
|
|
224
|
+
type=field.type.get_type(),
|
|
225
|
+
)
|
|
226
|
+
for field in self.fields
|
|
227
|
+
),
|
|
228
|
+
removed_fields=self.spec.removed_numbers,
|
|
229
|
+
)
|
|
230
|
+
for field in self.fields:
|
|
231
|
+
field.type.register_records(registry)
|
|
232
|
+
|
|
201
233
|
|
|
202
234
|
class _Frozen:
|
|
203
235
|
def __setattr__(self, name: str, value: Any):
|
soialib/impl/type_adapter.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
2
|
from typing import Protocol
|
|
3
3
|
|
|
4
|
+
import soialib.reflection
|
|
4
5
|
from soialib import spec
|
|
5
6
|
from soialib.impl.function_maker import ExprLike
|
|
6
7
|
|
|
@@ -53,3 +54,10 @@ class TypeAdapter(Protocol):
|
|
|
53
54
|
self,
|
|
54
55
|
resolve_type_fn: Callable[[spec.Type], "TypeAdapter"],
|
|
55
56
|
) -> None: ...
|
|
57
|
+
|
|
58
|
+
def get_type(self) -> soialib.reflection.Type: ...
|
|
59
|
+
|
|
60
|
+
def register_records(
|
|
61
|
+
self,
|
|
62
|
+
registry: dict[str, soialib.reflection.Record],
|
|
63
|
+
) -> None: ...
|
soialib/reflection.py
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import typing
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
Callable,
|
|
8
|
+
Final,
|
|
9
|
+
Generic,
|
|
10
|
+
Literal,
|
|
11
|
+
Mapping,
|
|
12
|
+
Optional,
|
|
13
|
+
TypeAlias,
|
|
14
|
+
TypeVar,
|
|
15
|
+
Union,
|
|
16
|
+
cast,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class TypeDescriptor:
|
|
22
|
+
type: "Type"
|
|
23
|
+
records: tuple["Record", ...]
|
|
24
|
+
|
|
25
|
+
def as_json(self) -> Any:
|
|
26
|
+
return _TYPE_DESCRIPTOR_SERIALIZER.to_json(self)
|
|
27
|
+
|
|
28
|
+
def as_json_code(self) -> str:
|
|
29
|
+
return json.dumps(self.as_json(), indent=2)
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def from_json(json: Any) -> "TypeDescriptor":
|
|
33
|
+
return _TYPE_DESCRIPTOR_SERIALIZER.from_json(json)
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def from_json_code(json_code: str) -> "TypeDescriptor":
|
|
37
|
+
return _TYPE_DESCRIPTOR_SERIALIZER.from_json(json.loads(json_code))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class PrimitiveType:
|
|
42
|
+
kind: Literal["primitive"]
|
|
43
|
+
value: Literal[
|
|
44
|
+
"bool",
|
|
45
|
+
"int32",
|
|
46
|
+
"int64",
|
|
47
|
+
"uint64",
|
|
48
|
+
"float32",
|
|
49
|
+
"float64",
|
|
50
|
+
"timestamp",
|
|
51
|
+
"string",
|
|
52
|
+
"bytes",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class OptionalType:
|
|
58
|
+
kind: Literal["optional"]
|
|
59
|
+
value: "Type"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class ArrayType:
|
|
64
|
+
kind: Literal["array"]
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True)
|
|
67
|
+
class Array:
|
|
68
|
+
item: "Type"
|
|
69
|
+
key_chain: tuple[str, ...]
|
|
70
|
+
|
|
71
|
+
value: Array
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True)
|
|
75
|
+
class RecordType:
|
|
76
|
+
kind: Literal["record"]
|
|
77
|
+
value: str
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
Type: TypeAlias = Union[PrimitiveType, OptionalType, ArrayType, RecordType]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True)
|
|
84
|
+
class Field:
|
|
85
|
+
name: str
|
|
86
|
+
type: Optional[Type]
|
|
87
|
+
number: int
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass(frozen=True)
|
|
91
|
+
class Record:
|
|
92
|
+
kind: Literal["struct", "enum"]
|
|
93
|
+
id: str
|
|
94
|
+
fields: tuple[Field, ...]
|
|
95
|
+
removed_fields: tuple[int, ...]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ==============================================================================
|
|
99
|
+
# INTERNAL: JSON serialization framework
|
|
100
|
+
# ==============================================================================
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
_T = TypeVar("_T")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(frozen=True)
|
|
107
|
+
class _Serializer(Generic[_T]):
|
|
108
|
+
to_json: Callable[[_T], Any]
|
|
109
|
+
from_json: Callable[[Any], _T]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass(frozen=True)
|
|
113
|
+
class _FieldSerializer(Generic[_T]):
|
|
114
|
+
name: str
|
|
115
|
+
serializer: _Serializer[_T]
|
|
116
|
+
default: Optional[_T] = None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _primitive_serializer(check_type_fn: Callable[[Any], _T]) -> _Serializer[_T]:
|
|
120
|
+
return _Serializer(check_type_fn, check_type_fn)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _literal_union_serializer(s: set[_T]) -> _Serializer[_T]:
|
|
124
|
+
def check_in(input: _T) -> _T:
|
|
125
|
+
if input not in s:
|
|
126
|
+
raise ValueError(f"{input} is not in {s}")
|
|
127
|
+
return input
|
|
128
|
+
|
|
129
|
+
return _Serializer(check_in, check_in)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _dataclass_serializer(
|
|
133
|
+
type: typing.Type[_T], fields: Iterable[_FieldSerializer]
|
|
134
|
+
) -> _Serializer[_T]:
|
|
135
|
+
def to_json(obj: _T) -> dict[str, Any]:
|
|
136
|
+
json = {}
|
|
137
|
+
for field in fields:
|
|
138
|
+
value = getattr(obj, field.name)
|
|
139
|
+
if value != field.default:
|
|
140
|
+
json[field.name] = field.serializer.to_json(value)
|
|
141
|
+
return json
|
|
142
|
+
|
|
143
|
+
def from_json(json: dict[str, Any]) -> _T:
|
|
144
|
+
def field_from_json(field: _FieldSerializer) -> Any:
|
|
145
|
+
value_json = json.get(field.name)
|
|
146
|
+
if value_json is None:
|
|
147
|
+
if field.default is not None:
|
|
148
|
+
return field.default
|
|
149
|
+
else:
|
|
150
|
+
# Will raise an exception.
|
|
151
|
+
json[field.name]
|
|
152
|
+
return field.serializer.from_json(value_json)
|
|
153
|
+
|
|
154
|
+
return type(**{f.name: field_from_json(f) for f in fields})
|
|
155
|
+
|
|
156
|
+
return _Serializer(to_json, from_json)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _union_serializer(
|
|
160
|
+
kind_to_serializer: Mapping[str, _Serializer],
|
|
161
|
+
) -> _Serializer:
|
|
162
|
+
def to_json(input: Any) -> dict[str, Any]:
|
|
163
|
+
kind = cast(Any, input).kind
|
|
164
|
+
serializer = kind_to_serializer[kind]
|
|
165
|
+
return serializer.to_json(input)
|
|
166
|
+
|
|
167
|
+
def from_json(json: dict[str, Any]) -> Any:
|
|
168
|
+
kind = json["kind"]
|
|
169
|
+
serializer = kind_to_serializer[kind]
|
|
170
|
+
return serializer.from_json(json)
|
|
171
|
+
|
|
172
|
+
return _Serializer(to_json, from_json)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _listuple_serializer(
|
|
176
|
+
item_serializer: _Serializer[_T],
|
|
177
|
+
) -> _Serializer[tuple[_T, ...]]:
|
|
178
|
+
def to_json(input: tuple[_T, ...]) -> list[Any]:
|
|
179
|
+
return [item_serializer.to_json(e) for e in input]
|
|
180
|
+
|
|
181
|
+
def from_json(json: list[Any]) -> tuple[_T, ...]:
|
|
182
|
+
return tuple(item_serializer.from_json(e) for e in json)
|
|
183
|
+
|
|
184
|
+
return _Serializer(to_json, from_json)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _forwarding_serializer(
|
|
188
|
+
get_serializer_fn: Callable[[], _Serializer[_T]],
|
|
189
|
+
) -> _Serializer[_T]:
|
|
190
|
+
def to_json(input: _T) -> Any:
|
|
191
|
+
return get_serializer_fn().to_json(input)
|
|
192
|
+
|
|
193
|
+
def from_json(json: Any) -> _T:
|
|
194
|
+
return get_serializer_fn().from_json(json)
|
|
195
|
+
|
|
196
|
+
return _Serializer(to_json, from_json)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# ==============================================================================
|
|
200
|
+
# INTERNAL: JSON serialization of TypeDescriptor
|
|
201
|
+
# ==============================================================================
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _type_serializer() -> _Serializer[Type]:
|
|
205
|
+
return _TYPE_SERIALIZER
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
_TYPE_SERIALIZER: Final = _union_serializer(
|
|
209
|
+
{
|
|
210
|
+
"primitive": _dataclass_serializer(
|
|
211
|
+
PrimitiveType,
|
|
212
|
+
[
|
|
213
|
+
_FieldSerializer("kind", _literal_union_serializer({"primitive"})),
|
|
214
|
+
_FieldSerializer(
|
|
215
|
+
"value",
|
|
216
|
+
_literal_union_serializer(
|
|
217
|
+
{
|
|
218
|
+
"bool",
|
|
219
|
+
"int32",
|
|
220
|
+
"int64",
|
|
221
|
+
"uint64",
|
|
222
|
+
"float32",
|
|
223
|
+
"float64",
|
|
224
|
+
"timestamp",
|
|
225
|
+
"string",
|
|
226
|
+
"bytes",
|
|
227
|
+
}
|
|
228
|
+
),
|
|
229
|
+
),
|
|
230
|
+
],
|
|
231
|
+
),
|
|
232
|
+
"optional": _dataclass_serializer(
|
|
233
|
+
OptionalType,
|
|
234
|
+
[
|
|
235
|
+
_FieldSerializer(
|
|
236
|
+
"kind",
|
|
237
|
+
_literal_union_serializer({"optional"}),
|
|
238
|
+
),
|
|
239
|
+
_FieldSerializer(
|
|
240
|
+
"value",
|
|
241
|
+
_forwarding_serializer(_type_serializer),
|
|
242
|
+
),
|
|
243
|
+
],
|
|
244
|
+
),
|
|
245
|
+
"array": _dataclass_serializer(
|
|
246
|
+
ArrayType,
|
|
247
|
+
[
|
|
248
|
+
_FieldSerializer(
|
|
249
|
+
"kind",
|
|
250
|
+
_literal_union_serializer({"array"}),
|
|
251
|
+
),
|
|
252
|
+
_FieldSerializer(
|
|
253
|
+
"value",
|
|
254
|
+
_dataclass_serializer(
|
|
255
|
+
ArrayType.Array,
|
|
256
|
+
[
|
|
257
|
+
_FieldSerializer(
|
|
258
|
+
"item",
|
|
259
|
+
_forwarding_serializer(_type_serializer),
|
|
260
|
+
),
|
|
261
|
+
_FieldSerializer(
|
|
262
|
+
"key_chain",
|
|
263
|
+
_listuple_serializer(_primitive_serializer(str)),
|
|
264
|
+
default=(),
|
|
265
|
+
),
|
|
266
|
+
],
|
|
267
|
+
),
|
|
268
|
+
),
|
|
269
|
+
],
|
|
270
|
+
),
|
|
271
|
+
"record": _dataclass_serializer(
|
|
272
|
+
RecordType,
|
|
273
|
+
[
|
|
274
|
+
_FieldSerializer(
|
|
275
|
+
"kind",
|
|
276
|
+
_literal_union_serializer({"record"}),
|
|
277
|
+
),
|
|
278
|
+
_FieldSerializer(
|
|
279
|
+
"value",
|
|
280
|
+
_primitive_serializer(str),
|
|
281
|
+
),
|
|
282
|
+
],
|
|
283
|
+
),
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
_FIELD_SERIALIZER: Final = _dataclass_serializer(
|
|
289
|
+
Field,
|
|
290
|
+
[
|
|
291
|
+
_FieldSerializer(
|
|
292
|
+
"name",
|
|
293
|
+
_primitive_serializer(str),
|
|
294
|
+
),
|
|
295
|
+
_FieldSerializer(
|
|
296
|
+
"type",
|
|
297
|
+
_forwarding_serializer(_type_serializer),
|
|
298
|
+
),
|
|
299
|
+
_FieldSerializer(
|
|
300
|
+
"number",
|
|
301
|
+
_primitive_serializer(int),
|
|
302
|
+
),
|
|
303
|
+
],
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
_RECORD_SERIALIZER: Final = _dataclass_serializer(
|
|
308
|
+
Record,
|
|
309
|
+
[
|
|
310
|
+
_FieldSerializer(
|
|
311
|
+
"kind",
|
|
312
|
+
_literal_union_serializer({"struct", "enum"}),
|
|
313
|
+
),
|
|
314
|
+
_FieldSerializer(
|
|
315
|
+
"id",
|
|
316
|
+
_primitive_serializer(str),
|
|
317
|
+
),
|
|
318
|
+
_FieldSerializer(
|
|
319
|
+
"fields",
|
|
320
|
+
_listuple_serializer(_FIELD_SERIALIZER),
|
|
321
|
+
),
|
|
322
|
+
_FieldSerializer(
|
|
323
|
+
"removed_fields",
|
|
324
|
+
_listuple_serializer(_primitive_serializer(int)),
|
|
325
|
+
default=(),
|
|
326
|
+
),
|
|
327
|
+
],
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
_TYPE_DESCRIPTOR_SERIALIZER = _dataclass_serializer(
|
|
332
|
+
TypeDescriptor,
|
|
333
|
+
[
|
|
334
|
+
_FieldSerializer(
|
|
335
|
+
"type",
|
|
336
|
+
_TYPE_SERIALIZER,
|
|
337
|
+
),
|
|
338
|
+
_FieldSerializer(
|
|
339
|
+
"records",
|
|
340
|
+
_listuple_serializer(_RECORD_SERIALIZER),
|
|
341
|
+
),
|
|
342
|
+
],
|
|
343
|
+
)
|
soialib/serializer.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import json as jsonlib
|
|
2
2
|
from collections.abc import Callable
|
|
3
3
|
from dataclasses import FrozenInstanceError
|
|
4
|
+
from functools import cached_property
|
|
4
5
|
from typing import Any, Generic, TypeVar, cast, final
|
|
5
6
|
from weakref import WeakValueDictionary
|
|
6
7
|
|
|
8
|
+
import soialib.reflection
|
|
7
9
|
from soialib.impl.function_maker import Expr, LineSpan, make_function
|
|
8
10
|
from soialib.impl.type_adapter import TypeAdapter
|
|
9
11
|
from soialib.never import Never
|
|
@@ -19,6 +21,7 @@ class Serializer(Generic[T]):
|
|
|
19
21
|
"_to_dense_json_fn",
|
|
20
22
|
"_to_readable_json_fn",
|
|
21
23
|
"_from_json_fn",
|
|
24
|
+
"__dict__",
|
|
22
25
|
)
|
|
23
26
|
|
|
24
27
|
_adapter: TypeAdapter
|
|
@@ -57,6 +60,15 @@ class Serializer(Generic[T]):
|
|
|
57
60
|
def from_json_code(self, json_code: str) -> T:
|
|
58
61
|
return self._from_json_fn(jsonlib.loads(json_code))
|
|
59
62
|
|
|
63
|
+
@cached_property
|
|
64
|
+
def type_descriptor(self) -> soialib.reflection.TypeDescriptor:
|
|
65
|
+
records: dict[str, soialib.reflection.Record] = {}
|
|
66
|
+
self._adapter.register_records(records)
|
|
67
|
+
return soialib.reflection.TypeDescriptor(
|
|
68
|
+
type=self._adapter.get_type(),
|
|
69
|
+
records=tuple(records.values()),
|
|
70
|
+
)
|
|
71
|
+
|
|
60
72
|
def __setattr__(self, name: str, value: Any):
|
|
61
73
|
raise FrozenInstanceError(self.__class__.__qualname__)
|
|
62
74
|
|
soialib/service.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import json
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Callable, Generic, Literal, Mapping, TypeAlias, Union, cast
|
|
5
|
+
|
|
6
|
+
from soialib.method import Method, Request, Response
|
|
7
|
+
|
|
8
|
+
RequestHeaders: TypeAlias = Mapping[str, str]
|
|
9
|
+
ResponseHeaders: TypeAlias = dict[str, str]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ServiceImpl:
|
|
13
|
+
_number_to_method_impl: dict[int, "_MethodImpl"]
|
|
14
|
+
|
|
15
|
+
def add_method(
|
|
16
|
+
self,
|
|
17
|
+
method: Method[Request, Response],
|
|
18
|
+
impl: Union[
|
|
19
|
+
Callable[[Request], Response],
|
|
20
|
+
Callable[[Request, RequestHeaders], Response],
|
|
21
|
+
Callable[[Request, RequestHeaders, ResponseHeaders], Response],
|
|
22
|
+
],
|
|
23
|
+
) -> "ServiceImpl":
|
|
24
|
+
signature = inspect.Signature.from_callable(impl)
|
|
25
|
+
num_positional_params = 0
|
|
26
|
+
for param in signature.parameters.values():
|
|
27
|
+
if param.kind in (
|
|
28
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
29
|
+
inspect.Parameter.POSITIONAL_ONLY,
|
|
30
|
+
):
|
|
31
|
+
num_positional_params += 1
|
|
32
|
+
if param.kind == inspect.Parameter.VAR_POSITIONAL:
|
|
33
|
+
raise ValueError("Method implementation cannot accept *args")
|
|
34
|
+
if num_positional_params not in range(1, 4):
|
|
35
|
+
raise ValueError(
|
|
36
|
+
"Method implementation must accept 1 to 3 positional parameters"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def resolved_impl(
|
|
40
|
+
req: Request, req_headers: RequestHeaders, res_headers: ResponseHeaders
|
|
41
|
+
) -> Response:
|
|
42
|
+
if num_positional_params == 1:
|
|
43
|
+
return cast(Callable[[Request], Response], impl)(req)
|
|
44
|
+
elif num_positional_params == 2:
|
|
45
|
+
return cast(Callable[[Request, RequestHeaders], Response], impl)(
|
|
46
|
+
req, req_headers
|
|
47
|
+
)
|
|
48
|
+
else:
|
|
49
|
+
return cast(
|
|
50
|
+
Callable[[Request, RequestHeaders, ResponseHeaders], Response], impl
|
|
51
|
+
)(req, req_headers, res_headers)
|
|
52
|
+
|
|
53
|
+
number = method.number
|
|
54
|
+
if number in self._number_to_method_impl:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
f"Method with the same number already registered ({number})"
|
|
57
|
+
)
|
|
58
|
+
self._number_to_method_impl[number] = _MethodImpl(
|
|
59
|
+
method=method,
|
|
60
|
+
impl=resolved_impl,
|
|
61
|
+
)
|
|
62
|
+
return self
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True)
|
|
65
|
+
class RawResponse:
|
|
66
|
+
data: str
|
|
67
|
+
type: Literal["ok-json", "bad-request", "server-error"]
|
|
68
|
+
|
|
69
|
+
def handle_request(
|
|
70
|
+
self,
|
|
71
|
+
req_body: str,
|
|
72
|
+
req_headers: RequestHeaders,
|
|
73
|
+
res_headers: ResponseHeaders | None,
|
|
74
|
+
) -> RawResponse:
|
|
75
|
+
if req_body == "list":
|
|
76
|
+
|
|
77
|
+
def method_to_json(method: Method) -> Any:
|
|
78
|
+
return {
|
|
79
|
+
"method": method.name,
|
|
80
|
+
"number": method.number,
|
|
81
|
+
"request": method.request_serializer.type_descriptor.as_json(),
|
|
82
|
+
"response": method.response_serializer.type_descriptor.as_json(),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
json_code = json.dumps(
|
|
86
|
+
{
|
|
87
|
+
"methods": [
|
|
88
|
+
method_to_json(method_impl.method)
|
|
89
|
+
for method_impl in self._number_to_method_impl.values()
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
indent=2,
|
|
93
|
+
)
|
|
94
|
+
return self.RawResponse(json_code, "ok-json")
|
|
95
|
+
|
|
96
|
+
parts = req_body.split(":", 3)
|
|
97
|
+
if len(parts) != 4:
|
|
98
|
+
return self.RawResponse(
|
|
99
|
+
"bad request: invalid request format", "bad-request"
|
|
100
|
+
)
|
|
101
|
+
method_name = parts[0]
|
|
102
|
+
method_number_str = parts[1]
|
|
103
|
+
format = parts[2]
|
|
104
|
+
request_data = parts[3]
|
|
105
|
+
try:
|
|
106
|
+
method_number = int(method_number_str)
|
|
107
|
+
except Exception:
|
|
108
|
+
return self.RawResponse(
|
|
109
|
+
"bad request: can't parse method number", "bad-request"
|
|
110
|
+
)
|
|
111
|
+
method_impl = self._number_to_method_impl.get(method_number)
|
|
112
|
+
if not method_impl:
|
|
113
|
+
return self.RawResponse(
|
|
114
|
+
f"bad request: method not found: {method_name}; number: {method_number}",
|
|
115
|
+
"bad-request",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
req: Any = method_impl.method.request_serializer.from_json_code(
|
|
120
|
+
request_data
|
|
121
|
+
)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
return self.RawResponse(
|
|
124
|
+
f"bad request: can't parse JSON: {e}", "bad-request"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
res: Any = method_impl.impl(req, req_headers, res_headers or {})
|
|
129
|
+
except Exception as e:
|
|
130
|
+
return self.RawResponse(f"server error: {e}", "server-error")
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
res_json = method_impl.method.response_serializer.to_json_code(
|
|
134
|
+
res, readable=(format == "readable")
|
|
135
|
+
)
|
|
136
|
+
except Exception as e:
|
|
137
|
+
return self.RawResponse(
|
|
138
|
+
f"server error: can't serialize response to JSON: {e}", "server-error"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return self.RawResponse(res_json, "ok-json")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@dataclass(frozen=True)
|
|
145
|
+
class _MethodImpl(Generic[Request, Response]):
|
|
146
|
+
method: Method[Request, Response]
|
|
147
|
+
impl: Callable[[Request, RequestHeaders, ResponseHeaders], Response]
|
soialib/service_client.py
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import http.client
|
|
2
|
-
from typing import Final, Mapping
|
|
2
|
+
from typing import Final, Mapping
|
|
3
3
|
from urllib.parse import urlparse
|
|
4
4
|
|
|
5
5
|
from soialib import Method
|
|
6
|
-
|
|
7
|
-
Request = TypeVar("Request")
|
|
8
|
-
Response = TypeVar("Response")
|
|
6
|
+
from soialib.method import Request, Response
|
|
9
7
|
|
|
10
8
|
|
|
11
9
|
class ServiceClient:
|
|
@@ -13,7 +11,7 @@ class ServiceClient:
|
|
|
13
11
|
_host: Final[str] # May include the port
|
|
14
12
|
_path: Final[str]
|
|
15
13
|
|
|
16
|
-
def __init__(self, service_url: str
|
|
14
|
+
def __init__(self, service_url: str):
|
|
17
15
|
parsed_url = urlparse(service_url)
|
|
18
16
|
if parsed_url.query:
|
|
19
17
|
raise ValueError("Service URL must not contain a query string")
|
soialib/spec.py
CHANGED
|
@@ -18,7 +18,6 @@ class PrimitiveType(enum.Enum):
|
|
|
18
18
|
@dataclass(frozen=True)
|
|
19
19
|
class ArrayType:
|
|
20
20
|
item: "Type"
|
|
21
|
-
# TODO: comment
|
|
22
21
|
key_attributes: tuple[str, ...] = ()
|
|
23
22
|
|
|
24
23
|
|
|
@@ -27,7 +26,6 @@ class OptionalType:
|
|
|
27
26
|
other: "Type"
|
|
28
27
|
|
|
29
28
|
|
|
30
|
-
# TODO: comment
|
|
31
29
|
Type = Union[PrimitiveType, ArrayType, OptionalType, str]
|
|
32
30
|
|
|
33
31
|
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
soia_client-1.0.11.dist-info/licenses/LICENSE,sha256=SaAftKkX6hfSOiPdENQPS70tifH3PDHgazq8eK2Pwfw,1064
|
|
2
|
-
soialib/_.py,sha256=I4SxBM3brmE9D6hyrDrdkbfVnDLVRWaGwnj0C9Zxh54,52
|
|
3
|
-
soialib/__init__.py,sha256=UyNrMaKEwCmYecsEwgsglMJvjKzIbDp7idbIxLdBz_k,499
|
|
4
|
-
soialib/keyed_items.py,sha256=q7MCn82obf-0jh7FcAhuw4eh9-wtuHIpkEFcSfc8EaY,338
|
|
5
|
-
soialib/method.py,sha256=2qWG4jMqYhS3hA8y8YDu3iqzhXA_AKebpB38RWNmsYQ,452
|
|
6
|
-
soialib/module_initializer.py,sha256=Riq6B6cS9HUG1W8tyaa0GkiG3F4fGX70bKU0UOCnrRw,4205
|
|
7
|
-
soialib/never.py,sha256=bYU63XyNX4e2wOUXQHhHWGO-an4IFr9_ur1ut6GmGN0,47
|
|
8
|
-
soialib/serializer.py,sha256=jSDanuJ4TEPWEO0ssYSKy2CDLUPV0zwCUzNGHk6uFRQ,3122
|
|
9
|
-
soialib/serializers.py,sha256=vNLNsfuu6dF0ivJF6v7wQj5Yr6uo2kZHXG5tMrYiVTA,2564
|
|
10
|
-
soialib/service_client.py,sha256=DvBu5-w64FKvuu8Ew9v3Ph6FYGTMcfCwz4eEwKRE2Dc,2284
|
|
11
|
-
soialib/spec.py,sha256=w-eMMUqOchOlCJaXppyAa2JpSBrMo9lrMvqz8VaEX4I,3699
|
|
12
|
-
soialib/timestamp.py,sha256=lXBNH8mPmzflkNjSKZSBl2XS-ot9N8N92B_zGO2SMtU,4078
|
|
13
|
-
soialib/impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
soialib/impl/arrays.py,sha256=fghIo1Uhn0wx-b4IjZVOqqPyKHI0KmcAfboNhCdzAi0,4871
|
|
15
|
-
soialib/impl/enums.py,sha256=kSH0Puub0pZJ4hAVw52y0ktDX1IkHpPan3IDp6OHIIs,15990
|
|
16
|
-
soialib/impl/function_maker.py,sha256=MvCDv1WwzKGJzZbNCnJ_8-MP3m1xTIabumXA-9Ydd9M,5639
|
|
17
|
-
soialib/impl/optionals.py,sha256=pWuhfTIYM7669Rko-oVlBhHLqO3vgASW7fL0Yos3AWM,2076
|
|
18
|
-
soialib/impl/primitives.py,sha256=AHMVvs3BWvPfggsdFnY5dwwqAwiNVFYJa_rJ-WjMJfk,7424
|
|
19
|
-
soialib/impl/repr.py,sha256=7WX0bEAVENTjlyZIcbT8TcJylS7IRIyafGCmqaIMxFM,1413
|
|
20
|
-
soialib/impl/structs.py,sha256=g__J0w6FAteHW2ZF69yuhZGNfm5-XCorBQW_T1r3PCg,25621
|
|
21
|
-
soialib/impl/type_adapter.py,sha256=e72nBDqOP0uWNY10EtG7qOvRTzujA-LUVA30Ff7eeac,1935
|
|
22
|
-
soia_client-1.0.11.dist-info/METADATA,sha256=c0HD3ulDxxyRoi2V-r6tOxlq0oI-hwm16X_ejUExWO0,1667
|
|
23
|
-
soia_client-1.0.11.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
24
|
-
soia_client-1.0.11.dist-info/top_level.txt,sha256=2vPmAo5G0SrCxYrNdJKJJVdpalYppgjO2mmz2PtsFUI,8
|
|
25
|
-
soia_client-1.0.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|