soia-client 1.0.11__tar.gz → 1.0.13__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 (34) hide show
  1. {soia_client-1.0.11 → soia_client-1.0.13}/PKG-INFO +1 -1
  2. {soia_client-1.0.11 → soia_client-1.0.13}/pyproject.toml +1 -1
  3. {soia_client-1.0.11 → soia_client-1.0.13}/soia_client.egg-info/PKG-INFO +1 -1
  4. {soia_client-1.0.11 → soia_client-1.0.13}/soia_client.egg-info/SOURCES.txt +2 -0
  5. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/__init__.py +4 -0
  6. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/impl/arrays.py +20 -1
  7. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/impl/enums.py +34 -2
  8. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/impl/optionals.py +13 -0
  9. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/impl/primitives.py +62 -0
  10. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/impl/structs.py +33 -1
  11. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/impl/type_adapter.py +8 -0
  12. soia_client-1.0.13/soialib/reflection.py +343 -0
  13. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/serializer.py +12 -0
  14. soia_client-1.0.13/soialib/service.py +147 -0
  15. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/service_client.py +3 -5
  16. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/spec.py +0 -2
  17. {soia_client-1.0.11 → soia_client-1.0.13}/tests/test_module_initializer.py +249 -2
  18. {soia_client-1.0.11 → soia_client-1.0.13}/LICENSE +0 -0
  19. {soia_client-1.0.11 → soia_client-1.0.13}/README +0 -0
  20. {soia_client-1.0.11 → soia_client-1.0.13}/setup.cfg +0 -0
  21. {soia_client-1.0.11 → soia_client-1.0.13}/soia_client.egg-info/dependency_links.txt +0 -0
  22. {soia_client-1.0.11 → soia_client-1.0.13}/soia_client.egg-info/top_level.txt +0 -0
  23. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/_.py +0 -0
  24. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/impl/__init__.py +0 -0
  25. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/impl/function_maker.py +0 -0
  26. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/impl/repr.py +0 -0
  27. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/keyed_items.py +0 -0
  28. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/method.py +0 -0
  29. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/module_initializer.py +0 -0
  30. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/never.py +0 -0
  31. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/serializers.py +0 -0
  32. {soia_client-1.0.11 → soia_client-1.0.13}/soialib/timestamp.py +0 -0
  33. {soia_client-1.0.11 → soia_client-1.0.13}/tests/test_serializers.py +0 -0
  34. {soia_client-1.0.11 → soia_client-1.0.13}/tests/test_timestamp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soia-client
3
- Version: 1.0.11
3
+ Version: 1.0.13
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.11"
7
+ version = "1.0.13"
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.4
2
2
  Name: soia-client
3
- Version: 1.0.11
3
+ Version: 1.0.13
4
4
  Author-email: Tyler Fibonacci <gepheum@gmail.com>
5
5
  License: MIT License
6
6
 
@@ -11,8 +11,10 @@ soialib/keyed_items.py
11
11
  soialib/method.py
12
12
  soialib/module_initializer.py
13
13
  soialib/never.py
14
+ soialib/reflection.py
14
15
  soialib/serializer.py
15
16
  soialib/serializers.py
17
+ soialib/service.py
16
18
  soialib/service_client.py
17
19
  soialib/spec.py
18
20
  soialib/timestamp.py
@@ -6,13 +6,17 @@ from soialib.serializers import (
6
6
  optional_serializer,
7
7
  primitive_serializer,
8
8
  )
9
+ from soialib.service import RequestHeaders, ResponseHeaders, ServiceImpl
9
10
  from soialib.service_client import ServiceClient
10
11
  from soialib.timestamp import Timestamp
11
12
 
12
13
  __all__ = [
13
14
  "KeyedItems",
14
15
  "Method",
16
+ "RequestHeaders",
17
+ "ResponseHeaders",
15
18
  "Serializer",
19
+ "ServiceImpl",
16
20
  "ServiceClient",
17
21
  "Timestamp",
18
22
  "array_serializer",
@@ -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]
@@ -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)
@@ -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()
@@ -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()
@@ -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):
@@ -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: ...
@@ -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
+ )
@@ -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
 
@@ -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]
@@ -1,11 +1,9 @@
1
1
  import http.client
2
- from typing import Final, Mapping, TypeVar
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, use_https: bool = False):
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")
@@ -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
 
@@ -6,6 +6,7 @@ from soialib import spec
6
6
  from soialib.keyed_items import KeyedItems
7
7
  from soialib.method import Method
8
8
  from soialib.module_initializer import init_module
