proto-builder 1.0.0__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 proto-builder might be problematic. Click here for more details.
- proto_builder-1.0.0/PKG-INFO +3 -0
- proto_builder-1.0.0/pyproject.toml +8 -0
- proto_builder-1.0.0/setup.cfg +4 -0
- proto_builder-1.0.0/src/__init__.py +0 -0
- proto_builder-1.0.0/src/proto_builder/base_builder.py +358 -0
- proto_builder-1.0.0/src/proto_builder/service_builder.py +285 -0
- proto_builder-1.0.0/src/proto_builder/session_builder.py +320 -0
- proto_builder-1.0.0/src/proto_builder/tree_structure.py +453 -0
- proto_builder-1.0.0/src/proto_builder/utils.py +147 -0
- proto_builder-1.0.0/src/proto_builder.egg-info/PKG-INFO +3 -0
- proto_builder-1.0.0/src/proto_builder.egg-info/SOURCES.txt +11 -0
- proto_builder-1.0.0/src/proto_builder.egg-info/dependency_links.txt +1 -0
- proto_builder-1.0.0/src/proto_builder.egg-info/top_level.txt +2 -0
|
File without changes
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
from .utils import BUILTIN_MODULES, COLLECTIONS, NONE_TYPE, PROTO, ProtoConfig, ProtoType
|
|
2
|
+
from .tree_structure import Node, VariantMode
|
|
3
|
+
from enum import Enum
|
|
4
|
+
import inspect
|
|
5
|
+
import types
|
|
6
|
+
from typing import (
|
|
7
|
+
get_args,
|
|
8
|
+
get_origin,
|
|
9
|
+
get_type_hints,
|
|
10
|
+
Union,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseBuilder:
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
config: ProtoConfig | None = None,
|
|
19
|
+
):
|
|
20
|
+
|
|
21
|
+
self.config = config or ProtoConfig()
|
|
22
|
+
|
|
23
|
+
def format_proto_field(
|
|
24
|
+
self,
|
|
25
|
+
proto: ProtoType,
|
|
26
|
+
index: int,
|
|
27
|
+
) -> str:
|
|
28
|
+
|
|
29
|
+
if proto.optional:
|
|
30
|
+
type_decl = f"optional {proto.p_type}"
|
|
31
|
+
elif proto.repeated:
|
|
32
|
+
type_decl = f"repeated {proto.p_type}"
|
|
33
|
+
elif proto.p_type:
|
|
34
|
+
type_decl = proto.p_type
|
|
35
|
+
else:
|
|
36
|
+
type_decl = None
|
|
37
|
+
|
|
38
|
+
if type_decl:
|
|
39
|
+
return f" {type_decl} {proto.name} = {index};"
|
|
40
|
+
return f" {proto.name} = {index};"
|
|
41
|
+
|
|
42
|
+
def is_enum(
|
|
43
|
+
self,
|
|
44
|
+
f_type: type,
|
|
45
|
+
) -> bool:
|
|
46
|
+
|
|
47
|
+
return inspect.isclass(f_type) and issubclass(f_type, Enum)
|
|
48
|
+
|
|
49
|
+
def is_union(
|
|
50
|
+
self,
|
|
51
|
+
f_type: type,
|
|
52
|
+
) -> bool:
|
|
53
|
+
|
|
54
|
+
return f_type in (types.UnionType, Union)
|
|
55
|
+
|
|
56
|
+
def is_custom(
|
|
57
|
+
self,
|
|
58
|
+
f_type: type,
|
|
59
|
+
) -> bool:
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
inspect.isclass(f_type)
|
|
63
|
+
and f_type not in PROTO
|
|
64
|
+
and not self.is_enum(f_type)
|
|
65
|
+
and f_type.__module__ not in BUILTIN_MODULES
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def get_class_fields(
|
|
69
|
+
self,
|
|
70
|
+
cls: type,
|
|
71
|
+
) -> dict:
|
|
72
|
+
|
|
73
|
+
return get_type_hints(cls, include_extras=True)
|
|
74
|
+
|
|
75
|
+
def enum_params(
|
|
76
|
+
self,
|
|
77
|
+
enum_class: type[Enum],
|
|
78
|
+
) -> list[str]:
|
|
79
|
+
|
|
80
|
+
return [item.name for item in enum_class]
|
|
81
|
+
|
|
82
|
+
def is_collection(
|
|
83
|
+
self,
|
|
84
|
+
f_type: type,
|
|
85
|
+
) -> bool:
|
|
86
|
+
|
|
87
|
+
return f_type in COLLECTIONS or get_origin(f_type) in COLLECTIONS
|
|
88
|
+
|
|
89
|
+
def is_nested_collection(
|
|
90
|
+
self,
|
|
91
|
+
f_type: type,
|
|
92
|
+
) -> bool:
|
|
93
|
+
|
|
94
|
+
origin = get_origin(f_type)
|
|
95
|
+
args = get_args(f_type)
|
|
96
|
+
|
|
97
|
+
if f_type in COLLECTIONS:
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
if origin in (list, tuple, set):
|
|
101
|
+
return bool(args) and self.is_collection(args[0])
|
|
102
|
+
|
|
103
|
+
if origin is dict:
|
|
104
|
+
if len(args) != 2:
|
|
105
|
+
return True
|
|
106
|
+
key_type, value_type = args
|
|
107
|
+
return self.is_collection(key_type) or self.is_collection(value_type)
|
|
108
|
+
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
# def collect_custom_types(
|
|
112
|
+
# self,
|
|
113
|
+
# f_type: type,
|
|
114
|
+
# ):
|
|
115
|
+
# result = []
|
|
116
|
+
# stack = [f_type]
|
|
117
|
+
|
|
118
|
+
# while stack:
|
|
119
|
+
# current = stack.pop()
|
|
120
|
+
# origin = get_origin(current)
|
|
121
|
+
# args = get_args(current)
|
|
122
|
+
|
|
123
|
+
# if self.is_union(origin):
|
|
124
|
+
# stack.extend(arg for arg in args if arg is not NONE_TYPE)
|
|
125
|
+
# continue
|
|
126
|
+
|
|
127
|
+
# if self.is_collection(origin):
|
|
128
|
+
# stack.extend(arg for arg in args if arg is not NONE_TYPE)
|
|
129
|
+
# continue
|
|
130
|
+
|
|
131
|
+
# if self.is_custom(origin) or self.is_enum(origin):
|
|
132
|
+
# result.append(origin)
|
|
133
|
+
# continue
|
|
134
|
+
|
|
135
|
+
# if self.is_custom(current) or self.is_enum(current):
|
|
136
|
+
# result.append(current)
|
|
137
|
+
|
|
138
|
+
# return result
|
|
139
|
+
|
|
140
|
+
def collect_custom_types(
|
|
141
|
+
self,
|
|
142
|
+
f_type: type,
|
|
143
|
+
):
|
|
144
|
+
result = []
|
|
145
|
+
stack = [f_type]
|
|
146
|
+
|
|
147
|
+
while stack:
|
|
148
|
+
current = stack.pop()
|
|
149
|
+
origin = get_origin(current)
|
|
150
|
+
args = get_args(current)
|
|
151
|
+
|
|
152
|
+
if self.is_union(origin):
|
|
153
|
+
stack.extend(arg for arg in reversed(args) if arg is not NONE_TYPE)
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
if self.is_collection(origin):
|
|
157
|
+
stack.extend(arg for arg in reversed(args) if arg is not NONE_TYPE)
|
|
158
|
+
if origin not in result:
|
|
159
|
+
result.append(origin)
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
if current not in result:
|
|
163
|
+
result.append(current)
|
|
164
|
+
|
|
165
|
+
return result
|
|
166
|
+
|
|
167
|
+
def create_path_str(
|
|
168
|
+
self,
|
|
169
|
+
*args: Node | str,
|
|
170
|
+
) -> str:
|
|
171
|
+
|
|
172
|
+
return ".".join(
|
|
173
|
+
n.name if isinstance(n, Node) else n
|
|
174
|
+
for n in args
|
|
175
|
+
if isinstance(n, (Node, str))
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def _variant_paths(
|
|
179
|
+
self,
|
|
180
|
+
node: Node,
|
|
181
|
+
*,
|
|
182
|
+
exact: bool,
|
|
183
|
+
mode: VariantMode = "include-self",
|
|
184
|
+
) -> list[str]:
|
|
185
|
+
|
|
186
|
+
return [
|
|
187
|
+
self.create_path_str(*variant)
|
|
188
|
+
for variant in node.path_variants_to_root(mode=mode)
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
def _arg_names(
|
|
192
|
+
self,
|
|
193
|
+
node: Node,
|
|
194
|
+
) -> tuple[str]:
|
|
195
|
+
|
|
196
|
+
custom_args = self.collect_custom_types(node.data.get("field_type"))
|
|
197
|
+
return tuple(f_type.__name__ for f_type in custom_args if f_type is not None)
|
|
198
|
+
|
|
199
|
+
def is_removed(
|
|
200
|
+
self,
|
|
201
|
+
node: Node,
|
|
202
|
+
) -> bool:
|
|
203
|
+
|
|
204
|
+
arg_names = self._arg_names(node)
|
|
205
|
+
|
|
206
|
+
# Exact
|
|
207
|
+
remove_exact_include_self = self.config.get_remove("exact", "include")
|
|
208
|
+
remove_exact_exclude_self = self.config.get_remove("exact", "exclude")
|
|
209
|
+
|
|
210
|
+
# Scope
|
|
211
|
+
remove_scope_include_self = self.config.get_remove("scope", "include")
|
|
212
|
+
remove_scope_exclude_self = self.config.get_remove("scope", "exclude")
|
|
213
|
+
|
|
214
|
+
# For absolute path like `Class1` | contains self
|
|
215
|
+
if any(arg in remove_exact_include_self or arg in remove_scope_include_self for arg in arg_names):
|
|
216
|
+
return True
|
|
217
|
+
|
|
218
|
+
for variant in node.path_variants_to_root(mode="all"):
|
|
219
|
+
|
|
220
|
+
# For relative path like Class1.Classs2, contains self
|
|
221
|
+
if self.create_path_str(*variant) in remove_exact_include_self:
|
|
222
|
+
return True
|
|
223
|
+
|
|
224
|
+
if self.create_path_str(*variant) in remove_scope_include_self:
|
|
225
|
+
return True
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# For relative path like Class1.var, contains self
|
|
229
|
+
if any(self.create_path_str(*variant, arg) in remove_exact_include_self for arg in arg_names):
|
|
230
|
+
return True
|
|
231
|
+
|
|
232
|
+
if any(self.create_path_str(*variant, arg) in remove_scope_include_self for arg in arg_names):
|
|
233
|
+
return True
|
|
234
|
+
|
|
235
|
+
# For relative path like Class1.Classs2, exclude self
|
|
236
|
+
for variant in node.path_variants_to_root(mode="exclude-self"):
|
|
237
|
+
if self.create_path_str(*variant) in remove_exact_exclude_self:
|
|
238
|
+
return True
|
|
239
|
+
|
|
240
|
+
if self.create_path_str(*variant) in remove_scope_exclude_self:
|
|
241
|
+
return True
|
|
242
|
+
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
def is_optional(
|
|
246
|
+
self,
|
|
247
|
+
node: Node,
|
|
248
|
+
) -> bool:
|
|
249
|
+
|
|
250
|
+
if self.config.optional_all:
|
|
251
|
+
return True
|
|
252
|
+
|
|
253
|
+
arg_names = self._arg_names(node)
|
|
254
|
+
|
|
255
|
+
# Exact
|
|
256
|
+
optional_exact_include_self = self.config.get_optional("exact", "include")
|
|
257
|
+
|
|
258
|
+
# Scope
|
|
259
|
+
optional_scope_include_self = self.config.get_optional("scope", "include")
|
|
260
|
+
optional_scope_exclude_self = self.config.get_optional("scope", "exclude")
|
|
261
|
+
|
|
262
|
+
# For absolute path like `Class1` | contains self
|
|
263
|
+
if any(arg in optional_exact_include_self or arg in optional_scope_include_self for arg in arg_names):
|
|
264
|
+
return True
|
|
265
|
+
|
|
266
|
+
# Scope
|
|
267
|
+
for variant in node.path_variants_to_root(mode="all"):
|
|
268
|
+
|
|
269
|
+
# For relative path like Class1.Classs2, contains self
|
|
270
|
+
if self.create_path_str(*variant) in optional_scope_include_self:
|
|
271
|
+
return True
|
|
272
|
+
|
|
273
|
+
# For relative path like Class1.var, contains self
|
|
274
|
+
if any(self.create_path_str(*variant, arg) in optional_scope_include_self or arg in optional_scope_include_self for arg in arg_names):
|
|
275
|
+
return True
|
|
276
|
+
|
|
277
|
+
for variant in node.path_variants_to_root(mode="exclude-self"):
|
|
278
|
+
|
|
279
|
+
# Exact
|
|
280
|
+
# For relative path like Class1.var, contains self
|
|
281
|
+
if any(self.create_path_str(*variant, arg) in optional_exact_include_self for arg in arg_names):
|
|
282
|
+
return True
|
|
283
|
+
|
|
284
|
+
# Scope
|
|
285
|
+
# For relative path like Class1.Classs2, exclude self
|
|
286
|
+
if self.create_path_str(*variant) in optional_scope_exclude_self:
|
|
287
|
+
return True
|
|
288
|
+
|
|
289
|
+
# Exact
|
|
290
|
+
for variant in node.path_variants_to_root(tags={"class"}, tag_mode="include", mode="include-self"):
|
|
291
|
+
|
|
292
|
+
# For relative path like Class1.Classs2, contains self
|
|
293
|
+
if self.create_path_str(*variant) in optional_exact_include_self:
|
|
294
|
+
return True
|
|
295
|
+
|
|
296
|
+
return False
|
|
297
|
+
|
|
298
|
+
def resolve_type(
|
|
299
|
+
self,
|
|
300
|
+
node: Node,
|
|
301
|
+
f_type: type,
|
|
302
|
+
) -> type:
|
|
303
|
+
|
|
304
|
+
arg_names = self._arg_names(node)
|
|
305
|
+
|
|
306
|
+
# Exact
|
|
307
|
+
override_exact_include_self = self.config.get_override("exact", "include")
|
|
308
|
+
|
|
309
|
+
# Scope
|
|
310
|
+
override_scope_include_self = self.config.get_override("scope", "include")
|
|
311
|
+
override_scope_exclude_self = self.config.get_override("scope", "exclude")
|
|
312
|
+
|
|
313
|
+
# For absolute path like `Class1`, contains self
|
|
314
|
+
for arg in arg_names:
|
|
315
|
+
|
|
316
|
+
if arg in override_exact_include_self:
|
|
317
|
+
return override_exact_include_self.get(arg).type
|
|
318
|
+
|
|
319
|
+
if arg in override_scope_include_self:
|
|
320
|
+
return override_scope_include_self.get(arg).type
|
|
321
|
+
|
|
322
|
+
# Scope
|
|
323
|
+
for variant in node.path_variants_to_root(mode="all"):
|
|
324
|
+
|
|
325
|
+
# For relative path like Class1.Classs2, contains self
|
|
326
|
+
path = self.create_path_str(*variant)
|
|
327
|
+
if path in override_scope_include_self:
|
|
328
|
+
return override_scope_include_self.get(path).type
|
|
329
|
+
|
|
330
|
+
for arg in arg_names:
|
|
331
|
+
path = self.create_path_str(*variant, arg)
|
|
332
|
+
if path in override_scope_include_self:
|
|
333
|
+
return override_scope_include_self.get(path).type
|
|
334
|
+
|
|
335
|
+
for variant in node.path_variants_to_root(mode="exclude-self"):
|
|
336
|
+
|
|
337
|
+
# Exact
|
|
338
|
+
# For relative path like Class1.var, contains self
|
|
339
|
+
for arg in arg_names:
|
|
340
|
+
path = self.create_path_str(*variant, arg)
|
|
341
|
+
if path in override_exact_include_self:
|
|
342
|
+
return override_exact_include_self.get(path).type
|
|
343
|
+
|
|
344
|
+
# Scope
|
|
345
|
+
# For relative path like Class1.Classs2, exclude self
|
|
346
|
+
path = self.create_path_str(*variant)
|
|
347
|
+
if path in override_scope_exclude_self:
|
|
348
|
+
return override_scope_exclude_self.get(path).type
|
|
349
|
+
|
|
350
|
+
# Exact
|
|
351
|
+
for variant in node.path_variants_to_root(tags={"class"}, tag_mode="include", mode="include-self"):
|
|
352
|
+
|
|
353
|
+
# For relative path like Class1.Classs2, contains self
|
|
354
|
+
path = self.create_path_str(*variant)
|
|
355
|
+
if path in override_exact_include_self:
|
|
356
|
+
return override_exact_include_self.get(path).type
|
|
357
|
+
|
|
358
|
+
return f_type
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
from .utils import OTHER, MODULES, NONE_TYPE, ProtoConfig, Session, NodeData, Message
|
|
2
|
+
from .base_builder import BaseBuilder
|
|
3
|
+
from .session_builder import SessionBuilder
|
|
4
|
+
from .tree_structure import Node
|
|
5
|
+
import inspect
|
|
6
|
+
from dataclasses import asdict
|
|
7
|
+
from typing import (
|
|
8
|
+
get_origin,
|
|
9
|
+
get_args,
|
|
10
|
+
get_type_hints,
|
|
11
|
+
Callable,
|
|
12
|
+
Literal,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
class ServiceBuilder(BaseBuilder):
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
config: ProtoConfig,
|
|
20
|
+
):
|
|
21
|
+
|
|
22
|
+
super().__init__(config)
|
|
23
|
+
self.session_builder = SessionBuilder(config)
|
|
24
|
+
self.trees: list[Node] = []
|
|
25
|
+
|
|
26
|
+
def should_generate_message(
|
|
27
|
+
self,
|
|
28
|
+
params: dict[str, Node],
|
|
29
|
+
) -> bool:
|
|
30
|
+
|
|
31
|
+
if not params:
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
if len(params) > 1:
|
|
35
|
+
return True
|
|
36
|
+
|
|
37
|
+
tree = next(iter(params.values()))
|
|
38
|
+
if tree.root.data.get("is_custom"):
|
|
39
|
+
|
|
40
|
+
if tree.root.data.get("message_type") == "enum":
|
|
41
|
+
return True
|
|
42
|
+
|
|
43
|
+
for t in self.trees:
|
|
44
|
+
|
|
45
|
+
if t.root.same_as(tree.root):
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
if t.root.contains_subtree(tree.root):
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
def merge_model(
|
|
54
|
+
self,
|
|
55
|
+
tree: Node,
|
|
56
|
+
) -> None:
|
|
57
|
+
|
|
58
|
+
if not any(
|
|
59
|
+
t.root.contains_subtree(tree.root)
|
|
60
|
+
for t in self.trees
|
|
61
|
+
):
|
|
62
|
+
self.trees.append(tree)
|
|
63
|
+
|
|
64
|
+
def method_models(
|
|
65
|
+
self,
|
|
66
|
+
fn: Callable,
|
|
67
|
+
cls_name: str,
|
|
68
|
+
):
|
|
69
|
+
|
|
70
|
+
sig = inspect.signature(fn)
|
|
71
|
+
hints = get_type_hints(fn)
|
|
72
|
+
method_node = Node(cls_name).root.child(fn.__name__)
|
|
73
|
+
|
|
74
|
+
def add_model(cls: type):
|
|
75
|
+
if cls and (self.is_custom(cls) or self.is_enum(cls)):
|
|
76
|
+
tree = self.session_builder.build_tree(cls)
|
|
77
|
+
|
|
78
|
+
if self.is_removed(tree.root):
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
self.merge_model(tree)
|
|
82
|
+
|
|
83
|
+
def add_type(
|
|
84
|
+
tp,
|
|
85
|
+
name: str | None = None,
|
|
86
|
+
):
|
|
87
|
+
|
|
88
|
+
node = method_node.child(name or "")
|
|
89
|
+
resolved = self.resolve_type(node, tp)
|
|
90
|
+
origin = get_origin(resolved)
|
|
91
|
+
if self.is_union(origin):
|
|
92
|
+
for arg in get_args(resolved):
|
|
93
|
+
if arg is not NONE_TYPE:
|
|
94
|
+
add_model(arg)
|
|
95
|
+
else:
|
|
96
|
+
add_model(resolved)
|
|
97
|
+
|
|
98
|
+
for param in sig.parameters.values():
|
|
99
|
+
add_type(hints.get(param.name, param.annotation), param.name)
|
|
100
|
+
|
|
101
|
+
add_type(hints.get("return"))
|
|
102
|
+
|
|
103
|
+
def build_session(
|
|
104
|
+
self,
|
|
105
|
+
name: str,
|
|
106
|
+
fn: Callable,
|
|
107
|
+
cls_name: str,
|
|
108
|
+
) -> Session:
|
|
109
|
+
|
|
110
|
+
sig = inspect.signature(fn)
|
|
111
|
+
hints = get_type_hints(fn)
|
|
112
|
+
|
|
113
|
+
params = {
|
|
114
|
+
pname: hints.get(pname, param.annotation)
|
|
115
|
+
for pname, param in sig.parameters.items()
|
|
116
|
+
}
|
|
117
|
+
ret = hints.get("return")
|
|
118
|
+
|
|
119
|
+
base_name = name.title().replace("_", "")
|
|
120
|
+
request_name = f"{base_name}Request"
|
|
121
|
+
response_name = f"{base_name}Response"
|
|
122
|
+
input_params: dict[str, Node] = {}
|
|
123
|
+
output_params: dict[str, Node] = {}
|
|
124
|
+
|
|
125
|
+
for param_name, param_type in params.items():
|
|
126
|
+
node = Node(cls_name).child(fn.__name__).child("arg").child(param_name)
|
|
127
|
+
node.data.update(asdict(NodeData(message_name=param_type.__name__, field_type=param_type)))
|
|
128
|
+
|
|
129
|
+
if self.is_removed(node):
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
resolved_type = self.resolve_type(node, param_type)
|
|
133
|
+
input_params[param_name] = self.session_builder.build_tree(resolved_type, node)
|
|
134
|
+
|
|
135
|
+
if not input_params:
|
|
136
|
+
request_name = OTHER["empty"]
|
|
137
|
+
|
|
138
|
+
elif len(input_params) == 1:
|
|
139
|
+
name, tree = next(iter(input_params.items()))
|
|
140
|
+
f_type = tree.last_node().data.get("field_type")
|
|
141
|
+
if self.is_custom(f_type) and any(t.root.contains_subtree(tree.root) for t in self.trees):
|
|
142
|
+
request_name = resolved_type.__name__
|
|
143
|
+
|
|
144
|
+
if ret:
|
|
145
|
+
|
|
146
|
+
ret_origin = get_origin(ret)
|
|
147
|
+
ret_args = get_args(ret)
|
|
148
|
+
if ret_origin is tuple:
|
|
149
|
+
for arg in ret_args:
|
|
150
|
+
node = Node(cls_name).child(fn.__name__).child("return").child(arg.__name__)
|
|
151
|
+
node.data.update(asdict(NodeData(field_type=arg)))
|
|
152
|
+
|
|
153
|
+
if self.is_removed(node):
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
resolved_type = self.resolve_type(node, arg)
|
|
157
|
+
tree = self.session_builder.build_tree(resolved_type, node)
|
|
158
|
+
|
|
159
|
+
if self.is_union(type(arg)):
|
|
160
|
+
output_params.update({"value": tree})
|
|
161
|
+
else:
|
|
162
|
+
output_params.update({arg.__name__.lower() + "_value": tree})
|
|
163
|
+
else:
|
|
164
|
+
node = Node(cls_name).child(fn.__name__).child("return")
|
|
165
|
+
resolved_type = self.resolve_type(node, ret)
|
|
166
|
+
tree = self.session_builder.build_tree(resolved_type, node)
|
|
167
|
+
output_params = {"value": tree}
|
|
168
|
+
|
|
169
|
+
if not output_params:
|
|
170
|
+
response_name = OTHER["empty"]
|
|
171
|
+
|
|
172
|
+
elif len(output_params) == 1:
|
|
173
|
+
name, tree = next(iter(output_params.items()))
|
|
174
|
+
f_type = tree.last_node().data.get("field_type")
|
|
175
|
+
if self.is_custom(f_type) and any(t.root.contains_subtree(tree.root) for t in self.trees):
|
|
176
|
+
response_name = resolved_type.__name__
|
|
177
|
+
|
|
178
|
+
return Session(fn.__name__, request_name, response_name, input_params, output_params)
|
|
179
|
+
|
|
180
|
+
def message_from_fields(
|
|
181
|
+
self,
|
|
182
|
+
name: str,
|
|
183
|
+
fields: dict[str, Node],
|
|
184
|
+
) -> Message:
|
|
185
|
+
|
|
186
|
+
message: Message = Message()
|
|
187
|
+
lines = [f"message {name} {{"]
|
|
188
|
+
|
|
189
|
+
idx = 1
|
|
190
|
+
for field_name, tree in fields.items():
|
|
191
|
+
|
|
192
|
+
if self.is_removed(tree):
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
proto = self.session_builder.proto_type(tree.last_node())
|
|
196
|
+
proto.name = field_name
|
|
197
|
+
lines.append(self.format_proto_field(proto, idx))
|
|
198
|
+
message.modules.append(proto.p_type)
|
|
199
|
+
idx += 1
|
|
200
|
+
|
|
201
|
+
lines.append("}")
|
|
202
|
+
if len(lines) > 2:
|
|
203
|
+
message.text = "\n".join(lines)
|
|
204
|
+
|
|
205
|
+
return message
|
|
206
|
+
|
|
207
|
+
def build(
|
|
208
|
+
self,
|
|
209
|
+
service_cls: type,
|
|
210
|
+
package_name: str = "generated",
|
|
211
|
+
) -> str:
|
|
212
|
+
|
|
213
|
+
self.trees = []
|
|
214
|
+
|
|
215
|
+
cls_name = service_cls.__name__
|
|
216
|
+
methods = {
|
|
217
|
+
name: fn
|
|
218
|
+
for name, fn in service_cls.__dict__.items()
|
|
219
|
+
if inspect.isfunction(fn) and getattr(fn, "__isabstractmethod__", False)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
for fn in methods.values():
|
|
223
|
+
self.method_models(fn, cls_name)
|
|
224
|
+
|
|
225
|
+
sessions = {
|
|
226
|
+
name: self.build_session(name, fn, cls_name)
|
|
227
|
+
for name, fn in methods.items()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
header = [
|
|
231
|
+
'syntax = "proto3";',
|
|
232
|
+
f"package {package_name};",
|
|
233
|
+
]
|
|
234
|
+
|
|
235
|
+
service = [f"service {cls_name} {{"]
|
|
236
|
+
for name, session in sessions.items():
|
|
237
|
+
service.append(f" rpc {name} ({session.request_name}) returns ({session.response_name});")
|
|
238
|
+
service.append("}")
|
|
239
|
+
|
|
240
|
+
messages: list[str] = []
|
|
241
|
+
modules: list[str] = []
|
|
242
|
+
for tree in self.trees:
|
|
243
|
+
msg_list: list[Message] = self.session_builder.node_to_message(tree.root)
|
|
244
|
+
for msg in msg_list:
|
|
245
|
+
messages.append(msg.text)
|
|
246
|
+
modules.extend(msg.modules)
|
|
247
|
+
|
|
248
|
+
for session in sessions.values():
|
|
249
|
+
|
|
250
|
+
if self.should_generate_message(session.input_params):
|
|
251
|
+
msg = self.message_from_fields(session.request_name, session.input_params)
|
|
252
|
+
if msg.text:
|
|
253
|
+
messages.append(msg.text)
|
|
254
|
+
modules.extend(msg.modules)
|
|
255
|
+
|
|
256
|
+
if self.should_generate_message(session.output_params):
|
|
257
|
+
msg = self.message_from_fields(session.response_name, session.output_params)
|
|
258
|
+
if msg.text:
|
|
259
|
+
messages.append(msg.text)
|
|
260
|
+
modules.extend(msg.modules)
|
|
261
|
+
|
|
262
|
+
if not session.input_params or not session.output_params:
|
|
263
|
+
modules.append(OTHER["empty"])
|
|
264
|
+
|
|
265
|
+
imports: list[str] = []
|
|
266
|
+
for mdl in modules:
|
|
267
|
+
if mdl in MODULES:
|
|
268
|
+
imp = f'import "{MODULES[mdl]}";'
|
|
269
|
+
if imp not in imports:
|
|
270
|
+
imports.append(imp)
|
|
271
|
+
|
|
272
|
+
content: list[str] = []
|
|
273
|
+
|
|
274
|
+
content.append("\n".join(header))
|
|
275
|
+
if imports:
|
|
276
|
+
content.append("\n\n")
|
|
277
|
+
content.append("\n".join(imports))
|
|
278
|
+
if service:
|
|
279
|
+
content.append("\n\n")
|
|
280
|
+
content.append("\n".join(service))
|
|
281
|
+
if messages:
|
|
282
|
+
content.append("\n\n")
|
|
283
|
+
content.append("\n\n".join(messages))
|
|
284
|
+
|
|
285
|
+
return "".join(content)
|