nsj-rest-lib2 0.0.26__py3-none-any.whl → 0.0.29__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.
- nsj_rest_lib2/compiler/compiler.py +81 -0
- nsj_rest_lib2/compiler/dto_compiler.py +1 -1
- nsj_rest_lib2/compiler/edl_model/api_model.py +95 -2
- nsj_rest_lib2/compiler/function_compiler.py +550 -0
- nsj_rest_lib2/compiler/function_model.py +70 -0
- nsj_rest_lib2/compiler/model.py +4 -0
- nsj_rest_lib2/compiler/property_compiler.py +111 -0
- nsj_rest_lib2/compiler/util/type_naming_util.py +22 -0
- nsj_rest_lib2/controller/dynamic_controller.py +26 -0
- nsj_rest_lib2/service/entity_config_writer.py +134 -0
- nsj_rest_lib2/service/entity_loader.py +56 -6
- {nsj_rest_lib2-0.0.26.dist-info → nsj_rest_lib2-0.0.29.dist-info}/METADATA +2 -2
- {nsj_rest_lib2-0.0.26.dist-info → nsj_rest_lib2-0.0.29.dist-info}/RECORD +15 -12
- {nsj_rest_lib2-0.0.26.dist-info → nsj_rest_lib2-0.0.29.dist-info}/WHEEL +0 -0
- {nsj_rest_lib2-0.0.26.dist-info → nsj_rest_lib2-0.0.29.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import re
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Dict, Optional, Sequence
|
|
7
|
+
|
|
8
|
+
import black
|
|
9
|
+
|
|
10
|
+
from nsj_rest_lib2.compiler.compiler_structures import PropertiesCompilerStructure
|
|
11
|
+
from nsj_rest_lib2.compiler.edl_model.api_model import HandlerConfig, HandlerMapping
|
|
12
|
+
from nsj_rest_lib2.compiler.edl_model.entity_model_base import EntityModelBase
|
|
13
|
+
from nsj_rest_lib2.compiler.edl_model.primitives import PrimitiveTypes
|
|
14
|
+
from nsj_rest_lib2.compiler.function_model import (
|
|
15
|
+
FunctionBindingConfig,
|
|
16
|
+
FunctionCompilationOutput,
|
|
17
|
+
FunctionRelationBinding,
|
|
18
|
+
)
|
|
19
|
+
from nsj_rest_lib2.compiler.util.str_util import CompilerStrUtil
|
|
20
|
+
from nsj_rest_lib2.compiler.util.type_naming_util import compile_function_class_name
|
|
21
|
+
from nsj_rest_lib2.compiler.util.type_util import TypeUtil
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class _MappingNode:
|
|
26
|
+
attr: str
|
|
27
|
+
python_name: str
|
|
28
|
+
dto_field: Optional[str]
|
|
29
|
+
children: list["_MappingNode"] = field(default_factory=list)
|
|
30
|
+
pg_type: Optional[str] = None
|
|
31
|
+
is_array: bool = False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class _FieldSpec:
|
|
36
|
+
name: str
|
|
37
|
+
annotation: str
|
|
38
|
+
type_field_name: Optional[str]
|
|
39
|
+
relation_class: Optional[str]
|
|
40
|
+
is_relation: bool
|
|
41
|
+
is_list: bool
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class _ClassSpec:
|
|
46
|
+
name: str
|
|
47
|
+
type_name: str
|
|
48
|
+
function_name: str
|
|
49
|
+
fields: list[_FieldSpec]
|
|
50
|
+
children: list["_ClassSpec"] = field(default_factory=list)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class FunctionCompiler:
|
|
54
|
+
"""
|
|
55
|
+
Responsável por compilar tipos usados por funções de banco (Insert/Update)
|
|
56
|
+
com base na configuração declarada no EDL.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def compile_insert(
|
|
60
|
+
self,
|
|
61
|
+
entity_model: EntityModelBase,
|
|
62
|
+
properties_structure: PropertiesCompilerStructure,
|
|
63
|
+
handler_config: Optional[HandlerConfig],
|
|
64
|
+
prefx_class_name: str,
|
|
65
|
+
) -> FunctionCompilationOutput:
|
|
66
|
+
return self._compile(
|
|
67
|
+
entity_model,
|
|
68
|
+
properties_structure,
|
|
69
|
+
handler_config,
|
|
70
|
+
prefx_class_name,
|
|
71
|
+
operation="insert",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def compile_update(
|
|
75
|
+
self,
|
|
76
|
+
entity_model: EntityModelBase,
|
|
77
|
+
properties_structure: PropertiesCompilerStructure,
|
|
78
|
+
handler_config: Optional[HandlerConfig],
|
|
79
|
+
prefx_class_name: str,
|
|
80
|
+
) -> FunctionCompilationOutput:
|
|
81
|
+
return self._compile(
|
|
82
|
+
entity_model,
|
|
83
|
+
properties_structure,
|
|
84
|
+
handler_config,
|
|
85
|
+
prefx_class_name,
|
|
86
|
+
operation="update",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# ------------------------------------------------------------------ #
|
|
90
|
+
# Internal helpers
|
|
91
|
+
# ------------------------------------------------------------------ #
|
|
92
|
+
|
|
93
|
+
def _compile(
|
|
94
|
+
self,
|
|
95
|
+
entity_model: EntityModelBase,
|
|
96
|
+
properties_structure: PropertiesCompilerStructure,
|
|
97
|
+
handler_config: Optional[HandlerConfig],
|
|
98
|
+
prefx_class_name: str,
|
|
99
|
+
operation: str,
|
|
100
|
+
) -> FunctionCompilationOutput:
|
|
101
|
+
if not handler_config or handler_config.impl != "pg_function":
|
|
102
|
+
return FunctionCompilationOutput()
|
|
103
|
+
|
|
104
|
+
if not handler_config.call or not handler_config.call.arg_binding:
|
|
105
|
+
return FunctionCompilationOutput()
|
|
106
|
+
|
|
107
|
+
arg_binding = handler_config.call.arg_binding
|
|
108
|
+
if not arg_binding.mapping or not arg_binding.type_name:
|
|
109
|
+
return FunctionCompilationOutput()
|
|
110
|
+
|
|
111
|
+
function_name = handler_config.function_ref
|
|
112
|
+
if not function_name:
|
|
113
|
+
return FunctionCompilationOutput()
|
|
114
|
+
|
|
115
|
+
mapping_nodes = self._build_nodes(arg_binding.mapping)
|
|
116
|
+
if not mapping_nodes:
|
|
117
|
+
return FunctionCompilationOutput()
|
|
118
|
+
|
|
119
|
+
class_name = compile_function_class_name(
|
|
120
|
+
entity_model.id,
|
|
121
|
+
prefx_class_name,
|
|
122
|
+
[],
|
|
123
|
+
operation,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
field_bindings: Dict[str, str] = {}
|
|
127
|
+
relation_bindings: Dict[str, FunctionRelationBinding] = {}
|
|
128
|
+
|
|
129
|
+
class_spec = self._build_class_spec(
|
|
130
|
+
mapping_nodes,
|
|
131
|
+
entity_model,
|
|
132
|
+
properties_structure,
|
|
133
|
+
function_name,
|
|
134
|
+
class_name,
|
|
135
|
+
arg_binding.type_name,
|
|
136
|
+
path=[],
|
|
137
|
+
operation=operation,
|
|
138
|
+
field_bindings=field_bindings,
|
|
139
|
+
relation_bindings=relation_bindings,
|
|
140
|
+
prefx_class_name=prefx_class_name,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
module_ast, has_relations, needs_any = self._build_module_ast(
|
|
144
|
+
class_spec, operation
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if not module_ast:
|
|
148
|
+
return FunctionCompilationOutput()
|
|
149
|
+
|
|
150
|
+
imports = self._build_imports(operation, has_relations, needs_any)
|
|
151
|
+
module = ast.Module(body=imports + module_ast, type_ignores=[])
|
|
152
|
+
module = ast.fix_missing_locations(module)
|
|
153
|
+
code = ast.unparse(module)
|
|
154
|
+
code = black.format_str(code, mode=black.FileMode())
|
|
155
|
+
|
|
156
|
+
return FunctionCompilationOutput(
|
|
157
|
+
class_name=class_name,
|
|
158
|
+
code=code,
|
|
159
|
+
field_bindings=field_bindings,
|
|
160
|
+
relation_bindings=relation_bindings,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def _build_nodes(self, mappings: Sequence[HandlerMapping]) -> list[_MappingNode]:
|
|
164
|
+
nodes: list[_MappingNode] = []
|
|
165
|
+
used_names: Dict[str, int] = {}
|
|
166
|
+
|
|
167
|
+
for entry in mappings or []:
|
|
168
|
+
if not entry.attr:
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
python_name = self._sanitize_identifier(entry.attr)
|
|
172
|
+
python_name = self._ensure_unique_name(python_name, used_names)
|
|
173
|
+
dto_field = self._extract_dto_field(entry.from_)
|
|
174
|
+
|
|
175
|
+
node = _MappingNode(
|
|
176
|
+
attr=entry.attr,
|
|
177
|
+
python_name=python_name,
|
|
178
|
+
dto_field=dto_field,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if entry.mapping:
|
|
182
|
+
pg_type, is_array = self._parse_type_declaration(entry.as_)
|
|
183
|
+
node.pg_type = pg_type
|
|
184
|
+
node.is_array = is_array
|
|
185
|
+
node.children = self._build_nodes(entry.mapping)
|
|
186
|
+
|
|
187
|
+
nodes.append(node)
|
|
188
|
+
|
|
189
|
+
return nodes
|
|
190
|
+
|
|
191
|
+
def _build_class_spec(
|
|
192
|
+
self,
|
|
193
|
+
nodes: Sequence[_MappingNode],
|
|
194
|
+
entity_model: EntityModelBase,
|
|
195
|
+
properties_structure: PropertiesCompilerStructure,
|
|
196
|
+
function_name: str,
|
|
197
|
+
class_name: str,
|
|
198
|
+
type_name: str,
|
|
199
|
+
path: list[str],
|
|
200
|
+
operation: str,
|
|
201
|
+
field_bindings: Dict[str, str],
|
|
202
|
+
relation_bindings: Dict[str, FunctionRelationBinding],
|
|
203
|
+
prefx_class_name: str,
|
|
204
|
+
) -> _ClassSpec:
|
|
205
|
+
fields: list[_FieldSpec] = []
|
|
206
|
+
children_specs: list[_ClassSpec] = []
|
|
207
|
+
|
|
208
|
+
for node in nodes:
|
|
209
|
+
if node.children:
|
|
210
|
+
if not node.pg_type:
|
|
211
|
+
raise ValueError(
|
|
212
|
+
f"O campo '{node.attr}' possui sub-mapeamento, porém o tipo Postgres não foi informado em 'as'."
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
child_class_name = compile_function_class_name(
|
|
216
|
+
entity_model.id,
|
|
217
|
+
prefx_class_name,
|
|
218
|
+
path + [node.python_name],
|
|
219
|
+
operation,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
child_spec = self._build_class_spec(
|
|
223
|
+
node.children,
|
|
224
|
+
entity_model,
|
|
225
|
+
properties_structure,
|
|
226
|
+
function_name,
|
|
227
|
+
child_class_name,
|
|
228
|
+
node.pg_type,
|
|
229
|
+
path + [node.python_name],
|
|
230
|
+
operation,
|
|
231
|
+
field_bindings,
|
|
232
|
+
relation_bindings,
|
|
233
|
+
prefx_class_name,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
children_specs.append(child_spec)
|
|
237
|
+
fields.append(
|
|
238
|
+
_FieldSpec(
|
|
239
|
+
name=node.python_name,
|
|
240
|
+
annotation=self._relation_annotation(
|
|
241
|
+
child_class_name, node.is_array
|
|
242
|
+
),
|
|
243
|
+
type_field_name=(
|
|
244
|
+
node.attr if node.python_name != node.attr else None
|
|
245
|
+
),
|
|
246
|
+
relation_class=child_class_name,
|
|
247
|
+
is_relation=True,
|
|
248
|
+
is_list=node.is_array,
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
if node.dto_field:
|
|
253
|
+
relation_bindings[node.dto_field] = FunctionRelationBinding(
|
|
254
|
+
field_name=node.python_name,
|
|
255
|
+
function_type_class=child_class_name,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
continue
|
|
259
|
+
|
|
260
|
+
annotation = self._resolve_annotation(node, properties_structure)
|
|
261
|
+
fields.append(
|
|
262
|
+
_FieldSpec(
|
|
263
|
+
name=node.python_name,
|
|
264
|
+
annotation=annotation,
|
|
265
|
+
type_field_name=(
|
|
266
|
+
node.attr if node.python_name != node.attr else None
|
|
267
|
+
),
|
|
268
|
+
relation_class=None,
|
|
269
|
+
is_relation=False,
|
|
270
|
+
is_list=False,
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
if node.dto_field and node.dto_field != node.python_name:
|
|
275
|
+
field_bindings[node.dto_field] = node.python_name
|
|
276
|
+
|
|
277
|
+
return _ClassSpec(
|
|
278
|
+
name=class_name,
|
|
279
|
+
type_name=type_name,
|
|
280
|
+
function_name=function_name,
|
|
281
|
+
fields=fields,
|
|
282
|
+
children=children_specs,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def _build_module_ast(
|
|
286
|
+
self,
|
|
287
|
+
spec: _ClassSpec,
|
|
288
|
+
operation: str,
|
|
289
|
+
) -> tuple[list[ast.stmt], bool, bool]:
|
|
290
|
+
class_defs: list[ast.stmt] = []
|
|
291
|
+
has_relation = False
|
|
292
|
+
needs_any = False
|
|
293
|
+
|
|
294
|
+
for child in spec.children:
|
|
295
|
+
child_defs, child_has_relation, child_needs_any = self._build_module_ast(
|
|
296
|
+
child, operation
|
|
297
|
+
)
|
|
298
|
+
class_defs.extend(child_defs)
|
|
299
|
+
has_relation = has_relation or child_has_relation
|
|
300
|
+
needs_any = needs_any or child_needs_any
|
|
301
|
+
|
|
302
|
+
body: list[ast.stmt] = []
|
|
303
|
+
for field in spec.fields:
|
|
304
|
+
annotation_expr = self._build_annotation_expr(field.annotation, field)
|
|
305
|
+
descriptor_call = self._build_descriptor_call(field)
|
|
306
|
+
body.append(
|
|
307
|
+
ast.AnnAssign(
|
|
308
|
+
target=ast.Name(id=field.name, ctx=ast.Store()),
|
|
309
|
+
annotation=annotation_expr,
|
|
310
|
+
value=descriptor_call,
|
|
311
|
+
simple=1,
|
|
312
|
+
)
|
|
313
|
+
)
|
|
314
|
+
if field.is_relation:
|
|
315
|
+
has_relation = True
|
|
316
|
+
if field.annotation == "Any":
|
|
317
|
+
needs_any = True
|
|
318
|
+
|
|
319
|
+
if not body:
|
|
320
|
+
body = [ast.Pass()]
|
|
321
|
+
|
|
322
|
+
decorator_name = (
|
|
323
|
+
"InsertFunctionType" if operation == "insert" else "UpdateFunctionType"
|
|
324
|
+
)
|
|
325
|
+
base_class = (
|
|
326
|
+
"InsertFunctionTypeBase"
|
|
327
|
+
if operation == "insert"
|
|
328
|
+
else "UpdateFunctionTypeBase"
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
decorator = ast.Call(
|
|
332
|
+
func=ast.Name(id=decorator_name, ctx=ast.Load()),
|
|
333
|
+
args=[],
|
|
334
|
+
keywords=[
|
|
335
|
+
ast.keyword(arg="type_name", value=ast.Constant(value=spec.type_name)),
|
|
336
|
+
ast.keyword(
|
|
337
|
+
arg="function_name", value=ast.Constant(value=spec.function_name)
|
|
338
|
+
),
|
|
339
|
+
],
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
class_defs.append(
|
|
343
|
+
ast.ClassDef(
|
|
344
|
+
name=spec.name,
|
|
345
|
+
bases=[ast.Name(id=base_class, ctx=ast.Load())],
|
|
346
|
+
keywords=[],
|
|
347
|
+
decorator_list=[decorator],
|
|
348
|
+
body=body,
|
|
349
|
+
)
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
return class_defs, has_relation, needs_any
|
|
353
|
+
|
|
354
|
+
def _build_imports(
|
|
355
|
+
self, operation: str, has_relation_fields: bool, needs_any: bool
|
|
356
|
+
) -> list[ast.stmt]:
|
|
357
|
+
imports: list[ast.stmt] = [
|
|
358
|
+
ast.Import(names=[ast.alias(name="datetime", asname=None)]),
|
|
359
|
+
ast.Import(names=[ast.alias(name="uuid", asname=None)]),
|
|
360
|
+
]
|
|
361
|
+
|
|
362
|
+
if needs_any:
|
|
363
|
+
imports.append(
|
|
364
|
+
ast.ImportFrom(
|
|
365
|
+
module="typing",
|
|
366
|
+
names=[ast.alias(name="Any", asname=None)],
|
|
367
|
+
level=0,
|
|
368
|
+
)
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
decorator_module = (
|
|
372
|
+
"nsj_rest_lib.decorator.insert_function_type"
|
|
373
|
+
if operation == "insert"
|
|
374
|
+
else "nsj_rest_lib.decorator.update_function_type"
|
|
375
|
+
)
|
|
376
|
+
decorator_name = (
|
|
377
|
+
"InsertFunctionType" if operation == "insert" else "UpdateFunctionType"
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
base_module = "nsj_rest_lib.entity.function_type_base"
|
|
381
|
+
base_name = (
|
|
382
|
+
"InsertFunctionTypeBase"
|
|
383
|
+
if operation == "insert"
|
|
384
|
+
else "UpdateFunctionTypeBase"
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
imports.extend(
|
|
388
|
+
[
|
|
389
|
+
ast.ImportFrom(
|
|
390
|
+
module=decorator_module,
|
|
391
|
+
names=[ast.alias(name=decorator_name, asname=None)],
|
|
392
|
+
level=0,
|
|
393
|
+
),
|
|
394
|
+
ast.ImportFrom(
|
|
395
|
+
module="nsj_rest_lib.descriptor.function_field",
|
|
396
|
+
names=[ast.alias(name="FunctionField", asname=None)],
|
|
397
|
+
level=0,
|
|
398
|
+
),
|
|
399
|
+
ast.ImportFrom(
|
|
400
|
+
module=base_module,
|
|
401
|
+
names=[ast.alias(name=base_name, asname=None)],
|
|
402
|
+
level=0,
|
|
403
|
+
),
|
|
404
|
+
]
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
if has_relation_fields:
|
|
408
|
+
imports.append(
|
|
409
|
+
ast.ImportFrom(
|
|
410
|
+
module="nsj_rest_lib.descriptor.function_relation_field",
|
|
411
|
+
names=[ast.alias(name="FunctionRelationField", asname=None)],
|
|
412
|
+
level=0,
|
|
413
|
+
)
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
return imports
|
|
417
|
+
|
|
418
|
+
def _build_annotation_expr(
|
|
419
|
+
self, annotation: str, field: _FieldSpec
|
|
420
|
+
) -> ast.expr:
|
|
421
|
+
if field.is_relation and field.is_list:
|
|
422
|
+
return ast.Subscript(
|
|
423
|
+
value=ast.Name(id="list", ctx=ast.Load()),
|
|
424
|
+
slice=ast.Name(id=field.relation_class, ctx=ast.Load()),
|
|
425
|
+
ctx=ast.Load(),
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
if field.is_relation:
|
|
429
|
+
return ast.Name(id=field.relation_class, ctx=ast.Load())
|
|
430
|
+
|
|
431
|
+
if annotation == "Any":
|
|
432
|
+
return ast.Name(id="Any", ctx=ast.Load())
|
|
433
|
+
|
|
434
|
+
if "." in annotation:
|
|
435
|
+
parts = annotation.split(".")
|
|
436
|
+
expr = ast.Name(id=parts[0], ctx=ast.Load())
|
|
437
|
+
for part in parts[1:]:
|
|
438
|
+
expr = ast.Attribute(value=expr, attr=part, ctx=ast.Load())
|
|
439
|
+
return expr
|
|
440
|
+
|
|
441
|
+
return ast.Name(id=annotation, ctx=ast.Load())
|
|
442
|
+
|
|
443
|
+
def _build_descriptor_call(self, field: _FieldSpec) -> ast.Call:
|
|
444
|
+
descriptor_cls = (
|
|
445
|
+
"FunctionRelationField" if field.is_relation else "FunctionField"
|
|
446
|
+
)
|
|
447
|
+
keywords: list[ast.keyword] = []
|
|
448
|
+
if field.type_field_name:
|
|
449
|
+
keywords.append(
|
|
450
|
+
ast.keyword(
|
|
451
|
+
arg="type_field_name",
|
|
452
|
+
value=ast.Constant(value=field.type_field_name),
|
|
453
|
+
)
|
|
454
|
+
)
|
|
455
|
+
return ast.Call(
|
|
456
|
+
func=ast.Name(id=descriptor_cls, ctx=ast.Load()),
|
|
457
|
+
args=[],
|
|
458
|
+
keywords=keywords,
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
def _resolve_annotation(
|
|
462
|
+
self,
|
|
463
|
+
node: _MappingNode,
|
|
464
|
+
properties_structure: PropertiesCompilerStructure,
|
|
465
|
+
) -> str:
|
|
466
|
+
dto_field = node.dto_field
|
|
467
|
+
if not dto_field:
|
|
468
|
+
return "Any"
|
|
469
|
+
|
|
470
|
+
prop = properties_structure.properties.get(dto_field) if properties_structure else None
|
|
471
|
+
if not prop or not isinstance(prop.type, PrimitiveTypes):
|
|
472
|
+
return "Any"
|
|
473
|
+
|
|
474
|
+
return TypeUtil.property_type_to_python_type(prop.type)
|
|
475
|
+
|
|
476
|
+
# ------------------------------------------------------------------ #
|
|
477
|
+
# Utility helpers
|
|
478
|
+
# ------------------------------------------------------------------ #
|
|
479
|
+
|
|
480
|
+
def _sanitize_identifier(self, name: str) -> str:
|
|
481
|
+
candidate = CompilerStrUtil.to_snake_case(name)
|
|
482
|
+
candidate = re.sub(r"\W+", "_", candidate)
|
|
483
|
+
if not candidate:
|
|
484
|
+
candidate = "field"
|
|
485
|
+
if candidate[0].isdigit():
|
|
486
|
+
candidate = f"_{candidate}"
|
|
487
|
+
return candidate
|
|
488
|
+
|
|
489
|
+
def _ensure_unique_name(self, name: str, used: Dict[str, int]) -> str:
|
|
490
|
+
counter = used.setdefault(name, 0)
|
|
491
|
+
if counter == 0:
|
|
492
|
+
used[name] = 1
|
|
493
|
+
return name
|
|
494
|
+
|
|
495
|
+
while True:
|
|
496
|
+
candidate = f"{name}_{counter}"
|
|
497
|
+
counter += 1
|
|
498
|
+
if candidate not in used:
|
|
499
|
+
used[name] = counter
|
|
500
|
+
used[candidate] = 1
|
|
501
|
+
return candidate
|
|
502
|
+
|
|
503
|
+
def _extract_dto_field(self, source: Optional[str]) -> Optional[str]:
|
|
504
|
+
if not source:
|
|
505
|
+
return None
|
|
506
|
+
|
|
507
|
+
if source.startswith("body."):
|
|
508
|
+
segment = source[5:].split(".", 1)[0]
|
|
509
|
+
return CompilerStrUtil.to_snake_case(segment)
|
|
510
|
+
|
|
511
|
+
return None
|
|
512
|
+
|
|
513
|
+
def _parse_type_declaration(self, declaration: Optional[str]) -> tuple[str, bool]:
|
|
514
|
+
if not declaration:
|
|
515
|
+
return (None, False)
|
|
516
|
+
|
|
517
|
+
declaration = declaration.strip()
|
|
518
|
+
if declaration.endswith("[]"):
|
|
519
|
+
return (declaration[:-2], True)
|
|
520
|
+
return (declaration, False)
|
|
521
|
+
|
|
522
|
+
def _relation_annotation(self, class_name: str, is_array: bool) -> str:
|
|
523
|
+
if is_array:
|
|
524
|
+
return f"list[{class_name}]"
|
|
525
|
+
return class_name
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def inject_function_bindings(
|
|
529
|
+
target_config: FunctionBindingConfig,
|
|
530
|
+
insert_output: FunctionCompilationOutput,
|
|
531
|
+
update_output: FunctionCompilationOutput,
|
|
532
|
+
) -> FunctionBindingConfig:
|
|
533
|
+
"""
|
|
534
|
+
Popula um FunctionBindingConfig a partir dos resultados de compilação das
|
|
535
|
+
funções de insert/update.
|
|
536
|
+
"""
|
|
537
|
+
|
|
538
|
+
for dto_field, function_field in insert_output.field_bindings.items():
|
|
539
|
+
target_config.insert_fields[dto_field] = function_field
|
|
540
|
+
|
|
541
|
+
for dto_field, relation in insert_output.relation_bindings.items():
|
|
542
|
+
target_config.insert_relations[dto_field] = relation
|
|
543
|
+
|
|
544
|
+
for dto_field, function_field in update_output.field_bindings.items():
|
|
545
|
+
target_config.update_fields[dto_field] = function_field
|
|
546
|
+
|
|
547
|
+
for dto_field, relation in update_output.relation_bindings.items():
|
|
548
|
+
target_config.update_relations[dto_field] = relation
|
|
549
|
+
|
|
550
|
+
return target_config
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Dict, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class FunctionRelationBinding:
|
|
9
|
+
"""
|
|
10
|
+
Representa o vínculo entre um campo do DTO e um campo declarado
|
|
11
|
+
no FunctionType correspondente.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
field_name: str
|
|
15
|
+
function_type_class: Optional[str] = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class FunctionBindingConfig:
|
|
20
|
+
"""
|
|
21
|
+
Estrutura usada pelo PropertyCompiler para aplicar metadados de
|
|
22
|
+
binding entre DTOs e FunctionTypes (insert/update).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
insert_fields: Dict[str, str] = field(default_factory=dict)
|
|
26
|
+
update_fields: Dict[str, str] = field(default_factory=dict)
|
|
27
|
+
insert_relations: Dict[str, FunctionRelationBinding] = field(
|
|
28
|
+
default_factory=dict
|
|
29
|
+
)
|
|
30
|
+
update_relations: Dict[str, FunctionRelationBinding] = field(
|
|
31
|
+
default_factory=dict
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def merge_insert_field(self, dto_field: str, function_field: str) -> None:
|
|
35
|
+
if not dto_field or not function_field:
|
|
36
|
+
return
|
|
37
|
+
self.insert_fields[dto_field] = function_field
|
|
38
|
+
|
|
39
|
+
def merge_update_field(self, dto_field: str, function_field: str) -> None:
|
|
40
|
+
if not dto_field or not function_field:
|
|
41
|
+
return
|
|
42
|
+
self.update_fields[dto_field] = function_field
|
|
43
|
+
|
|
44
|
+
def merge_insert_relation(
|
|
45
|
+
self, dto_field: str, binding: FunctionRelationBinding
|
|
46
|
+
) -> None:
|
|
47
|
+
if not dto_field or not binding:
|
|
48
|
+
return
|
|
49
|
+
self.insert_relations[dto_field] = binding
|
|
50
|
+
|
|
51
|
+
def merge_update_relation(
|
|
52
|
+
self, dto_field: str, binding: FunctionRelationBinding
|
|
53
|
+
) -> None:
|
|
54
|
+
if not dto_field or not binding:
|
|
55
|
+
return
|
|
56
|
+
self.update_relations[dto_field] = binding
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class FunctionCompilationOutput:
|
|
61
|
+
"""
|
|
62
|
+
Resultado da compilação de um FunctionType (insert/update).
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
class_name: Optional[str] = None
|
|
66
|
+
code: Optional[str] = None
|
|
67
|
+
field_bindings: Dict[str, str] = field(default_factory=dict)
|
|
68
|
+
relation_bindings: Dict[str, FunctionRelationBinding] = field(
|
|
69
|
+
default_factory=dict
|
|
70
|
+
)
|
nsj_rest_lib2/compiler/model.py
CHANGED
|
@@ -43,3 +43,7 @@ class CompilerResult:
|
|
|
43
43
|
self.api_resource: str | None = None
|
|
44
44
|
self.api_verbs: list[str] | None = None
|
|
45
45
|
self.relations_dependencies: list[RelationDependency] | None = None
|
|
46
|
+
self.insert_function_class_name: str | None = None
|
|
47
|
+
self.source_insert_function: str | None = None
|
|
48
|
+
self.update_function_class_name: str | None = None
|
|
49
|
+
self.source_update_function: str | None = None
|