nsj-rest-lib2 0.0.10__py3-none-any.whl → 0.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.
@@ -1,4 +1,5 @@
1
1
  import re
2
+
2
3
  from typing import Any
3
4
 
4
5
  from nsj_rest_lib2.compiler.compiler_structures import (
@@ -7,8 +8,8 @@ from nsj_rest_lib2.compiler.compiler_structures import (
7
8
  )
8
9
  from nsj_rest_lib2.compiler.dto_compiler import DTOCompiler
9
10
  from nsj_rest_lib2.compiler.edl_model.primitives import REGEX_EXTERNAL_REF
10
- from nsj_rest_lib2.compiler.edl_model.repository_model import RepositoryModel
11
11
  from nsj_rest_lib2.compiler.entity_compiler import EntityCompiler
12
+ from nsj_rest_lib2.compiler.model import CompilerResult
12
13
  from nsj_rest_lib2.compiler.property_compiler import EDLPropertyCompiler
13
14
 
14
15
  from nsj_rest_lib2.compiler.edl_model.entity_model import EntityModel
@@ -26,17 +27,6 @@ from nsj_rest_lib2.settings import get_logger
26
27
  # TODO Criar imagem docker base para as aplicações, usando venv (para poderem atualizar o pydantic)
27
28
 
28
29
 
29
- class CompilerResult:
30
- def __init__(self):
31
- self.dto_class_name: str | None = None
32
- self.dto_code: str | None = None
33
- self.entity_class_name: str | None = None
34
- self.entity_code: str | None = None
35
- self.api_expose: bool | None = None
36
- self.api_resource: str | None = None
37
- self.api_verbs: list[str] | None = None
38
-
39
-
40
30
  class EDLCompiler:
41
31
  def __init__(self) -> None:
42
32
  self._properties_compiler = EDLPropertyCompiler()
@@ -114,6 +104,8 @@ class EDLCompiler:
114
104
  props_pk,
115
105
  enum_classes,
116
106
  related_imports,
107
+ relations_dependencies,
108
+ fixed_filters,
117
109
  ) = self._properties_compiler.compile(
118
110
  properties_structure,
119
111
  map_unique_by_property,
@@ -123,7 +115,11 @@ class EDLCompiler:
123
115
 
124
116
  # Gerando o código do DTO
125
117
  dto_class_name, code_dto = self._dto_compiler.compile(
126
- entity_model, ast_dto_attributes, enum_classes, related_imports
118
+ entity_model,
119
+ ast_dto_attributes,
120
+ enum_classes,
121
+ related_imports,
122
+ fixed_filters,
127
123
  )
128
124
 
129
125
  # Gerando o código da Entity
@@ -142,6 +138,7 @@ class EDLCompiler:
142
138
  compiler_result.api_expose = entity_model.api.expose
143
139
  compiler_result.api_resource = entity_model.api.resource
144
140
  compiler_result.api_verbs = entity_model.api.verbs
141
+ compiler_result.relations_dependencies = relations_dependencies
145
142
 
146
143
  return compiler_result
147
144
 
@@ -244,10 +241,20 @@ class EDLCompiler:
244
241
  def _list_dependencies(self, entity_model: EntityModel) -> list[str]:
245
242
  entities: list[str] = []
246
243
 
244
+ # Adicionando dependências por traits
247
245
  if entity_model.trait_from:
248
246
  entities.append(entity_model.trait_from)
249
247
 
250
- # Populando com as dependências de propriedades de relacionamento 1_N
248
+ # Populando com as dependências de propriedades de relacionamento
249
+ relations = self._list_dependencies_relations(entity_model)
250
+ entities.extend(relations)
251
+
252
+ return entities
253
+
254
+ def _list_dependencies_relations(self, entity_model) -> list[str]:
255
+ entities = []
256
+
257
+ # Relacionamento 1_N
251
258
  for pkey in entity_model.properties:
252
259
  prop = entity_model.properties[pkey]
253
260
 
@@ -3,6 +3,7 @@ import ast
3
3
  import black
4
4
 
5
5
  from nsj_rest_lib2.compiler.edl_model.entity_model import EntityModel
6
+ from nsj_rest_lib2.compiler.edl_model.primitives import BasicTypes
6
7
  from nsj_rest_lib2.compiler.util.str_util import CompilerStrUtil
7
8
  from nsj_rest_lib2.compiler.util.type_naming_util import compile_dto_class_name
8
9
 
@@ -17,6 +18,7 @@ class DTOCompiler:
17
18
  ast_dto_attributes: list[ast.stmt],
18
19
  enum_classes: list[ast.stmt],
19
20
  related_imports: list[tuple[str, str, str]],
21
+ fixed_filters: list[tuple[str, BasicTypes]],
20
22
  ) -> tuple[str, str]:
21
23
  """
22
24
  Compila o código do DTO a partir do AST e retorna o código compilado.
@@ -71,6 +73,18 @@ class DTOCompiler:
71
73
  names=[ast.alias(name="DTOBase", asname=None)],
72
74
  level=0,
73
75
  ),
76
+ # from nsj_rest_lib.descriptor.dto_left_join_field import EntityRelationOwner
77
+ ast.ImportFrom(
78
+ module="nsj_rest_lib.descriptor.dto_left_join_field",
79
+ names=[ast.alias(name="EntityRelationOwner", asname=None)],
80
+ level=0,
81
+ ),
82
+ # from nsj_rest_lib.descriptor.dto_object_field import DTOObjectField
83
+ ast.ImportFrom(
84
+ module="nsj_rest_lib.descriptor.dto_object_field",
85
+ names=[ast.alias(name="DTOObjectField", asname=None)],
86
+ level=0,
87
+ ),
74
88
  ]
75
89
 
76
90
  for import_ in related_imports:
@@ -89,6 +103,19 @@ class DTOCompiler:
89
103
  )
90
104
  )
91
105
 
106
+ # Keywords para tipos usados em fixed_filters
107
+ keywords_decorator_dto = []
108
+ for fixed_filter in fixed_filters:
109
+ keywords_decorator_dto.append(
110
+ ast.keyword(
111
+ arg="fixed_filters",
112
+ value=ast.Dict(
113
+ keys=[ast.Constant(value=fixed_filter[0])],
114
+ values=[ast.Constant(value=fixed_filter[1])],
115
+ ),
116
+ )
117
+ )
118
+
92
119
  # Criando o ast da classe
93
120
  class_name = compile_dto_class_name(entity_model.id)
94
121
  ast_class = ast.ClassDef(
@@ -99,7 +126,7 @@ class DTOCompiler:
99
126
  ast.Call(
100
127
  func=ast.Name(id="DTO", ctx=ast.Load()),
101
128
  args=[],
102
- keywords=[],
129
+ keywords=keywords_decorator_dto,
103
130
  )
104
131
  ],
105
132
  body=ast_dto_attributes,
@@ -1,5 +1,5 @@
1
1
  from pydantic import BaseModel, Field, model_validator
2
- from typing import List, Optional, Literal
2
+ from typing import Optional
3
3
 
4
4
 
5
5
  class ColumnMetaModel(BaseModel):
@@ -1,6 +1,8 @@
1
+ from typing import List, Optional
1
2
  from pydantic import BaseModel, Field
2
3
 
3
4
  from nsj_rest_lib2.compiler.edl_model.primitives import BasicTypes, PropertyType
5
+ from nsj_rest_lib2.compiler.edl_model.property_meta_model import DomainConfigModel
4
6
 
5
7
 
6
8
  class TraitPropertyMetaModel(BaseModel):
@@ -8,3 +10,7 @@ class TraitPropertyMetaModel(BaseModel):
8
10
  value: BasicTypes = Field(
9
11
  ..., description="Valor fixo da propriedade de condicionamento do trait."
10
12
  )
13
+ domain_config: Optional[List[DomainConfigModel]] = Field(
14
+ None,
15
+ description="Lista de valores permitidos.",
16
+ )
@@ -0,0 +1,45 @@
1
+ import uuid
2
+
3
+ from typing import Any
4
+
5
+
6
+ class RelationDependency:
7
+ def __init__(self):
8
+ self.tenant: int | None = None
9
+ self.grupo_empresarial: uuid.UUID | None = None
10
+ self.entity_resource: str | None = None
11
+ self.entity_scope: str | None = None
12
+
13
+ def to_dict(self) -> dict[str, Any]:
14
+ return {
15
+ "tenant": self.tenant,
16
+ "grupo_empresarial": (
17
+ str(self.grupo_empresarial) if self.grupo_empresarial else None
18
+ ),
19
+ "entity_resource": self.entity_resource,
20
+ "entity_scope": self.entity_scope,
21
+ }
22
+
23
+ def from_dict(self, data: dict[str, Any]) -> "RelationDependency":
24
+ self.tenant = data.get("tenant")
25
+ self.grupo_empresarial = (
26
+ uuid.UUID(data["grupo_empresarial"])
27
+ if data.get("grupo_empresarial")
28
+ else None
29
+ )
30
+ self.entity_resource = data.get("entity_resource")
31
+ self.entity_scope = data.get("entity_scope")
32
+
33
+ return self
34
+
35
+
36
+ class CompilerResult:
37
+ def __init__(self):
38
+ self.dto_class_name: str | None = None
39
+ self.dto_code: str | None = None
40
+ self.entity_class_name: str | None = None
41
+ self.entity_code: str | None = None
42
+ self.api_expose: bool | None = None
43
+ self.api_resource: str | None = None
44
+ self.api_verbs: list[str] | None = None
45
+ self.relations_dependencies: list[RelationDependency] | None = None
@@ -7,12 +7,17 @@ from nsj_rest_lib2.compiler.compiler_structures import (
7
7
  )
8
8
  from nsj_rest_lib2.compiler.edl_model.entity_model import EntityModel
9
9
  from nsj_rest_lib2.compiler.edl_model.primitives import (
10
+ BasicTypes,
10
11
  CardinalityTypes,
11
12
  PrimitiveTypes,
12
13
  REGEX_EXTERNAL_REF,
13
14
  REGEX_INTERNAL_REF,
14
15
  )
15
16
  from nsj_rest_lib2.compiler.edl_model.property_meta_model import PropertyMetaModel
17
+ from nsj_rest_lib2.compiler.edl_model.trait_property_meta_model import (
18
+ TraitPropertyMetaModel,
19
+ )
20
+ from nsj_rest_lib2.compiler.model import RelationDependency
16
21
  from nsj_rest_lib2.compiler.util.str_util import CompilerStrUtil
17
22
  from nsj_rest_lib2.compiler.util.type_naming_util import (
18
23
  compile_dto_class_name,
@@ -40,6 +45,8 @@ class EDLPropertyCompiler:
40
45
  list[str],
41
46
  list[ast.stmt],
42
47
  list[tuple[str, str, str]],
48
+ list[RelationDependency],
49
+ list[tuple[str, BasicTypes]],
43
50
  ]:
44
51
 
45
52
  # TODO Criar opção de campo calculado?
@@ -70,6 +77,8 @@ class EDLPropertyCompiler:
70
77
  props_pk = []
71
78
  enum_classes = []
72
79
  related_imports = []
80
+ relations_dependencies = []
81
+ fixed_filters = []
73
82
 
74
83
  if properties_structure.properties is None:
75
84
  return (ast_dto_attributes, ast_entity_attributes, props_pk, enum_classes)
@@ -80,7 +89,7 @@ class EDLPropertyCompiler:
80
89
  # DTO
81
90
  ## Tratando propriedade simples (não array, não object)
82
91
  if isinstance(prop.type, PrimitiveTypes):
83
- self.compile_simple_property(
92
+ self._compile_simple_property(
84
93
  properties_structure,
85
94
  map_unique_by_property,
86
95
  entity_model,
@@ -94,119 +103,48 @@ class EDLPropertyCompiler:
94
103
 
95
104
  elif isinstance(prop.type, str):
96
105
  external_match = re.match(REGEX_EXTERNAL_REF, prop.type)
106
+ internal_match = re.match(REGEX_INTERNAL_REF, prop.type)
97
107
 
98
108
  if external_match:
99
109
  # Resolvendo o id da entidade
100
110
  related_entity_id = external_match.group(2)
101
-
102
- # Resolvendo o nome das classes de DTO e Entity
103
- related_dto_class_name = compile_dto_class_name(related_entity_id)
104
- related_entity_class_name = compile_entity_class_name(
105
- related_entity_id
106
- )
107
-
108
- # Resolvendo o caminho do import
109
111
  related_entity_key = external_match.group(0)
110
112
 
111
- related_entity = entity_models.get(related_entity_key)
112
- if not related_entity:
113
- raise Exception(
114
- f"Entidade '{entity_model.id}' possui uma referência externa para uma entidade inexistente: '{related_entity_key}', por meio da propriedade: '{pkey}'."
115
- )
116
-
117
- tenant = related_entity.tenant
118
- grupo_empresarial = related_entity.grupo_empresarial
119
- grupo_key, tenant_key, default_key = compile_namespace_keys(
120
- tenant, grupo_empresarial
113
+ self._compile_external_relation(
114
+ related_entity_id,
115
+ related_entity_key,
116
+ entity_model,
117
+ entity_models,
118
+ properties_structure,
119
+ ast_dto_attributes,
120
+ related_imports,
121
+ relations_dependencies,
122
+ pkey,
123
+ prop,
121
124
  )
122
125
 
123
- if (
124
- tenant
125
- and tenant != 0
126
- and grupo_empresarial
127
- and grupo_empresarial != "00000000-0000-0000-0000-000000000000"
128
- ):
129
- related_import = grupo_key
130
- elif tenant and tenant != 0:
131
- related_import = tenant_key
132
- else:
133
- related_import = default_key
134
-
135
- related_imports.append(
136
- (
137
- related_import,
138
- related_dto_class_name,
139
- related_entity_class_name,
140
- )
141
- )
142
-
143
- # Instanciando o ast
144
- if prop.cardinality == CardinalityTypes.C1_N:
145
- # Para relacionamentos 1_N
146
- keywords = [
147
- ast.keyword(
148
- arg="dto_type",
149
- value=ast.Name(
150
- id=related_dto_class_name, ctx=ast.Load()
151
- ),
152
- ),
153
- ast.keyword(
154
- arg="entity_type",
155
- value=ast.Name(
156
- id=related_entity_class_name, ctx=ast.Load()
157
- ),
158
- ),
159
- ]
160
-
161
- # Resolvendo a coluna usada no relacionamento
162
- if (
163
- not properties_structure.entity_properties
164
- or pkey not in properties_structure.entity_properties
165
- or not properties_structure.entity_properties[
166
- pkey
167
- ].relation_column
168
- ):
169
- raise Exception(
170
- f"Propriedade '{pkey}' possui um relacionamento, mas nenhuma coluna de relacioanamento foi apontada na propriedade correspondente no repository."
171
- )
172
-
173
- relation_column = properties_structure.entity_properties[
174
- pkey
175
- ].relation_column
176
-
177
- keywords.append(
178
- ast.keyword(
179
- arg="related_entity_field",
180
- value=ast.Constant(value=relation_column),
181
- )
182
- )
183
-
184
- ast_attr = ast.AnnAssign(
185
- target=ast.Name(
186
- id=CompilerStrUtil.to_snake_case(pkey), ctx=ast.Store()
187
- ),
188
- annotation=ast.Name(
189
- id="list",
190
- ctx=ast.Load(),
191
- ),
192
- value=ast.Call(
193
- func=ast.Name(id="DTOListField", ctx=ast.Load()),
194
- args=[],
195
- keywords=keywords,
196
- ),
197
- simple=1,
198
- )
199
-
200
- ast_dto_attributes.append(ast_attr)
201
- else:
202
- # TODO
203
- pass
204
-
205
- elif re.match(REGEX_INTERNAL_REF, prop.type):
126
+ elif internal_match:
206
127
  # TODO
207
128
  pass
208
129
  else:
209
- raise Exception(f"Tipo de propriedade não suportado: {prop.type}")
130
+ raise Exception(
131
+ f"Tipo da propriedade '{pkey}' não suportado: {prop.type}"
132
+ )
133
+
134
+ for pkey in properties_structure.trait_properties:
135
+ prop = properties_structure.trait_properties[pkey]
136
+
137
+ self._compile_trait_property(
138
+ properties_structure,
139
+ map_unique_by_property,
140
+ pkey,
141
+ prop,
142
+ ast_dto_attributes,
143
+ ast_entity_attributes,
144
+ fixed_filters,
145
+ entity_model,
146
+ enum_classes,
147
+ )
210
148
 
211
149
  return (
212
150
  ast_dto_attributes,
@@ -214,9 +152,411 @@ class EDLPropertyCompiler:
214
152
  props_pk,
215
153
  enum_classes,
216
154
  related_imports,
155
+ relations_dependencies,
156
+ fixed_filters,
217
157
  )
218
158
 
219
- def compile_simple_property(
159
+ def _compile_trait_property(
160
+ self,
161
+ properties_structure: PropertiesCompilerStructure,
162
+ map_unique_by_property: dict[str, IndexCompilerStructure],
163
+ pkey: str,
164
+ prop: TraitPropertyMetaModel,
165
+ ast_dto_attributes: list[ast.stmt],
166
+ ast_entity_attributes: list[ast.stmt],
167
+ fixed_filters: list[tuple[str, BasicTypes]],
168
+ entity_model: EntityModel,
169
+ enum_classes: list[ast.stmt],
170
+ ):
171
+ enum_class_name = None
172
+ keywords = []
173
+
174
+ if (
175
+ properties_structure.main_properties
176
+ and pkey in properties_structure.main_properties
177
+ ):
178
+ keywords.append(ast.keyword(arg="resume", value=ast.Constant(True)))
179
+
180
+ if properties_structure.required and pkey in properties_structure.required:
181
+ keywords.append(ast.keyword(arg="not_null", value=ast.Constant(True)))
182
+
183
+ if (
184
+ properties_structure.partition_data
185
+ and pkey in properties_structure.partition_data
186
+ ):
187
+ keywords.append(ast.keyword(arg="partition_data", value=ast.Constant(True)))
188
+
189
+ if pkey in map_unique_by_property:
190
+ unique = map_unique_by_property[pkey].index_model
191
+ keywords.append(
192
+ ast.keyword(
193
+ arg="unique",
194
+ value=ast.Constant(unique.name),
195
+ )
196
+ )
197
+
198
+ if (
199
+ properties_structure.search_properties
200
+ and pkey in properties_structure.search_properties
201
+ ):
202
+ keywords.append(ast.keyword(arg="search", value=ast.Constant(True)))
203
+ else:
204
+ keywords.append(ast.keyword(arg="search", value=ast.Constant(False)))
205
+
206
+ if (
207
+ properties_structure.metric_label
208
+ and pkey in properties_structure.metric_label
209
+ ):
210
+ keywords.append(ast.keyword(arg="metric_label", value=ast.Constant(True)))
211
+
212
+ if prop.type == PrimitiveTypes.CPF:
213
+ keywords.append(
214
+ ast.keyword(
215
+ arg="validator",
216
+ value=ast.Attribute(
217
+ value=ast.Call(
218
+ func=ast.Name(id="DTOFieldValidators", ctx=ast.Load()),
219
+ args=[],
220
+ keywords=[],
221
+ ),
222
+ attr="validate_cpf",
223
+ ctx=ast.Load(),
224
+ ),
225
+ )
226
+ )
227
+ elif prop.type == PrimitiveTypes.CNPJ:
228
+ keywords.append(
229
+ ast.keyword(
230
+ arg="validator",
231
+ value=ast.Attribute(
232
+ value=ast.Call(
233
+ func=ast.Name(id="DTOFieldValidators", ctx=ast.Load()),
234
+ args=[],
235
+ keywords=[],
236
+ ),
237
+ attr="validate_cnpj",
238
+ ctx=ast.Load(),
239
+ ),
240
+ )
241
+ )
242
+ elif prop.type == PrimitiveTypes.CPF_CNPJ:
243
+ keywords.append(
244
+ ast.keyword(
245
+ arg="validator",
246
+ value=ast.Attribute(
247
+ value=ast.Call(
248
+ func=ast.Name(id="DTOFieldValidators", ctx=ast.Load()),
249
+ args=[],
250
+ keywords=[],
251
+ ),
252
+ attr="validate_cpf_or_cnpj",
253
+ ctx=ast.Load(),
254
+ ),
255
+ )
256
+ )
257
+ elif prop.type == PrimitiveTypes.EMAIL:
258
+ keywords.append(
259
+ ast.keyword(
260
+ arg="validator",
261
+ value=ast.Attribute(
262
+ value=ast.Call(
263
+ func=ast.Name(id="DTOFieldValidators", ctx=ast.Load()),
264
+ args=[],
265
+ keywords=[],
266
+ ),
267
+ attr="validate_email",
268
+ ctx=ast.Load(),
269
+ ),
270
+ )
271
+ )
272
+
273
+ # Trtando de uma definição de enum
274
+ if prop.domain_config:
275
+ result = self._compile_domain_config(pkey, prop, entity_model)
276
+ if not result:
277
+ raise Exception(f"Erro desconhecido ao compilar a propriedade {pkey}")
278
+
279
+ enum_class_name, ast_enum_class = result
280
+ enum_classes.append(ast_enum_class)
281
+
282
+ # Resolvendo o nome da propriedade no Entity
283
+ if (
284
+ properties_structure.entity_properties
285
+ and pkey in properties_structure.entity_properties
286
+ ):
287
+ entity_field_name = properties_structure.entity_properties[pkey].column
288
+ else:
289
+ entity_field_name = pkey
290
+
291
+ # Escrevendo, se necessário, o alias para o nome da entity
292
+ if entity_field_name != pkey:
293
+ keywords.append(
294
+ ast.keyword(
295
+ arg="entity_field",
296
+ value=ast.Constant(value=entity_field_name),
297
+ )
298
+ )
299
+
300
+ # Instanciando o atributo AST
301
+ if not isinstance(prop.type, PrimitiveTypes):
302
+ raise Exception(
303
+ f"Tipo da trait_property '{pkey}' não suportado: {prop.type} (deveria ser um Tipo Primitivo)"
304
+ )
305
+
306
+ # Instanciando o atributo AST
307
+ if enum_class_name:
308
+ prop_type = enum_class_name
309
+ else:
310
+ prop_type = TypeUtil.property_type_to_python_type(prop.type)
311
+
312
+ ast_attr = ast.AnnAssign(
313
+ target=ast.Name(id=CompilerStrUtil.to_snake_case(pkey), ctx=ast.Store()),
314
+ annotation=ast.Name(
315
+ id=prop_type,
316
+ ctx=ast.Load(),
317
+ ),
318
+ value=ast.Call(
319
+ func=ast.Name(id="DTOField", ctx=ast.Load()),
320
+ args=[],
321
+ keywords=keywords,
322
+ ),
323
+ simple=1,
324
+ )
325
+
326
+ ast_dto_attributes.append(ast_attr)
327
+
328
+ # Entity
329
+ ast_entity_attr = ast.AnnAssign(
330
+ target=ast.Name(
331
+ id=CompilerStrUtil.to_snake_case(entity_field_name),
332
+ ctx=ast.Store(),
333
+ ),
334
+ annotation=ast.Name(
335
+ id=TypeUtil.property_type_to_python_type(prop.type),
336
+ ctx=ast.Load(),
337
+ ),
338
+ value=ast.Constant(value=None),
339
+ simple=1,
340
+ )
341
+
342
+ ast_entity_attributes.append(ast_entity_attr)
343
+
344
+ # Guardando como um fixed_filter
345
+ # TODO Pernsar em validações para esse value (se está de acordo com o tipo ou enum)
346
+ fixed_filters.append((pkey, prop.value))
347
+
348
+ def _compile_external_relation(
349
+ self,
350
+ related_entity_id: str,
351
+ related_entity_key: str,
352
+ entity_model: EntityModel,
353
+ entity_models: dict[str, EntityModel],
354
+ properties_structure: PropertiesCompilerStructure,
355
+ ast_dto_attributes: list[ast.stmt],
356
+ related_imports: list[tuple[str, str, str]],
357
+ relations_dependencies: list[RelationDependency],
358
+ pkey: str,
359
+ prop: PropertyMetaModel,
360
+ ):
361
+ # Resolvendo o nome das classes de DTO e Entity
362
+ related_dto_class_name = compile_dto_class_name(related_entity_id)
363
+ related_entity_class_name = compile_entity_class_name(related_entity_id)
364
+
365
+ # Resolvendo o caminho do import
366
+ related_entity = entity_models.get(related_entity_key)
367
+ if not related_entity:
368
+ raise Exception(
369
+ f"Entidade '{entity_model.id}' possui uma referência externa para uma entidade inexistente: '{related_entity_key}', por meio da propriedade: '{pkey}'."
370
+ )
371
+
372
+ tenant = related_entity.tenant
373
+ grupo_empresarial = related_entity.grupo_empresarial
374
+ grupo_key, tenant_key, default_key = compile_namespace_keys(
375
+ tenant, grupo_empresarial
376
+ )
377
+
378
+ if (
379
+ tenant
380
+ and tenant != 0
381
+ and grupo_empresarial
382
+ and grupo_empresarial != "00000000-0000-0000-0000-000000000000"
383
+ ):
384
+ related_import = grupo_key
385
+ elif tenant and tenant != 0:
386
+ related_import = tenant_key
387
+ else:
388
+ related_import = default_key
389
+
390
+ related_imports.append(
391
+ (
392
+ related_import,
393
+ related_dto_class_name,
394
+ related_entity_class_name,
395
+ )
396
+ )
397
+
398
+ # Gravando a dependência de relacionamento
399
+ relation_dependency = RelationDependency()
400
+ relation_dependency.entity_resource = related_entity.api.resource
401
+ relation_dependency.entity_scope = related_entity.escopo
402
+ relation_dependency.tenant = tenant
403
+ relation_dependency.grupo_empresarial = grupo_empresarial
404
+ relations_dependencies.append(relation_dependency)
405
+
406
+ # Instanciando o ast
407
+ if prop.cardinality == CardinalityTypes.C1_N:
408
+ # Para relacionamentos 1_N
409
+ self._build_ast_1_N(
410
+ properties_structure,
411
+ ast_dto_attributes,
412
+ pkey,
413
+ related_dto_class_name,
414
+ related_entity_class_name,
415
+ )
416
+
417
+ elif prop.cardinality == CardinalityTypes.C1_1:
418
+ self._build_ast_1_1(
419
+ properties_structure,
420
+ ast_dto_attributes,
421
+ pkey,
422
+ related_dto_class_name,
423
+ related_entity_class_name,
424
+ )
425
+
426
+ elif prop.cardinality == CardinalityTypes.CN_N:
427
+ # TODO
428
+ pass
429
+ else:
430
+ raise Exception(
431
+ f"Propriedade '{pkey}' da entidade '{entity_model.id}' possui cardinalidade inválida ou não suportada: {prop.cardinality}"
432
+ )
433
+
434
+ def _build_ast_1_N(
435
+ self,
436
+ properties_structure: PropertiesCompilerStructure,
437
+ ast_dto_attributes: list[ast.stmt],
438
+ pkey: str,
439
+ related_dto_class_name: str,
440
+ related_entity_class_name: str,
441
+ ):
442
+ # Propriedade do property descriptor
443
+ keywords = [
444
+ ast.keyword(
445
+ arg="dto_type",
446
+ value=ast.Name(id=related_dto_class_name, ctx=ast.Load()),
447
+ ),
448
+ ast.keyword(
449
+ arg="entity_type",
450
+ value=ast.Name(id=related_entity_class_name, ctx=ast.Load()),
451
+ ),
452
+ ]
453
+
454
+ # Resolvendo a coluna usada no relacionamento
455
+ if (
456
+ not properties_structure.entity_properties
457
+ or pkey not in properties_structure.entity_properties
458
+ or not properties_structure.entity_properties[pkey].relation_column
459
+ ):
460
+ raise Exception(
461
+ f"Propriedade '{pkey}' possui um relacionamento, mas nenhuma coluna de relacioanamento foi apontada na propriedade correspondente no repository."
462
+ )
463
+
464
+ relation_column = properties_structure.entity_properties[pkey].relation_column
465
+
466
+ keywords.append(
467
+ ast.keyword(
468
+ arg="related_entity_field",
469
+ value=ast.Constant(value=relation_column),
470
+ )
471
+ )
472
+
473
+ ast_attr = ast.AnnAssign(
474
+ target=ast.Name(id=CompilerStrUtil.to_snake_case(pkey), ctx=ast.Store()),
475
+ annotation=ast.Name(
476
+ id="list",
477
+ ctx=ast.Load(),
478
+ ),
479
+ value=ast.Call(
480
+ func=ast.Name(id="DTOListField", ctx=ast.Load()),
481
+ args=[],
482
+ keywords=keywords,
483
+ ),
484
+ simple=1,
485
+ )
486
+
487
+ ast_dto_attributes.append(ast_attr)
488
+
489
+ def _build_ast_1_1(
490
+ self,
491
+ properties_structure: PropertiesCompilerStructure,
492
+ ast_dto_attributes: list[ast.stmt],
493
+ pkey: str,
494
+ related_dto_class_name: str,
495
+ related_entity_class_name: str,
496
+ ):
497
+ # Propriedade do property descriptor
498
+ keywords = [
499
+ ast.keyword(
500
+ arg="entity_type",
501
+ value=ast.Name(id=related_entity_class_name, ctx=ast.Load()),
502
+ ),
503
+ ]
504
+
505
+ # Resolvendo a coluna usada no relacionamento
506
+ if (
507
+ not properties_structure.entity_properties
508
+ or pkey not in properties_structure.entity_properties
509
+ or not properties_structure.entity_properties[pkey].relation_column
510
+ ):
511
+ raise Exception(
512
+ f"Propriedade '{pkey}' possui um relacionamento, mas nenhuma coluna de relacioanamento foi apontada na propriedade correspondente no repository."
513
+ )
514
+
515
+ relation_column = str(
516
+ properties_structure.entity_properties[pkey].relation_column
517
+ )
518
+
519
+ owner_relation = False
520
+ if "/" in relation_column:
521
+ owner_relation = True
522
+ relation_column = relation_column.split("/")[-1]
523
+
524
+ keywords.append(
525
+ ast.keyword(
526
+ arg="relation_field",
527
+ value=ast.Constant(value=relation_column),
528
+ )
529
+ )
530
+
531
+ if not owner_relation:
532
+ keywords.append(
533
+ ast.keyword(
534
+ arg="entity_relation_owner",
535
+ value=ast.Attribute(
536
+ value=ast.Name(id="EntityRelationOwner", ctx=ast.Load()),
537
+ attr="OTHER",
538
+ ctx=ast.Load(),
539
+ ),
540
+ )
541
+ )
542
+
543
+ ast_attr = ast.AnnAssign(
544
+ target=ast.Name(id=CompilerStrUtil.to_snake_case(pkey), ctx=ast.Store()),
545
+ annotation=ast.Name(
546
+ id=related_dto_class_name,
547
+ ctx=ast.Load(),
548
+ ),
549
+ value=ast.Call(
550
+ func=ast.Name(id="DTOObjectField", ctx=ast.Load()),
551
+ args=[],
552
+ keywords=keywords,
553
+ ),
554
+ simple=1,
555
+ )
556
+
557
+ ast_dto_attributes.append(ast_attr)
558
+
559
+ def _compile_simple_property(
220
560
  self,
221
561
  properties_structure,
222
562
  map_unique_by_property,
@@ -461,7 +801,7 @@ class EDLPropertyCompiler:
461
801
  def _compile_domain_config(
462
802
  self,
463
803
  pkey: str,
464
- prop: PropertyMetaModel,
804
+ prop: PropertyMetaModel | TraitPropertyMetaModel,
465
805
  entity_model: EntityModel,
466
806
  ) -> tuple[str, ast.stmt] | None:
467
807
  if not prop.domain_config:
@@ -1,11 +1,14 @@
1
1
  import datetime
2
2
  import json
3
+ import re
3
4
  import sys
4
5
  import threading
5
6
  import types
6
7
 
7
8
  from nsj_rest_lib.settings import get_logger
8
9
 
10
+ from nsj_rest_lib2.compiler.edl_model.primitives import REGEX_EXTERNAL_REF
11
+ from nsj_rest_lib2.compiler.model import RelationDependency
9
12
  from nsj_rest_lib2.compiler.util.type_naming_util import compile_namespace_keys
10
13
  from nsj_rest_lib2.exception import MissingEntityConfigException
11
14
  from nsj_rest_lib2.redis_config import get_redis
@@ -21,14 +24,15 @@ class LoadedEntity:
21
24
  self.loaded_at: datetime.datetime = datetime.datetime.now()
22
25
  self.api_expose: bool = False
23
26
  self.api_verbs: list[str] = []
27
+ self.relations_dependencies: list[RelationDependency] = []
24
28
 
25
29
 
26
30
  class Namespace:
27
31
  def __init__(self):
28
32
  self.key: str = ""
29
33
  self.loaded_entities: dict[str, LoadedEntity] = {}
30
- self.entities_dict: dict = None
31
- self.module: types.ModuleType = None
34
+ self.entities_dict: dict = {}
35
+ self.module: types.ModuleType = types.ModuleType("empty")
32
36
 
33
37
 
34
38
  namespaces_dict: dict[str, Namespace] = {}
@@ -43,17 +47,18 @@ class EntityLoader:
43
47
  entity_resource: str,
44
48
  tenant: str | None,
45
49
  grupo_empresarial: str | None,
50
+ escopo: str = ESCOPO_RESTLIB2,
46
51
  ) -> tuple[str, str, dict, bool, list[str]]:
47
52
  # Montando as chaves dos namespaces
48
53
  grupo_key, tenant_key, default_key = compile_namespace_keys(
49
54
  tenant, grupo_empresarial
50
55
  )
51
56
 
52
- result = self._load_entity_source_from_memory(
57
+ result = self._search_entity_namespace_in_memory(
53
58
  entity_resource, grupo_key, tenant_key, default_key
54
59
  )
55
60
 
56
- # Se conseguiu carregar da memória, verifica se houve alteração no hash, em relação ao redis
61
+ # Se conseguiu localizar na memória, verifica se houve alteração no hash, em relação ao redis
57
62
  if result is not None:
58
63
  # Desempacotando o result e recuperando informações do namespace
59
64
  (
@@ -67,6 +72,21 @@ class EntityLoader:
67
72
  entities_dict = namespace.entities_dict
68
73
  api_expose = loaded_entity.api_expose
69
74
  api_verbs = loaded_entity.api_verbs
75
+ relations_dependencies = loaded_entity.relations_dependencies
76
+
77
+ # Verificando se alguma de suas dependências precisariam ser recarregadas
78
+ for rd in relations_dependencies:
79
+ if rd.entity_resource is None or rd.entity_scope is None:
80
+ raise RuntimeError(
81
+ f"Erro: Dependência de entidade mal formada na entidade {entity_resource}."
82
+ )
83
+
84
+ self.load_entity_source(
85
+ rd.entity_resource,
86
+ str(rd.tenant),
87
+ str(rd.grupo_empresarial),
88
+ rd.entity_scope,
89
+ )
70
90
 
71
91
  # Se o tempo entre o carregamento e agora for maior do que MIN_TIME_SOURCE_REFRESH minutos,
72
92
  # verifica se precisa de refresh
@@ -83,6 +103,7 @@ class EntityLoader:
83
103
  tenant_key,
84
104
  default_key,
85
105
  entity_config_key,
106
+ escopo=escopo,
86
107
  )
87
108
 
88
109
  # Se não achar no redis, usa o que estava em memória
@@ -142,7 +163,12 @@ class EntityLoader:
142
163
 
143
164
  # Se não conseguir recuperar a entidade, procura no redis:
144
165
  loaded_config = self._load_entity_config_from_redis(
145
- entity_resource, grupo_key, tenant_key, default_key, None
166
+ entity_resource,
167
+ grupo_key,
168
+ tenant_key,
169
+ default_key,
170
+ None,
171
+ escopo=escopo,
146
172
  )
147
173
 
148
174
  # Se também não achar no redis, lanca exceção
@@ -215,6 +241,10 @@ class EntityLoader:
215
241
  api_expose = entity_config["api_expose"]
216
242
  # api_resource = entity_config["api_resource"]
217
243
  api_verbs = entity_config["api_verbs"]
244
+ relations_dependencies = [
245
+ RelationDependency().from_dict(rd)
246
+ for rd in entity_config.get("relations_dependencies", [])
247
+ ]
218
248
  except json.JSONDecodeError as e:
219
249
  if not check_refresh:
220
250
  raise RuntimeError(
@@ -226,6 +256,20 @@ class EntityLoader:
226
256
  )
227
257
  return None
228
258
 
259
+ # Verificando se alguma de suas dependências precisariam ser carregadas (ou recarregadas)
260
+ for rd in relations_dependencies:
261
+ if rd.entity_resource is None or rd.entity_scope is None:
262
+ raise RuntimeError(
263
+ f"Erro: Dependência de entidade mal formada na entidade {entity_resource}."
264
+ )
265
+
266
+ self.load_entity_source(
267
+ rd.entity_resource,
268
+ str(rd.tenant),
269
+ str(rd.grupo_empresarial),
270
+ rd.entity_scope,
271
+ )
272
+
229
273
  # Verificando se a entidade precisa ou não de refresh
230
274
  if check_refresh:
231
275
  loaded_namespace = namespaces_dict.get(entity_config_key)
@@ -283,6 +327,7 @@ class EntityLoader:
283
327
  loaded_entity.entity_hash = entity_hash
284
328
  loaded_entity.api_expose = api_expose
285
329
  loaded_entity.api_verbs = api_verbs
330
+ loaded_entity.relations_dependencies = relations_dependencies
286
331
 
287
332
  namespace.loaded_entities[entity_resource] = loaded_entity
288
333
 
@@ -302,30 +347,31 @@ class EntityLoader:
302
347
  tenant_key: str,
303
348
  default_key: str,
304
349
  entity_config_key: str | None,
350
+ escopo: str,
305
351
  ) -> tuple[str, str] | None:
306
352
  get_logger().debug(
307
- f"Procurando a configuração da entidade {entity_resource} no redis"
353
+ f"Procurando a configuração da entidade {entity_resource} no redis. Tenant key: {tenant_key} e Grupo key: {grupo_key}"
308
354
  )
309
355
 
310
356
  if entity_config_key is not None:
311
357
  entity_config_str = get_redis(
312
- "entity_config", ESCOPO_RESTLIB2, entity_config_key, entity_resource
358
+ "entity_config", escopo, entity_config_key, entity_resource
313
359
  )
314
360
 
315
361
  else:
316
362
  entity_config_key = grupo_key
317
363
  entity_config_str = get_redis(
318
- "entity_config", ESCOPO_RESTLIB2, grupo_key, entity_resource
364
+ "entity_config", escopo, grupo_key, entity_resource
319
365
  )
320
366
  if entity_config_str is None:
321
367
  entity_config_key = tenant_key
322
368
  entity_config_str = get_redis(
323
- "entity_config", ESCOPO_RESTLIB2, tenant_key, entity_resource
369
+ "entity_config", escopo, tenant_key, entity_resource
324
370
  )
325
371
  if entity_config_str is None:
326
372
  entity_config_key = default_key
327
373
  entity_config_str = get_redis(
328
- "entity_config", ESCOPO_RESTLIB2, default_key, entity_resource
374
+ "entity_config", escopo, default_key, entity_resource
329
375
  )
330
376
 
331
377
  # Se não encontrar no redis, retorna None
@@ -334,7 +380,7 @@ class EntityLoader:
334
380
 
335
381
  return (entity_config_key, entity_config_str)
336
382
 
337
- def _load_entity_source_from_memory(
383
+ def _search_entity_namespace_in_memory(
338
384
  self,
339
385
  entity_resource: str,
340
386
  grupo_key: str,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nsj_rest_lib2
3
- Version: 0.0.10
3
+ Version: 0.0.12
4
4
  Summary: Biblioteca para permitir a distribuição de rotas dinâmicas numa API, configuradas por meio de EDLs declarativos (em formato JSON).
5
5
  Home-page: https://github.com/Nasajon/nsj_rest_lib2
6
6
  Author: Nasajon Sistemas
@@ -4,21 +4,22 @@ nsj_rest_lib2/redis_config.py,sha256=4KLcvYS3nJO7PMQgF6F9_j6r-TyqcS7TBbd3LEQuKDU
4
4
  nsj_rest_lib2/settings.py,sha256=Hn_o1HZmievnYb8D1kNT2Nq-OEjxbyNjOiOpbnFsMwE,367
5
5
  nsj_rest_lib2/compiler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  nsj_rest_lib2/compiler/ai_compiler.py,sha256=shAtN4T0ai_52qh15L3mK1QbeC6glHOR6C3J3gj14II,9029
7
- nsj_rest_lib2/compiler/compiler.py,sha256=wkREdCrQ6mq6-R-5nC5ZXDGNpSEIPjuSjt-NCxOS6to,12228
7
+ nsj_rest_lib2/compiler/compiler.py,sha256=tWnhDuHpvpJMWMPxt6U76ZS6J-hSpiB7xRZaeOP3CMc,12334
8
8
  nsj_rest_lib2/compiler/compiler_structures.py,sha256=2bM4_7lG1fytDGxJl6SU9pLsbspiNV36gVn9-O-23Q8,1009
9
- nsj_rest_lib2/compiler/dto_compiler.py,sha256=GAKWFvks0Duqs2EsEr1-BFx2-Kny72jJMbl9ZyIEDXw,4125
9
+ nsj_rest_lib2/compiler/dto_compiler.py,sha256=4e9_8EsNSHPCUEmiuEPdudj9_tFuw2O4ZTv6EYwJrGc,5333
10
10
  nsj_rest_lib2/compiler/entity_compiler.py,sha256=zLXO6USY4Rr0Hnk4wGep5K8DiHxJv-W-BBz_-g4OhCA,4307
11
- nsj_rest_lib2/compiler/property_compiler.py,sha256=RR5F-7D2nLG6ThjC0rdqwU3G5SMN1P5tBusZaAPwIMk,18865
11
+ nsj_rest_lib2/compiler/model.py,sha256=QDBoM26qoZdiNcykl1nvXCrFDhg-6Q__QzVq6uY1QzE,1460
12
+ nsj_rest_lib2/compiler/property_compiler.py,sha256=kr9Pi0kkIrvgEmxrmV_NEQuqekM1h6-jqvF85nZTUcY,29808
12
13
  nsj_rest_lib2/compiler/edl_model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
14
  nsj_rest_lib2/compiler/edl_model/ai_entity_edl.py,sha256=664QBDcOgVnyfwtUOXO1W7AKaZhueBG335x5DuogruY,7644
14
15
  nsj_rest_lib2/compiler/edl_model/api_model.py,sha256=pH0Uiq_64AGvkHqwY44TrulWWZXbi6M0EKJWUhSqKj0,837
15
- nsj_rest_lib2/compiler/edl_model/column_meta_model.py,sha256=3OLsRwEz66Cktzew6Hie3HZXK9u20GXlWUU0HtFtwfk,709
16
+ nsj_rest_lib2/compiler/edl_model/column_meta_model.py,sha256=s0sEVkoW1hV2_hto1mws4XhzKGH_b4NzhaOiwFH25Ks,694
16
17
  nsj_rest_lib2/compiler/edl_model/entity_model.py,sha256=sXVfC30P3-q2-fQ3XB_-3vpNjqFPIK5oBIVbwkXNFLU,3662
17
18
  nsj_rest_lib2/compiler/edl_model/index_model.py,sha256=cXWlu0hxtro5vvYoirkDW4R3PCnBW5oCCWjRJ6AX5zE,508
18
19
  nsj_rest_lib2/compiler/edl_model/primitives.py,sha256=cWEys87xDpgotyRUXkzvFvY4LHeawzBEEyz0Xed18AQ,1454
19
20
  nsj_rest_lib2/compiler/edl_model/property_meta_model.py,sha256=x2SApvI-5MZTErAHeOXnR6qfjflhSs8r6Y9KMtCykV0,3960
20
21
  nsj_rest_lib2/compiler/edl_model/repository_model.py,sha256=Vt1HxlaoifP4w5ij1laKDkD1-COBihE8EX1HkdEDUSo,977
21
- nsj_rest_lib2/compiler/edl_model/trait_property_meta_model.py,sha256=ZC2oTvc9Y2Oiy4NLNHQLiMOLRWDAOdF8GJxOw4nqHdk,354
22
+ nsj_rest_lib2/compiler/edl_model/trait_property_meta_model.py,sha256=SBSfiu4v0PMsWRsGDySR3P2tkabLbDZxx_FFy_3rGXA,605
22
23
  nsj_rest_lib2/compiler/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
24
  nsj_rest_lib2/compiler/util/str_util.py,sha256=lVP1yHhj1pOd6ULtTnkcfX6Gqrpe4yCBratHUhBNGcI,843
24
25
  nsj_rest_lib2/compiler/util/type_naming_util.py,sha256=sahlxtYUPcksqlX1hygaoTF7vuD8tY8eosgOR04YLAs,612
@@ -26,8 +27,8 @@ nsj_rest_lib2/compiler/util/type_util.py,sha256=HTKOH4uRTOY0YgoM8oUv_6cEcReE_bgK
26
27
  nsj_rest_lib2/controller/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
28
  nsj_rest_lib2/controller/dynamic_controller.py,sha256=XMqxe1NW-NE5XwomXb4pSNdALQHpP74Hc26R4fnmXqg,15194
28
29
  nsj_rest_lib2/service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
- nsj_rest_lib2/service/entity_loader.py,sha256=PJX-cPPCllhZuqSZzj8ZX_0HZQBnt20dqLc9E0e0GtA,13472
30
- nsj_rest_lib2-0.0.10.dist-info/METADATA,sha256=-I_ppkmCUjV4aXsBTij-0Ih2JUxbjb5XYyntbSJrhFE,1361
31
- nsj_rest_lib2-0.0.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
32
- nsj_rest_lib2-0.0.10.dist-info/top_level.txt,sha256=L6zh0EfH8_rur7OJ8_V-El-XEMf4qg3bkF8ADgqLVIA,14
33
- nsj_rest_lib2-0.0.10.dist-info/RECORD,,
30
+ nsj_rest_lib2/service/entity_loader.py,sha256=KVLFQSqj4DL-K-T29ksJE2or9QkekFmyAGUIKjQX6Qk,15372
31
+ nsj_rest_lib2-0.0.12.dist-info/METADATA,sha256=CTk1vS9FJ_U2e6-Vz38wj3Ba2efwuKPW-iDpHlosHzk,1361
32
+ nsj_rest_lib2-0.0.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
33
+ nsj_rest_lib2-0.0.12.dist-info/top_level.txt,sha256=L6zh0EfH8_rur7OJ8_V-El-XEMf4qg3bkF8ADgqLVIA,14
34
+ nsj_rest_lib2-0.0.12.dist-info/RECORD,,