9
+ from soialib.reflection import TypeDescriptor
9
10
  from soialib.timestamp import Timestamp
10
11
 
11
12
 
@@ -96,12 +97,12 @@ class ModuleInitializerTestCase(unittest.TestCase):
96
97
  spec.Field(
97
98
  name="i64",
98
99
  number=5,
99
- type=spec.PrimitiveType.INT32,
100
+ type=spec.PrimitiveType.INT64,
100
101
  ),
101
102
  spec.Field(
102
103
  name="u64",
103
104
  number=6,
104
- type=spec.PrimitiveType.INT32,
105
+ type=spec.PrimitiveType.UINT64,
105
106
  ),
106
107
  spec.Field(
107
108
  name="s",
@@ -1119,3 +1120,249 @@ class ModuleInitializerTestCase(unittest.TestCase):
1119
1120
  c = module["C"]
1120
1121
  Point = module["Point"]
1121
1122
  self.assertEqual(c, Point(x=1.5, y=2.5))
1123
+
1124
+ def test_enum_type_descriptor(self):
1125
+ module = self.init_test_module()
1126
+ json_value_cls = module["JsonValue"]
1127
+ type_descriptor = json_value_cls.SERIALIZER.type_descriptor
1128
+ self.assertEqual(
1129
+ type_descriptor.as_json(),
1130
+ {
1131
+ "type": {"kind": "record", "value": "my/module.soia:JsonValue"},
1132
+ "records": [
1133
+ {
1134
+ "kind": "enum",
1135
+ "id": "my/module.soia:JsonValue",
1136
+ "fields": [
1137
+ {
1138
+ "name": "bool",
1139
+ "type": {"kind": "primitive", "value": "bool"},
1140
+ "number": 2,
1141
+ },
1142
+ {
1143
+ "name": "number",
1144
+ "type": {"kind": "primitive", "value": "float64"},
1145
+ "number": 3,
1146
+ },
1147
+ {
1148
+ "name": "string",
1149
+ "type": {"kind": "primitive", "value": "string"},
1150
+ "number": 4,
1151
+ },
1152
+ {
1153
+ "name": "array",
1154
+ "type": {
1155
+ "kind": "array",
1156
+ "value": {
1157
+ "item": {
1158
+ "kind": "record",
1159
+ "value": "my/module.soia:JsonValue",
1160
+ },
1161
+ },
1162
+ },
1163
+ "number": 5,
1164
+ },
1165
+ {
1166
+ "name": "object",
1167
+ "type": {
1168
+ "kind": "record",
1169
+ "value": "my/module.soia:JsonValue.Object",
1170
+ },
1171
+ "number": 6,
1172
+ },
1173
+ ],
1174
+ "removed_fields": [100, 101],
1175
+ },
1176
+ {
1177
+ "kind": "struct",
1178
+ "id": "my/module.soia:JsonValue.Object",
1179
+ "fields": [
1180
+ {
1181
+ "name": "entries",
1182
+ "type": {
1183
+ "kind": "array",
1184
+ "value": {
1185
+ "item": {
1186
+ "kind": "record",
1187
+ "value": "my/module.soia:JsonValue.ObjectEntry",
1188
+ },
1189
+ "key_chain": ["name"],
1190
+ },
1191
+ },
1192
+ "number": 0,
1193
+ }
1194
+ ],
1195
+ },
1196
+ {
1197
+ "kind": "struct",
1198
+ "id": "my/module.soia:JsonValue.ObjectEntry",
1199
+ "fields": [
1200
+ {
1201
+ "name": "name",
1202
+ "type": {"kind": "primitive", "value": "string"},
1203
+ "number": 0,
1204
+ },
1205
+ {
1206
+ "name": "value",
1207
+ "type": {
1208
+ "kind": "record",
1209
+ "value": "my/module.soia:JsonValue",
1210
+ },
1211
+ "number": 1,
1212
+ },
1213
+ ],
1214
+ },
1215
+ ],
1216
+ },
1217
+ )
1218
+ self.assertEqual(
1219
+ str(TypeDescriptor.from_json(type_descriptor.as_json())),
1220
+ str(type_descriptor),
1221
+ )
1222
+ self.assertEqual(
1223
+ TypeDescriptor.from_json(type_descriptor.as_json()),
1224
+ type_descriptor,
1225
+ )
1226
+
1227
+ def test_optional_type_descriptor(self):
1228
+ module = self.init_test_module()
1229
+ segment_cls = module["Segment"]
1230
+ type_descriptor = segment_cls.SERIALIZER.type_descriptor
1231
+ self.assertEqual(
1232
+ type_descriptor.as_json(),
1233
+ {
1234
+ "type": {"kind": "record", "value": "my/module.soia:Segment"},
1235
+ "records": [
1236
+ {
1237
+ "kind": "struct",
1238
+ "id": "my/module.soia:Segment",
1239
+ "fields": [
1240
+ {
1241
+ "name": "a",
1242
+ "type": {
1243
+ "kind": "record",
1244
+ "value": "my/module.soia:Point",
1245
+ },
1246
+ "number": 0,
1247
+ },
1248
+ {
1249
+ "name": "bb",
1250
+ "type": {
1251
+ "kind": "record",
1252
+ "value": "my/module.soia:Point",
1253
+ },
1254
+ "number": 1,
1255
+ },
1256
+ {
1257
+ "name": "c",
1258
+ "type": {
1259
+ "kind": "optional",
1260
+ "value": {
1261
+ "kind": "record",
1262
+ "value": "my/module.soia:Point",
1263
+ },
1264
+ },
1265
+ "number": 2,
1266
+ },
1267
+ ],
1268
+ },
1269
+ {
1270
+ "kind": "struct",
1271
+ "id": "my/module.soia:Point",
1272
+ "fields": [
1273
+ {
1274
+ "name": "x",
1275
+ "type": {"kind": "primitive", "value": "float32"},
1276
+ "number": 0,
1277
+ },
1278
+ {
1279
+ "name": "y",
1280
+ "type": {"kind": "primitive", "value": "float32"},
1281
+ "number": 2,
1282
+ },
1283
+ ],
1284
+ "removed_fields": [1],
1285
+ },
1286
+ ],
1287
+ },
1288
+ )
1289
+ self.assertEqual(
1290
+ str(TypeDescriptor.from_json(type_descriptor.as_json())),
1291
+ str(type_descriptor),
1292
+ )
1293
+ self.assertEqual(
1294
+ TypeDescriptor.from_json(type_descriptor.as_json()),
1295
+ type_descriptor,
1296
+ )
1297
+
1298
+ def test_primitive_type_descriptor(self):
1299
+ module = self.init_test_module()
1300
+ segment_cls = module["Primitives"]
1301
+ type_descriptor = segment_cls.SERIALIZER.type_descriptor
1302
+ self.assertEqual(
1303
+ type_descriptor.as_json(),
1304
+ {
1305
+ "type": {"kind": "record", "value": "my/module.soia:Primitives"},
1306
+ "records": [
1307
+ {
1308
+ "kind": "struct",
1309
+ "id": "my/module.soia:Primitives",
1310
+ "fields": [
1311
+ {
1312
+ "name": "bool",
1313
+ "type": {"kind": "primitive", "value": "bool"},
1314
+ "number": 0,
1315
+ },
1316
+ {
1317
+ "name": "bytes",
1318
+ "type": {"kind": "primitive", "value": "bytes"},
1319
+ "number": 1,
1320
+ },
1321
+ {
1322
+ "name": "f32",
1323
+ "type": {"kind": "primitive", "value": "float32"},
1324
+ "number": 2,
1325
+ },
1326
+ {
1327
+ "name": "f64",
1328
+ "type": {"kind": "primitive", "value": "float64"},
1329
+ "number": 3,
1330
+ },
1331
+ {
1332
+ "name": "i32",
1333
+ "type": {"kind": "primitive", "value": "int32"},
1334
+ "number": 4,
1335
+ },
1336
+ {
1337
+ "name": "i64",
1338
+ "type": {"kind": "primitive", "value": "int64"},
1339
+ "number": 5,
1340
+ },
1341
+ {
1342
+ "name": "u64",
1343
+ "type": {"kind": "primitive", "value": "uint64"},
1344
+ "number": 6,
1345
+ },
1346
+ {
1347
+ "name": "s",
1348
+ "type": {"kind": "primitive", "value": "string"},
1349
+ "number": 7,
1350
+ },
1351
+ {
1352
+ "name": "t",
1353
+ "type": {"kind": "primitive", "value": "timestamp"},
1354
+ "number": 8,
1355
+ },
1356
+ ],
1357
+ }
1358
+ ],
1359
+ },
1360
+ )
1361
+ self.assertEqual(
1362
+ str(TypeDescriptor.from_json(type_descriptor.as_json())),
1363
+ str(type_descriptor),
1364
+ )
1365
+ self.assertEqual(
1366
+ TypeDescriptor.from_json(type_descriptor.as_json()),
1367
+ type_descriptor,
1368
+ )
File without changes
File without changes
File without changes
File without changes